看这一篇就够啦,双指针题型解题模板总结

前言

对于双指针题型,大家肯定都不陌生:快慢指针,头尾指针,二分查找等等,所以一般双指针题型都会出现在哪里呢? “ 数 组 ” \color{red}{“数组”} 。因为数组的结构特点,它可以把双指针的特点和考点发挥的淋漓尽致。
所以今天我们的题型讲解也主要是针对于数组内容。下面开始上干货!

一丶模板讲解

双指针题型的坑点挺多,但是按照指针走向,我们可以大致分为两种。一种是同向,一种是异向。今天的模板也是大致根据这两种来进行。

(1)同向指针

首先就是同向指针

在这里插入图片描述

关于同向指针,我们要把数组分为这三部分。

[0,i),在这个区间的数据代表处理过并且我需要的数据,[i,j)这个区间代表我处理过但是不需要的数据,[ j , arr.length)代表我们目前没有处理过的数据。

但是这里注意了!! 这 里 区 间 的 开 和 闭 是 需 要 根 据 题 目 来 确 定 的 \color{red}{这里区间的开和闭是需要根据题目来确定的} ,我们不能一概而论全。但是总体思想是一致的。而且根据这种方法处理过的数组,它的稳定性不会被破坏。这里的稳定性指的是相对位置。

这里的解题步骤大致就是两步:

1.定义两个指针i和j,并且i和j一般都等于02.while j < arr.length:
	if 如果说我们需要arr[j],那么我们就让arr[i] = arr[j],然后i往前走
	一步,让它指向下一个位置。
	else 如果不需要j这个元素,那么我们就跳过arr[j]这个元素,让j指向下一
	个位置,同时不需要改变i的位置。

当然,这里的 if 后面的 arr[i] = arr[j]并不固定,根据题目来确定。

(2)异向指针

接着就是异向指针
在这里插入图片描述

异向指针还是把数组分为三部分

[0,i)和(j,arr.length)都是已经处理好的数据,是我们需要的,[i,j]是我们待处理的元素。

这里注意,我们异向双指针后的数组元素是不具有稳定性的。也就是相对元素位置会改变。

这里的解题步骤还是可以大致分为两部分。

1.定义双指针,i = 0,j = arr.length - 1.
2.while i <= j:
	根据arr[i] 和 arr[j]的值来确定你接下来要做什么。
	移动至少一个指针向他们自身的方向

这里对上面的内容进行说明:这里根据arr[i] 和 arr[j]的元素来确定你要做什么,意思就是你要根据题目的内容,然后确定他们自身的动作,比如说:交换。动作做完之后,再移动至少一个指针。

二丶考点说明

这里的考点说明用单个题来讲有点笼统,所以就抛出对应的题目,然后题目涉及到了的考点,我来说明。

初级考察

(1)344. 反转字符串

在这里插入图片描述
这个题就很简单了,我们直接套异向指针的模板就行。

class Solution {
    public void reverseString(char[] s) {
        int i = 0,j = s.length - 1;
        while(i < j){
            char tmp = s[i];
            s[i] = s[j];
            s[j] = tmp;
            i++;
            j--;
        }
    }
}

这里的就是

i 和 j 操作考察。但是注意了,我这里写的是 i < j,不是 <= ,这里不固定,并不影响题目。这道题先上手一下异向指针。

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

在这里插入图片描述
这道题就是对于细节的考察了。如果说是一个乱序的数组,那么暴力遍历就可以解决。但是这是有序数组,而且你去掉重复元素之后元素相对位置也是不变的。所以这道题我们就可以使用同向双指针处理。

class Solution {
    public int removeDuplicates(int[] nums) {
        int cur = 0;
        for(int i = 0;i < nums.length;i++){
            if(nums[i] != nums[cur]){
                cur++;
                nums[cur] = nums[i];
            }
        }
        cur++;
        return cur;
    }
}

这道题哪里的点需要注意呢?(注意这里的 i 和 j 说的是模板中的标记方式)

[0,j]是我们处理过的元素集合,而在这段元素集合中,[0,i]是我们需要的元素,也就是不重复元素。而(i,j]是我们不需要的元素,也就是相同的元素。所以我们的条件就很清楚,arr[i] != arr[j],但是arr[i + 1] == arr[j]。

所以这里考察的点有两个:
1. 关 于 开 区 间 和 闭 区 间 的 选 择 \color{red}{1.关于开区间和闭区间的选择} 1.
2. 关 于 i 和 j 元 素 前 移 的 判 断 \color{red}{2.关于i和j元素前移的判断} 2.ij

(3)1047. 删除字符串中的所有相邻重复项

在这里插入图片描述
首先一拿到题肯定第一思路就是同向指针,因为这题内涵就是元素一定是要相对稳定。虽然这题比较特殊,但是大体思路和上面一致,我们还是调用模板套用就好啦。但是细节处肯定是要注意的。

