1. 题目解析与解题思路
1.1 Remove Duplicates from Sorted Array
这道题要求我们在原地(in-place)移除已排序数组中的重复元素,并返回新数组的长度。关键在于理解题目要求:不能使用额外的数组空间,必须在原数组上修改,且不需要考虑新长度之后的元素。
核心思路:使用双指针技巧。慢指针(slow)指向当前不重复元素的最后一个位置,快指针(fast)用于遍历数组。当nums[fast] != nums[slow]时,将nums[fast]复制到nums[slow+1],然后slow++。
python复制def removeDuplicates(nums):
if not nums:
return 0
slow = 0
for fast in range(1, len(nums)):
if nums[fast] != nums[slow]:
slow += 1
nums[slow] = nums[fast]
return slow + 1
时间复杂度:O(n),只需遍历一次数组
空间复杂度:O(1),没有使用额外空间
1.2 Majority Element
这道题要求找出数组中出现次数超过⌊n/2⌋次的元素(主元素)。有多种解法,最优解法是Boyer-Moore投票算法。
投票算法原理:维护一个候选元素和计数器。遍历数组时,如果计数器为0,选择当前元素作为候选;如果当前元素等于候选,计数器加1,否则减1。最终剩下的候选就是主元素。
python复制def majorityElement(nums):
count = 0
candidate = None
for num in nums:
if count == 0:
candidate = num
count += (1 if num == candidate else -1)
return candidate
时间复杂度:O(n)
空间复杂度:O(1)
1.3 Linked List Cycle
这道题要求判断链表是否有环。经典解法是快慢指针(Floyd判圈算法)。
算法原理:慢指针每次移动一步,快指针每次移动两步。如果存在环,快慢指针最终会相遇;如果快指针到达链表尾部(None),则无环。
python复制def hasCycle(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
return True
return False
时间复杂度:O(n)
空间复杂度:O(1)
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 双指针技巧深度解析
2.1 双指针的三种常见模式
- 同向双指针:如Remove Duplicates问题,两个指针从同一侧出发,快指针探索,慢指针构建结果。
- 相向双指针:如Two Sum问题,一个指针从头部开始,一个从尾部开始,向中间移动。
- 快慢指针:如链表环检测,两个指针以不同速度移动。
2.2 双指针适用场景
- 数组/链表中的元素比较
- 滑动窗口问题
- 原地修改数组
- 环检测和链表中间节点查找
2.3 边界条件处理
在实现双指针算法时,需要特别注意:
- 空数组/链表的处理
- 单元素数组/链表
- 指针移动时的越界检查
- 循环终止条件
3. 投票算法与位操作解法
3.1 Majority Element的多种解法
除了投票算法,还可以用以下方法:
哈希表统计法:
python复制from collections import defaultdict
def majorityElement(nums):
counts = defaultdict(int)
for num in nums:
counts[num] += 1
if counts[num] > len(nums)//2:
return num
时间复杂度O(n),空间复杂度O(n)
排序法:
直接返回排序后数组的中间元素
python复制def majorityElement(nums):
nums.sort()
return nums[len(nums)//2]
时间复杂度O(nlogn),空间复杂度O(1)或O(n)取决于排序实现
3.2 位操作解法
对于整数数组,可以逐位统计:
python复制def majorityElement(nums):
major = 0
for i in range(32):
mask = 1 << i
count = 0
for num in nums:
if num & mask:
count += 1
if count > len(nums)//2:
major |= mask
return major if major < (1 << 31) else major - (1 << 32)
适用于元素范围较大的情况
4. 链表环检测的扩展问题
4.1 找出环的入口节点
在检测到环后,将慢指针移回链表头部,然后两个指针都以每次一步的速度移动,再次相遇点即为环入口。
python复制def detectCycle(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
slow = head
while slow != fast:
slow = slow.next
fast = fast.next
return slow
return None
4.2 计算环的长度
在快慢指针相遇后,保持一个指针不动,另一个指针继续移动并计数,直到再次相遇。
python复制def cycleLength(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
count = 1
fast = fast.next
while slow != fast:
fast = fast.next
count += 1
return count
return 0
4.3 链表相交问题
判断两个链表是否相交,可以:
- 遍历链表A到末尾,记录长度和尾节点
- 同样处理链表B
- 如果尾节点不同,则不相交
- 如果相同,将较长链表的指针移动到与较短链表相同长度的位置
- 同时移动两个指针,直到找到相同节点
5. 面试实战技巧
5.1 解题步骤建议
- 理解题意:明确输入输出要求,特别是边界条件
- 举例说明:用具体例子验证理解是否正确
- 暴力解法:先想最直观的解法,即使效率不高
- 优化思路:分析暴力解法的问题,思考优化方向
- 代码实现:写出清晰、模块化的代码
- 测试验证:用多个测试用例验证代码正确性
5.2 常见错误与避免方法
- 指针越界:在移动指针前检查next是否为None
- 无限循环:确保循环条件能正确终止
- 边界条件:空输入、单元素、全相同元素等情况
- 变量初始化:特别是计数器、指针的初始值
- 返回值错误:注意题目要求的返回值类型和含义
5.3 复杂度分析要点
- 时间复杂度:明确最坏、平均、最好情况
- 空间复杂度:考虑额外使用的空间,递归调用的栈空间
- 权衡比较:不同解法的时间空间权衡
- 优化极限:思考是否存在更优解法
6. 相关题目扩展练习
6.1 数组类题目
- Remove Element:类似Remove Duplicates,但移除特定值
- Move Zeroes:将0移到数组末尾,保持非零元素顺序
- Two Sum II:在排序数组中找两数之和等于目标值
- Container With Most Water:求最大水容器面积
6.2 链表类题目
- Reverse Linked List:反转链表
- Merge Two Sorted Lists:合并两个有序链表
- Palindrome Linked List:判断链表是否为回文
- Intersection of Two Linked Lists:找两个链表的交点
6.3 进阶题目
- Majority Element II:找出所有出现超过⌊n/3⌋次的元素
- Find the Duplicate Number:数组中只有一个重复数,找出它
- Linked List Cycle II:找出环的入口节点
- Rotate Array:旋转数组k次
7. 代码实现细节优化
7.1 Remove Duplicates的边界处理
原始实现可以优化初始条件检查:
python复制def removeDuplicates(nums):
if len(nums) < 2:
return len(nums)
slow = 0
for fast in range(1, len(nums)):
if nums[fast] != nums[slow]:
slow += 1
nums[slow] = nums[fast]
return slow + 1
7.2 Majority Element的验证步骤
虽然题目保证存在主元素,但实际面试中可以添加验证步骤:
python复制def majorityElement(nums):
candidate, count = None, 0
for num in nums:
if count == 0:
candidate = num
count += 1 if num == candidate else -1
# 验证步骤
if nums.count(candidate) > len(nums)//2:
return candidate
return -1 # 或者抛出异常
7.3 链表环检测的防御性编程
处理可能的无效输入:
python复制def hasCycle(head):
if not head or not head.next:
return False
slow, fast = head, head.next
while fast and fast.next:
if slow == fast:
return True
slow = slow.next
fast = fast.next.next
return False
8. 实际工程应用场景
8.1 数组去重的应用
- 数据库查询去重:避免返回重复记录
- 日志处理:合并相同错误日志
- 数据分析:预处理阶段清理重复数据
- 缓存系统:防止重复缓存相同内容
8.2 主元素识别的应用
- 选举系统:快速统计获胜者
- 数据分析:识别主要影响因素
- 系统监控:检测高频事件
- 推荐系统:确定用户主要偏好
8.3 链表环检测的应用
- 内存管理:检测循环引用
- 死锁检测:识别进程等待环
- 状态机验证:检查无限循环状态
- 依赖分析:避免循环依赖
9. 不同语言实现差异
9.1 Java实现要点
java复制// Remove Duplicates
public int removeDuplicates(int[] nums) {
if (nums.length == 0) return 0;
int slow = 0;
for (int fast = 1; fast < nums.length; fast++) {
if (nums[fast] != nums[slow]) {
nums[++slow] = nums[fast];
}
}
return slow + 1;
}
9.2 C++实现注意事项
cpp复制// Majority Element
int majorityElement(vector<int>& nums) {
int count = 0;
int candidate = 0;
for (int num : nums) {
if (count == 0) {
candidate = num;
}
count += (num == candidate) ? 1 : -1;
}
return candidate;
}
9.3 JavaScript特性处理
javascript复制// Linked List Cycle
function hasCycle(head) {
let slow = head;
let fast = head;
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
if (slow === fast) {
return true;
}
}
return false;
}
10. 性能优化与测试策略
10.1 大规模数据测试
- 随机测试:生成随机数组验证正确性
- 极端测试:全相同元素、无重复元素等
- 性能测试:百万级数据测试运行时间
- 内存测试:检测内存使用情况
10.2 算法选择权衡
- 时间优先:选择时间复杂度最优的算法
- 空间优先:选择空间复杂度最优的算法
- 实现复杂度:平衡代码复杂度和性能
- 可读性:团队协作时考虑代码可维护性
10.3 实际性能对比
以Majority Element为例:
- 投票算法:O(n)时间,O(1)空间
- 哈希表法:O(n)时间,O(n)空间
- 排序法:O(nlogn)时间,O(1)或O(n)空间
在LeetCode测试中,投票算法通常最快,内存消耗最少。
