算法通关村第3关|数组与双指针思想(白银)|Leetcode 27. 移除元素、26. 删除有序数组中的重复项、905. 按奇偶排序数组、189. 轮转数组、剑指 Offer 05. 替换空格

1. 删除元素专题

1.1 原地移除所有数组为val的元素

27. 移除元素

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

思考

在删除的时候,从删除位置开始的所有元素都要向前移动,所以这题的关键是如果有很多值为val的元素的时候,如何避免反复向前移动呢?

快慢双指针
  • 定义两个指针slow和fast,初始值都是0。
    • slow之前的位置都是有效部分
    • fast表示当前要访问的元素
  • 遍历的时候,fast不断向后移动:
    • 如果nums[fast]的值不为val,则将其移动到nums[slow++]
    • 如果nums[fast]的值为val,则fast继续向前移动,slow先等待

在这里插入图片描述

class Solution {
    public int removeElement(int[] nums, int val) {
      int slow=0;
        for (int fast = 0; fast < nums.length - 1; fast++) {
            if (nums[fast]!=val) {
                nums[slow++]=nums[fast];
            }
        }
        //返回最后新长度
        return slow;
    }
}
对撞双指针(交换移除)

思想:从右侧找到不是val的值来顶替左侧是val的值。

以nums = [0,1,2,2,3,0,4,2], val = 2为例:

  • 若left指向元素不为2一直往右移动,当nums[left]==2,用nums[right]覆盖,若nums[right]==2,则向左移动直到nums[right]!=2
  • 终止条件:left==right
class Solution {
    public int removeElement(int[] nums, int val) {
        int left = 0;
        int right = nums.length - 1;

        for (left = 0; 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;
    }
}
拓展🤔==“对撞双指针+覆盖”法==

思路:nums[left]==val的时候,将nums[right]位置的元素覆盖nums[left]right--继续循环,否则才让left++

❗️这里特别注意一点,若nums[right]==val,此时将nums[left]覆盖后,还是会有nums[left]==val,则在下次循环中,继续将right–后的元素覆盖给它。

class Solution {
    public int removeElement(int[] nums, int val) {
        int left=0;
       int right= nums.length-1;
        for ( left = 0; left <=right;) {
            if (nums[left]==val)
                nums[left]=nums[right--];
            else
                left++;
        }
        return left;
    }
}

特别说明

举例数组[3,2,2,3],val=3: left=right的时候在数组下标为1的位置出,此时nums[left]!=3,要执行left++操作——>left!=right,且left>right,left=数组长度了。

1.2 删除有序数组的重复项

26. 删除有序数组中的重复项

给你一个 升序排列 的数组 nums ,请你** 原地** 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。

考虑 nums 的唯一元素的数量为 k ,你需要做以下事情确保你的题解可以被通过:

  • 更改数组 nums ,使 nums 的前 k 个元素包含唯一元素,并按照它们最初在 nums 中出现的顺序排列。nums 的其余元素与 nums 的大小不重要。
  • 返回 k

思路:

  • 快指针fast表示遍历数组到达的下标位置,初始fast=0
  • 慢指针slow表示下一个不同元素要填入的下标位置,初始slow=1
class Solution {
    public int removeDuplicates(int[] nums) {
        int slow=1;//表示下一个不同元素要填入的下标位置
        for (int fast = 0; fast < nums.length; fast++) {
            if (nums[fast]!=nums[slow-1]) {//若不等,则将fast指向元素覆盖到slow位置出,slow在指向下一个要填入元素的位置
                nums[slow]=nums[fast];
                slow++;
            }
        }
        return slow;
    }
}

疑问

for循环中,fast < nums.length,而不是fast < nums.length-1

在这段代码中,我们需要遍历整个数组,并通过检查当前元素与上一个不同元素是否相等来判断是否是重复元素。如果循环条件是 fast < nums.length-1,那么最后一个元素将被忽略,因为循环在 fast 到达 nums.length-1 时结束,而最后一个元素无法与下一个元素进行比较。

因此,为了确保将最后一个元素也考虑在内,循环的条件应为 fast < nums.length。这样,在最后一次循环中,fast 将达到 nums.length-1,并检查最后一个元素是否与前一个元素重复。如果重复,则不会将其添加到 slow 的位置,否则将其添加到 slow 位置并将 slow 的值加1。最后返回 slow,即为不重复元素的个数。

2. 元素奇偶移动专题

905. 按奇偶排序数组

给你一个整数数组 nums,将 nums 中的的所有偶数元素移动到数组的前面,后跟所有奇数元素。

返回满足此条件的 任一数组 作为答案。

思路

对撞型双指针

在这里插入图片描述

  • left=0right=arr.length-1
  • left:从0开始逐个检查每个位置是否为偶数。是偶数,跳过;是奇数,停下
  • right:从右向左检查。是奇数,跳过;是偶数,停下
  • 交换array[left]array[right]
  • 再次循环,直到left>=right
class Solution {
    public int[] sortArrayByParity(int[] nums) {
        int left=0;
        int right= nums.length-1;
        while (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;
    }
}

3. 数组轮转问题

189. 轮转数组

给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。

这里介绍一种简单的方法:两轮翻转

思路

  1. 首先对整个数组实行翻转,例如 [1,2,3,4,5,6,7] 我们先将其整体翻转成[7,6,5,4,3,2,1]。
  2. 从 k 处分隔成左右两个部分,这里就是根据k将其分成两组 [7,6,5] 和[4,3,2,1]。
  3. 最后将两个再次翻转就得到[5,6,7] 和[1,2,3,4],最终结果就是[5,6,7,1,2,3,4]
class Solution {
    public void rotate(int[] nums, int k) {
        k%= nums.length;//这里要注意
       reverse(nums,0, nums.length-1);
       reverse(nums,0,k-1);
       reverse(nums,k, nums.length-1);
    }
    public void reverse(int[] nums,int start,int end){
        while (start < end) {
            int temp=nums[start];
            nums[start]=nums[end];
            nums[end]=temp;
            start+=1;
            end-=1;
        }
    }
}

4. 字符串替换空格问题

剑指 Offer 05. 替换空格

请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。

class Solution {
    public String replaceSpace(String s) {
        StringBuilder res = new StringBuilder();
        for (char c : s.toCharArray()) {
            if (c==' ') {
                res.append("%20");
            }else {
                res.append(c);
            }
        }
        return res.toString();
    }
}

这个 StringBuilder 对象 res 在这个程序中用于保存替换后的字符串。因为字符串是不可变类型,因此我们需要一个可变类型来保存新的字符串。每次遇到空格时,我们将 %20 添加到 res 中,否则将该字符添加到 res 中。遍历结束后,我们可以通过调用 toString() 方法将其转换为字符串类型。

使用 StringBuilder 对象可以大大提高字符串操作的效率。因为当我们对一个字符串进行重复的添加、删除、替换操作时,每次操作都会生成一个新的字符串对象,这会导致大量的内存分配和垃圾回收,并且效率低下。而使用 StringBuilder 对象则可以避免这些问题,因为它是可变类型,每次操作都是在原有的对象上进行修改,而不会生成新的字符串对象。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值