LeetCode 热题100之技巧关卡

1.只出现一次的数字

在这里插入图片描述
思路分析1:使用哈希表存储每个数字和该数字出现的次数。遍历数组即可得到每个数字出现的次数,并更新哈希表,最后遍历哈希表,得到只出现一次的数字。

具体实现代码(详解版):

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        unordered_map<int,int> mp;
        for(int num : nums) mp[num]++;
        for(const auto& pair : mp){
            if(pair.second == 1){
                return pair.first;
            }
        }
        return -1;
    }
};
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

思路分析2:脑筋急转弯之位运算(异或操作):这里不需要额外的哈希表,直接利用了异或的性质来完成运算。

  • 任何数与 0 异或等于其本身:a ^ 0 = a
  • 任何数与自己异或等于 0:a ^ a = 0
  • 异或操作满足交换律和结合律:a ^ b ^ a = (a ^ a) ^ b = 0 ^ b = b
  • 因此,对于数组中的所有元素,由于出现两次的元素会被抵消(变为 0),最终 res 中只剩下出现一次的元素。

体实现代码(详解版):

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int res = 0;
        for(auto e : nums) res ^= e;//异或操作
        return res;
    }
};

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

2.多数元素

在这里插入图片描述
思路分析1:哈希表。
具体实现代码(详解版):

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int n = nums.size();
        unordered_map<int,int> mp;

        for(int num : nums) mp[num]++;

        for(const auto& pair : mp){
            if(pair.second > n / 2){
                return pair.first;
            }
        }
        return -1;
    }
};
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

思路分析2:Boyer-Moore 投票算法

  • 核心思想:Boyer-Moore 投票算法的核心是**“相互抵消”的策略**:
    • 候选多数元素:算法假设存在一个候选多数元素,并通过不断的遍历和计数来验证这个候选多数元素的有效性。
    • 计数:算法维护一个计数器 count,用来跟踪候选元素在数组中出现的“净次数”。
    • 抵消策略:
      • 如果遇到的元素等于当前候选元素,则计数加 1。
      • 如果遇到的元素不等于当前候选元素,则计数减 1。
      • 当计数减到 0 时,认为当前候选元素不再可能是多数元素(因为支持它的数量被其他元素抵消了),因此换一个新的候选元素并重置计数为 1。

为什么这样操作能找到多数元素?
假设 nums 中的多数元素为 M,它的出现次数超过了数组长度的一半(即大于 ⌊n/2⌋ 次),那么:在计数 count 的增减过程中,多数元素的出现次数无法被其他元素完全抵消掉。
换句话说,虽然其他元素可能抵消掉多数元素的一部分计数,但由于多数元素的数量大于数组中所有其他元素数量的总和,它最终会成为候选元素。

具体实现代码(详解版):

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int can = nums[0];//初始化候选者
        int cnt = 0;//计数器初始为0

        for(int i = 1; i < nums.size() ; i ++){
            if(nums[i] == cnt){//投一票
                cnt ++;
            }
            else{
                cnt --;//计数器减一,不等于候选者
                if(cnt == 0){//计数器为0,说明当前候选者不够格,淘汰
                    can = nums[i];//更新候选者
                    cnt = 1;//计数器归为1
                }
            }
        }
        return can;
    }
};

3. 颜色分类

在这里插入图片描述
思路分析1:不让调用sort,那就手搓快排!Acwing 排序

具体实现代码(详解版):

class Solution {
public:
    void quick_sort(vector<int>& nums,int l ,int r){
        if(l >= r) return;

        int i = l - 1, j = r + 1,x = nums[l + r >> 1];

        while(i < j){
            do i ++;while(nums[i] < x);
            do j --;while(nums[j] > x);
            if(i < j) swap(nums[i],nums[j]);

        }
        quick_sort(nums,l,j),quick_sort(nums,j + 1, r);
    }
    void sortColors(vector<int>& nums) {
        quick_sort(nums,0,nums.size() - 1);
    }
};

