双指针
977.有序数组的平方
可用数组中绝对值的最小值和其左边一个索引,为两个指针的起始点进行遍历(平方值从小到大)
class Solution {
public:
int abs(int a){
if(a >= 0) return a;
else return -a;
}
vector<int> sortedSquares(vector<int>& nums) {
int l=0,r=0;
while(r<nums.size() && nums[r]<0) r++;
l = r - 1;
vector<int> res;
while(l>=0 && r<nums.size()){
if(abs(nums[l])<=nums[r]){
res.push_back(nums[l]*nums[l]);
l--;
}
else{
res.push_back(nums[r]*nums[r]);
r++;
}
}
while(l>=0){
res.push_back(nums[l]*nums[l]);
l--;
}
while(r<nums.size()){
res.push_back(nums[r]*nums[r]);
r++;
}
return res;
}
};
也可以从数组的左右端点进行遍历,这两个数的平方值中必有一个为最大值,这样依次遍历到的是平方值递减的数组,所以我们对于创建的数组res从后往前存入,这样就实现了res的递增。
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
int n = nums.size();
// 创建一个n个元素且全为0的数组
vector<int> res(n, 0);
int k = n;
int i = 0, j = n - 1;
while(i<=j){
if(nums[i]*nums[i] <= nums[j]*nums[j]){
res[--k] = nums[j]*nums[j];
j--;
}
else{
res[--k] = nums[i]*nums[i];
i++;
}
}
return res;
}
};
滑动窗口
209.长度最小的子数组
暴力法
枚举每个左端点,然后继续枚举右端点
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int n = nums.size();
int minlen = n + 1;
for(int i = 0; i < n; i++){
if(nums[i] >= target) return 1;
int sum = nums[i];
int j = i + 1;
while(j < n && sum < target){
sum += nums[j];
j++;
}
if(sum >= target)
minlen = min(minlen, j - i);
}
if(minlen == n + 1) return 0;
else return minlen;
}
};
只能过18个点
滑动窗口
滑动窗口与暴力法的不同在于它的循环只需要遍历一次,并且它遍历的不是左端点,而是右端点。
实现滑动窗口,主要确定如下三点:
- 窗口内代表了什么
窗口内代表满足条件的区间 - 窗口的左端点什么时候开始移动?
当满足条件时,左端点开始移动,直到不满足条件位置,这个过程中可以进行记录的操作 - 右端点什么时候开始移动?
循环每次移动的就是右端点
根据上述条件,我们代入到题目中
窗口内代表满足区间和>=target的区间
左端点再区间和>=target的时候开始移动,直到不满足条件为止
右端点每次循环即会移动一次
模板:
- 首先外循环遍历右端点
- 在循环内设置变量
- 再次用循环判断区间
[l,r]
是否满足条件,如果满足条件则l
向右移动,直到不满足条件为止(或者从不满足条件到满足条件为止),这期间可以持续记录某些值的 操作
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int n = nums.size();
int minlen = n + 1;
int l = 0, r = 0;
int sum = 0;
while(r < n){
sum += nums[r];
while(l <= r && sum >= target){
int sublen = r - l + 1;
minlen = min(minlen, sublen);
sum-=nums[l++];
}
r++;
}
if(minlen == n + 1) return 0;
return minlen;
}
};
904.水果成蓝
套模板
class Solution {
public:
int totalFruit(vector<int>& fruits) {
map<int,int> mp;
int i = 0, j = 0;
int n = fruits.size();
int value;
int maxsize = 0;
// 1.首先外循环遍历右指针
while(j < n){
// 进行赋值操作,把当前水果的数量放入map里
value = fruits[j];
mp[value] += 1;
// 内循环遍历左指针,直到不满足条件为止
while(mp.size() > 2){
value = fruits[i];
mp[value]--;
// 如果没有水果了就把它从map里删除
if(mp[value] == 0) mp.erase(value);
i++;
}
// 在循环之后,区间是已经是满足条件了(水果种类<=2),这时候开始记录值
int sum = 0;
for (const auto& pair : mp){
sum += pair.second;
}
maxsize = max(maxsize,sum);
// 右指针移动
j++;
}
return maxsize;
}
};
76.最小覆盖字串
class Solution {
public:
bool check(unordered_map<char,int>& a,unordered_map<char,int>& b){
// 判断b是否是a的子集
for(const auto& pair: b){
if(pair.second > a[pair.first]) return false;
}
return true;
}
string minWindow(string s, string t) {
int i = 0, j = 0;
int n = s.length();
int minlen = n + 1;
unordered_map<char,int> a,b;
int l = -1;
for(i = 0; i < t.length(); i++){
b[t[i]] += 1;
}
i = 0;
// 首先右端点进行循环
while(j < n){
// 进行赋值操作
char c = s[j];
a[c] += 1;
// 左端点进行循环移动直到区间不满足条件为止,期间一直记录长度
while(check(a,b) && i<=j){
// 满足条件的同时一直记录最小长度和左边界
int len = j - i + 1;
if(len<minlen){
minlen = len;
l = i;
}
// 左端点移动
a[s[i]] -= 1;
i++;
}
// 右端点移动
j++;
}
if(l == -1) return "";
else return s.substr(l,minlen);
}
};
模拟
59.螺旋矩阵
注意边界条件,固定为左闭右开,统一化
l和r是用来遍历的指针,rb等为边界,遍历的圈数为loop
n为奇数或偶数时中心点是否需要赋值也需要判断
每一圈遍历之后边界对应缩小或放大
l和r会回到该圈的起始位置,所以再一圈遍历完后需要让l和r都+1
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> a(n, vector<int>(n, 0));
int t = 0;
int l = 0, r = 0;
int rb = n - 1, bb = n - 1, lb = 0, tb = 0;
int target = n * n;
// 转的圈数
int loop = n / 2;
while(loop--){
while(l < rb && t<= target){
a[r][l++] = ++t;
}
rb--;
while(r < bb && t<= target){
a[r++][l] = ++t;
}
bb--;
while(l > lb && t<= target){
a[r][l--] = ++t;
}
lb++;
while(r > tb && t<= target){
a[r--][l] = ++t;
}
tb++;
// l和r最终会回到起始位置,下一圈的位置需要+1
l++,r++;
}
// 如果n为奇数,那么需要赋值中心点
if(n %2 !=0) a[n/2][n/2] = ++t;
return a;
}
};