双指针的妙用-算法通关村

双指针的妙用-算法通关村


1.双指针思想

  • 所谓的双指针其实就是两个变量,不一定真的是指针。
  • 例:删除重复元素 [1,2,2,2, 3, 3,3,5,5,7,8],重复元素中保留一个。删除后的结果为 [1,2,3,5,7,8]。
  • 使用双指针可以方便解决这个问题:
  • 首先我们定义两个指针slow、fast。slow表示当前位置之前的元素都是不重复的,而fast则一直向后找,直到找到与slow位置不一样的,找到之后就将slow向后移动一个位置,并将arrlfast」复制给arr[slow],之后fast继续向后找,循环执行。找完之后slow以及之前的元素就都是单一的了
  • 这种一个在前一个在后的方式也称为快慢指针,从两端向中间走,这种就称为对撞型指针或者相向指针。还有一种比较少见的就是从中间向两边走。

2.删除元素专题

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

  • LeetCode27: 给你一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,并返回移除后数组的新长度。要求:不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

  • 例子1:
    输入:nums = [3,2,2,3],val = 3
    输出:2,nums = [2,2]
    例子2:
    输入:nums = [0,1,2,2,3,0,4,2],val = 2
    输出:5,nums = [0,1,4,0,3](第二种方法结果)
  • 在删除的时候,从删除位置开始的所有元素都要向前移动,所以这题的关键是如果有很多值为val的元素的时候,如何避免反复向前移动呢?

第一种:快慢指针
  • 整体思想就是定义两个指针slow和fast,初始值都是0。Slow之前的位置都是有效部分,fast表示当前要访问的元素。这样遍历的时候,fast不断向后移动:

    • 如果num[fast]的值不为val,则将其移动到numslslow++J处。
    • 如果nums[fast]的值为val,则fast继续向前移动,slow先等待 .
  •   public static int removeElement(int[] nums, int val){
              int slow = 0;
              for(int fast = 0; fast < nums.length; fast++){
                  if(nums[fast] != val){
                      nums[slow] = nums[fast];
                      slow++;
                  }
              }
              return slow;
          }
    
第二种:对撞双指针
  • 对撞指针,有的地方叫做交换移除,核心思想是从右侧找到不是val的值来顶替左侧是val的值。我们以nums =[0,1,2,2,3,0,4,2], val = 2为例:

  •   public static int removeElement3(int[] nums, int val){
              int left = 0, 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],继续循环,如果nums[left] 等于val就继续覆盖,否则才让left++,这也是双指针方法的方法。

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

2.2删除有序数组中的重复项

  • LeetCode26:给你一个有序数组nums,请你原地删除重复出现的元素,使每个元素只出现一次,返回删除后数组的新长度。不要使用额外的数组空间,你必须在原地修改输入数组 并在使用O(1) 额外空间的条件下完成。

  • 示例1:
    输入:nums = [1,1,2]
    输出:2,nums = [1,2]
    解释:函数应该返回新的长度 2,并且原数组 nums 的前两个元素被修改为 1, 2。不需要考虑数组中超出新长度后面的元素。
    例子2:
    输入:nums = [0,0,1,1,1,2,2,3,3,4]
    输出:5,nums = [0,1,2,3,4]
    解释:函数应该返回新的长度 5,并且原数组 nums 的前两个元素被修改为 0, 1, 2, 3, 4。不需要考虑数组中超出新长度后面的元素。
  •   public static int removeDuplicates(int[] nums){
              //slow表示可以放入新元素的位置
              int slow = 1;
              //循环起到了快指针的作用
              for(int fast = 0; fast < nums.length; fast++){
                  if(nums[fast] != nums[slow-1]){
                      nums[slow] = nums[fast];
                      slow++;
                  }
              }
              return slow;
          }
    

3.元素奇偶移动专题

  • LeetCode905,按奇偶排序数组。给定一个非负整数数组 A,返回一个数组,在该数组中,A的所有偶数元素之后跟着所有奇数元素。你可以返回满足此条件的任何数组作为答案。

  • 例如:
    输入:[3, 1, 2, 4]
    输出:[2, 4, 3, 1]
    输出 [4, 2, 3, 1] [2, 4, 1, 3] [4, 2, 1, 3] 也会被接受。
  • 可以采用对撞双指针方法:

  • 维护两个指针 left=0 和 right=arr.length-1,left从0开始逐个检查每个位置是否为偶数,如果是则跳过,如果是奇数则停下来。然后right从右向左检查,如果是奇数则跳过偶数则停下来。然后交换arraylleftl和arraylright]。之后再继续巡循环,直到left>=right。

  •   public static int[] sortArrayByParity(int[] arr){
              int left = 0, right = arr.length-1;
              while(left < right){
                  if(arr[left] %2 > arr[right] %2){
                      int temp = arr[left];
                      arr[left] = arr[right];
                      arr[right] = temp;
                  }
                  if(arr[left] %2 == 0) left++;
                  if(arr[right] %2 == 1) right--;
              }
              return arr;
          }
    

