☰
【算法C++】双指针(一)
2026/10/24 4:11:34 网站建设 项目流程

双指针的基本思想:

用两个指针在序列上移动,配合条件减少循环次数,把 O (n²) 暴力优化到 O (n),一般只用一趟或者两趟遍历。

两个指针通常叫左指针、右指针(慢指针、快指针),按移动方式分成两类:

  1. 快慢指针(同向双指针)两个指针朝同一个方向走,一个走得快、一个慢。 常见场景:链表找环、链表中点、滑动窗口、移除数组元素。 核心:快指针负责探测,慢指针标记有效位置。
  2. 对撞指针(相向双指针)左指针在开头,右指针在末尾,往中间靠拢。 前提:数组有序。 常见场景:两数之和、三数之和、反转数组、判断回文。 核心:根据当前和 / 大小关系,决定移左还是移右。

关键点

  • 双指针本质:利用单调性,避免重复枚举
  • 不一定同时移动,每次只动其中一个
  • 适用:数组、字符串、链表;大多要求有序或者有单调性质

练习题:

移动零

「数组分两块」是⾮常常⻅的⼀种题型,主要就是根据⼀种划分⽅式,将数组的内容分成左右两部
分。这种类型的题,⼀般就是使⽤「双指针」来解决。
1.题⽬链接:283. 移动零 - 力扣(LeetCode)
2.题⽬描述:
给定⼀个数组nums,编写⼀个函数将所有0移动到数组的末尾,同时保持⾮零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进⾏操作。
⽰例 1:
输⼊: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
⽰例 2:
输⼊: nums = [0]输出:[0]
3.解法(快排的思想:数组划分区间 - 数组分两块):

底层逻辑:设置dest 和 cur 把,数组分为三个部分,dest 之前的数组为无0原顺序的数组,dest 和 cur 的都为储存0元素,cur 到 数组末的部分为还未检查部分,cur == nums.size(),检查完毕

dest 和 cur 运行逻辑:

cur 如果不为0的元素那么就与 dest 的位置进行 swap 调换,dest ++,如果是为0不做调换,cur每次进行++操作确保不会死循环。

代码展示:

class Solution { public: void moveZeroes(vector<int>& nums) { size_t dest,cur; dest = cur = 0; while(cur!=nums.size()) { if(nums[cur]!=0) { std::swap(nums[dest++],nums[cur]); } cur++; } } };

复写零

1089. 复写零 - 力扣(LeetCode)

给你一个长度固定的整数数组arr,请你将该数组中出现的每个零都复写一遍,并将其余的元素向右平移。

注意:请不要在超过该数组长度的位置写入元素。请对输入的数组就地进行上述修改,不要从函数返回任何东西。

示例 1:

输入:arr = [1,0,2,3,0,4,5,0]输出:[1,0,0,2,3,0,0,4]解释:调用函数后,输入的数组将被修改为:[1,0,0,2,3,0,0,4]

示例 2:

输入:arr = [1,2,3]输出:[1,2,3]解释:调用函数后,输入的数组将被修改为:[1,2,3]

我的想法:

通过消耗空间,建一个目标数组,在进行拷贝给arr数组,达到目的。

class Solution { public: void duplicateZeros(vector<int>& arr) { vector<int> Marr; for(size_t i = 0,j=0;j<arr.size(),i!=arr.size();i++) { if(arr[i]==0) { j+=2; Marr.push_back(0); Marr.push_back(0); } else { Marr.push_back(arr[i]); j++; } } for(size_t i = 0;i<arr.size();i++) { arr[i]=Marr[i]; } } };

最优解法:

如果「从前向后」进⾏原地复写操作的话,由于0的出现会复写两次,导致没有复写的数「被覆
盖掉」。因此我们选择「从后往前」的复写策略。
但是「从后向前」复写的时候,我们需要找到「最后⼀个复写的数」,因此我们的⼤体流程分两
步:
i.先找到最后⼀个复写的数;
ii.然后从后向前进⾏复写操作。
class Solution { public: void duplicateZeros(vector<int>& arr) { int dest = -1,cur = 0,n = arr.size(); while(cur < n ) { if(arr[cur]) dest++; else dest+=2; if(dest >= n - 1) break; cur++; } if(dest == n) { arr[n - 1] = 0; dest -= 2,cur--; } while(cur>=0 && dest != cur) { if(arr[cur])arr[dest--]=arr[cur--]; else { arr[dest--]=0; arr[dest--]=0; cur--; } } } };

快乐数:

202. 快乐数 - 力扣(LeetCode)

编写一个算法来判断一个数n是不是快乐数。

「快乐数」定义为:

  • 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
  • 然后重复这个过程直到这个数变为 1,也可能是无限循环但始终变不到 1。
  • 如果这个过程结果为1,那么这个数就是快乐数。

如果n是快乐数就返回true;不是,则返回false。

我的思路:

正常解题,一步一步解析分析出2为什么不是快乐数,进行计算。

发现:

2->4->16->37->58->89->145->42->20->4这个死循环里面,发现如果数据一旦到了4的时候就是死循环,就符合非快乐数,所以进行循环4的拦截。

class Solution { public: bool isHappy(int n) { int count,_n; while(n!=1 && n!=4) { count = 0; while(n!=0) { _n = n%10; count +=(_n*_n); n/=10; } n = count; } return n == 1; } };

但缺点也很明显,需要计算,而却也不能真的确定真的就是4会导致死循环,有可能还存在其他死循环的可能性,不能真的解决问题。

快慢指针解题:

如果是非快乐数就会在一定的有序的数字之间跳动,类似循环,快乐数就会排到一定的数值都会变成1,1,1的循环,有快慢指针解决的话,在有循环的数值又快指针追到慢指针或者在1的循环快指针追到慢指针,也确实可以实现证明是否为快乐数。

class Solution { public: int isfun(int n) // 返回 n 这个数每⼀位上的平⽅和 { int sum = 0; while(n) { int t = n % 10; sum += t * t; n /= 10; } return sum; } bool isHappy(int n) { int slow = n, fast = isfun(n); while(slow != fast) { slow = isfun(slow); fast = isfun(isfun(fast)); } return slow == 1; } };

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询