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

双指针的思想

双指针其实就是两个变量,看题可以理解

双指针的分类:如图这种一个在前一个在后的方式称为快慢指针

                        从两端向中间走:对撞型指针或者相向指针(常用)

                        从中间向两边走:背向型

插入排序用的是快慢型双指针

快速排序用的是对撞型双指针:二叉树的前序遍历+对撞型双指针

删除元素专题

该专题其实是添加的变形

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

Leetcode27 

快慢双指针
思路分析:

slow之前的为有效部分,fast则指向当前访问元素

遍历过程会出现两种情况:

如果nums[fast]的值不为val,就将其所指向的值移动到nums[slow++]处,slow有效个数+1;

如果nums[fast]的值等于val,仍未找到与val不同的值,fast++(继续遍历链表),slow等待

代码实现:
 public int removeElement(int[] nums, int val) {
           int length = nums.length;
         int slow = 0;
         //fast从头到尾遍历整个链表,fast不管怎样都要进行++
         for (int fast = 0;fast<length;fast++){
             //找到不同的值
             if (nums[fast] != val){
                nums[slow++] = nums[fast];
             }
         }
         //slow是有效长度,每发现一个都要进行++
         return slow;
    }

对撞双指针

对撞双指针也叫做交换移除

思路分析:

与上一种做法思路类似,只不过是从右侧往左遍历,找到不是val的值来顶替左侧是val的值

当左指针>右指针时结束遍历(如果是奇数的数组有可能会出现恰好中间值也满足条件的情况)

代码实现
  public int removeElement(int[] nums, int val) {
        int right = nums.length - 1;
        int left = 0;
        //从左往右开始进行改变,将左边全部变为为有效值
        for (; left <= right; ) {
            if (nums[left] == val && nums[right] != val) {
                //恰好直接进行互换并覆盖,两个指针同时移动
                nums[left++] = nums[right--];
            }
            else if (nums[left] != val){
                left++;
            }
            else if (nums[right] == val){
                right--;
            }
        }
        return left;
    }

删除有序数组中的重复项

Leetcode26

思路分析:

一眼双指针类型的题目,一个指针负责数组遍历,另一个指向有效数组的最后一个位置

注意事项:slow指针从1开始,毕竟如果数组只有一个元素直接返回slow就可以了 

                  数组中的第一个元素不需要比较,从第二位开始就可以了

代码实现:
 public int removeDuplicates(int[] nums) {
        int slow = 1;
        int fast = 0;

        for (; fast < nums.length ; fast++) {
            if (nums[fast] != nums[slow-1]) {
               nums[slow] = nums[fast];
               slow++;
            }
        }
        return slow;
    }

元素奇偶移动专题

leetcode905

题目要求:奇偶排序数组,令数组所有的偶数元素在奇数元素的前面

思路分析:

方法一:创建一个新数组,两次遍历数组分别把偶数和奇数添加到新数组中去(不推荐)

方法二:与上一道题类似,使用对撞型指针的方法(只不过将比较的对象变成了奇偶数)

由于需要整个数组中的所有元素,不能使用覆盖法(与删除有所区别),不过也是左右指针同时遍历,如果左边符合条件就往右走一步,右边符合条件就往左走一步(直到left>right)遍历完整条链表

代码实现:

方法二:

public int[] sortArrayByParity(int[] nums) {
       int left = 0;
       int right = nums.length-1;
       int temp;
       //左右指针相遇后退出
       for (;left <= right;){
           //左右条件都满足,左右各走一步
           if (nums[left] %2 ==1 && nums[right] %2 ==0){
               temp = nums[left];
               nums[left] = nums[right];
               nums[right]= temp;
               left++;
               right--;
           }
           //左边符合条件往后走
            if (nums[left] %2 ==0){
               left++; 
           }
           //右边符合条件往左走
            if (nums[right] %2 ==1){
               right--;
           }
       }
       return nums;
    }

数组轮转问题

leetcode189

题目要求:将一个数组中的元素向右轮转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]

翻转方法:就是双指针前后交换

代码实现
public static void rotate(int[] nums, int k) {
        //当k大于nums.length其实是走过一轮,要取余
        k %= nums.length;
        //两次翻转
        reverse(nums, 0, nums.length - 1);
        //第二轮翻转
        reverse(nums, 0, k - 1);
        reverse(nums, k, nums.length - 1);
    }

    //翻转方法
    public static 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;
        }
    }

数组的区间专题

Leetcode228

思路分析:

经典的双指针问题,让慢指针记录连续区间的起始位置,让快指针进行逐一遍历

一旦发现不连续则记录该区间,并将该区间记录到list中去

存在两个边界:fast+1 == nums.length要写在前面,不然会导致后面的条件出现数组越界的问题

                        nums[fast] + 1 != nums[fast+1]       连续的条件,如果满足就继续遍历  

代码实现:

public static List<String> summaryRanges(int[] nums) {
        List<String> res = new ArrayList<>();
        // slow 初始指向第 1 个区间的起始位置
        int slow = 0;
        for (int fast = 0; fast < nums.length; fast++) {
            // fast 向后遍历,直到不满足连续递增(即 nums[fast] + 1 != nums[fast + 1])
            // 或者 fast 达到数组边界,则当前连续递增区间 [slow, 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;
    }

字符串替换空格问题

题目要求:实现一个函数,将一个字符串中的每个空格替换成"%20" 

思路分析:

第一种方法:如果长度不可变,就重新申请个更大空间,原数组出现空格位置直接替换即可

Java中字符串可以直接拼接

第二种方法:从头开始遍历整个字符串,遇到空格就将后面元素移动(效率低)

                    先遍历一遍字符串,统计出字符串中空格的总数,找出新串的长度 = 原来的长度+2*空格数目

然后从字符串的尾部开始复制和替换操作,利用双指针分别指向原始字符串和新字符串的末尾

出现两种情况: fast指向的不是空格,则将其直接复制到slow位置,然后fast和slow同时向前走

                          fast指向的是空格,在slow位置插入一个%20,fast向前走一步

//方法一
public static String replaceSpace1(StringBuffer str) {
        String res = "";
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            if (c == ' ')
                res += "%20";
            else
                res += c;
        }
        return res;
    }

//方法二
public static String replaceSpace2(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;

         //当slow == fast说明前面已经没有空格了,可以节约时间
        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();
    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值