1.双指针思想
所谓的双指针其实就是两个变量,不一定是真的指针,我们在前面也已经使用过了。双指针思想简单好用,在处理数组和字符串时很常见。
常见的双指针思想有三种:
1.快慢指针,也就是常规的一前一后。
2.对撞型指针,两个指针从两端开始,向中间走。
3.背向型指针,两个指针从中间向两边走,这种比较少见。
2.删除元素专题
2.1原地移除所有数值等于val的元素
leetcode力扣 27https://leetcode.cn/problems/remove-element/题目:给你一个数组nums和一个值val,你需要原地移除所有数值等于val的元素,并返回移除后数组的新长度。 要求:不使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。‘
示例: 输入: nums = [0,1,2,2,3,0,4,2] , val = 2
输出:5,nums = [0,1,3,0,4]
在删除的时候,从删除位置后面开始的所有元素都要向前移动,所以本题的关键是如何在有很多元素等于val时,如何避免反复向前移动。 这里使用双指针就可以很好地解决。
第一种:快慢指针
先创建slow,fast都等于0;然后通过for循环将fast向后遍历,若fast指向的值不为val,就将其复制至slow处,然后slow+1。这样只要fast遍历至数组尾部结束循环后,slow之前的元素就是有效元素,个数刚好是slow,返回slow。
这里需要注意的是,if里的fast别写成fast++了,因为for第三个表达式里写了,fast进行一轮循环后是一定会+1的,所以再写就是错的了。
int removeElement(vector<int>& nums, int val) {
int slow = 0;
for(int fast=0;fast<nums.size();fast++){
//若fast指向的值不是val,就将其复制至前方
if(nums[fast]!=val){
nums[slow++]=nums[fast];
}
}
return slow;
}
第二种:对撞型指针
left=0,right=size-1。然后开始for循环遍历,若left指向的值等于val且right指向的值不为val,就将left,right指向的值交换。然后,对应进行指针加加减减就行
需要注意for循环条件是left<=right,具体为什么可以自己带例子进去试试就知道了。还有就是注意第一个if里的不要写成left++,right--。因为如果条件成立,交换值之后,下面的两个if判断会进行加加减减,如果写了后面一次循环可能就会一下+2,-2,最后返回的left会造成意想不到的错误。
int removeElement(vector<int>& nums, int val) {
int left=0;
for(int right=nums.size()-1;left<=right; ){
if(nums[left]==val && nums[right]!=val){
int temp=nums[left];
nums[left]=nums[right];
nums[right]=temp;
}
if(nums[left]!=val){ left++;}
if(nums[right]==val){ right--;}
}
return left ;
}
到这里我们可以发现,快慢指针最后的有效元素的顺序与原序列里是一致的,而对撞型最后的就不一定了。
2.2删除有序数组中的重复项
leetcode力扣 26https://leetcode.cn/problems/remove-duplicates-from-sorted-array/description/题目:给你一个非严格递增排列的数组 nums ,请你原地删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的相对顺序应该保持 一致 。然后返回 nums 中唯一元素的个数。
示例:输入:nums = [0,0,1,1,1,2,2,3,3,4]
输出:5,nums = [0,1,2,3,4]
这题使用双指针的快慢指针很方便,也只需要遍历一次。
创建slow,fast都等于1,因为nums[0]是肯定要留下的,之后用fast开始遍历,nums[fast]!=nums [slow-1] 时,就将fast处的值复制给slow处,并且slow++。这样最后,slow左侧的所有数值就是有效数值了,个数刚好为left,返回left。
但是,我们在for循环里做了个升级,只有fast>slow才会交换,为什么呢?假如nums=[1,2,3,4],这样的话,就是一直在自己给自己复制了,所以加入了小if判断,但在小if里别写slow++了,因为大if里有了。
int removeDuplicates(vector<int>& nums) {
int slow=1;
for(int fast=1;fast<nums.size();fast++){
if(nums[fast]!=nums[slow-1]){
if(fast>slow) {nums[slow]=nums[fast];} //减少了原地重复复制
slow++;
}
}
return slow;
}
进阶:上面这题既然重复元素可以保留一个,那是否可以最多保留2个,3个或者K个呢?甚至一个都不要呢?感兴趣的可以继续研究一下leetcode力扣 80一题.。
3.元素奇偶移动
根据某些条件移动元素也是一类常见的题目,排序其实就是在移动元素
leetcode力扣 905https://leetcode.cn/problems/sort-array-by-parity/description/
题目:给定一个非负整数数组 A,返回一个数组,在该数组中, A 的所有偶数元素之后跟着所有奇数元素。你可以返回满足此条件的任何数组作为答案。
示例: 输入:[3,1,2,4]
输出:[2,4,3,1],答案不唯一,满足要求即可
这题最容易想到的就是新创建一个同大小的数组,然后遍历两次原数组,先将偶数复制到新数组的前半段,然后将奇数复制到后半段,但有没有空间复杂度为O(1)的方法呢?
使用对撞型双指针就可以
创建left=0,right=size-1。然后同时遍历数组,如果left指向的是奇数且right指向的是偶数就交换它们的值。若不是,就让left找到奇数,right找到偶数,然后再交换。
这里的循环结束条件<或<=都可以,但<=在某些时候会多执行一次循环,所以使用<更快。
vector<int> sortArrayByParity(vector<int>& nums) {
int left=0;
for(int right=nums.size()-1;left<right; ){
//left指向奇数且right指向偶数,交换
if(nums[left]%2 >nums[right]%2){
int temp=nums[left];
nums[left]=nums[right];
nums[right]=temp;
}
if(nums[left]%2==0) left++;
if(nums[right]%2==1) right--;
}
return nums;
}
有没有发现对撞型双指针的方法使用起来很像,就是处理条件换了一下,其实这就是对撞双指针的解题模板。多写几题,记住就好。
4.数组轮换问题
leetcode力扣 189https://leetcode.cn/problems/rotate-array/description/题目:给你一个数组,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。
示例: 输入: nums = [1,2,3,4,5,6,7], k = 3
输出: nums = [5,6,7,1,2,3,4]
这题用反转可以很快实现,观察上面的例子,我们先对整个nums反转,然后再对前k个元素反转,再对后面剩余的元素反转,就会变成了答案。
所以我们先定义一个反转函数,参数left,right表示反转的区域。然后先反转整个数组,在对前k个元素反转,最后对后面剩余的元素反转就可以了。
这里要注意,先要k=k%size,不然传参数的时候会错误。
//反转数组
void reverse(vector<int>& nums, int left, int right){
for( ;left<right;left++,right-- ){
int temp=nums[left];
nums[left]=nums[right];
nums[right]=temp;
}
}
void rotate(vector<int>& nums, int k) {
k=k%nums.size();
reverse(nums, 0, nums.size()-1);
reverse(nums, 0, k-1);
reverse(nums, k, nums.size()-1);
}
5.数组的区间问题
leetcode力扣 228https://leetcode.cn/problems/summary-ranges/description/
拓展:
leetcode力扣 163https://leetcode.cn/problems/missing-ranges/description/
6.字符串替换空格问题
nowcoder牛客https://www.nowcoder.com/questionTerminal/4060ac7e3e404ad1a894ef3e17650423?answerType=1&f=discussion题目:请实现一个函数,将一个字符串中的每个空格替换成“%20”。
示例:当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
这里有两种情况:
首先遍历原数组,算出空格的数量count。newlength=length+2*count。
1.如果数组长度不可变,那就必须申请一个更大的数组,newsize=size+2*count。之后遍历原数组,复制到新数组,遇到空格就改为“%20“。
2.如果数组长度可变,或者说数组的长度足够,那么创建olds=length-1,news=newlength-1。开始遍历,当str[olds]==' '时,就在news位置相应加上”%20“,然后下标更新。最后在newlength处写入”\0“结束符。
注意循环条件 olds<news && olds>=0,前面是为了防止自己复制自己,多余操作,后面是当olds遍历完原字符串后就结束。前面可以省略但后面不能省略。
void replaceSpace(char *str,int length) {
if(str==NULL) return ;
int count=0;
int i=0;
//计算空格数量
while(i<length){
if(str[i]==' ') count++;
i++;
}
int newlength=length + 2*count;
int olds=length-1;
int news=newlength-1;
while(olds<news && olds>=0){
if(str[olds]==' '){
str[news--]='0';
str[news--]='2';
str[news--]='%';
olds--;
}else{
str[news--]=str[olds--];
}
}
//加上结束符
str[newlength]='\0';
}