双指针算法实战
双指针是处理有序数组问题的利器。通过利用数据的单调性,我们可以将原本需要 O(n²) 甚至 O(n³) 的暴力解法优化到 O(n) 或 O(n²)。本文将结合四个经典例题,深入讲解双指针在不同场景下的应用技巧。
【611.有效三角形个数】
题目描述
给定一个包含非负整数的数组,统计其中能构成三角形的三元组个数。
核心思路
构成三角形的条件是任意两边之和大于第三边。对于排序后的数组,若固定最长边 c,只需满足 a + b > c 即可(其中 a, b 为较短两边)。因此,我们可以先对数组升序排序,然后从右向左遍历,将当前元素视为最长边,利用双指针在左侧寻找满足条件的组合。
实现细节
- 排序:首先将数组按升序排列。
- 固定最大边:从倒数第三个元素开始向前遍历,设当前索引为
i。 - 双指针查找:左指针
left指向起始位置,右指针right指向i-1。- 若
nums[left] + nums[right] > nums[i],说明left到right-1之间的所有元素与right都能与nums[i]构成三角形,数量为right - left。随后right--继续尝试更小的最长边。 - 若和小于等于
nums[i],则需增大较短边,即left++。
- 若
- 去重与边界:注意
right >= left时停止循环,且i至少为 2。

代码实现
class Solution {
public:
int triangleNumber(vector<int>& nums) {
sort(nums.begin(), nums.end()); // 升序排序
int count = 0;
// 从右往左依次将最大值作为第三边
for (int i = nums.size() - 1; i >= 2; i--) {
int left = 0; // 左指针(最短边)
int right = i - 1; // 右指针(最长边)
while (left < right) {
if (nums[left] + nums[right] > nums[i]) { // 两边之和大于第三边
count += right - left; // 更新结果
right--; // 寻找其他可能结果
} else {
left++; // 寻找与最长边相加可能大于第三边的最短边
}
}
}
return count;
}
};
【179.查找总价格为目标值的两个商品】
题目描述
在一个已排序的数组中,找到两个数,使它们的和等于目标值。
核心思路
这是最经典的双指针应用场景。由于数组已有序,我们不需要像哈希表那样存储中间状态。直接让左指针指向最小值,右指针指向最大值,根据当前和与目标值的大小关系移动指针。
实现细节
- 和小于目标值:左指针右移 (
left++),以增大总和。 - 和大于目标值:右指针左移 (
right--),以减小总和。 - 相等:找到答案,返回结果。
代码实现
class Solution {
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) {
left++;
} else if (price[left] + price[right] > target) {
right--;
} else {
break;
}
}
return {price[left], price[right]};
}
};
【15.三数之和】
题目描述
找出数组中所有和为 0 且不重复的三元组。
核心思路
将问题转化为两数之和。固定一个数 nums[i] 作为第一个数,问题就变成了在剩余部分找两个数之和为 -nums[i]。同样使用双指针求解。
实现细节
- 排序:必须排序以便去重和双指针操作。
- 固定首元素:遍历数组,将
nums[i]设为基准。若nums[i] > 0,后续不可能有解,可直接结束。 - 双指针搜索:
left = i + 1,right = n - 1。 - 去重策略:
- 当找到一组解后,跳过所有相同的
left和right值。 - 在固定
nums[i]时,若nums[i] == nums[i-1],则跳过,避免重复计算。
- 当找到一组解后,跳过所有相同的

代码实现
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
sort(nums.begin(), nums.end());
vector<vector<int>> ret;
int n = nums.size();
for (int i = 0; i < n - 2; ) {
int target = nums[i];
// 剪枝:如果当前数大于 0,后面不可能和为 0
if (target > 0) break;
int left = i + 1, right = n - 1;
while (left < right) {
long sum = (long)nums[left] + nums[right];
if (sum < -target) {
left++;
} else if (sum > -target) {
right--;
} else {
ret.push_back({target, nums[left], nums[right]});
left++, right--;
// 去重
while (left < right && nums[left] == nums[left - 1]) left++;
while (left < right && nums[right] == nums[right + 1]) right--;
}
}
i++;
// 去重 target
while (i < n - 2 && nums[i] == nums[i - 1]) i++;
}
return ret;
}
};
【18.四数之和】
题目描述
找出数组中所有和为 target 的不重复四元组。
核心思路
在三数之和的基础上再嵌套一层循环。固定前两个数,将问题转化为两数之和;或者固定一个数,转化为三数之和。这里采用固定一个数,调用三数之和逻辑的方式。
实现细节
- 排序:同上。
- 双重固定:外层循环固定第一个数
nums[i],内层逻辑复用三数之和的思路(固定第二个数,双指针找后两个)。 - 溢出处理:求和时使用
long类型防止整数溢出。 - 去重:每一层循环都要跳过重复元素。

代码实现
class Solution {
public:
// 辅助函数:在 pos 之后找三个数和为 target_val
vector<vector<int>> threeSumHelper(vector<int>& nums, int pos, int target_val) {
vector<vector<int>> ret;
int n = nums.size();
for (int i = pos + 1; i < n - 1; ) {
long target = (long)nums[i] - (long)target_val;
int left = i + 1, right = n - 1;
while (left < right) {
long sum = (long)nums[left] + nums[right];
if (sum < -target) {
left++;
} else if (sum > -target) {
right--;
} else {
ret.push_back({nums[i], nums[left], nums[right]});
left++, right--;
while (left < right && nums[left] == nums[left - 1]) left++;
while (left < right && nums[right] == nums[right + 1]) right--;
}
}
i++;
while (i < n - 1 && nums[i] == nums[i - 1]) i++;
}
return ret;
}
vector<vector<int>> fourSum(vector<int>& nums, int target) {
sort(nums.begin(), nums.end());
vector<vector<int>> result;
int n = nums.size();
for (int i = 0; i < n - 3; ) {
auto subRet = threeSumHelper(nums, i, target - nums[i]);
if (!subRet.empty()) {
for (auto& e : subRet) {
e.push_back(nums[i]);
result.push_back(e);
}
}
i++;
while (i < n - 3 && nums[i] == nums[i - 1]) i++;
}
return result;
}
};