class Solution {
    public String removeDuplicates(String s) {
        int i = -1;
        int j =  0;
        char[] arr = s.toCharArray();
        while(j < arr.length){
            if(i >= 0 && arr[i] == arr[j]){
                i--;
            }else{
                i++;
                arr[i] = arr[j];
            }
            j++;
        }
        return String.copyValueOf(arr,0,i+1);
    }
}

然后说一下这道题特殊的点:

1.关于初始 i 和 j 的下标,这里注意我定义的是 i 是 -1,我这样定义的目的是什么呢?对,就是[0,i]元素是处理过的并且是我们需要的元素。而(i,j]是我们处理过的不需要的元素。
2.就是关于 i 的移动,可以发现,当i位置的元素不是我们需要的元素时候,它不是不动,而是往后移一位。

所以这题考点如下:
1. 关 于 开 区 间 和 闭 区 间 的 选 择 \color{red}{1.关于开区间和闭区间的选择} 1.
2. 关 于 i 下 标 移 动 的 选 择 , 以 及 移 动 的 条 件 判 断 \color{red}{2.关于 i 下标移动的选择,以及移动的条件判断} 2.i

(4)80. 删除有序数组中的重复项 II

在这里插入图片描述

注意了,这道题和leetcode的26题还是有点相似的,但是多了一个细节,就是让每个元素最多出现两次,这就有一个前提的隐含条件。

如果说元素个数 <= 2,那么直接就返回数组本身的长度就好啦。

那么继续讨论,如果说我们想要每个元素最多出现两次,那么前提条件是什么呢? arr[j] != arr[i - 1] 或者说 arr[j] != arr[i - 2]。这里i - 1还是 i - 2要根据我们i元素是开区间还是闭区间来选择。那么如果等于的时候我们怎么办呢?那么就让 j往前走,知道 arr[j] != arr[i - 2]的时候停止,然后把arr[j]的值赋值给arr[i]。

(注意:这里其实思路很清晰了,如果我们选择用 i - 1作为判断条件,也就是i为闭区间的时候,代码会变得很麻烦,所以我们要选择用 i - 2,上面为了思路完整和逻辑的严谨所以我才会说两个都可以)

class Solution {
    public int removeDuplicates(int[] nums) {
        if(nums.length < 3){
            return nums.length;
        }
        int i = 2;
        int j = 2;
        while(j < nums.length){
            if(nums[j] != nums[i - 2]){
                nums[i] = nums[j];
                i++;
            }
            j++;
        }
        return i;
    }
}

所以考点其实还是很明显:
1. 关 于 开 区 间 和 闭 区 间 的 选 择 \color{red}{1.关于开区间和闭区间的选择} 1.
2. 移 动 的 条 件 判 断 \color{red}{2.移动的条件判断} 2.

进阶考察

(5)121. 买卖股票的最佳时机

在这里插入图片描述
注意了嗷,这个题还是比较特殊的。因为除了我们的i 和 j 以外,我们还需要一个中间变量,来串联起这个题所有的线索。
但是我们的思路还是不变的,先给代码,然后开始讲解:

class Solution {
    public int maxProfit(int[] prices) {
        int i = 0;//记录最小价格下标
        int max = 0;//记录差价
        int j = 0;//用来进行遍历
        while(j < prices.length){
            if(prices[j] < prices[i]){
                i = j;
            }else if(prices[j] - prices[i] > max){
                max = prices[j] - prices[i];
            }
            j++;
        }
        return max;
    }
}

是不是感觉if 和 else的判断完全不一样但是我却还说模板能用上有点矛盾?

1.这一题和我们前面的题不一样的是在哪里?我们都是用arr[i]来作为判断条件,但是这题不一样,是用 arr[i] 和 arr[j]的差值来作为判断条件。
2.这一道题[0,i]还可以用“已经是处理好的,并且是我们需要的元素集合”来理解嘛?当然可以,但是不是所有的元素集合,而是其中一组的元素集合。而且我们也只需要其中一对元素集合。
3.那 i 和 j之间的元素集合还可以用“处理过但是我们不需要的元素集合”来理解嘛?当然可以,因为i和j之间任何一对元素的差值都没有[0,i]之间元素差值最大的那个大。

所以这道题考点在哪里呢?

1. 关 于 元 素 i 移 动 的 判 断 \color{red}{1.关于元素i移动的判断} 1.i
2. 动 态 规 划 思 想 的 考 察 \color{red}{2.动态规划思想的考察} 2.

(6)11. 盛最多水的容器

在这里插入图片描述

这一题更加特殊了,因为定睛一看,和121题是挺像的,但是仔细一看是完全不一样的。因为这一题增加了一种新的思想的考察,也就是递归思想的考察。
那么先从头说起,如果我们用同向双指针来做的话是一种怎样的情况?

如果是同向双指针的话,可以发现无从下手,甚至如果你仔细思考,可以发现如果是同向双指针其实就是暴力解法。这样的话就违背了我们使用双指针的初衷。

那么就要从两边走起,可是问题又来了?