思路分析2:三指针(第一次见):问题实际上是经典的 荷兰国旗问题(Dutch National Flag Problem),可以使用 三指针法(Three-pointer technique) 进行解决。该方法的核心思想是通过三个指针将数组分为三部分:一部分是 0(红色),一部分是 1(白色),一部分是 2(蓝色)。

  • 指针定义
    • low:指向当前区间的第一个 1 或 0,用于区分红色区域和白色区域。
    • mid:当前扫描指针,扫描所有元素,负责区分白色区域和蓝色区域。
    • high:指向当前区间的最后一个 1 或 2,用于区分蓝色区域和白色区域。
  • 遍历数组
    • 当 nums[mid] == 0 时,将 nums[mid] 和 nums[low] 交换,并增加 low 和 mid 指针。
    • 当 nums[mid] == 1 时,直接增加 mid 指针。
    • 当 nums[mid] == 2 时,将 nums[mid] 和 nums[high] 交换,并减少 high 指针。此时 mid 指针不增加,因为交换后的 nums[mid] 可能是 0 或 1,需要进一步处理。
  • 终止条件:当 mid 超过 high 时,排序完成。

具体实现代码(详解版):

class Solution {
public:
    void sortColors(vector<int>& nums) {
        int low = 0, mid = 0, high = nums.size() - 1;

        // 当 mid 指针小于或等于 high 指针时,继续排序
        while (mid <= high) {
            if (nums[mid] == 0) {
                // 将 0 移到低位
                swap(nums[low], nums[mid]);
                low++;
                mid++;
            } else if (nums[mid] == 1) {
                // 1 已经在正确的位置,继续处理下一个
                mid++;
            } else {
                // 将 2 移到高位
                swap(nums[mid], nums[high]);
                high--;
            }
        }
    }
};

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

4.下一个排列

在这里插入图片描述
思路分析:先找到第一个降序对的位置,然后这就是我们需要修改替换的位置,用谁替换呢?当然是它右边第一个比它大的数字。这是为了确保我们选择一个 最小的 大于 nums[i] 的数字,这样交换后能保证下一个排列是字典序最小的。

  • 从右向左查找第一个降序对:我们从右向左遍历数组,找到第一个满足 nums[i] < nums[i+1] 的位置 i。这个位置的元素是需要改变的元素,因为它决定了排序的次序。

为什么从右向左遍历?
如果我们从右向左找,意味着我们优先改变最后一个不符合递增的数字,这样保证我们修改的是最小的可能的地方,这样能生成下一个较小的、更大的排列。

  • 如果我们遍历到头部都没有找到这样的 i,这意味着数组已经是降序排列的,也就是当前排列已经是最大的排列。此时,我们只需将整个数组反转,得到最小的排列(升序排列)。

说明整个数组是降序排列的,比如 [3, 2, 1]。此时,数组已经是最大的排列了。我们将整个数组反转,得到字典序最小的排列。

  • 找到大于 nums[i] 的最小元素:如果找到了 i,接下来我们需要在 i+1 到数组末尾的部分中找到一个比 nums[i] 大的最小元素。假设该元素为 nums[j]。

为什么从右边查找 j?
为了确保我们选择一个 最小的 大于 nums[i] 的数字,这样交换后能保证下一个排列是字典序最小的。

  • 交换 nums[i] 和 nums[j]:交换 nums[i] 和 nums[j],这时 nums[i] 会变成一个稍微大的元素,确保字典序的顺序。
  • 反转 i+1 到数组末尾的部分:交换后,数组的右半部分并不一定是升序排列的,因此我们需要将它反转成升序,确保得到的排列是下一个字典序排列。

具体实现代码(详解版):

