力扣hot100-《技巧》章节题解

力扣hot100-《技巧》章节题解

周三 2月 11 2026 技巧
2876 字 · 14 分钟

一、搜索插入位置

136. 只出现一次的数字

简单

给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。

示例 1 :

输入:nums = [2,2,1]

输出:1

示例 2 :

输入:nums = [4,1,2,1,2]

输出:4

示例 3 :

输入:nums = [1]

输出:1

提示:

  • 1 <= nums.length <= 3 * 10^4
  • -3 * 10^4 <= nums[i] <= 3 * 10^4
  • 除了某个元素只出现一次以外,其余每个元素均出现两次。

1.1 异或^

异或运算 a ⊕ a = 0,我们可以用异或来消除所有重复出现的元素,最后剩下的一定是只出现一次的元素。

a ⊕ b = b ⊕ a

(a ⊕ b) ⊕ c = a ⊕ (b ⊕ c)

❓直接初始化ans = 0会不会影响结果

  • 如果唯一出现的元素是0,那么异或完所有数字后结果仍然为0,不影响
  • 如果唯一出现的元素不是0,0可以看成任意两个重复的数字异或出来的结果,也不影响
JAVA
class Solution {
    public int singleNumber(int[] nums) {
        int ans = 0;
        for(int num: nums) ans ^= num;
        return ans;
    }
}

二、多数元素

169. 多数元素

简单

给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

示例 1:

PLAINTEXT
输入:nums = [3,2,3]
输出:3

示例 2:

PLAINTEXT
输入:nums = [2,2,1,1,1,2,2]
输出:2

提示:

  • n == nums.length
  • 1 <= n <= 5 * 10^4
  • -10^9 <= nums[i] <= 10^9
  • 输入保证数组中一定有一个多数元素。

进阶:尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。


2.1 严格众数

目标元素在数组中出现次数 大于 ⌊ n/2 ⌋ ,说明其他元素加起来的次数一定小于⌊ n/2 ⌋

任意两个不一样的元素一比一消去,消去后剩下的数就是目标数

就像竞选投票一样,现在有很多个竞选人,nums[i] = 111表示给第111位竞选者投了一票,现在我们要找出得票最多的人

轮流唱票,在最终的两个人对决之中,你我各得一票 等于 我们俩个人都不得票,从中任意选取一个人为临时胜出者即可

由于是严格众数,所以最终获胜的竞选人的票数一定会压其他人一头

JAVA
class Solution {
    public int majorityElement(int[] nums) {
       int ans = 0;
       int ticket = 0;
       for (int n : nums) {
        if (ticket == 0) {	// 你我各得一票 等于 我们俩个人都不得票
            ans = n;		// 选取当前竞选者为临时胜出者
            ticket = 1;		// 重新开始计票
        } else ticket += n == ans ? 1 : -1;	// 投的是同一个人,加一票,否则扣一票
       }
       return ans;
    }
}

三、颜色分类

75. 颜色分类

中等

给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums原地 对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

我们使用整数 012 分别表示红色、白色和蓝色。

必须在不使用库内置的 sort 函数的情况下解决这个问题。

示例 1:

PLAINTEXT
输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]

示例 2:

PLAINTEXT
输入:nums = [2,0,1]
输出:[0,1,2]

提示:

  • n == nums.length
  • 1 <= n <= 300
  • nums[i]012

进阶:

  • 你能想出一个仅使用常数空间的一趟扫描算法吗?

3.1 两次双指针

l)指针保护已经归位的数字,用r指针寻找需要归位的数字

  • 第一趟r不断向右找0,找到了就换到l指针处,交给其保护
  • r拉回到l指针位置
  • 第二趟r不断向右找1,并把他们放到左边
JAVA
class Solution {
    public void sortColors(int[] nums) {
        int l = 0, r = 0;

        // 处理0
        while (r < nums.length) {
            // r指针向右找0
            while (r < nums.length && nums[r] != 0) r++;
            if (r == nums.length) break;

            // 找到0了把他换到左边,并移动左边界保护已处理的0
            int tmp = nums[l];
            nums[l] = nums[r];
            nums[r] = tmp;
            l++;
            r++;
        }

        r = l;  // 拉回到左指针
        // 处理1
        while (r < nums.length) {
            // r指针向右找1
            while (r < nums.length && nums[r] != 1) r++;
            if (r == nums.length) break;

            // 找到1了把他换到左边,并移动左边界保护已处理的0和1
            int tmp = nums[l];
            nums[l] = nums[r];
            nums[r] = tmp;
            l++;
            r++;
        }
    }
}

3.2 一次双指针

  1. 对于一个已经有序的颜色分类数组,如果要插入一个新的颜色
  • 插入2,直接在数组末尾插入
  • 插入1,在1的末尾和2的开头插入(等价于把第一个2变为1,然后在末尾插一个2)
  • 插入0,在0的末尾和1的开头插入(等价于把第一个1变为0,把第一个2变为1,然后在末尾插一个2)
  1. 遍历到第i个元素时,相当于:对于一个已经有序的颜色分类数组 [0, i - 1],插入一个新的颜色 nums[i]
  2. 从1的结果来看,无论nums[i](新插入的颜色)是什么,都有一个共同的操作:插一个2i处(即:有序数组 [0, i - 1]的末尾)
  • 所以我们先记录nums[i]的值为x,然后直接把他设为2
  • 然后判断x是否为1,如果为1,把第一个2变为1
  • 然后判断x是否为0,如果为0,把第一个2变为1,把第一个1变为0

l)保护已经处理完的0,用r)保护已经处理完的1和0,2个指针分割三个区域

[0, l)都为0,[l, r)都为1,[r, nums.length)都为2

