Leetcode刷题报告2——双指针法

前言

这部分总共是4道题,我就挑两道比较典型的题写一下博客吧。

双指针法的核心思路是通过合理的安排,减少暴力枚举的次数,争取在常数复杂度内解决问题。


15. 三数之和

题干

难度:中等

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != kj != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。

**注意:**答案中不可以包含重复的三元组。

示例 1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。

示例 2:

输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。

示例 3:

输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。

提示:

  • 3 <= nums.length <= 3000
  • -105 <= nums[i] <= 105

题解

在哈希表那一章我们写了两数之和,当时要求是a+b=target,这道题很自然就想到固定abc之一,让a+b=-c,转化为两数之和问题,这样可以在常熟复杂度内解决。但是需要去重。

这是哈希表的核心代码,不再给出输入输出部分:

class Solution {
public:
    std::vector<std::vector<int>> threeSum(std::vector<int>& nums) {
        std::sort(nums.begin(), nums.end());
        std::vector<std::vector<int>> ans;
        for (int i = 0; i < nums.size(); i++) {
            if (i > 0 && nums[i] == nums[i - 1]) continue; // 跳过重复的i
            std::unordered_set<int> seen;
            for (int j = i + 1; j < nums.size(); j++) {
                int complement = -nums[i] - nums[j];
                if (seen.count(complement)) {
                    ans.push_back({ nums[i], complement, nums[j] });
                    while (j + 1 < nums.size() && nums[j] == nums[j+1]) j++; // 跳过重复的j
                }
                seen.insert(nums[j]);
            }
        }
        return ans;
    }
};

这种方法也能通过测试,只是效率略低,问题在于find过程中,每个数仍然被扫描了多次,没有真正意义上实现常数阶复杂度。

以下是完整的双指针法,确保每个数只会被扫过一次。

class Solution {
public:
    std::vector<std::vector<int>> threeSum(std::vector<int>& nums) {
        std::vector<std::vector<int>> ans;
        std::sort(nums.begin(), nums.end());
        for (int i = 0;i < nums.size();++i) {
            if (i > 0 && nums[i] == nums[i - 1])continue;
            int k = nums.size() - 1;
            for (int j = i + 1;j < nums.size();++j) {
                if (j > i + 1 && nums[j] == nums[j - 1]) continue;
                while (j < k && nums[i] + nums[j] + nums[k] > 0) k--;
                if (k == j) break;
                if (nums[i] + nums[j] + nums[k] == 0) ans.push_back({ nums[i],nums[j],nums[k] });
            }
        }
        return ans;
    }
};

知识点总结

  • 双指针法和单调性紧密相关,利用排序保证单调性。
  • 再次总结哈希表stl家族:unordered_map unordered_multimap unordered_set unordered_multiset 对应键唯一/允许键重复的桶结构,还有值唯一/允许值重复的集合结构。
  • 插入哈希表方法:insert和emplace。
  • 插入顺序容器方法:insert,push_back,emplace_back。
  • 现代c++统一初始化:利用{},实现变量、函数、类、容器等等几乎所有场景的初始化。
  • 初始化列表(initializer_list):利用{},创建initializer_list模板类。几乎所有的stl都提供了利用initializer_list的构造函数。

42. 接雨水

题干

难度:困难

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

示例 1:

img

输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 

示例 2:

输入:height = [4,2,0,3,2,5]
输出:9

提示:

  • n == height.length
  • 1 <= n <= 2 * 104
  • 0 <= height[i] <= 105

题解

我的第一道困难题,其实也不是非常困难,可能因为之前在洛谷写过类似的题的缘故。之前做过一些双指针法-单调栈/单调队列的问题。直接给出代码吧。

class Solution {
public:
    int trap(std::vector<int>& height) {
        int l = 0, r = height.size() - 1, maxH = 0, sumArea = 0, blockArea = 0;
        for (int i : height) {
            maxH = std::max(maxH, i);
            blockArea += i;
        }
        for (int h = 1;h <= maxH;++h) {
            while (height[l] < h) l++;
            while (height[r] < h) r--;
            sumArea += (r - l + 1);
        }
        return sumArea - blockArea;
    }
};

非常简单,但是足够解决问题了,方法就是先计算总面积,再减去柱子面积,即可得到水的面积。总面积通过双指针可得。

其实没什么好说的,这道题同样有单调栈解法,不过我没有写,可以看一下洛谷的单调栈问题:P1950 长方形 - 洛谷

另外洛谷的[P2866 USACO06NOV] Bad Hair Day S - 洛谷这道题和本题核心高度一致。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值