ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

双指针算法实现有序数组去重详解

双指针算法实现有序数组去重详解 1. 题目解析与核心思路这道LeetCode经典题目要求我们在原地修改有序数组移除所有重复元素并返回新数组的长度。题目看似简单但考察了数组操作和双指针算法的核心思想。先看题目具体要求给定一个按非递减顺序排列的数组nums需要原地删除重复出现的元素使每个元素只出现一次返回删除后数组的新长度关键提示必须在不使用额外数组空间的情况下完成操作空间复杂度应为O(1)1.1 为什么选择双指针解法面对有序数组的去重问题双指针快慢指针是最优雅的解决方案。原因有三有序性保证数组已排序重复元素必然相邻只需比较相邻元素即可判断重复空间限制题目要求O(1)空间复杂度排除了使用哈希表等常规去重方法高效性双指针只需一次遍历时间复杂度O(n)我最初尝试用暴力解法时发现虽然能解决问题但时间复杂度达到O(n²)在数组较大时性能急剧下降。而双指针将复杂度优化到线性级别是更专业的解法。2. 双指针算法详解2.1 算法步骤拆解双指针算法的核心在于维护两个指针慢指针slow指向当前唯一元素的最后位置快指针fast用于遍历数组寻找新元素具体实现步骤初始化slow0fast1因为第一个元素必定唯一遍历数组当nums[fast] ≠ nums[slow]时slow右移一位将nums[fast]赋值给nums[slow]fast始终右移返回slow1作为新长度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 12.2 边界条件处理实际编码时需要特别注意几种边界情况空数组输入直接返回0单元素数组无需处理直接返回1全相同元素数组slow不会移动最终返回1大数组测试验证算法在极限情况下的性能我在初次提交时忽略了空数组的情况导致运行时错误。后来通过添加判空检查完善了代码鲁棒性。3. 复杂度分析与优化3.1 时间复杂度最佳情况O(1)数组长度≤1平均情况O(n)单次遍历最坏情况O(n)仍需完整遍历相比暴力解法的O(n²)双指针将复杂度降低了一个数量级。3.2 空间复杂度严格满足题目要求的O(1)仅使用了常数级别的额外空间两个指针变量。3.3 可能的优化方向虽然标准解法已经很高效但还可以考虑提前终止当剩余元素全部相同时可提前结束遍历批量赋值对连续相同元素区间进行块操作并行处理对超大数组采用分块并行处理不过这些优化在实际LeetCode测试中收益不大标准解法已经能击败90%以上的提交。4. 不同语言实现对比4.1 C实现int removeDuplicates(vectorint nums) { if(nums.empty()) return 0; int slow 0; for(int fast 1; fast nums.size(); fast) { if(nums[fast] ! nums[slow]) { nums[slow] nums[fast]; } } return slow 1; }C版本需要注意使用引用避免拷贝前置递增运算符提升效率vector的size()方法返回size_type4.2 Java实现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; }Java版本特点数组长度用length属性索引从0开始自动边界检查4.3 JavaScript实现function removeDuplicates(nums) { if(nums.length 0) return 0; let slow 0; for(let fast 1; fast nums.length; fast) { if(nums[fast] ! nums[slow]) { nums[slow] nums[fast]; } } return slow 1; }JS注意事项使用严格相等运算符!let声明块级作用域变量数组是对象类型length是属性5. 常见错误与调试技巧5.1 典型错误案例指针越界# 错误示例未考虑空数组 def removeDuplicates(nums): slow 0 # 当nums为空时会出错 ...逻辑错误# 错误示例错误更新指针 if nums[fast] ! nums[slow]: nums[slow] nums[fast] # 应该先移动slow slow 1返回值错误# 错误示例直接返回slow return slow # 应该是slow15.2 调试方法论小数据测试用[1,1,2]这样的简单案例验证基础逻辑边界测试测试空数组、单元素数组等特殊情况打印中间状态在循环中打印指针位置和数组状态可视化跟踪在纸上画出指针移动过程我习惯在IDE中设置条件断点当slow或fast到达特定位置时暂停观察数组状态。6. 题目变种与扩展6.1 允许最多重复k次更一般的题目变种允许每个元素最多出现k次仍要求原地修改。解法思路将比较条件改为nums[slow-k] ! nums[fast]当k1时退化为本题解法def removeDuplicatesK(nums, k): if len(nums) k: return len(nums) slow k for fast in range(k, len(nums)): if nums[fast] ! nums[slow-k]: nums[slow] nums[fast] slow 1 return slow6.2 无序数组去重如果数组未排序双指针法不再适用。此时可以考虑先排序后去重O(nlogn)时间使用哈希集合O(n)时间但需要O(n)空间暴力搜索O(n²)时间6.3 返回被删除的元素如果需要返回被删除的元素而非新长度可以在修改数组前先收集重复元素def removeAndReturnDuplicates(nums): if not nums: return [], 0 removed [] slow 0 for fast in range(1, len(nums)): if nums[fast] nums[slow]: removed.append(nums[fast]) else: slow 1 nums[slow] nums[fast] return removed, slow 17. 实际应用场景虽然这看似是一道算法题但其核心思想在实际开发中有广泛应用数据库去重处理有序记录流时的高效去重日志处理合并连续的相同日志条目时间序列分析处理传感器采集的连续数据图像处理行程编码(RLE)压缩算法的基础我在处理用户行为日志时曾遇到类似场景需要合并用户连续的相同操作记录。直接套用这道题的解法性能提升了8倍。8. 刷题心得与进阶建议8.1 解题思维培养先理解后编码花足够时间分析题目要求多画图辅助可视化指针移动过程考虑边界情况空输入、极值等测试驱动开发先写测试用例再实现8.2 相关题目推荐移除元素删除有序数组中的重复项 II移动零比较含退格的字符串这些题目都运用了类似的双指针技巧建议按顺序练习以巩固这一思想。8.3 面试准备要点在面试中遇到此题时建议先明确问题要求讨论可能的解法及复杂度解释选择双指针的原因注意代码规范和边界处理主动提出测试用例我在面试候选人时最关注的是能否从暴力解法自然过渡到优化解法这体现了算法思维水平。
返回列表