class Solution {
public:
    void nextPermutation(vector<int>& nums) {
        int n = nums.size();
        
        // 第一步:从右向左找到第一个降序对 nums[i] < nums[i + 1]
        int i = n - 2;  // 从倒数第二个元素开始
        while (i >= 0 && nums[i] >= nums[i + 1]) {
            i--;  // 如果 nums[i] >= nums[i + 1],继续向左移动
        }
        
        // 如果找到了降序对
        if (i >= 0) {
            // 第二步:从右边找第一个比 nums[i] 大的元素 nums[j]
            int j = n - 1;
            while (nums[j] <= nums[i]) {
                j--;  // 找到第一个大于 nums[i] 的元素
            }
            // 第三步:交换 nums[i] 和 nums[j]
            swap(nums[i], nums[j]);
        }
        
        // 第四步:反转 nums[i+1] 到数组末尾的部分,确保得到的序列是升序
        reverse(nums.begin() + i + 1, nums.end());
    }
};

  • 时间复杂度:O(n)
  • 空间复杂度:O(1),所有操作都是在原数组上进行的,没有使用额外的空间。

5.寻找重复数

在这里插入图片描述
思路分析1:二分查找

  • 利用数组的值的范围
    • 数组中的元素范围是 [1, n-1],且总共有 n 个元素,因此数组中至少有一个元素重复。
  • 二分查找
    • 通过统计数组中小于等于某个值mid的元素个数来确定重复元素的位置
    • 如果小于等等于mid的元素个数超过mid,说明重复的元素在[1,mid]内;
    • 否则,重复的元素在[mid + 1,n - 1]区间。

具体实现代码(详解版):

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        int l = 1, r = nums.size() - 1;  // 数字范围在 1 到 n-1 之间
        
        while (l < r) {
            int mid = l + (r - l) / 2;  // 计算中间值
            
            // 统计小于等于 mid 的元素个数
            int count = 0;
            for (int num : nums) {
                if (num <= mid) {
                    count++;
                }
            }
            
            // 如果小于等于 mid 的元素个数大于 mid,说明重复的元素在 [l, mid] 区间
            if (count > mid) {
                r = mid;  // 缩小搜索范围
            } else {
                l = mid + 1;  // 否则在 [mid + 1, r] 区间
            }
        }
        
        return l;  // 最终 l 和 r 会指向重复元素
    }
};

  • 时间复杂度:每次我们都通过二分法将搜索空间减半,并且需要遍历一次数组来统计小于等于 mid 的元素个数。因此时间复杂度为 O(n log n)。
  • 空间复杂度:O(1)

思路分析2:快慢指针法

  • 快慢指针方法的核心思想是利用 环形链表 的特性来检测重复元素。

数组与链表的映射:我们可以把数组视为一个链表,其中每个元素 nums[i] 表示指向索引 nums[i] 的下一节点。这样,数组的值实际上就成为了链表的节点值。
环的形成:由于数组中有重复元素,必然会形成一个环。例如,如果有重复的数字 x,那么在 x 所在的位置将会有两个指针指向该位置,这样形成了一个环。
快慢指针:慢指针(Tortoise)每次走一步,快指针(Hare)每次走两步;如果链表中存在环,快慢指针必定会相遇。如果它们相遇,那么相遇点就是环的入口,即重复元素所在的位置。

  • 初始化:设置慢指针 slow 和快指针 fast,都从数组的第一个元素开始。
  • 第一次相遇:快指针每次走两步,慢指针每次走一步。当它们相遇时,说明链表中存在环,且相遇点就是环中的一个位置
  • 找到环的入口:将慢指针移动到数组的起始位置,而快指针保持在相遇点,然后两者每次都走一步。当它们再次相遇时,遇到的点就是环的入口,也就是重复的数字。

具体实现代码(详解版):

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        // 第一步:初始化慢指针和快指针
        int slow = nums[0], fast = nums[0];
        
        // 第二步:快慢指针在环中相遇
        do {
            slow = nums[slow];          // 慢指针每次走一步
            fast = nums[nums[fast]];    // 快指针每次走两步
        } while (slow != fast);  // 直到慢指针和快指针相遇
        
        // 第三步:将慢指针移到数组起始位置
        slow = nums[0];
        
        // 第四步:慢指针和快指针每次走一步,直到它们再次相遇
        while (slow != fast) {
            slow = nums[slow];  // 慢指针每次走一步
            fast = nums[fast];  // 快指针每次走一步
        }
        
        // 返回重复的数字(即相遇点)
        return slow;
    }
};

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/915157.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何优化Kafka消费者的性能

