前言
题单LeetCode 热题 100的技巧部分,给出解析。
136. Single Number
题目描述
Given a non-empty array of integers nums
, every element appears twice except for one. Find that single one.
You must implement a solution with a linear runtime complexity and use only constant extra space.
Example 1:
1 | Input: nums = [2,2,1] |
Example 2:
1 | Input: nums = [4,1,2,1,2] |
Example 3:
1 | Input: nums = [1] |
Constraints:
1 <= nums.length <= 3 * 10^4
-3 * 10^4 <= nums[i] <= 3 * 10^4
- Each element in the array appears twice except for one element which appears only once.
解题方法
给定一个非空整数数组 nums
,其中除了一个元素之外,其他每个元素都出现了两次。要求找到那个只出现一次的元素。
这个问题要求我们实现一个线性时间复杂度的解决方案,并且只能使用常量级别的额外空间。
因为要求只能够使用常量级别的额外空间,所以我们就不能够使用哈希表来处理这个问题。
可以考虑使用异或操作:
利用异或操作:
- 异或(XOR)操作有两个非常重要的性质:
- $a⊕a=0$(任何数与自己异或结果为0)
- $a⊕0=a$(任何数与0异或结果还是这个数)
- 异或(XOR)操作有两个非常重要的性质:
应用异或找到单一数字:
- 如果我们将数组中的所有数字都进行异或操作,相同的数字由于异或的性质会互相抵消为0,最终剩下的就是那个只出现一次的数字。
- 这种方法的时间复杂度是 O(n),并且只使用了 O(1) 的额外空间。
- 如果我们将数组中的所有数字都进行异或操作,相同的数字由于异或的性质会互相抵消为0,最终剩下的就是那个只出现一次的数字。
代码实现
1 | def singleNumber(self, nums: List[int]) -> int: |
复杂度分析
时间复杂度:$O(n)$。
空间复杂度:$O(1)$。
169. Majority Element
题目描述
Given an array nums
of size n
, return the majority element.
The majority element is the element that appears more than ⌊n / 2⌋
times. You may assume that the majority element always exists in the array.
Example 1:
1 | Input: nums = [3,2,3] |
Example 2:
1 | Input: nums = [2,2,1,1,1,2,2] |
Constraints:
n == nums.length
1 <= n <= 5 * 10^4
-10^9 <= nums[i] <= 10^9
Follow-up: Could you solve the problem in linear time and in O(1)
space?
解题方法
摩尔投票法
摩尔投票法可以很方便地解决这个问题。
哈希表
key
:元素本身,value
:出现次数。
遍历数组,统计出现次数之后计算谁出现次数最大,大于 $⌊n / 2⌋$ 则为众数。
排序
一定会出现众数,则从小到大排序之后取最中间的那个数就是结果。
代码实现
摩尔投票法
1 | def majorityElement(self, nums: List[int]) -> int: |
哈希表
1 | def majorityElement(self, nums: List[int]) -> int: |
排序
1 | def majorityElement(self, nums: List[int]) -> int: |
复杂度分析
方法 | 时间复杂度 | 空间复杂度 |
---|---|---|
摩尔投票法 | $O(n)$ | $O(1)$ |
哈希表 | $O(n)$ | $O(n)$ |
排序 | $O(nlogn)$ | 根据排序算法决定 |
75. Sort Colors
题目描述
Given an array nums
with n
objects colored red, white, or blue, sort them in-place so that objects of the same color are adjacent, with the colors in the order red, white, and blue.
We will use the integers 0
, 1
, and 2
to represent the color red, white, and blue, respectively.
You must solve this problem without using the library’s sort function.
Example 1:
1 | Input: nums = [2,0,2,1,1,0] |
Example 2:
1 | Input: nums = [2,0,1] |
Constraints:
n == nums.length
1 <= n <= 300
nums[i]
is either0
,1
, or2
.
Follow up: Could you come up with a one-pass algorithm using only constant extra space?
解题方法
排序
要求原地变换,使用冒泡排序可解。
双指针
这是一个经典的荷兰国旗问题,可以用双指针法(Two Pointers)来实现一次遍历的解决方案,同时只需要常量的额外空间。我们将数组中的 0
、1
和 2
分别视作红色、白色和蓝色,需要将它们按照顺序排序,即先把所有 0
放在前面,接着是 1
,最后是 2
。
解题思路: 我们使用三个指针:
low
:指向最左边的未放好的红色(0)位置。mid
:当前处理的位置。high
:指向最右边的未放好的蓝色(2)位置。
我们遍历数组 nums
,当 mid
小于等于 high
时:
- 如果
nums[mid] == 0
,将其与nums[low]
交换,并将low
和mid
都向右移动。 - 如果
nums[mid] == 1
,不需要交换,只需将mid
向右移动。 - 如果
nums[mid] == 2
,将其与nums[high]
交换,并将high
向左移动,但不移动mid
,因为从high
交换过来的元素仍需要处理。
计数排序
除了使用双指针法(Two Pointers)的一次遍历解法外,还可以使用计数排序法(Counting Sort),这是一个两次遍历的解法。虽然这种方法的时间复杂度也是 O(n)
,但它相对更简单,适合更容易理解和实现。
思路:
- 首先遍历数组,统计每种颜色(即
0
、1
、2
)的出现次数。 - 然后根据统计结果,重新写回数组,使得数组中按顺序排列红色(
0
)、白色(1
)、蓝色(2
)。
步骤:
- 初始化三个计数器
count0
、count1
和count2
,分别记录0
、1
和2
的出现次数。 - 遍历整个数组,统计每个数字的出现次数。
- 根据计数结果,按照
0
的数量写入0
,再按照1
的数量写入1
,最后按照2
的数量写入2
。
代码实现
排序
1 | class Solution: |
双指针
1 | class Solution: |
计数排序
1 | class Solution: |
复杂度分析
方法 | 时间复杂度 | 空间复杂度 |
---|---|---|
排序 | $O(n^2)$ | $O(1)$ |
双指针 | $O(n)$ | $O(1)$ |
计数排序 | $O(n)$ | $O(1)$ |
31. Next Permutation
题目描述
A permutation of an array of integers is an arrangement of its members into a sequence or linear order.
- For example, for
arr = [1,2,3]
, the following are all the permutations ofarr
:[1,2,3], [1,3,2], [2, 1, 3], [2, 3, 1], [3,1,2], [3,2,1]
.
The next permutation of an array of integers is the next lexicographically greater permutation of its integer. More formally, if all the permutations of the array are sorted in one container according to their lexicographical order, then the next permutation of that array is the permutation that follows it in the sorted container. If such arrangement is not possible, the array must be rearranged as the lowest possible order (i.e., sorted in ascending order).
- For example, the next permutation of
arr = [1,2,3]
is[1,3,2]
. - Similarly, the next permutation of
arr = [2,3,1]
is[3,1,2]
. - While the next permutation of
arr = [3,2,1]
is[1,2,3]
because[3,2,1]
does not have a lexicographical larger rearrangement.
Given an array of integers nums
, find the next permutation of nums
.
The replacement must be in place and use only constant extra memory.
Example 1:
1 | Input: nums = [1,2,3] |
Example 2:
1 | Input: nums = [3,2,1] |
Example 3:
1 | Input: nums = [1,1,5] |
Constraints:
1 <= nums.length <= 100
0 <= nums[i] <= 100
解题方法
题目要求找到一个数组的下一个排列,即字典序中的下一个较大排列。如果当前排列已经是字典序的最大排列,那么需要返回字典序最小的排列(升序排列)。要求就地替换,且只使用常数级额外空间。
解题思路:
从右往左找到第一个不符合递增趋势的元素:
- 从数组末尾开始,找到第一个前一位小于后一位的元素,记作
nums[i]
。此时,[i+1, n-1]
部分是一个递减序列。
- 从数组末尾开始,找到第一个前一位小于后一位的元素,记作
从右往左找到比
nums[i]
大的最小元素:- 在递减序列中,从右往左找到第一个比
nums[i]
大的元素,记作nums[j]
。
- 在递减序列中,从右往左找到第一个比
交换
nums[i]
和nums[j]
:- 交换这两个元素后,
[i+1, n-1]
部分仍然是递减序列。
- 交换这两个元素后,
反转
[i+1, n-1]
部分:- 将
[i+1, n-1]
部分反转,使其变为升序(最小字典序)。
- 将
如果找不到 nums[i]
,说明整个数组是递减的,直接将其反转为升序即可。
代码实现
1 | class Solution: |
复杂度分析
时间复杂度: $O(n)$,最多遍历数组两次:一次寻找i
和j
,一次反转后半部分。
空间复杂度: $O(1)$,使用了常数级别的额外空间。
287. Find the Duplicate Number
题目描述
Given an array of integers nums
containing n + 1
integers where each integer is in the range [1, n]
inclusive.
There is only one repeated number in nums
, return this repeated number.
You must solve the problem without modifying the array nums
and uses only constant extra space.
Example 1:
1 | Input: nums = [1,3,4,2,2] |
Example 2:
1 | Input: nums = [3,1,3,4,2] |
Example 3:
1 | Input: nums = [3,3,3,3,3] |
Constraints:
1 <= n <= 10^5
nums.length == n + 1
1 <= nums[i] <= n
- All the integers in
nums
appear only once except for precisely one integer which appears two or more times.
Follow up:
- How can we prove that at least one duplicate number must exist in
nums
? - Can you solve the problem in linear runtime complexity?
解题方法
本题同样可以使用「龟兔赛跑」的思想,但我们需要将输入的数组看成链表。
根据题目给出的前提,n + 1
长度的数组存储的数据大小区间在 [1, n]
里,且只有 1 个重复数字。
那么, 我们考虑将数组索引和数组的数值做一个映射关系:
$$
n→f(n)=n→nums[n]
$$
以数组 [1,3,4,2]
为例子,这个映射关系 $n→f(n)$ 为:
- 0→1 (索引 0 的值为 1)
- 1→3 (索引 1 的值为 3)
- 2→4 (索引 2 的值为 4)
- 3→2 (索引 3 的值为 2)
从数组的索引 0 出发,根据 $f(n)$ 计算出一个数值,将这个值作为新的下标,同样地再用这个函数计算,依次类推,直到下标出界,这样得出一个序列:
$0→1→3→2→4→null$
数组中没有重复数字时,可以发现序列不出现循环,那么数组中如果有重复的数字呢?
以数组 [1,3,4,2,2]
为例,分析序列:
$0→1→3→2→4→2→4→…$
其中 $2→4$ 是一个循环,所以,当数组中出现重复的数时,会产生多对一的映射,就会形成一个“环”。
因此,我们就可以使用龟兔赛跑算法,来解决环形链表问题:
- 数组中有一个重复整数等价于链表中存在一个环
- 数组中的重复整数等价于链表的环入口
代码实现
1 | class Solution: |
复杂度分析
时间复杂度: $O(n)$
空间复杂度: $O(1)$