JAVA
class Solution {
    public void sortColors(int[] nums) {
        int l = 0, r = 0;
        for (int i = 0; i < nums.length; i++) {
            int t = nums[i];
            nums[i] = 2;
            if (t <= 1) {
                nums[r++] = 1;
            }
            if (t == 0) {
                nums[l++] = 0;
            }
        }
    }
}

对于nums = [1, 0, 2, 1, 2, 0, 2, 1, 2, 2, 0]

  • nums = [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2],先全变成2
  • nums = [1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2],再变成1
  • nums = [1, 0, 2, 1, 2, 0, 2, 1, 2, 2, 0],然后变成0

num[i] = 2在所有循环内都会执行,所以从结果看是先全变成2,然后nums[p1]=1在所有<=1的情况下都会执行,所以从结果看是2中部分替换为1,同理0就是在2的1中部分替换为0


四、下一个排列

31. 下一个排列

中等

整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。

  • 例如,arr = [1,2,3] ,以下这些都可以视作 arr 的排列:[1,2,3][1,3,2][3,1,2][2,3,1]

整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。

  • 例如,arr = [1,2,3] 的下一个排列是 [1,3,2]
  • 类似地,arr = [2,3,1] 的下一个排列是 [3,1,2]
  • arr = [3,2,1] 的下一个排列是 [1,2,3] ,因为 [3,2,1] 不存在一个字典序更大的排列。

给你一个整数数组 nums ,找出 nums 的下一个排列。

必须 原地 修改,只允许使用额外常数空间。

示例 1:

PLAINTEXT
输入:nums = [1,2,3]
输出:[1,3,2]

示例 2:

PLAINTEXT
输入:nums = [3,2,1]
输出:[1,2,3]

示例 3:

PLAINTEXT
输入:nums = [1,1,5]
输出:[1,5,1]

提示:

  • 1 <= nums.length <= 100
  • 0 <= nums[i] <= 100

4.1 移动山谷

对于nums = [3, 1, 2, 4],横看成岭侧成峰,把它想象成一座山,人站在最右侧的山峰上从右往左看

PLAINTEXT
	  人			    人
	  |					|
|     |		===>	|   |
|   | |				|   | |
| | | |				| | | |
3 1 2 4				3 1 4 2

类比满十进1,当前位置数字为9,数字 变大 后,需要把当前位置数字重置为最小0,即 9 + 1 = 1 0

从小到大找,找到第一个变小的地方,再从小到大找一个比他大的数对换

即找到第一个 nums[l] < nums[l + 1],对于8397521

  • 这是唯一能“变大”的位置,即 8 3 97521
  • 因为 [l + 1 … nums.length)降序,即 83 97521

即找到 r 满足 nums[r] > nums[l],交换nums[l]和nums[r]后,数字会 变大,即8 5 97 3 21

  • 因为l右边是降序,所以翻转[l+1, nums.length)后就变成升序,这样子就是下一个排列:85 12379
JAVA
class Solution {
    public void nextPermutation(int[] nums) {
        int length = nums.length;
        
        // 从右向左找到第一个下降的位置
        int l = length - 2;
        while (l >= 0 && nums[l] >= nums[l + 1]) l--;
        
        
        // 找到了下降的位置
        if (l >= 0) {
            // 从右向左找到第一个比 nums[l] 大的数
            int r = length - 1;
            while (r >= 0 && nums[r] <= nums[l]) r--;
            
            // 交换 nums[l] 和 nums[r]
            swap(nums, l, r);
        }
        
        // 反转 l+1 到末尾的部分
        reverse(nums, l + 1, length - 1);
    }
    
    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
    
    private void reverse(int[] nums, int start, int end) {
        while (start < end) {
            swap(nums, start, end);
            start++;
            end--;
        }
    }
}

五、寻找重复数

287. 寻找重复数

中等

给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1n),可知至少存在一个重复的整数。

假设 nums 只有 一个重复的整数 ,返回 这个重复的数

你设计的解决方案必须 不修改 数组 nums 且只用常量级 O(1) 的额外空间。

示例 1:

PLAINTEXT
输入:nums = [1,3,4,2,2]
输出:2

示例 2:

PLAINTEXT
输入:nums = [3,1,3,4,2]
输出:3

示例 3 :

PLAINTEXT
输入:nums = [3,3,3,3,3]
输出:3 

提示:

  • 1 <= n <= 10^5
  • nums.length == n + 1
  • 1 <= nums[i] <= n
  • nums只有一个整数 出现 两次或多次 ,其余整数均只出现 一次

进阶:

  • 如何证明 nums 中至少存在一个重复的数字?
  • 你可以设计一个线性级时间复杂度 O(n) 的解决方案吗?

5.1 快慢指针

给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1n),可知至少存在一个重复的整数

题中这句话说明了nums数组的构成为:

  • 1到n的所有整数各一个
  • 随机一个重复的数字
  • 数字随机打乱

nums[i]当成指针,从下标为0开始,nums[nums[i]]将会无重复地遍历完数组的每个元素(因为1到n的所有整数各一个

但是由于唯一重复元素的存在,所以这个数组存在一个环,遍历入环后会在环中循环遍历

👉解法参考:环形链表 II

lc287-1.png

JAVA
class Solution {
    public int findDuplicate(int[] nums) {
        int slow = 0, fast = 0;
        while (true) {
            slow = nums[slow];
            fast = nums[nums[fast]];
            if (slow == fast) break;
        }

        int head = 0;
        while (head != slow) {
            head = nums[head];
            slow = nums[slow];
        }

        return slow;
    }
}

Thanks for reading!

力扣hot100-《技巧》章节题解

周三 2月 11 2026 技巧
2876 字 · 14 分钟