要优化 Kafka 消费者性能&#xff0c;你可以考虑以下策略&#xff1a; 并行消费&#xff1a;通过增加消费者组中的消费者数量来并行处理更多的消息&#xff0c;从而提升消费速度。 批量消费&#xff1a;配置 fetch.min.bytes 和 fetch.max.wait.ms 参数来控制批量消费的大小和…

服务器数据恢复——Ext4文件系统使用fsck后mount不上的数据恢复案例

关于Ext4文件系统的几个概念&#xff1a; 块组&#xff1a;Ext4文件系统的全部空间被划分为若干个块组&#xff0c;每个块组结构基本上相同。 块组描述符表&#xff1a;每个块组都对应一个块组描述符&#xff0c;这些块组描述符统一放在文件系统的前部&#xff0c;称为块组描述…

GIC寄存器介绍

往期内容 本专栏往期内容&#xff0c;interrtupr子系统&#xff1a; 深入解析Linux内核中断管理&#xff1a;从IRQ描述符到irq domain的设计与实现Linux内核中IRQ Domain的结构、操作及映射机制详解中断描述符irq_desc成员详解Linux 内核中断描述符 (irq_desc) 的初始化与动态分…

并发基础:(淘宝笔试题)三个线程分别打印 A,B,C,要求这三个线程一起运行,打印 n 次,输出形如“ABCABCABC....”的字符串【举一反三】

🚀 博主介绍:大家好,我是无休居士!一枚任职于一线Top3互联网大厂的Java开发工程师! 🚀 🌟 在这里,你将找到通往Java技术大门的钥匙。作为一个爱敲代码技术人,我不仅热衷于探索一些框架源码和算法技巧奥秘,还乐于分享这些宝贵的知识和经验。 💡 无论你是刚刚踏…

vue计算属性 初步使用案例

<template><div><h1>购物车</h1><div v-for"item in filteredItems" :key"item.id"><p>{{ item.name }} - {{ item.price }} 元</p><input type"number" v-model.number"item.quantity"…

springboot读取modbus数据

