【算法题】双指针(二)

一、盛最多水的容器

题目描述:

给定一个长度为 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)
  • 移动策略:每次移动较短边的指针(若移动长边,间距减小且短边不变/更小,面积必然减小;移动短边可能找到更长的边,从而增大面积)。
  • 遍历过程中持续更新最大面积,直到左右指针相遇。

完整代码:

cpp 复制代码
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 右移,尝试更大的较小边。

完整代码:

cpp 复制代码
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,直接返回这两个数。

完整代码:

cpp 复制代码
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 分别跳过重复元素。
    • 否则根据和的大小移动指针。

完整代码:

cpp 复制代码
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 < n0≤a,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 分别跳过重复元素。
    • 否则根据和的大小移动指针。

完整代码:

cpp 复制代码
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)(排序所需空间),存储结果的空间不计入额外复杂度。
相关推荐
点云SLAM2 小时前
Boost库中Math 模块的根搜索 / 根求解和示例
数学·算法·数值优化·根搜索 / 根求解和示例·函数根求解·boost模块
我搞slam2 小时前
EM Planner算法与代码解读
算法
CodeWizard~2 小时前
线性筛法求解欧拉函数以及欧拉反演
算法
45288655上山打老虎2 小时前
右值引用和移动语义
算法
liulilittle3 小时前
C++ 并发双阶段队列设计原理与实现
linux·开发语言·c++·windows·算法·线程·并发
白狐_7983 小时前
【项目实战】我用一个 HTML 文件写了一个“CET-6 单词斩”
前端·算法·html
Jasmine_llq3 小时前
《P3811 【模板】模意义下的乘法逆元》
数据结构·算法·线性求逆元算法·递推求模逆元
虹科网络安全3 小时前
艾体宝干货 | Redis Java 开发系列#2 数据结构
java·数据结构·redis