news 2026/11/4 3:36:08

双指针算法实战:移动零与盛水容器问题解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
双指针算法实战:移动零与盛水容器问题解析

1. 移动零问题解析与实现

1.1 问题描述与要求

给定一个数组 nums,我们需要将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。这个问题的难点在于必须在不复制数组的情况下原地对数组进行操作。

举个例子:

  • 输入:[0,1,0,3,12]
  • 输出:[1,3,12,0,0]

注意:原地操作意味着我们不能创建新数组来存储结果,必须直接在原数组上进行修改。

1.2 解决方案思路

我采用的是一种双指针的变体方法,具体思路如下:

  1. 使用一个变量 totalZeroes 来记录已经处理的零的数量
  2. 遍历数组,当遇到零时:
    • 使用 splice 方法移除当前零
    • 在数组末尾添加一个零
    • 增加 totalZeroes 计数
  3. 如果不是零,则正常移动指针

这种方法确保了:

  • 时间复杂度:O(n),因为每个元素最多被处理一次
  • 空间复杂度:O(1),没有使用额外的存储空间

1.3 代码实现详解

const moveZeroes = (nums) => { let totalZeroes = 0; for (let i = 0; i < nums.length - totalZeroes;) { if (nums[i] === 0) { nums.splice(i, 1); nums.push(0); totalZeroes++; } else { i++ } } return nums; }

1.4 性能分析与优化

虽然这个解法已经满足题目要求,但我们可以进一步优化:

  1. splice 和 push 操作虽然方便,但在大规模数据下性能不佳
  2. 更优的方法是使用双指针,一个指向当前处理位置,一个指向下一个非零元素应该放置的位置

优化后的版本:

function moveZeroesOptimized(nums) { let lastNonZeroFoundAt = 0; for (let i = 0; i < nums.length; i++) { if (nums[i] !== 0) { nums[lastNonZeroFoundAt++] = nums[i]; } } for (let i = lastNonZeroFoundAt; i < nums.length; i++) { nums[i] = 0; } return nums; }

这种实现避免了频繁的数组操作,性能更好。

2. 盛最多水的容器问题

2.1 问题理解与建模

这个问题要求我们在给定的高度数组中找到两条线,使它们与x轴构成的容器能容纳最多的水。容器的容量由两个因素决定:

  1. 两条线之间的距离(宽度)
  2. 两条线中较短的那条线的高度(高度)

数学表达式为:面积 = min(height[left], height[right]) * (right - left)

2.2 双指针解法

最优解法是使用双指针法,从数组的两端向中间移动:

const maxArea = (height) => { let max = 0, left = 0, right = height.length - 1; while (left < right) { max = Math.max(max, (right - left) * Math.min(height[left], height[right])); if (height[left] < height[right]) { left++; } else { right--; } } return max; };

2.3 算法正确性证明

为什么这种方法能找到最大面积?

  1. 初始时,宽度最大,可能找到较大的面积
  2. 移动较短的那一端,因为容器的容量受限于较短的那一端
  3. 通过不断移动指针,我们确保了不会错过任何可能的更大面积

2.4 复杂度分析

  • 时间复杂度:O(n),只需遍历一次数组
  • 空间复杂度:O(1),只使用了常数个额外空间

3. 三数之和问题

3.1 问题描述与难点

给定一个整数数组nums,找出所有不重复的三元组,使得三元组的和为0。主要难点在于:

  1. 避免重复的三元组
  2. 高效地找到所有可能的三元组

3.2 排序加双指针解法

const threeSum = (nums) => { nums.sort((a, b) => a - b); const result = []; for (let i = 0; i < nums.length - 2; i++) { if (i > 0 && nums[i] == nums[i - 1]) { continue; } let k = nums.length - 1; for (let j = i + 1; j < nums.length - 1; j++) { if (j > i + 1 && nums[j] === nums[j - 1]) { continue; } while(j < k && nums[i] + nums[j] + nums[k] > 0) { k--; } if (j === k) { break; } if (nums[i] + nums[j] + nums[k] === 0) { result.push([nums[i], nums[j], nums[k]]); } } } return result; }

3.3 关键步骤解析

  1. 首先对数组进行排序,这是为了:
    • 方便跳过重复元素
    • 使双指针法成为可能
  2. 外层循环固定第一个数
  3. 内层循环使用双指针寻找另外两个数
  4. 跳过重复元素以避免重复的三元组

3.4 复杂度分析

  • 时间复杂度:O(n²),排序O(nlogn),双层循环O(n²)
  • 空间复杂度:O(1)或O(n),取决于排序的实现

4. 接雨水问题

4.1 问题描述

给定n个非负整数表示柱子的高度,计算这些柱子能接多少雨水。这是一个经典的动态规划/双指针问题。

4.2 双指针解法

const trap = (height) => { let result = 0, left = 0, right = height.length - 1, maxLeft = 0, maxRight = 0; while(left < right) { if (height[left] <= height[right]) { if (height[left] >= maxLeft) { maxLeft = height[left]; } else { result += maxLeft - height[left]; } left++ } else { if (height[right] >= maxRight) { maxRight = height[right]; } else { result += maxRight - height[right]; } right--; } } return result; }