1、引入依赖 jlibmodbus <dependency><groupId>com.intelligt.modbus</groupId><artifactId>jlibmodbus</artifactId><version>1.2.9.7</version> </dependency> 2、数据获取 public String processData(String ip) {tr…

【0x0045】HCI_Write_Inquiry_Mode详解

目录 一、命令概述 二、命令格式及参数说明 2.1. HCI_Write_Inquiry_Mode命令格式 2.2. Inquiry_Mode 三、响应事件格式及参数 3.1. HCI_Command_Complete事件格式 3.2. 参数说明 3.2.1. 事件代码(Event Code) 3.2.2. 参数总长度(Parameter Total Length) 3.2.3.…

【C语言】指针的运算

指针的增量操作&#xff1a; int i 10; int *p &i;printf("p %p\n", p);//1024p; // 增加int 4个字节大小printf("p %p\n", p);//1028指针的增量运算取决于指针的数据类型&#xff0c;它将会增加数据类型的大小的字节。 指针的减量操作与增量同理…

电商系统开发:Spring Boot框架实战

3 系统分析 当用户确定开发一款程序时&#xff0c;是需要遵循下面的顺序进行工作&#xff0c;概括为&#xff1a;系统分析–>系统设计–>系统开发–>系统测试&#xff0c;无论这个过程是否有变更或者迭代&#xff0c;都是按照这样的顺序开展工作的。系统分析就是分析系…

【数据库】数据库迁移的注意事项有哪些?

数据库迁移是一个复杂且关键的过程&#xff0c;需要谨慎处理以确保数据的完整性和应用程序的正常运行。以下是一些数据库迁移时需要注意的事项&#xff1a; 1. 充分的前期准备 1.1 评估迁移需求 明确目标&#xff1a;确定迁移的具体目标&#xff0c;例如添加新字段、修改现…

pgsql和mysql的自增主键差异

1. 当有历史数据存在时&#xff0c; mysql的自增主键是默认从最大值自增。 pgsql的自增主键取初始值开始逐个尝试&#xff0c;所以存在可能与历史数据的主键重复的情况。 pgsql解决上述问题的方式&#xff1a;重设自增值。 SELECT SETVAL(t_db_filed_id_seq, (SELECT MAX(&q…

opencv入门学习总结

opencv学习总结 不多bb&#xff0c;直接上代码&#xff01;&#xff01;&#xff01; 案例一&#xff1a; import cv2 # 返回当前安装的 OpenCV 库的版本信息 并且是字符串格式 print(cv2.getVersionString()) """ 作用&#xff1a;它可以读取不同格式的图像文…

【VBA实战】用Excel制作排序算法动画续

为什么会产生用excel来制作排序算法动画的念头&#xff0c;参见【VBA实战】用Excel制作排序算法动画一文。这篇文章贴出我所制作的所有排序算法动画效果和源码&#xff0c;供大家参考。 冒泡排序&#xff1a; 插入排序&#xff1a; 选择排序&#xff1a; 快速排序&#xff1a;…

Go 语言已立足主流,编程语言排行榜24 年 11 月

Go语言概述 Go语言&#xff0c;简称Golang&#xff0c;是由Google的Robert Griesemer、Rob Pike和Ken Thompson在2007年设计&#xff0c;并于2009年11月正式宣布推出的静态类型、编译型开源编程语言。Go语言以其提高编程效率、软件构建速度和运行时性能的设计目标&#xff0c;…

《基于深度学习的车辆行驶三维环境双目感知方法研究》

复原论文思路&#xff1a; 《基于深度学习的车辆行驶三维环境双目感知方法研究》 1、双目测距的原理 按照上述公式算的话&#xff0c;求d的话&#xff0c;只和xl-xr有关系&#xff0c;这样一来&#xff0c;是不是只要两张图像上一个测试点的像素位置确定&#xff0c;对应的深…

机器学习在医疗健康领域的应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 机器学习在医疗健康领域的应用 机器学习在医疗健康领域的应用 机器学习在医疗健康领域的应用 引言 机器学习概述 定义与原理 发展…

2024136读书笔记|《飞鸟集》——使生如夏花之绚烂,死如秋叶之静美

2024136读书笔记|《飞鸟集》——使生如夏花之绚烂&#xff0c;死如秋叶之静美 《飞鸟集》[印]泰戈尔&#xff0c;一本有意思的诗集&#xff0c;中英文对照着读更有意思。“你是谁&#xff0c;读者&#xff0c;百年后读着我的诗&#xff1f;”让我觉得有些久别重逢&#xff0c;忽…

爱芯元智创始人仇肖莘荣获《财富》中国最具影响力的商界女性

爱芯元智宣布&#xff0c;《财富》&#xff08;中文版&#xff09;揭晓了2024年度“中国最具影响力的商界女性”榜单&#xff08;Most Powerful Women&#xff0c;简称MPW&#xff09;&#xff0c;爱芯元智创始人兼董事长仇肖莘博士荣登《财富》“MPW未来榜”&#xff0c;彰显了…

windows下qt5.12.11使用ODBC远程连接mysql数据库

1、下载并安装mysql驱动,下载地址:https://dev.mysql.com/downloads/ 2、配置ODBC数据源,打开64位的ODBC数据源配置工具:

河南省的一级科技查新机构有哪些?

科技查新&#xff0c;简称查新&#xff0c;是指权威机构对查新项目的新颖性作出文献评价的情报咨询服务。这一服务在科研立项、成果鉴定、项目申报等方面发挥着至关重要的作用。河南省作为中国的重要科技和教育基地&#xff0c;拥有多个一级科技查新机构&#xff0c;为本省及全…