前言(回顾一下):
Leetcode 283.移动零
思路:
使用双指针,左指针指向当前已经处理好的序列的尾部,右指针指向待处理序列的头部。右指针不断向右移动,每次右指针指向非零数,则将左右指针对应的数交换,同时左指针右移。
注意到以下性质:
①左指针左边均为非零数;
②右指针左边直到左指针处均为零。
因此每次交换,都是将左指针的零与右指针的非零数交换,且非零数的相对顺序并未改变。
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int i = 0, j = 0;
for(; i < nums.size(); i ++){
// 维护j的位置(指向小于i的&&nums[i] == 0的位置)
while(j < i && nums[j]) j ++;
swap(nums[i], nums[j]);
}
}
};
Leetcode 11. 盛最多水的容器
暴力求解
即枚举每一个可能的左右边界,比较出最大的那个矩形面积即可。
class Solution {
public:
int maxArea(vector<int>& height) {
int res = 0, n = height.size();
for(int i = 0; i < n; i ++){
for(int j = i + 1; j < n; j ++){
int h = min(height[i], height[j]);
res = max(res, h * (j - i));
}
}
return res;
}
}; //1e5的数据范围,n^2过不了
双指针优化
i是左指针,j是右指针。由于面积受限于较短边,因此若移动较大高度的指针,那么区域的长度-1,高度的上限仍然是较短边的边长,即面积只会减少(本题的单调性体现)。因此,可以优化掉这部分计算。(相向双指针)
class Solution {
public:
int maxArea(vector<int>& height) {
int res = 0, n = height.size();
for(int i = 0, j = n - 1; i < j; ){
res = max(res, (j - i) * min(height[i], height[j])); // 计算
// 移动指针
if(height[i] > height[j]) j --;
else i ++;
}
return res; // 时间:O(n) 空间:O(1)
}
};
Leetcode 167.两数之和 II - 输入有序数组
简单来说和两数之和的区别在于,数组本身有序,对应唯一答案。
单调性体现:如果nums[i] + nums[j] > target,那么i++只会让数值更大,所以最后的解一定只存在于让j--(让nums[j]变小)的情况内。
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
vector<int> res;
int n = numbers.size();
int i = 0, j = n - 1;
while (i < j){
if(numbers[i] + numbers[j] == target){
res.push_back(i + 1); res.push_back(j + 1);
}
// 维护指针位置
if(numbers[i] + numbers[j] > target) j --;
else i ++;
}
return res;
}
};
Leetcode 15. 三数之和
暴力解法
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
unordered_set<string> hash;
vector<vector<int>> res;
int n = nums.size();
sort(nums.begin(), nums.end());
for(int i = 0; i < n; i ++)
for(int j = i + 1; j < n; j ++)
for(int k = j + 1; k < n; k ++){
if(nums[i] + nums[j] + nums[k] == 0){
string x = to_string(nums[i]) + "," + to_string(nums[j]) + "," + to_string(nums[k]);
if(!hash.count(x)){ // 没出现过
vector<int> t = {nums[i], nums[j], nums[k]};
res.push_back(t);
hash.insert(x);
}
}
}
return res;
}
};
本题核心解决两个问题:
①如何去重?
②用双指针优化掉暴力解法(主指针i移动确定出target,j和k作为剩下位置的相向指针,转化成 Leetcode 167.两数之和 II - 输入有序数组)
双指针+判重
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> res;
int n = nums.size();
sort(nums.begin(), nums.end());
for(int i = 0; i < n - 2; i ++){ // 每次固定好i,剩下的j和k当做两数之和2即可
int x = nums[i];
if(x + nums[i + 1] + nums[i + 2] > 0) break; // 后面就不会有满足要求的条件了(优化①)
if(x + nums[n - 2] + nums[n - 1] < 0) continue;//x太小了,得大一点(优化②)
if(i > 0 && x == nums[i - 1]) continue; // 判重(i确定了target的取值=-x)
int j = i + 1, k = n - 1;
while (j < k){
int s = nums[j] + nums[k];
if(s == -x){
vector<int> t = {nums[i], nums[j], nums[k]};
res.push_back(t);
}
// 维护指针区间
if (s < -x){
j ++;
while(j < k && nums[j] == nums[j - 1]) j ++; // 判重
}else{
k --;
while(j < k && nums[k] == nums[k + 1]) k --; // 判重
}
}
}
return res;
}
};
Leetcode 42. 接雨水
方法一:前后缀分解
抽象成木板水桶,能接多少水取决于左边木板高、右边木板高和黑色柱体的高度。即
min(pre, suf) - height[i]
class Solution {
public: // 方法一: 前后缀分解 时间O(n),空间O(n)
int trap(vector<int>& height) {
int n = height.size(), res = 0;
vector<int> pre(n, 0); // 后缀数组
vector<int> suf(n, 0); // 前缀数组
pre[0] = height[0];
// 初始化前后缀数组
for(int i = 1; i < n; i ++){
pre[i] = max(pre[i - 1], height[i]);
}
suf[n - 1] = height[n - 1];
for(int i = n - 2; i >= 0; i --){
suf[i] = max(suf[i + 1], height[i]);
}
// 求解每一个桶能装多少水
for(int i = 0; i < n; i ++){
res += min(pre[i], suf[i]) - height[i];
}
return res;
}
};
方法二:相向双指针
思路:如果左边的前缀最大值比右边后缀最大值小,那么左边这个木桶的容量就是前缀最大值(单调性体现在:右边已经算出的后缀值在后面的计算过程中单调不减!!前缀值的计算也是单调不减),算完之后把它向右扩展。如果后缀最大值比前缀最大值小,那么右边这个木桶的容量就是后缀最大值,算完后把它向左扩展。
class Solution {
public: // 方法二:相向双指针 时间O(n),空间O(1)
int trap(vector<int>& height) {
int n = height.size(), res = 0;
int l = 0, r = n - 1;
int pre = 0, suf = 0; // 前缀高度和后缀高度
while (l < r){ // 谁小谁移动=>最后l==r的位置一定是最高的柱子
pre = max(pre, height[l]);
suf = max(suf, height[r]);
res = res + (pre < suf ? (pre - height[l ++]) : (suf - height[r --]));
}
return res;
}
};
方法三:单调栈
方法一/二均假设每个位置上都有一个桶,相当于是逐一竖着计算。那么是否可以采用横着计算的方式?-->"填水泥"
总的来说,单调栈的思路就是“找上一个更大元素,在找的过程中填坑”。
class Solution {
public:
int trap(vector<int>& height) {
deque<int> stack; // 模拟stack
int n = height.size(), res = 0;
for(int i = 0; i < n; i ++){
while(!stack.empty() && height[i] > height[stack.back()]){ // 需要填补
int bottem_h = height[stack.back()]; stack.pop_back(); // 修补pop出的位置
if(stack.empty()) break; // 左边阶梯上升则无法接水
int h = height[i];
int left_h = height[stack.back()];
int delta_width = i - stack.back() - 1;
res += (min(left_h, h) - bottem_h) * delta_width;
}
stack.push_back(i);
}
return res;
}
};
Leetcode 189. 轮转数组
方法一:拷贝数组
时间和空间的消耗都是O(n)的
class Solution {
public:
void rotate(vector<int>& nums, int k) { // 通过引用返回答案
int n = nums.size();
vector<int> t(nums);
for(int i = 0; i < n; i ++)
nums[(i + k) % n] = t[i];
}
};
方法二:原地作法,翻转
class Solution {
public:
void rotate(vector<int>& nums, int k) {
auto rev = [&](int l, int r){
while(l < r)
swap(nums[l ++], nums[r --]);
};
int n = nums.size();
k %= n; // 轮转k次 等效于 轮转k%n 次
rev(0, n - 1);
rev(0, k - 1);
rev(k, n - 1);
}
};