双指针算法详解:从原理到实战(含LeetCode经典例题)

双指针算法详解:从原理到实战(含LeetCode经典例题)

欢迎来到 s a y − f a l l 的文章 欢迎来到say-fall的文章 欢迎来到say−fall的文章

在这里插入图片描述

🌈say-fall:个人主页🚀专栏:《手把手教你学会C++》 | 《C语言从零开始到精通》 | 《数据结构与算法》 | 《小游戏与项目》💪格言:做好你自己,才能吸引更多人,与他们共赢,这才是最好的成长方式。


前言:

基于数据结构的扎实基础,算法思想能够有效提升问题解决的效率。为此,我们开设一个专门的算法专栏,用来探讨各类算法题目的解决方案。

在算法学习的道路上,双指针是一种简洁、高效且应用广泛的解题思想,它并非局限于某一种特定的数据结构,而是一种通过设置两个“标记点”(指针),协同遍历、筛选或修改数据,从而简化问题复杂度的核心思路。无论是数组、链表的遍历处理,还是数值组合、环形问题的求解,双指针都能发挥其独特优势——相较于暴力枚举的多层循环,它往往能将时间复杂度从O(n²)优化至O(n),大幅提升代码运行效率,同时让解题逻辑更清晰、代码更简洁易懂。
本文将系统拆解双指针算法的核心逻辑,按“快慢双指针”“首尾双指针”“快慢双指针解决环形问题”三大类别,结合LeetCode经典例题(如移动零、盛水最多的容器、三数之和、环形链表等),从算法思想、解题步骤、代码实现三个维度,手把手带你掌握双指针的用法。每道例题均搭配详细解析和优化思路,兼顾基础入门与进阶提升,帮助你真正理解双指针的核心逻辑。


文章目录


正文:

零、什么是双指针

需要说明的是,双指针并不局限于字面意义上的两个指针,而是通过标记两个目标点来简化问题的解决思路。接下来,我们通过具体实例深入解析这一算法。

一、快慢双指针

0. 算法思想:

前后快慢指针以慢指针为基准划分界限:慢指针左侧(包括其指向的元素)均小于等于基准值,右侧均大于基准值;或者是慢指针左或者右为同一元素。

1. 双指针快排思想

在这里插入图片描述


快速排序最初由霍尔(Hoare)提出,其算法逻辑相对复杂。为便于理解,后来发展出了前后指针法实现快排。以下是详细说明:

注:本文提到的"指针"并非真正意义上的指针,而是数组的索引下标关键概念说明:
key [基准值]
prev [慢指针]
cur [快指针]
  1. 首先选择最左侧元素作为基准值 key,初始化 prev 指针与 key 同位置,cur 指针指向下一个位置
  2. cur 指向的元素小于 key 时,移动 prev 指针并与 cur 进行元素交换(可以避免原地互换)
  3. cur 指针持续移动,遇到比基准值小的元素时暂停,此时移动 prev 指针并进行交换(非原地交换会将较小元素移至 prev 位置)
  4. 重复上述过程直到 cur 指针越界
  5. 最后交换 keyprev 指向的元素,确保基准值左侧元素均小于它,右侧元素均大于它
//双指针法快排intptrSort(int* a,int left,int right){int keyi = left;int prev = left, cur = left +1;while(cur <= right){if(a[cur]< a[keyi]){ prev++;if(a[cur]!= a[prev]){Swap(&a[cur],&a[prev]);}} cur++;}Swap(&a[keyi],&a[prev]);return prev;}

至此完成对基准值的排序,之后只需递归地对左右两个子区间执行相同操作(一般使用递归),即可完成整个数组的排序。

2. 双指针移动零

题目:leetcode:移动零

在这里插入图片描述

移动零问题可以类比为以零为基准的单次快速排序:

  1. 初始化两个指针:cur指向数组首元素,prev初始化为-1
  2. 遍历过程中:
    • 当cur遇到非零元素时,prev右移一位并与cur交换元素
    • 否则cur继续右移
  3. 重复上述过程直至cur到达数组末尾
classSolution{public:voidmoveZeroes(vector<int>& nums){for(int cur =0,dest =-1;cur<nums.size();cur++)if(nums[cur])swap(nums[++dest],nums[cur]);}};

3. 复写零

在这里插入图片描述


