0 Basic
- 位运算基础
1# 基本操作
2a & b # 按位与:两个都是 1 才为 1
3a | b # 按位或:有一个 1 就为 1
4a ^ b # 按位异或:不同为 1,相同为 0
5~a # 按位取反
6a << n # 左移 n 位(×2^n)
7a >> n # 右移 n 位(÷2^n)
8
9# 异或的重要性质
10a ^ 0 = a # 任何数异或 0 不变
11a ^ a = 0 # 任何数异或自身为 0
12a ^ b ^ a = b # 异或可以"消除"成对出现的数
1. 只出现一次的数字 | Easy
题目:非空数组,除一个元素只出现一次外,其余元素均出现两次。找出那个数。要求 O(n) 时间、O(1) 空间
思路分析:
- 朴素做法:哈希表/Counter统计每个数字出现次数,空间为O(n)
- 位运算异或:
a ^ 0 = a,a ^ a = 0,a ^ b ^ a = b
踩坑记录:
- 初始值为0(从第一个开始遍历),或者
nums[0](从第二个开始遍历)
代码:
1
2class Solution:
3 def singleNumber(self, nums: List[int]) -> int:
4 cnt = Counter(nums)
5 for k, v in cnt.items():
6 if v == 1:
7 return k
1class Solution:
2 def singleNumber(self, nums: List[int]) -> int:
3 res = 0
4 for num in nums:
5 res ^= num
6 return res
1from functools import reduce
2from operator import xor
3
4def singleNumber(nums: list[int]) -> int:
5 return reduce(xor, nums)
复杂度:
- 时间O(n)
- 空间O(1)
2. 多数元素 | Easy
题目:数组中出现次数 > ⌊n/2⌋ 的元素,保证存在
思路分析:
排序法:排序后直接返回
nums[len(nums) // 2]
哈希法:记录每个数出现次数
候选法:维护一个候选人 candidate 和一个"血量" cnt:
- 遇到和
candidate相同的数 →cnt += 1(援军) - 遇到不同的数 →
cnt -= 1(同归于尽) cnt == 0时 → 当前候选人被全歼,下一个数直接接任候选人
踩坑记录:
cnt归零那一刻要立刻换人,不要写成"先减再判断"导致漏换
代码:
1class Solution:
2 def majorityElement(self, nums: List[int]) -> int:
3 nums.sort()
4 return nums[len(nums) // 2]
1class Solution:
2 def majorityElement(self, nums: List[int]) -> int:
3 count = Counter(nums)
4 n = len(nums) // 2
5 for k, v in count.items():
6 if v > n:
7 return k
1class Solution:
2 def majorityElement(self, nums: List[int]) -> int:
3 candidate, count = 0, 0
4 for num in nums:
5 if count == 0:
6 candidate = num
7 count += 1 if candidate == num else -1
8 return candidate
复杂度:
排序:
- 时间O(nlogn)
- 空间O(1)
哈希:
- 时间O(n)
- 空间O(n)
候选:
- 时间O(n)
- 空间O(1)
3. 颜色分类 | Medium
题目:数组只含 0、1、2,原地排序。要求一趟扫描、常数空间
思路分析:
排序计数:数0/1/2各有多少个,然后原地修改,但是需要两趟
指针:三个指针zero, i, two把数组切成了四段
[0, zero) 全是 0
[zero, i) 全是 1
[i, two] 未知区(还没看过)
(two, n-1] 全是 2
遇到 0 和遇到 2,处理方式不对称:
- 遇到 0:和
nums[zero]交换。nums[zero]处于[zero, i)段,必然是 1(已验过身),换到i位置后直接i++安全 - 遇到 2:和
nums[two]交换。nums[two]来自未知区,没验过身,所以i绝对不能动,下一轮还要重新检查它
踩坑记录:
- 遇到 2 交换后
i不能++ - 循环条件是
while i <= two,不是i < n。two右边已经排好了,越过去会把 2 又搬回 zero和i在遇到 0 时同时++(因为zero <= i恒成立,交换的是一个 1 和一个 0)
代码:
1class Solution:
2 def sortColors(self, nums: List[int]) -> None:
3 count = [0] * 3
4 for num in nums:
5 count[num] += 1
6
7 i = 0
8 for color in range(3):
9 for _ in range(count[color]):
10 nums[i] = color
11 i += 1
1class Solution:
2 def sortColors(self, nums: List[int]) -> None:
3 zero, i, two = 0, 0, len(nums) - 1
4 while i <= two:
5 if nums[i] == 0:
6 nums[zero], nums[i] = nums[i], nums[zero]
7 zero += 1
8 i += 1
9 elif nums[i] == 1:
10 i += 1
11 else:
12 nums[two], nums[i] = nums[i], nums[two]
13 two -= 1
复杂度:
计数法:
- 时间O(n)
- 空间O(1)
指针法:
- 时间O(n)
- 空间O(1)
4. 下一个排列 | Medium
题目:原地把数组变成字典序的下一个更大排列;如果不存在(已是降序),变成最小排列(升序)
思路分析:
踩坑记录:
- 找
i的条件是nums[i] >= nums[i+1]才继续左移(带等号),否则遇到重复元素会停错位置 - 找
j的条件是nums[j] <= nums[i]才继续左移(带等号),必须找严格大于的 - 最后是 reverse 不是 sort,写
sort虽然结果对但复杂度退化成 O(n log n) i = -1的分支不要漏,靠if i >= 0包住交换,反转永远执行
代码:
1class Solution:
2 def nextPermutation(self, nums: List[int]) -> None:
3 n = len(nums)
4
5 i = n - 2
6 while i >= 0 and nums[i] >= nums[i + 1]:
7 i -= 1
8
9 if i >= 0:
10 j = n - 1
11 while j >= 0 and nums[j] <= nums[i]:
12 j -= 1
13 nums[i], nums[j] = nums[j], nums[i]
14
15 l, r = i + 1, n - 1
16 while l < r:
17 nums[l], nums[r] = nums[r], nums[l]
18 l += 1
19 r -= 1
复杂度:
- 时间O(n)
- 空间O(1)
5. 寻找重复数 | Medium
题目:长度 n+1 的数组,元素在 [1, n],只有一个重复数(可能重复多次)。要求不修改数组、O(1) 额外空间
思路分析:
踩坑记录:
- 起点必须是 0,不能是
nums[0]。因为 0 是唯一保证不在环上的节点 - 第一阶段用
while True + break,不能用while slow != fast:初始时两者都在 0,会直接退出
代码:
1class Solution:
2 def findDuplicate(self, nums: List[int]) -> int:
3 count = Counter(nums)
4 for k, v in count.items():
5 if v > 1:
6 return k
1class Solution:
2 def findDuplicate(self, nums: List[int]) -> int:
3 slow = fast = 0
4
5 while True:
6 slow = nums[slow]
7 fast = nums[nums[fast]]
8 if slow == fast:
9 break
10
11 slow = 0
12 while slow != fast:
13 slow = nums[slow]
14 fast = nums[fast]
15 if slow == fast:
16 return slow
复杂度:
- 时间O(n)
- 空间O(1)