如果说我们[0,arr.length - 1]对应了一组盛水容量,那么长度为arr.length - 1的底层下标长度就对应了两组,arr.length - 2就对应了四组,最后我们要怎样知道那一组最大呢?
不还是暴力遍历嘛?

那么这个时候就要用上我们的递归思想

把一个大问题一层一层转换为小的问题来进行考察。就比如说,我们知道底层长度为 arr.length对应了一组雨水容量,那么如果说我们能知道arr.length - 1的底层长度对应的水容量大于 或者 说可能大于arr.length对应的水溶量。那么问题是不是就转化为了,已经知道 目前arr.leng -1 水容量最大,求对应的arr.length - 2水容量。
注意,这个时候对应的就不是四组了,而是两组数据,也就是进入了套娃模式。

所以还是要比较嘛?当然不是,看下面的图,文字有点难以理解,用图来讲解能好理解很多。
在这里插入图片描述
现在对应的雨水容量是不是就只有这一组?也就是arr.length 对应的水容量就只有一组?
那么我们继续往下看:
在这里插入图片描述

这是不是就是arr.length - 1对应的水容量?是不是就是两组?
如果要知道那一组水容量要大于arr.length是不是要比较?当然不是!

注意,我们arr.length - 1两组水溶量的两边其中一遍都用到了arr.length的边长。那么就一个问题需要我们考虑清楚:
arr.length的两条边长,短的那一边对应的水溶量可不可能大于原来的水容量呢?当然不可能,短板效应嘛。只能小于或者等于了。因为能盛水多少是由短的那一边决定了的。所以为了保证不固定,所以那边小我们就要移动那一边。

代码如下:

class Solution {
    public int maxArea(int[] height) {
        int left = 0;
        int right = height.length - 1;
        int maxWater = 0;
        while(left < right){
            int max = Math.min(height[left],height[right]) * (right - left);
            maxWater = max > maxWater ? max : maxWater;
            if(height[left] > height[right]){
                right--;
            }else{
                left++;
            }
        }
        return maxWater;
    }
}

是不是感觉和我们的模板有点类似?
在这里插入图片描述
所以这一道题的考点用一句话总结:
用 递 归 思 想 处 理 题 目 方 向 , 用 动 态 规 划 思 想 来 处 理 细 节 。 \color{red}{用递归思想处理题目方向,用动态规划思想来处理细节。}

(7)42. 接雨水

在这里插入图片描述

先来看,如果说,我们用同向指针来进行处理,我们可以怎样想?

如果arr[i] >= arr[j],那么雨水容量增加arr[i] - arr[j],如果arr[i] < arr[j],那么i = j,然后继续j++。

对吧?乍一看没啥问题,但是有一个很重要前提,就是arr[arr.length - 1]最后一个元素是最大的,这样才可以保证,中途这个变换条件成立,但是很明显,上面图形最后一位元素高度不是最大的,所以会出现什么情况呢?
在这里插入图片描述
就是从图中黄色柱子开始,条件全面崩塌。

那么图中的黄色柱子有什么特点呢?没错,就是图中最高的那个柱子。

那么解题思想就出来了

用异向指针,i和j分别从两边开始,像上面我们同向指针处理那样向自己的方向前进,一遍前进,一边处理。

下面给出代码:

class Solution {
    public int trap(int[] height) {
        int left = 0,right = height.length - 1;
        int water = 0;
        int leftmax = 0,rightmax = 0;
        while(left < right){
            leftmax = Math.max(leftmax,height[left]);
            rightmax = Math.max(rightmax,height[right]);
            if(height[left] > height[right] ){
                 water += rightmax - height[right];
                --right;
            }else{
                
                water += leftmax - height[left];
                ++left;     
            }
        }
        return water;
    }
}

是不是感觉有点迷?
那来叙述一下:

11题的整体思路差不多,那一边小那一边就往自己对应的方向前进一步。然后一
直循环,直到left >= right。

具体的考点如下:
1. 动 态 规 划 思 想 的 考 察 \color{red}{1.动态规划思想的考察} 1.
2. i 和 j 移 动 过 程 中 数 据 的 处 理 \color{red}{2.i和j移动过程中数据的处理} 2.ij
3. 移 动 条 件 的 判 断 ( 其 实 也 就 是 动 态 规 划 的 考 察 ) \color{red}{3.移动条件的判断(其实也就是动态规划的考察)} 3.

三丶总结

双指针的考点总结其实就一下几个点:
1. 开 区 间 闭 区 间 的 判 断 \color{red}{1.开区间闭区间的判断} 1.
2. i 和 j 移 动 过 程 中 数 据 的 处 理 \color{red}{2.i和j移动过程中数据的处理} 2.ij
3. 移 动 条 件 的 判 断 \color{red}{3.移动条件的判断} 3.

但是题目难度上升一般都是在移动条件判断,为什么呢?

因为这里引用重难点动态规划等等题型和处理方法,也就是说,双指针其实也只是其中处理方法的一环而已。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值