数组与双指针

1.双指针思想

所谓的双指针其实就是两个变量,不一定是真的指针,我们在前面也已经使用过了。双指针思想简单好用,在处理数组和字符串时很常见。 

常见的双指针思想有三种:

1.快慢指针,也就是常规的一前一后。

2.对撞型指针,两个指针从两端开始,向中间走。

3.背向型指针,两个指针从中间向两边走,这种比较少见。

2.删除元素专题

2.1原地移除所有数值等于val的元素

leetcode力扣 27icon-default.png?t=N7T8https://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力扣 26icon-default.png?t=N7T8https://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力扣 905icon-default.png?t=N7T8https://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力扣 189icon-default.png?t=N7T8https://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力扣 228icon-default.png?t=N7T8https://leetcode.cn/problems/summary-ranges/description/

拓展:

leetcode力扣 163icon-default.png?t=N7T8https://leetcode.cn/problems/missing-ranges/description/

6.字符串替换空格问题

nowcoder牛客icon-default.png?t=N7T8https://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';

	}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值