4.3 算法思路

  1. 使用双指针从两端向中间移动
  2. 维护左右两边的最大值maxLeft和maxRight
  3. 对于当前位置,能接的雨水量取决于左右两边最大值中的较小值
  4. 移动较小值那一端的指针,因为较大值那一端决定了当前的水位

4.4 复杂度分析

  • 时间复杂度:O(n),只需遍历一次数组
  • 空间复杂度:O(1),只使用了常数个额外空间

5. 算法实战经验分享

5.1 调试技巧

在实际解决这些问题时,我发现以下调试技巧很有帮助:

  1. 对于双指针问题,在循环中打印指针位置和关键变量值
  2. 对于边界情况,手动计算预期结果并与程序输出对比
  3. 使用小规模测试用例验证算法正确性

5.2 常见错误

  1. 在移动零问题中,容易忽略原地操作的要求
  2. 在盛水容器问题中,可能会错误地同时移动两个指针
  3. 在三数之和问题中,容易忽略跳过重复元素的步骤
  4. 在接雨水问题中,可能会错误计算当前柱子的储水量

5.3 性能优化建议

  1. 尽量减少不必要的数组操作(如splice)
  2. 合理利用排序预处理数据
  3. 在双指针问题中,仔细考虑指针移动的条件
  4. 对于重复计算的值,可以使用变量缓存

6. 算法学习建议

6.1 系统学习方法

  1. 按类别刷题(如双指针、动态规划等)
  2. 每解决一个问题,总结其核心思想和变种
  3. 定期复习已解决的问题,尝试不同的解法
  4. 参加编程竞赛或刷题小组,与他人交流

6.2 资源推荐

  1. LeetCode官方题解和讨论区
  2. 《算法导论》等经典教材
  3. 优质算法博客和视频教程
  4. 在线判题系统的训练计划

6.3 面试准备策略

  1. 重点掌握常见算法类型和数据结构
  2. 练习在白板或纯文本环境中编写代码
  3. 训练清晰地表达解题思路的能力
  4. 准备一些常见的follow-up问题

通过系统地解决这些问题,我深刻理解了双指针技巧的强大之处。在实际编程中,选择合适的数据结构和算法往往能大幅提升代码效率。建议初学者从简单问题入手,逐步提高难度,同时注重理解算法背后的思想而非死记硬背代码。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/4 3:36:08

驾驭工程:复杂机械系统的控制与优化技术

1. 项目背景与核心价值Harness Engineering&#xff08;驾驭工程&#xff09;这个术语在工业领域特指对复杂机械系统的控制与优化技术体系。不同于普通的设备维护&#xff0c;驾驭工程更强调在动态环境中对系统行为的精确掌控。我在汽车制造和风电运维领域实践这套方法论已有7年…

作者头像 李华
网站建设 2026/11/4 3:36:06

CAXA打印精度控制:从坐标对齐到物理校准的全流程解析

1. 项目概述&#xff1a;CAXA打印不是“点一下就完事”的简单操作CAXA打印&#xff0c;这三个字在机械设计、工艺编制、车间图纸流转的日常中出现频率极高&#xff0c;但真正能把它从“能打出来”升级到“打得准、打得稳、打得省”的人&#xff0c;远比想象中少。我接触过几十家…

作者头像 李华
网站建设 2026/11/4 3:35:16

9月AI Agent榜深度解析:Hermes登顶、编码Agent对比与落地排坑

9 月的 AI Agent 排行榜刚出的时候&#xff0c;我其实是有点意外的。Hermes 直接冲到第一&#xff0c;把一堆我以为是老牌选手的产品挤到后面&#xff0c;Claude Code 和 Codex 虽然进了前十但也没能守住头部的位子。这份榜单我前后看了好几遍&#xff0c;又花了一周多时间把排…

作者头像 李华
网站建设 2026/11/4 3:35:12

Selenium元素定位实战:8种方法与最佳实践

1. 元素定位&#xff1a;自动化测试的基石刚接触Selenium时&#xff0c;我花了整整三天时间才搞明白为什么脚本总是报"NoSuchElementException"。后来才发现&#xff0c;80%的自动化测试问题都源于元素定位不当。就像建筑需要稳固的地基&#xff0c;可靠的元素定位是…

作者头像 李华
网站建设 2026/11/4 3:33:58

10秒声音克隆:Vocoflex如何实现少样本音色迁移与歌声转换

Dreamtonics 在 Synthesizer V 之后放出的 Vocoflex&#xff0c;我只想说一声&#xff1a;这次他们没打算做“另一个变声器”。标题里那句“学习 10 秒歌声就能模仿声音”&#xff0c;几乎把 AI 声音克隆多年来的门槛压缩到了极限。我上手试过之后&#xff0c;最大的感受是——…

作者头像 李华
网站建设 2026/11/4 3:33:32

Spring Boot+Vue3网上购物系统全链路设计与实现

简介&#xff1a;面向计算机专业毕业设计、课程设计与Web开发入门练手场景&#xff0c;这套网上购物系统资料包以论文与源码配套的形式&#xff0c;给出从需求分析、系统架构设计到前后端编码实现的完整工程样例&#xff0c;可帮助读者解决选题缺少思路、手上没有可运行参考项目…

作者头像 李华