1.双指针算法基础知识
在暴力算法中,往往有大量的重复计算,无用计算,可由两个指针合作带来的单调性优化
2.基本题目
剑指 Offer 57. 和为s的两个数字
class Solution {
public:
/*
1.暴力:ij两重循环o(n^2)从0开始遍历,但i不动时,j相当于很长一段都在找<target的值,
2.单调性:甚至[i]就算与最大数组合也偏小,不如直接一个从头一个从尾,如果偏小直接省略o(n)过程
*/
vector<int> twoSum(vector<int>& nums, int target) {
int n = nums.size();
int i = 0, j = n - 1;
while(i < j)
{
if(nums[i] + nums[j] == target) return {nums[i], nums[j]};
else if(nums[i] + nums[j] > target) j --;
else i ++;
}
return {};
}
};
剑指 Offer 57 - II. 和为s的连续正数序列
class Solution {
public:
/*
1.暴力:i指向头,j遍历找尾巴,求一段和,超过就i++,j继续从i遍历
2.单调性:
[i,j]段<sum时,j继续后移
[i,j]段>sum时,说明不存在i开头的数组和==sum,需要i++;i++后的[i,j]和如果<sum,说明前面的[i,j-k]之类的都<sum,也就优化了暴力做法的j每次从i遍历!太机智了
找到==sum就输出
*/
vector<vector<int>> findContinuousSequence(int sum) {
//不用暴力枚举i.j,由于i往后移动,在正整数序列中,j肯定往后移动的单调性,用双指针优化成o(n)
vector<vector<int>> res;
for(int i = 1,j = 1,s =1;i<=sum;i++)//注意是<=,是在i循环里面
{
while(s < sum) s += ++ j;//一次性加完,下一步就判断
if(s==sum && j-i>0)//长度>1
{
vector<int> line;
for(int k = i;k<=j;k++) line.push_back(k);
res.push_back(line);
}
s -= i;//i移位的时候减去
}
return res;
}
};
15. 三数之和
class Solution {
public:
/*
先排序,不重复的情况下枚举第一个数p1,在p1后面的部分使用双指针找p2p3
*/
vector<vector<int>> threeSum(vector<int>& nums) {
int n = nums.size();
sort(nums.begin(), nums.end());//排序
vector<vector<int>> ans;
for(int p1 = 0;p1 < n;p1 ++)//枚举p1
{
if(p1 > 0 && nums[p1] == nums[p1 - 1]) continue;//跳过p1重复数
int target = - nums[p1];
int p3 = n - 1, p2 = p1 + 1;//开始设置双指针
while(p2 < p3)
{
while(p2 > p1 + 1 && p2 < p3 && nums[p2] == nums[p2 - 1]) p2 ++;//跳过p2重复数
if(p2 == p3) break;//上一步保证了p2<p3,但跳出时可能时p2==p3,需要避免在之后的判断中
if(nums[p2] + nums[p3] == target)
{
ans.push_back({nums[p1], nums[p2], nums[p3]});
p2 ++,p3 --;
}
else if(nums[p2] + nums[p3] > target) p3 --;
else p2 ++;
}
}
return ans;
}
};
16. 最接近的三数之和
class Solution {
public:
/*
先排序,不重复的情况下枚举第一个数p1,在p1后面的部分使用双指针找p2p3
*/
int threeSumClosest(vector<int>& nums,int x) {
int n = nums.size();
sort(nums.begin(), nums.end());//排序
int best = 1e4;
for(int p1 = 0;p1 < n;p1 ++)//枚举p1
{
if(p1 > 0 && nums[p1] == nums[p1 - 1]) continue;//跳过p1重复数
int target = x - nums[p1];
int p3 = n - 1, p2 = p1 + 1;//开始设置双指针
while(p2 < p3)
{
while(p2 > p1 + 1 && p2 < p3 && nums[p2] == nums[p2 - 1]) p2 ++;//跳过p2重复数
if(p2 == p3) break;//上一步保证了p2<p3,但跳出时可能时p2==p3,需要避免在之后的判断中
int s = nums[p2] + nums[p3];
if(s == target) return x;
if(abs(s - target) < abs(best)) best = s - target;//比较一个全局res即可
if(s > target) p3 --;
else p2 ++;
}
}
return x + best;
}
};
剑指 Offer 21. 调整数组顺序使奇数位于偶数前面
class Solution {
public:
vector<int> exchange(vector<int>& nums) {
int n = nums.size();
int i = 0, j = n - 1;
while(i < j)
{
while(i < n && nums[i] % 2) i ++;//找到偶数停止
while(j >= 0 && nums[j] % 2 == 0) j --;//找到奇数停止
if(i < j) swap(nums[i], nums[j]);//i<j则交换
}
return nums;
}
};
283. 移动零
class Solution {
public:
void moveZeroes(vector<int>& nums) {
//flag指向非零元素将被放的位置,由于保证非零元素相对顺序,所以都从前往后遍历,先遍历到的也放前面
int flag = 0, j = 0;
int n = nums.size();
while(j < n)
{
if(nums[j] != 0) {swap(nums[flag], nums[j]); flag++}
j ++;
}
}
};
75. 颜色分类
注意:i左边的值是扫描过的,p0左边又全是0,所以p0是1,i与p0交换后i是1,直接i++即可
class Solution {
public:
void sortColors(vector<int>& nums) {
int n = nums.size();
int p0 = 0, p2 = n - 1;
for(int i = 0; i < n && i <=p2; )
{
if(nums[i] == 0){swap(nums[i],nums[p0]); p0 ++; i ++;}//是0和p0交换,p0++,保证p0左边严格=0,
else if(nums[i] == 2) {swap(nums[i], nums[p2]); p2 --;}//是2和p2交换,p2--,保证p2右边严格=2
else i ++;
}
}
};
11. 盛最多水的容器
class Solution {
public:
int maxArea(vector<int>& height) {
//双指针算法
//左右指针先指向左右端点ab,则容积=min(a,b)*t,若左小,则下一个可能的地方必须移动左:证明如下
//a<b,若移动b到c,新面积min(a,c)*(t-1),由于min(a,c)是<=a的,且t-1<t,所以肯定面积变小
//所以必须移动小的那个,才有可能变大,移动也就表示在接下来寻找更大的面积时,a不可能再作为边界了(min是<=a的,t也减小,相当于排除o(n)的遍历情况
int l = 0, r = height.size()-1;
int res = 0;
while(l<r)
{
int v = min(height[l], height[r]) * (r - l);
res = max(v, res);
if(height[l] <= height[r]) l ++;
else r --;
}
return res;
}
};
560. 和为K的子数组
hashmap优化的前缀和思想,不用单独建立pre前缀和数组
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
unordered_map<int,int> mp;
mp[0] = 1;
int cnt = 0, pre = 0;
for(auto x : nums)
{
pre += x;
if(mp.find(pre-k) != mp.end()) cnt += mp[pre-k];
mp[pre] ++;
}
return cnt;
}
};
941. 有效的山脉数组
class Solution {
public:
//方法1:线性扫描:先找到最高点坐标,且不能是0或者n-1;再往山下找
//方法2:双指针:<3返回false;从两端往上爬,相遇且不为0||n-1则是true
bool validMountainArray(vector<int>& A) {
bool decrease = false;
int n = A.size();
if(n == 0 || n == 1 ) return false;
if(A[1] < A[0] || A[n - 1] > A[n - 2]) return false;//单增或单减的情况
for(int i = 1; i < A.size(); i ++)
{
if(decrease)//反向
{
if(A[i] < A[i - 1]) continue;
else return false;
}
else//递增
{
if(A[i] > A[i - 1]) continue;
else if(A[i] == A[i - 1]) return false;
else decrease = !decrease;
}
}
return true;
}
};
//双指针
class Solution {
public:
bool validMountainArray(vector<int>& A) {
int n = A.size();
if(n < 3) return false;
int i = 0, j = n - 1;
while(i + 1 <= j && A[i + 1] > A[i]) i ++;
while(j - 1 >= i && A[j - 1] > A[j]) j --;
if(i == j && i != 0 && i != n - 1) return true;
else return false;
}
};
922. 按奇偶排序数组 II
class Solution {
public:
vector<int> sortArrayByParityII(vector<int>& A) {
//偶奇偶奇排列即可
int n = A.size();
int i = 0, j = 1;
for(int k = 0; k < n && i < n && j < n;)
{
if(A[k] % 2 == k % 2) {k ++; continue;}
else if(A[k] % 2 == 0) {swap(A[k], A[i]); i += 2;}
else if(A[k] % 2 == 1) {swap(A[k], A[j]); j += 2;}
}
return A;
}
};