4.数组轮转问题

  • LeetCode189: 给你一个数组,将数组中的元素向右轮转K个位置,其中K是非负数。

  • 例如:
    输入:nums = [1, 2, 3, 4, 5, 6, 7],k = 3
    输出:[5, 6, 7, 1, 2, 3, 4]
    解释:
    向右轮转 1 步:[7, 1, 2, 3, 4, 5, 6]
    向右轮转 2 步:[6, 7, 1, 2, 3, 4, 5]
    向右轮转 3 步:[5, 6, 7, 1, 2, 3, 4]
  • 两轮反转:

    • 首先对整个数组实行翻转,例如 [1,2,3,4,5,6,7] 我们先将其整体翻转成 [7,6,5.4,3,2,1]。
    • 从k处分隔成左右两个部分,这里就是根据k将其分成两组 [7,6,5] 和 [4,3,2,1]。
    • 最后将两个再次翻转就得到 [5,6,7] 和 [1,2,3,4] ,最终结果就是 [5,6,7,1,2,3,4]。
  •   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++;
                  end--;
              }
          }
    

5.数组的区间问题

  • LeetCode228.给定一个无重复元素的有序整数数组nums。返回恰好覆盖数组中所有数字的最小有序区间范围列表。也就是说,nums的每个元素都恰好被某个区间范围所覆盖,并且不存在属于某个范围但不属于 nums 的数字x。列表中的每个区间范围 [a,b] 应该按如下格式输出:“a->b”,如果 a != b"a",如果 a==b。

  • 示例1:
    输入:nums = [0, 1, 2, 4, 5, 7]
    输出:[“0->2”,“4->5”,“7”]
    解释:区间范围是:
    [0, 2]—> “0->2”
    [4, 5]—>“4->5”
    [7, 7]—>“7”
  • 示例2:
    输入:nums = [0, 2, 3, 4, 6, 8, 9]
    输出:[“0”,“2->4”,“6”,“8->9”]
    解释:区间范围是:
    [0, 0]—>“0”
    [2, 4]—>“2->4”
    [6, 6]—>“6”
    [8, 9]—>“8->9”
  • 慢指针指向每个区间的起始位置,快指针从慢指针位置开始向后遍历直到不满足连续递增(或快指针达到数组边界),则当前区间结束;然后将 slow指向更新为 fast +1,作为下一个区间的开始位置,fast继续向后遍历找下一个区间的结束位置,如此循环,直到输入数组遍历完毕。

  •   public static List<String> summaryRanges(int[] nums){
              List<String> res = new ArrayList();
              //slow 初始指向第一个区间的起始位置
              int slow = 0;
              for(int fast = 0; fast < nums.length; fast++){
                  //fast 向后遍历,直到不满足连续递增
                  //或者,fast 到达数组边界
                  if(fast +1 == nums.length || nums[fast]+1 == nums[fast+1]){
                      //将当前区间 [slow, fast] 写入结果列表
                      StringBuilder sb = new StringBuilder();
                      sb.append(nums[slow]);
                      if(slow != fast){
                          sb.append("->").append(nums[fast]);
                      }
                      res.add(sb.toString());
                      //将 slow 指向更新为 fast+1,作为下一个区间的起始位置
                      slow = fast+1;
                  }
              }
              return res;
          }
    

6.字符串替换空格问题

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

  •   public String replaceApace(StringBuffer str){
              StringBuilder res = new StringBuilder();
              for(int i = 0; i < str.length(); i++){
                  char c = str.charAt(i);
                  if(c == ' '){
                      res.append("%20");
                  }else{
                      res.append(c);
                  }
              }
              return res.toString();
          }
    
  • 比较好的方式是可以先遍历一次字符串,这样可以统计出字符串中空格的总数,由此计算出替换之后字符串的长度,每替换一个空格,长度增加2,即替换之后的字符串长度为:

    • 新串的长度=原来的长度+2*空格数目
    • 接下来从字符串的尾部开始复制和替换,用两个指针fast和slow分别指向原始字符串和新字符串的末尾,然后:slow不动,向前移动fast:
      • 若指向的不是空格,则将其复制到slow位置,然后fast和slow同时向前一步;
      • 若fast指向的是空格,则在slow位置插入一个%20,fast则只移动一步。
      循环执行上面两步,便可以完成替换。详细过程如下:
  •   public String replaceSpace(StringBuffer str){
              if(str == null){
                  return null;
              }
              int numOfBlank = 0;//空格数量
              int len = str.length();
              for(int i = 0; i < len; i++){//计算空格数量
                  if(str.charAt(i) == ' '){
                      numOfBlank++;
                  }
              }
              str.setLength(len + 2*numOfBlank);
              int fast = len-1;
              int slow = (len + 2*numOfBlank) -1;
              while(fast > 0 && slow > fast){
                  char c = str.charAt(fast);
                  if(c == ' '){
                      fast--;
                      str.setCharAt(slow--, '0');
                      str.setCharAt(slow--, '2');
                      str.setCharAt(slow--, '%');
                  }else{
                      str.setCharAt(slow, c);
                      fast--;
                      slow--;
                  }
              }
              return str.toString();
          }
    
  • 16
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

种一棵树leng

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值