代码改变世界

完整教程:【算法题】双指针(二)

2026-01-15 14:48  tlnshuju  阅读(1)  评论(0)    收藏  举报

一、盛最多水的容器

题目描述:
给定一个长度为 n 的整数数组 height,有 n 条垂线,第 i 条线的两个端点是 (i, 0)(i, height[i])。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水,返回容器可以存储的最大水量。
说明:不能倾斜容器。

示例

  • 输入:height = [1,8,6,2,5,4,8,3,7],输出:49
  • 输入:height = [1,1],输出:1

解题思路:
采用双指针法优化暴力枚举的时间复杂度:

  • 初始化左右指针 left(数组开头)、right(数组末尾),记录最大面积 ret
  • 容器面积由「短边高度」和「指针间距」决定:面积 = min(height[left], height[right]) * (right - left)
  • 移动策略:每次移动较短边的指针(若移动长边,间距减小且短边不变/更小,面积必然减小;移动短边可能找到更长的边,从而增大面积)。
  • 遍历过程中持续更新最大面积,直到左右指针相遇。

完整代码:

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]) left++;
  else right--;
  }
  return ret;
  }
  };

复杂度分析:

  • 时间复杂度O(n)O(n)O(n)。仅遍历数组一次,左右指针最多移动 nnn 次。
  • 空间复杂度O(1)O(1)O(1)。仅使用常数级额外变量。

二、有效三角形的个数

题目描述:
给定一个包含非负整数的数组 nums,返回其中可以组成三角形三条边的三元组个数。
三角形条件:任意两边之和大于第三边。

示例

  • 输入:nums = [2,2,3,4],输出:3(有效组合:[2,3,4](两个2分别组合)、[2,2,3]
  • 输入:nums = [4,2,3,4],输出:4

解题思路:
先排序数组,利用三角形的简化性质(排序后,若较小两边之和大于最大边,则必然满足三角形条件):

  1. 排序数组,使 nums[a] ≤ nums[b] ≤ nums[c],只需判断 nums[a] + nums[b] > nums[c]
  2. 固定最大边 nums[i](从数组末尾往前遍历),用双指针 left=0right=i-1 找符合条件的较小两边:
    • nums[left] + nums[right] > nums[i],则 right-left 个组合(leftright-1 都满足),right 左移。
    • 否则 left 右移,尝试更大的较小边。

完整代码:

class Solution {
public:
int triangleNumber(vector<int>& nums) {
  sort(nums.begin(), nums.end());
  int ret = 0;
  for(int i = nums.size() - 1; i >= 2; i--){
  int left = 0, right = i - 1;
  while(left < right){
  if(nums[left] + nums[right] > nums[i]){
  ret += right - left;
  right--;
  }
  else{
  left++;
  }
  }
  }
  return ret;
  }
  };

复杂度分析:

  • 时间复杂度O(n2)O(n^2)O(n2)。排序占 O(nlog⁡n)O(n\log n)O(nlogn),固定最大边的循环+双指针遍历占 O(n2)O(n^2)O(n2),总复杂度由后者主导。
  • 空间复杂度O(log⁡n)O(\log n)O(logn)。仅排序所需的递归栈/临时空间。

三、查找总价格为目标值的两个商品

题目描述:
购物车内的商品价格按升序记录于数组 price,请在购物车中找到两个商品的价格总和刚好是 target。若存在多种情况,返回任一结果即可。

示例

  • 输入:price = [3, 9, 12, 15], target = 18,输出:[3,15][15,3]
  • 输入:price = [8, 21, 27, 34, 52, 66], target = 61,输出:[27,34][34,27]

解题思路:
利用数组升序排列的特性,采用双指针法:

  • 初始化 left=0(数组开头)、right=price.size()-1(数组末尾)。
  • 计算当前和 price[left] + price[right]
    • 若和小于 targetleft 右移(需要更大的数)。
    • 若和大于 targetright 左移(需要更小的数)。
    • 若和等于 target,直接返回这两个数。

完整代码:

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) return {price[left], price[right]};
  else right--;
  }
  return {-1, -1};
  }
  };

复杂度分析:

  • 时间复杂度O(n)O(n)O(n)。仅遍历数组一次,双指针最多移动 nnn 次。
  • 空间复杂度O(1)O(1)O(1)。仅使用常数级额外变量。

四、三数之和

题目描述:
给你一个整数数组 nums,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != kj != k,同时满足 nums[i] + nums[j] + nums[k] == 0。请返回所有和为 0 且不重复的三元组(答案中不可包含重复的三元组)。