处理复写零问题的关键在于确定结束位置。以下是优化后的双指针解决方案:

  1. 初始化指针:将cur和dest都指向数组首元素,然后cur开始移动
  2. 遍历规则:
    • 当cur指向0时:dest移动两步
    • 当cur指向非零元素时:dest移动一步
  3. 终止条件:当dest到达数组末尾时停止
  4. 边界处理:
    • 若cur指向最后一个元素为0时,dest可能越界
    • 此时需要将dest回退两位,并将末尾元素置零,同时cur回退一位
  5. 逆向复写:由于已精确计算复写元素数量,无需担心原地复写导致的覆盖问题
classSolution{public:voidduplicateZeros(vector<int>& arr){//双指针找最后一个复写的数int cur =0,dest =-1,n = arr.size();while(1){if(arr[cur]==0) dest++; dest++;if(dest >= n-1)break;//找到退出 cur++;}//处理特殊情况if(dest == arr.size()){ cur--; dest -=2; arr[arr.size()-1]=0;}//从后向前复写while(cur >=0){if(arr[cur]==0) arr[dest--]= arr[cur]; arr[dest--]= arr[cur]; cur--;}}};

三、首尾双指针

0. 算法思想:

首尾分别设定指针,可以交换元素解决移动零类似问题;也可以利用单调性解决其他问题

1. 双指针移动零

题目:leetcode:移动零

在这里插入图片描述


如果不需要保持元素的先后顺序的话,也可以使用首尾双指针法

  1. 设置左右指针,leftright
  2. left 找0,right 找非零元素,交换
  3. 直到左右指针相遇
classSolution{public:voidmoveZeroes(vector<int>& nums){int left =0,right = nums.size()-1;while(left < right){if(nums[left]) left++;if(nums[right]==0) right--;if(left < right && nums[left]==0&& nums[right]!=0)swap(nums[left],nums[right]);}}};
在这里插入图片描述

2. 盛水最多的容器

  • 解法一:
    本题可以使用暴力解法来计算,即两层循环计算出 “体积” , 取其最大值。
  • 解法二:
    根据体积的计算公式 v = w * h :

题目:leetcode:盛水最多的容器

在这里插入图片描述
  1. 先排序,得到一个有顺序的数组
  2. 首尾设定指针,向内移动,宽度w必定减小,高度由于取最小值,会降低或者不变
  3. 则说明:计算出体积v以后,将二者高度较小的一方排除,向内移动,即可减少遍历次数
classSolution{public:intgetv(vector<int>& height,int left,int right){int width = right - left;int high = height[right];//假设法if(height[left]< height[right]){ high = height[left];}return width * high;}intmaxArea(vector<int>& height){int left =0;int right = height.size()-1;int v =0;while(left < right){int vv =getv(height, left, right);if(height[left]< height[right]) left++;else right--;if(vv > v) v = vv;}return v;}};

3. 两数之和

  • 解法一:
    双层循环,暴力枚举出所有可能值,找到两数之和为目标的两个数。
  • 解法二:

题目:两数之和

在这里插入图片描述
  1. 先排序,依旧设定首尾指针
  2. 于是存在:
    • 如果两指针所指元素之和小于目标值:变小一定得不到目标值,所以右指针不能左移,左指针右移
    • 如果两指针所指元素之和大于目标值:变大一定得不到目标值,所以左指针不能右移,右指针左移
  3. 遍历直到找到和为目标值的两个数
classSolution{public: vector<int>twoSum(vector<int>& price,int target){int left =0, right = price.size()-1;while(left < right){if(price[left]+ price[right]> target) right--;elseif(price[left]+ price[right]< target) left++;elsereturnvector<int>({price[left],price[right]});}returnvector<int>();}};

4. 三数之和

题目:三数之和

在这里插入图片描述

三数之和实际上是两数之和的变式:

解法一:

  • 与两数之和相同,三层循环可以枚举出想要结果,找到以后加入数组即可
    解法二:
  1. 依旧可以先排序,然后固定首尾其中一个数字,依次顺序固定,重复元素跳过
  2. 用两数之和类似双指针法,计算出三数之和为结果的数字,需要注意的是,重复元素跳过
  3. 最后遍历完成,结果加入二维数组
classSolution{public: vector<vector<int>>threeSum(vector<int>& nums){//优化sort(nums.begin(),nums.end());int n = nums.size();//利用单调性,定尾部,前面走双指针 vector<vector<int>> vv;for(int i = n-1;i >=2;i--){if( i < n-1&& nums[i]== nums[i +1]){continue;}int left =0,right = i-1;while(left < right){if(nums[left]+ nums[right]+ nums[i]>0) right--;elseif(nums[left]+ nums[right]+ nums[i]<0) left ++;else{// 找到符合条件的三元组,加入结果 vv.push_back({nums[left], nums[right], nums[i]});// 跳过左指针重复值while(left < right && nums[left]== nums[left +1])++left;// 跳过右指针重复值while(left < right && nums[right]== nums[right -1])--right;// 双指针收缩,继续找下一组++left;--right;}}}return vv;}};

5. 有效三角形的个数

题目:有效三角形的个数

实际上本题也算是三数之和的变式,只不过本体要求三数组成三角形而不是和为零:

解法一:三层循环遍历枚举
解法二:

  1. 先排序,依旧固定一边
  2. 利用三角形两小边之和大于第三边性质寻找三个符合要求的边,符合要求则计入次数
classSolution{public:inttriangleNumber(vector<int>& nums){int n = nums.size()-1;sort(nums.begin(),nums.end());int sum =0;for(int i = n; i>0; i--){int left =0,right = i-1;while(left < right){if(nums[left]+ nums[right]> nums[i]){ sum += right-left; right--;}else left++;}}return sum;}};

6. 四数之和

  • 提一点:本题需要注意的是样例里面存在超大数,所以需要注意 int 无法承载的问题

题目:四数之和

在这里插入图片描述


这里不再讲解原理,原理与三数之和类似,直接看代码:

classSolution{public: vector<vector<int>>fourSum(vector<int>& nums,int target){int n = nums.size();sort(nums.begin(),nums.end()); vector<vector<int>> vv;for(int j = n -1; j >=3;j--){if(j < n-1&& nums[j]== nums[j+1])continue;int t = target - nums[j];for(int i = j -1; i >=2;i--){if(i < j-1&& nums[i]== nums[i+1])continue;int left =0,right = i -1;while(left < right){if((longlong)nums[left]+ nums[right]+ nums[i]<(longlong)t) left++;elseif((longlong)nums[left]+ nums[right]+ nums[i]>(longlong)t) right--;else{ vv.push_back({nums[left], nums[right], nums[i],nums[j]});while(left < right && nums[left]== nums[left +1]) left++;while(left < right && nums[right]== nums[right -1]) right--; left++,right--;}}}}return vv;}};

三、快慢双指针解决环形问题

0. 算法思想:

用跑圈的问题来类比,如果一个快的人和慢的人在环形跑道跑步,如果他们一直是恒速前进,那么必定相遇,快慢双指针也类似这个问题

1. 环形链表

程序员遇到的经典环形问题是:leetcode:环形链表问题

在这里插入图片描述

采用快慢指针的思想,如果有环就必定能相遇,而如果没环必定能越界

/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode(int x) : val(x), next(NULL) {} * }; */classSolution{public:boolhasCycle(ListNode *head){typedefstructListNode ListNode; ListNode* fast = head; ListNode* slow = head;while(fast!=NULL&& fast->next!=NULL){ fast = fast->next->next; slow = slow->next;if(slow == fast){returntrue;}}returnfalse;}};

2. 快乐数

题目:leetcode:快乐数

在这里插入图片描述

本题的两种情况其实可以看作是一种情况:

  • 有环:环内无1,成一个大环
  • 有环:环内只有1,可看作一循环,即为快乐数
    所以只需要判断快慢指针相遇的时候他们所指元素的值是不是1,即可判断是否是快乐数
classSolution{public:intget_sosq(int& n)//获取平方和{int sum =0;while(n){ sum +=pow((n %10),2); n /=10;}return sum;}boolisHappy(int n){int show = n, fast = n; show =get_sosq(show); fast =get_sosq(fast); fast =get_sosq(fast);while(show != fast){ show =get_sosq(show); fast =get_sosq(fast); fast =get_sosq(fast);}if(fast ==1)returntrue;returnfalse;}};

  • 本节完…

Read more

基于Python的网络电视剧管理与可视化平台的设计和实现的详细项目实例(含完整的程序,数据库和GUI设计,代码详解) 还请多多点一下关注 加油 谢谢 你的鼓励是我前行的动力 谢谢支持 加油 谢谢

基于Python的网络电视剧管理与可视化平台的设计和实现的详细项目实例(含完整的程序,数据库和GUI设计,代码详解) 还请多多点一下关注 加油 谢谢 你的鼓励是我前行的动力 谢谢支持 加油 谢谢

目录 基于Python的网络电视剧管理与可视化平台的设计和实现的详细项目实例... 1 项目背景介绍... 1 项目目标与意义... 2 提高网络电视剧数据管理效率... 2 实现网络电视剧数据的可视化分析... 2 支持多渠道数据集成与更新... 2 促进网络电视剧产业智能化发展... 2 加强行业内容监管与分析能力... 2 推动数据驱动的营销策略实施... 3 促进技术创新与跨领域融合应用... 3 项目挑战及解决方案... 3 海量数据的高效采集与清洗... 3 数据存储与检索性能瓶颈... 3 数据可视化交互复杂性... 3 用户行为和偏好建模难题... 4 系统安全与隐私保护... 4 跨平台兼容与可扩展性设计... 4 实时数据处理与更新挑战... 4 项目模型架构... 4 项目模型描述及代码示例... 5 项目应用领域... 8 网络影视内容管理平台... 8 用户行为分析与精准推荐... 8 影视数据可视化展示... 8 广告投放与营销优化.

By Ne0inhk
Python数学可视化——显函数、隐函数及复杂曲线的交互式绘图技术

Python数学可视化——显函数、隐函数及复杂曲线的交互式绘图技术

Python数学可视化——显函数、隐函数及复杂曲线的交互式绘图技术 一、引言 在科学计算和数据分析中,函数与方程的可视化是理解数学关系和物理现象的重要工具。本文基于Python的Tkinter和Matplotlib库,实现一个功能完善的函数与方程可视化工具,支持显函数、隐函数、特殊曲线(如心形线)及物理场分布(如电势)的交互式绘图,并提供安全的表达式解析、图像保存等功能。 二、核心技术架构 2.1 系统架构与技术选型 * 界面层:使用Tkinter构建GUI,包含类型选择、表达式输入、预设函数下拉菜单等控件 * 计算层: * 显函数:通过np.linspace生成采样点,安全计算函数值 * 隐函数:基于等高线算法contour绘制等值线 * 安全机制:通过正则表达式过滤非法字符,限制白名单函数防止代码注入 * 可视化层:Matplotlib实现图表渲染,支持动态更新和交互式工具条 2.2 安全表达式解析 defis_valid_expression(expr):""

By Ne0inhk
Java vs Python 毕设到底选哪个?看完不纠结

Java vs Python 毕设到底选哪个?看完不纠结

Java vs Python 毕设到底选哪个?看完不纠结 适用对象:正在为“毕设到底用 Java 还是 Python”纠结的计算机 / 软工 / 信管学生。 对比维度:难度、时间、代码量、答辩通过率,最后给出按基础分层的选型建议。 一、先说结论:90% 同学可以直接这样选 非常直白的选择建议: * 如果你是零基础 / 基础薄弱 / 写代码头皮发麻: * 优先选 Python * 理由:语法简单,代码量少,很多功能用库就能搞定,照着开源项目改一改也快。 * 如果你本科主修 Java、写过 SSM / Spring Boot / 实训项目: * 优先选 Java * 理由:你已有积累;大部分老师、学校的模板、往届项目也偏 Java,

By Ne0inhk
2025华为OD机试真题+全流程解析+备考攻略+经验分享+Java/python/JavaScript/C++多种语言最佳实现

2025华为OD机试真题+全流程解析+备考攻略+经验分享+Java/python/JavaScript/C++多种语言最佳实现

华为OD全流程解析,备考攻略 快捷目录 * 华为OD全流程解析,备考攻略 * 一、什么是华为OD? * 二、什么是华为OD机试? * 三、华为OD面试流程 * 四、华为OD薪资待遇及职级体系 * 五、ABCDE卷类型及特点 * 六、题型与考点 * 七、机试备考策略 * 八、薪资与转正 * 九、常见问题解答 * 十、总结 * 2025 华为OD 机试真题 B卷 100分题型 * 2025 华为OD 机试真题 B卷 200分题型 * 2025 华为OD 机试真题 A卷 100分题型 * 2025 华为OD 机试真题 A卷 200分题型 一、什么是华为OD? 华为OD(Outsourcing Dispacth)

By Ne0inhk