LeetCode 热题 100 刷题笔记:数组与排列的经典解法

在算法学习的道路上,LeetCode 热题 100 是检验和提升算法能力的重要题库。今天,我将分享几道经典的数组与排列相关题目,涵盖只出现一次的数字多数元素下一个排列寻找重复数,并解析它们的解题思路与实现细节。


一、136. 只出现一次的数字

题目大意 :给定一个非空整数数组 nums,除了某个元素只出现一次外,其余每个元素均出现两次。找出那个只出现一次的元素。要求线性时间复杂度,且只使用常量额外空间。

解题思路:异或运算的特性

异或(^)运算有两个关键性质:

  • 任何数与自身异或结果为 0(即 a ^ a = 0);

  • 任何数与 0异或结果为自身(即 a ^ 0 = a)。

因此,遍历数组时,将所有元素依次异或,最终结果就是只出现一次的那个数(因为成对出现的元素会相互抵消为 0,只剩单独的元素)。

代码实现(C++)

复制代码
class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int result = 0;
        for (int num : nums) {
            result ^= num; // 异或运算,抵消成对元素
        }
        return result;
    }
};

二、169. 多数元素

题目大意 :给定一个大小为 n的数组 nums,返回其中的多数元素。多数元素是指在数组中出现次数大于⌊n/2⌋的元素。假设数组非空,且一定存在多数元素。

解题思路:摩尔投票法(Boyer-Moore Voting Algorithm)

摩尔投票法的核心思想是抵消

  • 维护一个候选元素 candidate和一个计数 count

  • 遍历数组时,若当前元素与 candidate相同,则 count++;否则 count--

  • count减为 0,则更换 candidate为当前元素,并重置 count=1

由于多数元素出现次数超过 n/2,最终剩下的 candidate一定是多数元素(其他元素的"票数"无法完全抵消多数元素的"票数")。

代码实现(C++)

复制代码
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int candidate = 0;
        int count = 0;
        for (int num : nums) {
            if (count == 0) { // 计数为0,更换候选
                candidate = num;
                count = 1;
            } else if (num == candidate) { // 与候选相同,计数+1
                count++;
            } else { // 与候选不同,计数-1
                count--;
            }
        }
        return candidate;
    }
};

三、31. 下一个排列

题目大意:整数数组的一个排列是其所有成员的序列或线性顺序。下一个排列是指其整数的下一个字典序更大的排列。如果不存在更大的排列,将数组重排为字典序最小的排列(升序)。要求原地修改,且只使用常量额外空间。

解题思路:四步法

  1. 从右往左找第一个下降点 :找到最大的索引 i,使得 nums[i] < nums[i+1](即"拐点",拐点左侧是降序,右侧是升序)。

  2. 从右往左找第一个大于 nums[i]的元素 :找到最大的索引 j > i,使得 nums[j] > nums[i]

  3. 交换 nums[i]nums[j]:此时拐点右侧仍保持升序。

  4. 反转 i+1到末尾的部分:使右侧变为升序(保证字典序最小)。

代码实现(C++)

复制代码
class Solution {
public:
    void nextPermutation(vector<int>& nums) {
        int n = nums.size();
        int i = n - 2; // 从倒数第二个元素开始找
        
        // Step 1: 找第一个下降点 i (nums[i] < nums[i+1])
        while (i >= 0 && nums[i] >= nums[i + 1]) {
            i--;
        }
        
        // Step 2: 若找到下降点,找第一个大于 nums[i] 的元素 j
        if (i >= 0) {
            int j = n - 1;
            while (j > i && nums[j] <= nums[i]) {
                j--;
            }
            swap(nums[i], nums[j]); // 交换
        }
        
        // Step 3: 反转 i+1 到末尾的部分(无论是否找到下降点都要做)
        reverse(nums.begin() + i + 1, nums.end());
    }
};

四、287. 寻找重复数

题目大意 :给定一个包含 n + 1个整数的数组 nums,数字都在 [1, n]范围内(包括 1n),可知至少有一个重复的整数。假设只有一个重复的整数,返回这个重复的数。要求不修改数组,且只使用常量额外空间。

解题思路:快慢指针(模拟环形链表)

将数组视为环形链表 :每个元素的值是下一个节点的索引(如 nums[0]指向 nums[nums[0]])。由于存在重复数,链表中必然有环,且重复数是环的入口。

步骤:

  1. 第一阶段:快慢指针相遇 :慢指针每次走一步(slow = nums[slow]),快指针每次走两步(fast = nums[nums[fast]]),直到相遇(进入环内)。

  2. 第二阶段:找环的入口:将其中一个指针放回起点,两个指针每次都走一步,再次相遇的位置即为重复数(环的入口)。

代码实现(C++)

复制代码
class Solution {
public:
    int findDuplicate(vector<int>& nums) {     
   // 第一阶段:快慢指针相遇(找环内相遇点)
        int slow = nums[0];
        int fast = nums[0];
        do {
            slow = nums[slow];       // 慢指针走一步
            fast = nums[nums[fast]]; // 快指针走两步
        } while (slow != fast);
        
        // 第二阶段:找环的入口(重复数)
        int p1 = nums[0];
        int p2 = slow; // 或 fast,此时两者相等
        while (p1 != p2) {
            p1 = nums[p1];
            p2 = nums[p2];
        }
        return p1;
    }
};

总结

这几道题分别考察了位运算投票算法排列的字典序规律链表成环问题的转化。通过掌握这些经典解法,不仅能解决对应的题目,还能加深对数组操作、算法思想(如抵消、双指针、模拟链表)的理解。

后续我会继续分享更多热题的解析,希望能和大家一起在算法学习中进步!

如果觉得内容有帮助,欢迎点赞、收藏和关注~

相关推荐
共享家95272 小时前
实现简化的高性能并发内存池
开发语言·数据结构·c++·后端
老四啊laosi2 小时前
[双指针] 3. 力扣--快乐数
算法·leetcode·快慢指针
rit84324992 小时前
利用随机有限集(RFS)理论结合ILQR和MPC控制蜂群的MATLAB实现
算法·matlab
会编程的土豆2 小时前
leetcode hot 100 之哈希
算法·leetcode·哈希算法
秋天的一阵风2 小时前
【LeetCode 刷题系列|第 3 篇】详解大数相加:从模拟竖式到简洁写法的优化之路🔢
前端·算法·面试
qwehjk20082 小时前
分布式计算C++库
开发语言·c++·算法
m0_716765232 小时前
C++提高编程--仿函数、常用遍历算法(for_each、transform)详解
java·开发语言·c++·经验分享·算法·青少年编程·visual studio
_深海凉_2 小时前
LeetCode热题100-反转链表
python·leetcode·链表
寻寻觅觅☆2 小时前
东华OJ-基础题-59-倒数数列(C++)
开发语言·c++·算法