跳到主要内容 双指针算法:从暴力遍历到线性高效重构数组操作 | 极客日志
C++ 算法
双指针算法:从暴力遍历到线性高效重构数组操作 双指针算法在数组操作中的核心应用,涵盖移动零、复写零、快乐数、盛水最多的容器、有效三角形个数、两数之和、三数之和及四数之和等经典题目。通过对比暴力解法与双指针优化方案,展示如何将时间复杂度从 O(n²) 或 O(n³) 降低至 O(n) 或 O(n log n),并提供 C++ 代码实现及详细原理解析。
氛围 发布于 2026/3/28 更新于 2026/4/16 2 浏览前言
双指针算法并非真正的指针,而是利用数组下标模拟两个指针的移动。该技巧能将原本需要嵌套循环的 O(n²) 问题优化至 O(n),实现花最少的力气办最大的事。
1、移动零
题目链接 :https://leetcode.cn/problems/move-zeroes/description/
算法原理
这类问题可以分为数组划分或者叫数组分块,并且使用双指针算法。
指针作用 :
left:始终指向已处理区间内的最后一个非 0 元素处
right:从左到右依次遍历数组
具体步骤 :
利用 right 从左到右依次遍历的过程中:
遇到非 0 元素:left++,交换 left 和 right 处的元素,right++
遇到 0:right++
只要保证 left 始终在最后一个非 0 元素的位置,right 从左往右遍历即可
代码演示 :
class Solution {
public :
void moveZeroes (vector<int >& nums) {
for (int right = 0 , left = -1 ; right < nums.size (); right++) {
if (nums[right]) swap (nums[right], nums[++left]);
}
}
};
2、复写零
题目链接 :https://leetcode.cn/problems/duplicate-zeros/
微信扫一扫,关注极客日志 微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
算法原理 最初尝试解决这道题时,首先考虑从前往后遍历数组:遇到非零元素就继续后移,遇到 0 就进行复写(添加一个 0)。但在画图模拟时发现,这种方式会覆盖还未进行操作的有效元素(因为复写 0 会占用额外位置,导致后续元素被提前覆盖),所以从前往后的算法思路不可行。
随后转向从后往前遍历的思路,但直接从后往前遍历难以精准控制元素的复写与位移。想到可以先找到最后一个需要参与复写的元素的位置,再基于这个位置从后往前进行复写操作,这样能避免元素覆盖问题,且更容易控制流程。
特殊情况处理 :如果最后一个复写的元素是 0,但是只有一个有效位置了,这时候 dest 就等于 n,只需要进行边界判断即可解决这种情况。
class Solution {
public :
void duplicateZeros (vector<int >& arr) {
int cur = 0 , dest = -1 , n = arr.size ();
while (dest < n) {
if (arr[cur]) dest++;
else dest += 2 ;
if (dest >= n - 1 ) break ;
cur++;
}
if (dest == n) {
arr[n - 1 ] = 0 ;
cur--;
dest -= 2 ;
}
while (cur >= 0 ) {
arr[dest--] = arr[cur];
if (arr[cur] == 0 ) {
arr[dest--] = 0 ;
}
cur--;
}
}
};
3、快乐数
算法原理 所以我们可以定义两个下标分别标识快指针和慢指针,快指针一次走两步,慢指针一次走一步,由于他俩每走一步,之间的距离缩减 1,那么他们肯定会在环中相遇。
class Solution {
public :
int qdsum (int n) {
int sum = 0 ;
while (n) {
int i = n % 10 ;
sum += i * i;
n /= 10 ;
}
return sum;
}
bool isHappy (int n) {
int slow = n;
int fast = qdsum (n);
while (slow != fast) {
slow = qdsum (slow);
fast = qdsum (qdsum (fast));
}
if (slow == 1 ) return true ;
else return false ;
}
};
4、盛水最多的容器
算法原理 固定第一个数,让第一个数和其后面的所有数进行穷举,然后每次刷新较大值,然后固定第二个数,以此类推……
class Solution {
public :
int maxArea (vector<int >& height) {
int left = 0 ;
int ret = 0 ;
while (left < height.size ()) {
int right = left + 1 ;
while (right < height.size ()) {
int v = (right - left) * min (height[left], height[right]);
ret = max (v, ret);
right++;
}
left++;
}
return ret;
}
};
class Solution {
public :
int maxArea (vector<int >& height) {
int left = 0 , right = height.size () - 1 , ret = 0 ;
while (left < right) {
int v = min (height[left], height[right]) * (right - left);
ret = max (v, ret);
if (height[left] > height[right]) {
right--;
} else {
left++;
}
}
return ret;
}
};
5、有效三角形的个数
算法原理 3 层 for 循环,时间复杂度是 O(N^3), 超时了。
class Solution {
public :
int triangleNumber (vector<int >& nums) {
int count = 0 ;
for (int i = 0 ; i < nums.size (); i++) {
for (int j = i + 1 ; j < nums.size (); j++) {
for (int k = j + 1 ; k < nums.size (); k++) {
if (nums[i] + nums[j] > nums[k] && nums[i] + nums[k] > nums[j] && nums[j] + nums[k] > nums[i]) {
count++;
}
}
}
}
return count;
}
};
class Solution {
public :
int triangleNumber (vector<int >& nums) {
int count = 0 ;
sort (nums.begin (), nums.end ());
for (int n = nums.size () - 1 ; n >= 2 ; n--) {
int left = 0 , right = n - 1 ;
while (left < right) {
if (nums[left] + nums[right] > nums[n]) {
count += (right - left);
right--;
} else {
left++;
}
}
}
return count;
}
};
时间复杂度从 O(N^2) 优化到了 O(N*logn + N^2)。
LCR 179. 查找总价格为目标值的两个商品
算法原理 class Solution {
public :
vector<int > twoSum (vector<int >& price, int target) {
for (int i = 0 ; i < price.size (); i++) {
for (int j = i + 1 ; j < price.size (); j++) {
int sum = price[i] + price[j];
if (sum == target) {
return {price[i], price[j]};
}
}
}
return {-1 , -1 };
}
};
相较于暴力求解的 O(N^2),通过单调性和双指针最坏只用遍历一遍数组就可以解决。
class Solution {
public :
vector<int > twoSum (vector<int >& price, int target) {
int left = 0 ;
int right = price.size () - 1 ;
for (int i = 0 ; i < price.size (); i++) {
int sum = price[left] + price[right];
if (sum > target) {
right--;
} else if (sum < target) {
left++;
} else {
return {price[left], price[right]};
}
}
return {-1 , -1 };
}
};
7、三数之和
算法解析 class Solution {
public :
vector<vector<int >> threeSum (vector<int >& nums) {
sort (nums.begin (), nums.end ());
vector<vector<int >> vv;
int n = nums.size ();
for (int i = 0 ; i < n; i++) {
if (i > 0 && nums[i] == nums[i - 1 ]) continue ;
for (int j = i + 1 ; j < n; j++) {
if (j > i + 1 && nums[j] == nums[j - 1 ]) continue ;
for (int k = j + 1 ; k < n; k++) {
if (k > j + 1 && nums[k] == nums[k - 1 ]) continue ;
if (nums[i] + nums[j] + nums[k] == 0 ) {
vv.push_back ({nums[i], nums[j], nums[k]});
}
}
}
}
return vv;
}
};
排序:为后面去重做准备;3 层 for 循环嵌套,时间复杂度为 O(N^3), 超时了。
class Solution {
public :
vector<vector<int >> threeSum (vector<int >& nums) {
sort (nums.begin (), nums.end ());
vector<vector<int >> vv;
for (int i = 0 ; i < nums.size (); i++) {
if (nums[i] > 0 ) {
break ;
}
if (i > 0 && nums[i] == nums[i - 1 ]) continue ;
int left = i + 1 ;
int right = nums.size () - 1 ;
while (left < right) {
int sum = -nums[i];
if (left < right && nums[left] + nums[right] == sum)
{
vv.push_back ({nums[i], nums[left], nums[right]});
left++;
while (left < right && nums[left] == nums[left - 1 ]) {
left++;
}
right--;
while (left < right && nums[right] == nums[right + 1 ]) {
right--;
}
}
else if (left < right && nums[left] + nums[right] > sum) {
right--;
} else {
left++;
}
}
}
return vv;
}
};
由于数据预先进行了排序,所以去重直接和下一个位置比较即可。
8、四数之和
算法原理 这道题和三数之和思路基本差不多,就是多固定了一个数。
class Solution {
public :
vector<vector<int >> fourSum (vector<int >& nums, int target) {
sort (nums.begin (), nums.end ());
vector<vector<int >> vv;
int n = nums.size ();
for (int a = 0 ; a < n;)
{
for (int b = a + 1 ; b < n;)
{
long long ami = (long long )target - nums[a] - nums[b];
int left = b + 1 ;
int right = n - 1 ;
while (left < right) {
int sum = nums[left] + nums[right];
if (sum > ami) {
right--;
} else if (sum < ami) {
left++;
} else {
vv.push_back ({nums[a], nums[b], nums[left++], nums[right--]});
while (left < right && nums[left] == nums[left - 1 ]) {
left++;
}
while (left < right && nums[right] == nums[right + 1 ]) {
right--;
}
}
}
b++;
while (b < n && nums[b] == nums[b - 1 ]) {
b++;
}
}
a++;
while (a < n && nums[a] == nums[a - 1 ]) {
a++;
}
}
return vv;
}
};