示例

  • 输入:nums = [-1,0,1,2,-1,-4],输出:[[-1,-1,2],[-1,0,1]]
  • 输入:nums = [0,1,1],输出:[]

解题思路:
先排序数组,结合固定单元素+双指针的策略,同时处理去重:

  1. 排序数组,便于去重和双指针查找。
  2. 固定第一个元素 nums[i]
    • nums[i] > 0,直接break(排序后后续数更大,和不可能为0)。
    • nums[i] 与前一个数重复,跳过(避免重复三元组)。
  3. 用双指针 left=i+1right=nums.size()-1nums[left] + nums[right] = -nums[i]
    • 找到符合条件的三元组后,leftright 分别跳过重复元素。
    • 否则根据和的大小移动指针。

完整代码:

class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
  vector<vector<int>> ret;
    sort(nums.begin(), nums.end());
    for(int i = 0; i < nums.size(); ) {
    if(nums[i] > 0) break;
    int left = i + 1, right = nums.size() - 1, target = -nums[i];
    while(left < right) {
    if(nums[left] + nums[right] < target) left++;
    else if (nums[left] + nums[right] > target) right--;
    else {
    ret.push_back({nums[i], nums[left++], nums[right--]});
    while(left < right && nums[left] == nums[left - 1]) left++;
    while(left < right && nums[right] == nums[right + 1]) right--;
    }
    }
    i++;
    while(i < nums.size() && nums[i] == nums[i - 1]) i++;
    }
    return ret;
    }
    };

复杂度分析:

  • 时间复杂度O(n2)O(n^2)O(n2)。排序占 O(nlog⁡n)O(n\log n)O(nlogn),固定元素+双指针遍历占 O(n2)O(n^2)O(n2)
  • 空间复杂度O(log⁡n)O(\log n)O(logn)(排序所需空间),存储结果的空间不计入额外复杂度。

五、四数之和

题目描述:
给你一个由 n 个整数组成的数组 nums 和一个目标值 target,请找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]]

  • 0≤a,b,c,d<n0 ≤ a, b, c, d < n0a,b,c,d<n 且互不相同
  • nums[a]+nums[b]+nums[c]+nums[d]==targetnums[a] + nums[b] + nums[c] + nums[d] == targetnums[a]+nums[b]+nums[c]+nums[d]==target
    答案可以按任意顺序返回。

示例

  • 输入:nums = [1,0,-1,0,-2,2], target = 0,输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]
  • 输入:nums = [2,2,2,2,2], target = 8,输出:[[2,2,2,2]]

解题思路:
在三数之和的基础上扩展为固定双元素+双指针,同时注意数值溢出问题:

  1. 排序数组,便于去重和双指针查找。
  2. 固定前两个元素 nums[i]nums[j]
    • nums[i] 与前一个数重复,跳过;若 nums[j] 与前一个数(且 j > i+1)重复,跳过。
  3. 用双指针 left=j+1right=nums.size()-1 找四数之和等于 target
    • 计算和时用 long long 避免整数溢出。
    • 找到符合条件的四元组后,leftright 分别跳过重复元素。
    • 否则根据和的大小移动指针。

完整代码:

class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
  sort(nums.begin(),nums.end());
  int n=nums.size();
  vector<vector<int>> ret;
    for(int i=0;i<n-3;)
    {
    for(int j=i+1;j<n-2;)
    {
    int left=j+1,right=n-1;
    while(left<right)
    {
    long long sum=(long)nums[i]+nums[j]+nums[left]+nums[right];
    if(sum>target)
    right--;
    else if(sum<target)
    left++;
    else
    {
    ret.push_back({nums[i],nums[j],nums[left],nums[right]});
    left++;right--;
    while(left<right&&nums[left]==nums[left-1]) left++;
    while(left<right&&nums[right]==nums[right+1]) right--;
    }
    }
    ++j;
    while(j<n&&nums[j]==nums[j-1]) j++;
    }
    ++i;
    while(i<n&&nums[i]==nums[i-1]) i++;
    }
    return ret;
    }
    };

复杂度分析:

  • 时间复杂度O(n3)O(n^3)O(n3)。排序占 O(nlog⁡n)O(n\log n)O(nlogn),两层固定循环+双指针遍历占 O(n3)O(n^3)O(n3)
  • 空间复杂度O(log⁡n)O(\log n)O(logn)(排序所需空间),存储结果的空间不计入额外复杂度。