Leetcode刷题报告2——双指针法
文章目录
- 前言
- [15. 三数之和](https://leetcode.cn/problems/3sum/)
- 题干
- 题解
- 知识点总结
- [42. 接雨水](https://leetcode.cn/problems/trapping-rain-water/)
- 题干
- 题解
前言
这部分总共是4道题,我就挑两道比较典型的题写一下博客吧。
双指针法的核心思路是通过合理的安排,减少暴力枚举的次数,争取在常数复杂度内解决问题。
15. 三数之和
题干
难度:中等
给你一个整数数组 nums
,判断是否存在三元组 [nums[i], nums[j], nums[k]]
满足 i != j
、i != k
且 j != 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; // 跳过重复的istd::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:
输入: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 - 洛谷这道题和本题核心高度一致。