1. 只出现一次的数字 (Single Number)
难度:简单
🎯 核心技巧:位运算(异或 XOR)
💡 解题思路:
题目要求线性时间复杂度且不使用额外空间。
利用异或运算(^)的性质:
- 归零律 : a ⊕ a = 0 a \oplus a = 0 a⊕a=0(任何数和自己异或归零)
- 恒等律 : a ⊕ 0 = a a \oplus 0 = a a⊕0=a(任何数和 0 异或不变)
- 交换律和结合律 : a ⊕ b ⊕ a = ( a ⊕ a ) ⊕ b = 0 ⊕ b = b a \oplus b \oplus a = (a \oplus a) \oplus b = 0 \oplus b = b a⊕b⊕a=(a⊕a)⊕b=0⊕b=b
解法:将数组中所有数字进行异或,成对出现的数字会互相抵消变成 0,最终剩下的结果就是那个只出现一次的数字。
💻 代码:
python
class Solution:
def singleNumber(self, nums: List[int]) -> int:
res = 0
for num in nums:
res ^= num
return res
2. 多数元素 (Majority Element)
难度:简单
🎯 核心技巧:摩尔投票法 (Boyer-Moore Voting Algorithm)
💡 解题思路:
题目定义多数元素是指出现次数大于 ⌊ n/2 ⌋ 的元素。
可以将这个问题看作"诸侯争霸":
- 维护一个
candidate(候选人)和一个count(票数)。 - 遍历数组:
- 如果
count为 0,假设当前数字为新的candidate,并将count设为 1。 - 如果当前数字等于
candidate,票数count + 1。 - 如果当前数字不等于
candidate,票数count - 1(互相抵消)。
- 如果
- 因为多数元素的数量超过一半,经过抵消后,最终剩下的
candidate一定是多数元素。
💻 代码:
python
class Solution:
def majorityElement(self, nums: List[int]) -> int:
candidate = None
count = 0
for num in nums:
if count == 0:
candidate = num
if num == candidate:
count += 1
else:
count -= 1
return candidate
3. 颜色分类 (Sort Colors)
难度:中等
🎯 核心技巧:三指针 (双指针的变种,荷兰国旗问题)
💡 解题思路:
要求原地排序且仅扫描一遍。我们需要将 0 放在左边,2 放在右边,1 自动就在中间了。
定义三个指针:
p0:指向 0 的右边界(下一个放 0 的位置)。p2:指向 2 的左边界(下一个放 2 的位置)。curr:当前遍历的指针。
逻辑:
- 如果
nums[curr] == 0:与p0交换,p0右移,curr右移。 - 如果
nums[curr] == 2:与p2交换,p2左移。注意:此时curr不移动,因为交换过来的数字可能是 0 或 2,需要再次判断。 - 如果
nums[curr] == 1:curr直接右移(不管它)。 - 当
curr > p2时停止。
💻 代码:
python
class Solution:
def sortColors(self, nums: List[int]) -> None:
p0 = 0
curr = 0
p2 = len(nums) - 1
while curr <= p2:
if nums[curr] == 0:
nums[curr], nums[p0] = nums[p0], nums[curr]
p0 += 1
curr += 1
elif nums[curr] == 2:
nums[curr], nums[p2] = nums[p2], nums[curr]
p2 -= 1
# 这里 curr 不自增,因为从后面换过来的数还需要判断
else:
curr += 1
4. 下一个排列 (Next Permutation)
难度:中等
🎯 核心技巧:两遍扫描(找"小数" -> 找"大数" -> 交换 -> 翻转)
💡 解题思路:
我们要找到一个比当前排列稍大一点点的排列。
- 从后往前 找第一个升序对 ( i , i + 1 ) (i, i+1) (i,i+1),即满足
nums[i] < nums[i+1]。此时nums[i]就是需要被替换的"较小数"。 - 如果找不到(整个数组是降序),说明这是最大的排列,直接翻转整个数组即可。
- 如果在步骤 1 找到了
i,再从后往前 找第一个比nums[i]大的数nums[j]("较大数")。 - 交换
nums[i]和nums[j]。 - 此时
i后面的部分是降序的,将其翻转为升序,以保证变大的幅度最小。
💻 代码:
python
class Solution:
def nextPermutation(self, nums: List[int]) -> None:
n = len(nums)
i = n - 2
# 1. 从后往前找第一个升序对 (i, i+1)
while i >= 0 and nums[i] >= nums[i + 1]:
i -= 1
if i >= 0:
# 2. 从后往前找第一个比 nums[i] 大的数
j = n - 1
while j >= 0 and nums[j] <= nums[i]:
j -= 1
# 3. 交换
nums[i], nums[j] = nums[j], nums[i]
# 4. 翻转 i 之后的所有元素
# (如果 i < 0,即没找到升序对,这里会翻转整个数组,符合题意)
left, right = i + 1, n - 1
while left < right:
nums[left], nums[right] = nums[right], nums[left]
left += 1
right -= 1
5. 寻找重复数 (Find the Duplicate Number)
难度:中等
🎯 核心技巧:快慢指针 (Floyd 判圈算法 / 链表成环)
💡 解题思路:
题目限制:不能修改数组(不能排序)、 O ( 1 ) O(1) O(1) 空间(不能用哈希表)。
这就转化为了链表找环入口的问题。
- 映射关系 :将数组下标 i i i 视为节点, n u m s [ i ] nums[i] nums[i] 视为
next指针指向的下一个节点索引。 - 因为有重复数,意味着有多个下标指向同一个值,这在图论中就形成了一个环。
步骤:
- 快慢指针相遇 :
slow走一步,fast走两步,直到它们相遇。 - 找环入口 :保持
slow在相遇点,将fast(或新建一个指针)重置回起点0。 - 同步前进:两个指针每次都走一步,再次相遇的点就是环的入口,也就是重复的数字。
💻 代码:
python
class Solution:
def findDuplicate(self, nums: List[int]) -> int:
# 第一阶段:快慢指针找相遇点
slow = nums[0]
fast = nums[nums[0]]
while slow != fast:
slow = nums[slow]
fast = nums[nums[fast]]
# 第二阶段:找环入口
ptr1 = 0
ptr2 = slow
while ptr1 != ptr2:
ptr1 = nums[ptr1]
ptr2 = nums[ptr2]
return ptr1