双指针刷题笔记
本文是力扣刷题笔记总结,题目地址见https://leetcode-cn.com/leetbook/read/array-and-string
内容为最后两章
本文内容包含:
1、双指针的思想
2、滑动窗口
3、实例一 移动数组中的零
4、实例二 移除数组中的重复元素
5、实例三 反转字符串中的单词
6、实例四 反转字符串
7、实例五 数组拆分
8、实例六 两数之和(有序数组)
9、实例七 移除元素
10、实例八 最大连续1的个数
11、实例九 长度最小的子数组
双指针的思想
双指针即使用两个指针来遍历数组。以翻转数组内容为例,指针i指向数组头部,指针j指向数组尾部,交换指针i与指针j指向的数组内容,之后,i向后走,j先前走,即如下代码所示:
vector<int> nums[...];
int i = 0;
int j = nums.length()-1;
while(i<j){
swap(nums[i],nums[j]);
++i;
--j;
}
这种技巧适用于从两端向中间迭代的问题模型。
滑动窗口
滑动窗口是双指针的一种。在滑动窗口中,同样有两个指针,一个称为慢指针,记为i,一个称为快指针,称为j,由于i和j的移动速度不同,(j-i)的值也在不断变化,就像一个不断变化长度的窗口,因此称为滑动窗口。
以此问题为例:
给你一个数组 nums
和一个值 val
,你需要 原地 移除所有数值等于 val
的元素,并返回移除后数组的新长度。
(图摘自力扣)
//没有真正的移除元素,只是返回了移除重复元素后的数组长度
//只有当j指向的值不是val的时候,i才向后移动
int i=0;
int j=0;
while(j<nums.length()-1){
if(nums[j]!=val){
nums[i]=nums[j];//保存所有不等于val的数组项
++i;
}
++j;
}
return i;
移除数组中的零
题目描述:给定一个数组 nums
,编写一个函数将所有 0
移动到数组的末尾,同时保持非零元素的相对顺序。
//使用滑动窗口,i之前的为已经处理好的串,j以及j之后的串为尚未处理的串
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int i=0;//慢指针
int j=0;//快指针
while(j<nums.size()){
if(nums[j]!=0){
swap(nums[i],nums[j]);
++i//i指针后移
}//依次保存所有的非零元素
++j;//j指针后移
}
return i;//返回元素个数,数组下标从0开始,故返回i(nums[i]=0)
}
};
实际上,在j指向的数组内容不为0的时候,i随j同步向后走,也即是i与j指向同一个元素。在j遇到0的时候,i不动(实际此时i指向的是0),j一直向下走到下一个非0的位置,将此位置的值与i指向的0交换
假如此时数组为1,2,0,0,3,将会是如下情况:
1、i=0 j=0 nums[j]!=0 交换nums[0]与nums[0] nums[0]=1 i=1 j=1
2、nums[j]=nums[1]!=0 交换nums[1]与nums[1] nums[1]=2 i=2 j=2
可以看到,在j第一次遇到0之前,每执行一次循环体,i与j都同步后移
3、num[j=2] ==0 j=3
4、nums[j=3] == 0 j=4
5、nums[4]!=0 交换nums[i=2]与nums[j=4]
6、函数返回
可以看出来,每一次i停止前进的时候,nums[i]均为0,也即是前i-1个元素都是符合题目要求的
移除数组中的重复元素
题目描述:给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
注意是重复出现,也就是不会有这种情况:[1,1,2,1]
此题与上题的思路相同,只是条件有所变化,同样假设i以及之前的串已经处理完毕,j以及j之后的串待处理
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int i=0;
int j=0;
if(nums.size()==0)
return 0;
while(j<nums.size()){
if(nums[i]!=nums[j]){
swap(nums[++i],nums[j]);
}
right++;
}
return left+1;
}
};
反转字符串中的单词
题目描述:给定一个字符串,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。
对每一个单词进行翻转即可。
class Solution {
public:
string reverseWords(string s) {
int leng=s.length();
int i=0;
int j=0;
while(j!=leng){
if(s[j]==' '||j==leng-1){//如果j走到空格或者末尾,此时i、j之间的串为一个单词,对此单词进行翻转
if(j==leng-1)
reverse(s.begin()+i,s.begin()+j+1);//最后一个单词特殊处理
else
reverse(s.begin()+i,s.begin()+j);
++j;
i=j;//i移向下一个单词的开头
}
else{
j++;//向后寻找空格
}
}
return s;
}
};
反转字符串
题目描述:编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符
class Solution {
public:
void reverseString(vector<char>& s) {
char temp;
int len=s.size();
for(int i=0;i<len/2;i++){
temp=s[i];
s[i]=s[len-i-1];
s[len-i-1]=temp;
}
}
};
数组拆分
题目描述:
给定长度为 2n 的整数数组 nums ,你的任务是将这些数分成 n 对, 例如 (a1, b1), (a2, b2), …, (an, bn) ,使得从 1 到 n 的 min(ai, bi) 总和最大。
分析:要使得min(ai,bi)最大,需要尽可能的囊括数组中的大值,假设数组为[1,2,3,4,5,6]
对于6,最大,可以使用6来囊括5,即(5,6)
对于4,为剩下的数据中最大的,可以用来囊括3,即(4,3)
剩下(2,1)
最大和为5+3+1=9
可以发现,得到的是偶数下标数据之和
class Solution {
public:
int arrayPairSum(vector<int>& nums) {
sort(nums.begin(),nums.end());
int res=0;
for(int i=0;i<nums.size();++i){
res=res+nums[i];
++i;、、偶数下标数据之和
}
return res;
}
};
两数之和(有序数组)
题目描述:给定一个已按照 升序排列 的整数数组 numbers ,请你从数组中找出两个数满足相加之和等于目标数 target 。
函数应该以长度为 2 的整数数组的形式返回这两个数的下标值。numbers 的下标 从 1 开始计数 ,所以答案数组应当满足 1 <= answer[0] < answer[1] <= numbers.length 。
使用双指针。i从头开始,j从尾开始,相加之和小于target,由于数组升序排列,i++,否则,j++,直至相等。
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
int len=numbers.size();
int i=0;
int j=len-1;
while(numbers[i]+numbers[j]!=target){
if(numbers[i]+numbers[j]<target)
i++;
else
j--;
}
return {i+1,j+1};
}
};
移除元素
题目描述:给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
分析见滑动窗口
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int i=0;
int j=0;
for(j=0;j<nums.size();j++){
if(nums[j]!=val){
nums[i]=nums[j];
i++;
}
}
return i;
}
};
最大连续1的个数
题目描述:给定一个二进制数组, 计算其中最大连续 1 的个数。
使用双指针。思路为:在j未指向1时,i与j同步前进。当j指向1时,i不再前进,j前进,j每前进一步,如果未指向0,便计算此时子串长度,更新结果,如果指向了0,i、j继续同步前进,寻找下一个1串
也即是i始终指向1串首部。
class Solution {
public:
int findMaxConsecutiveOnes(vector<int>& nums) {
int i=0;
int j=0;
int maxsize=0;
for(j=0;j<nums.size();j++){
if(nums[j]==1){
maxsize=(maxsize<j-i+1)?j-i+1:maxsize;
}
else{
i=j+1;
}
}
return maxsize;
}
};
长度最小的子数组
题目描述:给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
使用滑动窗口。首先处理特殊情况,之后,i、j从0、1开始遍历。
1、当和小于target的时候,i不动,j向后走,直到子数组和大于等于target,此时计算该子数组的长度,对最小长度进行更新
2、j不懂,i向前走,直到子数组和小于target,回到1
不断重复直到j走到数组末尾
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int leng = nums.size();
vector<int>::iterator it0=nums.begin();
int i = 0;
if (nums[i] >= target)
return 1;//首元素大于target,结果为1
int j = 1;
int minLength = leng;//初始值
int res;
bool flag=false;
while (j <= leng - 1) {
res = accumulate(it0 + i, it0 +1+ j, 0);
if (res >= target) {
flag=true;//触发flag说明有解
minLength = (minLength < (j - i + 1)) ? minLength : (j - i + 1);
if(res-nums[i]>=target)
i++;
else
j++;
}
else {
j++;
}
}
if(flag==0)
return 0;//未触发flag,无解
return minLength;
}
};