第一章——数组基础(LeetCode题型归纳篇——数组双指针python版)

LeetCode题型归纳——数组双指针

目录

一、双指针算法基础

所谓双指针算法,就是指的是在遍历的过程中,不是普通的使用单个指针进行循环访问,而是使用两个相同方向或者相反方向的指针进行扫描,从而达到相应的目的。双指针法充分使用了数组有序这一特征,从而在某些情况下能够简化一些运算,降低时间复杂度.
在这里插入图片描述

二、双指针算法分类

包含:

  • 相向双指针
  • 同向双指针 - 快慢指针
  • 同向双指针 - 滑动窗口
  • 分离双指针

下面分别介绍:

2.1 相向双指针

是指在有序数组中,将指向最左侧的索引定义为左指针 (left),最右侧的定义为右指针 (right),然后从两头向中间进行数组遍历 。(前提:数组有序,若无序,先排序)
在这里插入图片描述

2.1.1 两数之和(1)

在这里插入图片描述
在这里插入图片描述

方法一:暴力枚举
枚举数组中的每一个数 x,寻找数组中是否存在 target - x。当我们使用遍历整个数组的方式寻找 target - x 时,需要注意到每一个位于 x 之前的元素都已经和 x 匹配过,因此不需要再进行匹配。而每一个元素不能被使用两次,所以我们只需要在 x 后面的元素中寻找 target - x。

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        for i in range(len(nums) - 1):
            for j in range(i + 1, len(nums)):
                if nums[i] + nums[j] == target:
                    return [i, j]
class Solution {
    public int[] twoSum(int[] nums, int target) {
        for (int i = 0; i < nums.length - 1; i++){
            for (int j = i + 1; j < nums.length; j++){
                if (nums[i] + nums[j] == target){
                    return new int[] {i, j};
                }
            }
        }
        return new int[0];
    }
}

方法二:哈希表解法
       创建一个哈希表,对于每一个 x,我们首先查询哈希表中是否存在 target - x,若不存在则将 x 插入到哈希表中,即可保证不会让 x 和自己匹配,若存在,则返回 。
       例如:
       初始状态:
在这里插入图片描述

第一步:哈希表中没有11,所以把2插入,对应的索引0作为其值,
在这里插入图片描述

第二步:哈希表中没有6,所以把7插入,对应的索引1作为其值,
在这里插入图片描述

第三步:哈希表中有2,所以返回2对应的索引0和11对应的索引2
在这里插入图片描述

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        # 创建一个哈希表
        res = {}
        # 遍历
        for i in range(len(nums)):
            
            if target - nums[i] not in res:
                res[nums[i]] = i
            else:
                return [res[target - nums[i]], i]
class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> hashtable = new HashMap<>();
        for(int i = 0; i < nums.length; i++){
            if(hashtable.containsKey(target - nums[i])){
                return new int[] {hashtable.get(target - nums[i]), i};
            }else{
                hashtable.put(nums[i], i);
            }
        }
        return new int[0];
    }
}

方法三:相向双指针解法
双指针(L,R)法的思路很简单, L指针用来指向第一个值, R指针用来从第L指针的后面查找数组中是否含有和L指针指向值和为目标值的数。
两个指针分别从左从右开始扫描,每次判断这两个数相加是不是等于target,如果小了,那就把左边的指针向右移,同理如果大了,就把右指针往左移。(前提:数组是有序的)

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        # 复制原数组,为后续找原数组中的位置做准备
        array = nums.copy()
        # 首先对数组进行从小到大排序
        nums.sort()
        # 初始化左右指针
        left, right = 0, len(nums) - 1
        
        # 扫描,找到满足和为target的两个数的位置
        while left < right:
            if nums[left] + nums[right] < target:
                left += 1
            elif nums[left] + nums[right] > target:
                right -= 1
            else:
                break
                
        res = []
        # 找出该位置对应原数组中的索引
        for i in range(len(array)):
            if array[i] == nums[left] or array[i] == nums[right]:
                res.append(i)
        return res

修改一:
如果假设输入一个数组 nums 和一个目标和 target, 请你返回 nums 中能够凑出 target 的两个元素的值,比如输入 nums = [5,3,1,6], target = 9,那么算法返回两个元素 [3,6]。可以假设只有且仅有一对元素可以凑出 target。

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        # 对数组进行排序
        nums.sort()
        # 初始化双指针
        left, right = 0, len(nums) - 1
        # 开始遍历
        while left < right:
            sums = nums[left] + nums[right]
            if sums < target:
                left += 1
            elif sums > target:
                right -= 1
            else:
                # 本修改需要返回的是两个元素的值
                return [nums[left], nums[right]]
class Solution {
    public int[] twoSum(int[] nums, int target) {
        //方法三:排序+双指针
        int[] temp = Arrays.copyOf(nums, nums.length);
        Arrays.sort(nums);    //升序排序
        int begin = 0;
        int end = nums.length - 1;
        while(begin < end){
            if(nums[begin] + nums[end] == target){
                break;
            }else if(nums[begin] + nums[end] > target){
                end--;
            }else{
                begin++;
            }
        }
        int a = -1;
        int b = -1;
        for(int i=0; i<temp.length; i++){
            if(temp[i] == nums[begin] && a == -1){
                a = i;
                continue;
            }
            if(temp[i] == nums[end]){
                b = i;
            }
        }
        return new int[]{a, b};
    }
}

修改二:
nums 中可能有多对元素之和都等于 target,请你的算法返回所有和为 target 的元素对,其中不能出现重复,比如说输入为 nums = [1,3,1,2,2,3], target = 4,那么算法返回的结果就是:[[1,3],[2,2]]。
在这里插入图片描述

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        # 先进行排序
        nums.sort()
        # 初始化双指针
        left, right = 0, len(nums) - 1
        res = []
        # 开始遍历
        while left < right:
            sums = nums[left] + nums[right]
            if sums < target:
                left += 1
            elif sums > terget:
                right -= 1
            else:
                res.append([nums[left], nums[right]])
                # 去重,注意数组是有序的,所以可以这样
                while left < right and nums[left] == nums[left + 1]:
                    left += 1
                while left < right and nums[right] == nums[right - 1]:
                    right -= 1
                left += 1
                right -= 1
        return res

2.1.2 两数之和II - 输入有序数组(167)

在这里插入图片描述
在这里插入图片描述

具体步骤如下:

借助数组的有序性:

  1. 使用双指针,一个指针指向值较小的元素,一个指针指向值较大的元素。指向较小元素的指针从头向尾遍历,指向较大元素的指针从尾向头遍历。
  2. 如果两个指针指向元素的和 sum == target,那么得到要求的结果;
  3. 如果 sum > target,移动较大的元素,使 sum 变小一些;如果 sum < target,移动较小的元素,使 sum 变大一些。
class Solution:
    def twoSum(self, numbers: List[int], target: int) -> List[int]:
        # 初始化双指针
        left, right = 0, len(numbers) - 1
        # 开始遍历
        while left < right:
            sums = numbers[left] + numbers[right]
            if sums < target:
                left += 1
            elif sums > target:
                right -= 1
            else:
                return [left + 1, right + 1]
        # 若没有找到满足的元素对,返回[-1, -1]
        return [-1, -1]
class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int left = 0;
        int right = numbers.length - 1;
        while(left < right){
            int s = numbers[left] + numbers[right];
            if(s == target){
                return new int[] {left+1, right+1};
            }else if(s > target){
                right--;
            }else{
                left++;
            }
        }
        return new int[0];
    }
}

2.1.3 三数之和(15)

在这里插入图片描述
在这里插入图片描述

例如,给定数组 nums = [-1, 0, 1, 2, -1, -4],满足要求的三元组集合为:[[-1, 0, 1], [-1, -1, 2]]。
在这里插入图片描述

采取固定一个数,同时用双指针来查找另外两个数的方式。
在这里插入图片描述

具体步骤如下:

  1. 我们可以先对数组进行排序,然后我们选择一个数字做 C 位,然后我们在这个 C 位数字的右边进行双指针搜索:
  2. 从最左边 i+1(最小值)和最右边 len(nums)-1(最大值)两个数字开始,加上 C 位,计算总和是否等于 0。
    如果大于 0,说明实力太强了,就把右侧的数字左移一位。
    如果小于 0,说明实力太弱了,就把左边的数字右移一位。
  3. 当双指针碰到的时候,这轮循环结束,以该数字为 C 位的所有可能都已经尝试完毕了。
class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        # 先进行排序
        nums.sort()
        res = []
        # 固定一个数,然后用双指针找另外两个数
        for i in range(len(nums) - 2):
            # 剪枝
            if nums[i] > 0:
                break
            # 去重,如果当前固定的数和上一次循环相等,直接跳过
            if i > 0 and nums[i] == nums[i - 1]:
                continue
            # 初始化双指针
            left, right = i + 1, len(nums) - 1
            # 开始遍历双指针
            while left < right:
                sums = nums[i] + nums[left] + nums[right]
                if sums < 0:
                    left += 1
                elif sums > 0:
                    right -= 1
                else:
                    res.append([nums[i], nums[left], nums[right]])
                    # 去重
                    while left < right and nums[left] == nums[left + 1]:
                        left += 1
                    while left < right and nums[right] == nums[right - 1]:
                        right -= 1
                    left += 1
                    right -= 1
        return res
class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        int length = nums.length;
        List<List<Integer>> res = new ArrayList<>();
        Arrays.sort(nums);
        for(int i=0; i<length-2; i++){
            if(i > 0 && nums[i] == nums[i-1]){
                continue;
            }
            if(nums[i] + nums[i+1] + nums[i+2] > 0){
                break;
            }
            if(nums[i] + nums[length-2] + nums[length-1] < 0){
                continue;
            }
            int left = i + 1;
            int right = length - 1;
            while(left < right){
                int sum = nums[i] + nums[left] + nums[right];
                if(sum == 0){
                    List<Integer> temp = new ArrayList<>();
                    temp.add(nums[i]);
                    temp.add(nums[left]);
                    temp.add(nums[right]);
                    res.add(temp);
                    left++;
                    right--;
                    while(left < right && nums[left] == nums[left-1]){
                        left++;
                    }
                    while(left < right && nums[right] == nums[right+1]){
                        right--;
                    }
                }else if(sum < 0){
                    left++;
                }else{
                    right--;
                }
            }
        }
        return res;
    }
}

2.1.4 最接近的三数之和(16)

在这里插入图片描述

例如,给定数组 nums = [-1,2,1,-4], 和 target = 1。与 target 最接近的三个数的和为 2。(-1 + 2 + 1 = 2)。

具体步骤如下:

  1. 在数组 nums 中,进行遍历,每遍历一个值利用其下标 i,形成一个固定值nums[i]。
  2. 使用前指针指向 left = i + 1 处,后指针指向 right = len(nums) - 1 处,也就是结尾处,根据 sums = nums[i] + nums[left] + nums[right] 的结果,判断 sums 与目标 target 的距离,如果更近则更新结果 distance。
  3. 因为数组有序,如果 sums > target 则 right -= 1,如果 sums < target 则 left+= 1,如果 sums == target 则说明距离为 0,直接返回结果。
class Solution:
    def threeSumClosest(self, nums: List[int], target: int) -> int:
        # 先进行排序
        nums.sort()
        # 初始化最近距离和对应的三数之和
        distance_closest = abs(nums[0] + nums[1] + nums[2] - target)
        sums_closest = nums[0] + nums[1] + nums[2]
        # 固定一个数,然后用双指针遍历另外两个数
        for i in range(len(nums) - 2):
            # 初始化双指针
            left, right = i + 1, len(nums) - 1
            # 双指针遍历
            while left < right:
                sums = nums[i] + nums[left] + nums[right]
                distance = abs(sums - target)
                # 判断新距离是否小于最小距离,若是,则更新
                if distance < distance_closest:
                    distance_closest = distance
                    sums_closest = sums
                # 移动双指针
                if sums < target:
                    left += 1
                elif sums > target:
                    right -= 1
                # 如果等于target,则距离为0,直接返回
                else:
                    return target
        return sums_closest
class Solution {
    public int threeSumClosest(int[] nums, int target) {
        int dis = 50000;
        int res = 0;
        int n = nums.length;
        Arrays.sort(nums);

        for(int i=0; i<n-2; i++){
            if(i > 0 && nums[i] == nums[i-1]){
                continue;
            }
            int left = i + 1;
            int right = n - 1;
            while(left < right){
                int s = nums[i] + nums[left] + nums[right];
                if(Math.abs(s - target) < dis){
                    dis = Math.abs(s - target);
                    res = s;
                }
                if(s == target){
                    return target;
                }else if(s > target){
                    right--;
                    while(left < right && nums[right] == nums[right+1]){
                        right--;
                    }
                }else{
                    left++;
                    while(left < right && nums[left] == nums[left-1]){
                        left++;
                    }
                }
            }
        }
        return res;
    }
}

2.1.5 四数之和(18)

在这里插入图片描述
在这里插入图片描述

示例:
给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。满足要求的四元组集合为:[[-1, 0, 0, 1], [-2, -1, 1, 2], [-2, 0, 0, 2]]。

具体步骤如下:

  1. 使用两重循环分别枚举前两个数,然后在两重循环枚举到的数之后使用双指针枚举剩下的两个数。假设两重循环枚举到的前两个数分别位于下标 i 和 j,其中 i<j。
  2. 初始时,左右指针分别指向下标 j+1和下标 n-1。
  3. 每次计算四个数的和,并进行如下操作:
    如果和等于 target,则将枚举到的四个数加到答案中,然后将左指针右移直到遇到不同的数,将右指针左移直到遇到不同的数;
    如果和小于target,则将左指针右移一位;
    如果和大于target,则将右指针左移一位。

具体实现时,还可以进行一些剪枝操作:

  1. 在确定第一个数之后,如果nums[i]+nums[i+1]+nums[i+2]+nums[i+3]>target,说明此时剩下的三个数无论取什么值,四数之和一定大于 target, 因此退出第一重循环;
  2. 在确定第一个数之后,如果nums[i]+nums[n-3]+nums[n-2]+nums[n-1]<target, 说明此时剩下的三个数无论取什么值,四数之和一定小于 target, 因此第一重循环直接进入下一轮,枚举nums[i+1];
  3. 在确定前两个数之后,如果 nums[i]+nums[j]+nums[j+1]+nums[j+2]>target, 说明此时剩下的两个数无论取什么值,四数之和一定大于 target, 因此退出第二重循环;
  4. 在确定前两个数之后,如果 nums[i]+nums[j]+nums[n-2]+nums[n-1]<target,说明此时剩下的两个数无论取什么值,四数之和一定小于 target, 因此第二重循环直接进入下一轮,枚举 nums[j+1]。
class Solution:
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        # 先进行排序
        nums.sort()
        res = []
        # 固定两个数,双指针找另外两个数
        for i in range(len(nums) - 3):
            # 去重
            if i > 0 and nums[i] == nums[i - 1]:
                continue
            # 剪枝
            if nums[i] + nums[i+1] + nums[i+2] + nums[i+3] > target:
                break
            if nums[i] + nums[-3] + nums[-2] + nums[-1] < target:
                continue
            for j in range(i + 1, len(nums) - 2):
                # 去重,注意是 j > i + 1
                if j > i + 1 and nums[j] == nums[j - 1]:
                    continue
                # 剪枝
                if nums[i] + nums[j] + nums[j+1] + nums[j+2] > target:
                    break
                if nums[i] + nums[j] + nums[-2] + nums[-1] < target:
                    continue
                # 初始化双指针
                left, right = j + 1, len(nums) - 1
                # 遍历双指针
                while left < right:
                    sums = nums[i] + nums[j] + nums[left] + nums[right]
                    if sums < target:
                        left += 1
                    elif sums > target:
                        right -= 1
                    else:
                        res.append([nums[i], nums[j], nums[left], nums[right]])
                        # 去重
                        while left < right and nums[left] == nums[left + 1]:
                            left += 1
                        while left > right and nums[right] == nums[right - 1]:
                            right -= 1
                        left += 1
                        right -= 1
        return res
class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        //1、先排序
        Arrays.sort(nums);
        //2、二重遍历
        List<List<Integer>> res = new ArrayList<>();
        int n = nums.length;
        for(int i=0; i<n-3; i++){
            //去重
            if(i > 0 && nums[i] == nums[i-1]){
                continue;
            }
            //剪枝,java中四数之和可能超出int的范围,故转为long类型
            if((long)nums[i]+nums[i+1]+nums[i+2]+nums[i+3] > target){
                break;
            }
            if((long)nums[i]+nums[n-3]+nums[n-2]+nums[n-1] < target){
                continue;
            }
            for(int j=i+1; j<n-2; j++){
                //3、双指针寻找
                //去重
                if(j > i+1 && nums[j] == nums[j-1]){
                    continue;
                }
                //剪枝
                if((long)nums[i]+nums[j]+nums[j+1]+nums[j+2] > target){
                    break;
                }
                if((long)nums[i]+nums[j]+nums[n-2]+nums[n-1] < target){
                    continue;
                }
                int left = j + 1;
                int right = n - 1;
                while(left < right){
                    int s = nums[i] + nums[j] + nums[left] + nums[right];
                    if(s == target){
                        List<Integer> list = new ArrayList<>();
                        list.add(nums[i]);
                        list.add(nums[j]);
                        list.add(nums[left]);
                        list.add(nums[right]);
                        res.add(list);
                        left++;
                        right--;
                        while(left < right && nums[left] == nums[left-1]){
                            left++;
                        }
                        while(left < right && nums[right] == nums[right+1]){
                            right--;
                        }
                    }else if(s < target){
                        left++;
                    }else{
                        right--;
                    }
                }
            }
        }
        return res;
    }
}

2.1.6 盛最多水的容器(11)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

解决该问题的关键思想:木桶原理,即短的一端决定了水的多少。

设每一状态下水槽面积为 S(i, j),(0 <= i < j < n), 由于水槽的实际高度由两板中的短板决定,则可得面积公式 S(i,j)=min(h[i],h[j])×(j-i)。

在每一个状态下,无论长板或短板收窄 1 格,都会导致水槽的底边宽度 -1:
若向内移动短板,水槽的短板 min(h[i],h[j]) 可能变大,因此水槽面积 S(i,j) 可能增大。
若向内移动长板,水槽的短板 min(h[i],h[j]) 不变或变小,下个水槽的面积一定小于当前水槽面积。
因此,向内收窄短板可能可以获取面积最大值

换个角度理解:
若不指定移动规则,所有移动出现的 S(i,j) 的状态数为 C(n,2), 即暴力枚举出所有状态。
在状态 S(i,j) 下向内移动短板至 S(i+1,j)(假设 h[i]<h[j] ), 则相当于消去了
S(i,j-1),S(i,j-2),…,S(i,i+1) 状态集合。而所有消去状态的面积一定 <=S(i,j):
短板高度:相比 S(i,j) 相同或更短(<=h[i]);
底边宽度:相比 S(i,j) 更短。
因此所有消去的状态的面积都<S(i,j)。 通俗的讲,我们每次向内移动短板,所有的消去状态都不会
导致丢失面积最大值 。

class Solution:
    def maxArea(self, height: List[int]) -> int:
        # 这个问题不需要也不能将数组进行排序
        # 初始化双指针和最大水量
        left, right, max_water = 0, len(height) - 1, 0
        # 开始遍历
        while left < right:
            if height[left] < height[right]:
                water = height[left] * (right - left)
                left += 1
            else:
                water = height[right] * (right - left)
                right -= 1
            # 若此次水量比最大值更大,则更新最大水量
            if water > max_water:
                max_water = water        
        return max_water      
class Solution {
    public int maxArea(int[] height) {
        int n = height.length;
        int left = 0;
        int right = n - 1;
        int max_water = 0;

        while(left < right){
            int shorter_index = height[left] > height[right] ? right : left;
            int area = (right - left) * height[shorter_index];
            max_water = max_water >= area ? max_water : area;
            if(shorter_index == right){
                right--;
            }else{
                left++;
            }
        }
        return max_water;
    }
}

2.1.7 ⭐⭐有效三角形的个数 (611)

在这里插入图片描述

思路:
数组排序,便于后序的处理。

  1. 固定最长的边 c, 然后采用双指针在其左侧寻找合适的 a、 b:a 从最左侧开始(nums[0])、 b 从最右侧开始(nums[i-1])
  2. 如果 nums[left] + nums[right] > nums[i], 说明 [left,right]、[left+1,right]…[right-1,right] 均满足条件,以 nums[right] 为中间边的情况已全部考虑过,然后 right -= 1
  3. 如果 nums[left] + nums[right] <= nums[i], 两边之和太小,需要增大, left += 1
class Solution:
    def triangleNumber(self, nums: List[int]) -> int:
        # 先进行排序
        nums.sort()
        # 初始化满足条件的三角形个数
        count = 0
        # 固定最长边,用相向双指针寻找另外两边
        for i in range(len(nums) - 1, 1, -1):
            # 初始化左右指针
            left, right = 0, i - 1
            # 相向双指针开始遍历
            while left < right:
                sums = nums[left] + nums[right]
                if sums > nums[i]:
                    count += right - left
                    right -= 1
                else:
                    left += 1
        return count
class Solution {
    public int triangleNumber(int[] nums) {
        Arrays.sort(nums);
        int res = 0;
        int n = nums.length;
        //1、固定最长的边
        for(int i=n-1; i>=0; i--){
            int left = 0;
            int right = i - 1;
            while(left < right){
                if(nums[left]+nums[right] > nums[i]){
                    res += right - left;
                    right--;
                }else{
                    left++;
                }
            }
        }
        return res;
    }
}

2.2 同向双指针 - 快慢指针

快慢指针也是双指针,但是两个指针从同一侧开始遍历数组,将这两个指针分别定义为快指针(fast)和慢指针(slow),两个指针以不同的策略移动,直到两个指针的值相等(或其他特殊条件)为止,如 fast 每次增长两个, slow 每次增长一个。

2.2.1 删除排序数组中的重复项 (26)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        # 初始化快慢指针
        slow, fast = 0, 1
        # 开始遍历
        while fast < len(nums):
            # 若出现不同的数字,则慢指针加入该数值
            if nums[slow] != nums[fast]:
                slow += 1
                nums[slow] == nums[fast]
                fast += 1
            # 若出现相同的数字,则跳过,快指针指向下一个
            else:
                fast += 1
        返回新数组的长度
        return slow + 1
class Solution {
    public int removeDuplicates(int[] nums) {
        int slow = 0;
        int fast = 0;
        int n = nums.length;
        while(fast < n){
            if(nums[fast] == nums[slow]){
                fast++;
            }else{
                slow++;
                nums[slow] = nums[fast];
                fast++;
            }
        }
        return slow + 1;
    }
}

2.2.2 移除元素 (27)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

法一思路:
我们可以保留两个指针 i 和 j,其中 i 是慢指针, j 是快指针。当nums[j] 与给定的值相等时,递增 j 以跳过该元素。只要 nums[j] != val,我们就复制 nums[j] 到 nums[i] 并同时递增两个索引。重复这一过程,直到 j 到达数组的末尾,该数组的新长度为 i。

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        # 先进行排序
        nums.sort()
        # 初始化快慢指针
        slow, fast = 0, 0
        # 开始遍历
        while fast < len(nums):
            if nums[fast] == val:
                fast += 1
            else:
                nums[slow] = nums[fast]
                slow += 1
                fast += 1
        return slow

法二思路:
当我们遇到 nums[i] = val 时,我们可以将当前元素与最后一个元素进行交换,并释放最后一个元素。这实际上使数组的大小减少了 1。 然后再对这个位置的元素进行判断是否等于val,如此循环,如果不等于val,则保留该元素,即指针指向下一个位置。

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        # 初始化两个指针位置
        j, last = 0, len(nums) - 1
        开始遍历
        while j <= last:
            if nums[j] == val:
                nums[j] == nums[last]
                last -= 1
            else:
                j += 1
        return last + 1
class Solution {
    public int removeElement(int[] nums, int val) {
        int n = nums.length;
        int slow = 0;
        int fast = 0;
        while(fast < n){
            if(nums[fast] != val){
                nums[slow] = nums[fast];
                slow++;
            }
            fast++;
        }
        return slow;
    }
}

2.2.3 删除排序数组中的重复项 (80)

在这里插入图片描述
在这里插入图片描述

思路:
遍历整个表:
把当前的元素与它前面的对比,如果二者元素相同(为重复元素):此时统计重复的计数器 count += 1。题目要求只保留 2 个重复的元素,这里需要加入重复元素个数的判断:
这个元素正好重复了 2 次 => 则进行保留。列表长度 i+=1,然后 nums[i]=nums[j];
这个元素重复多于 2 次 => 不进行任何操作。体现在程序上不做处理。
把当前的元素与它前面的对比,如果二者元素不同(为新元素):此时把当前这个结点 (nums[j]) 添加到新表里面去, nums[i] = nums[j], 表长 i+1。

class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        # 初始化同向双指针和计数值
        slow, fast, count = 0, 1, 1
         # 开始遍历
        while fast < len(nums):
            # 遇到重复元素
            if nums[slow] == nums[fast]:
                # 首先技术次数加一
                count += 1
                # 判断次数是否超过两次,若是,则跳过该元素
                if count > 2:
                    fast += 1
                # 若不是,则将该元素加入,快慢指针分别加一
                else:
                    slow += 1
                    nums[slow] = nums[fast]
                    fast += 1
            # 遇到新元素
            else:
                # 初始化计数次数为1次,且将该元素加入
                count = 1
                slow += 1
                nums[slow] = nums[fast]
                fast += 1
        return slow + 1
class Solution {
    public int removeDuplicates(int[] nums) {
        int n = nums.length;
        int count = 1;
        int slow = 1;
        int fast = 1;
        while(fast < n){
            if(nums[fast] == nums[fast - 1]){
                if(count == 1){
                    count += 1;
                    nums[slow] = nums[fast];
                    slow++;
                }
            }else{
                count = 1;
                nums[slow] = nums[fast];
                slow++;
            }
            fast++;
        }
        return slow;
    }
}

2.2.4 移动零 (283)

在这里插入图片描述

思路:
nums 中, i 指针用于存放非零元素,j 指针用于遍历寻找非零元素(注: j 指针找到一个非零元素后,方法nums[i] 的位置 i++,用于下一个 j 指针找到的非零元素),j 指针遍历完后,最后 nums 数组还有空位置,存放 0 即可。

class Solution:
    def moveZeroes(self, nums: List[int]) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        # 初始化快慢双指针
        slow, fast = 0, 0
        # 开始遍历
        while fast < len(nums):
            if nums[fast] == 0:
                fast += 1
            else:
                nums[slow] = nums[fast]
                slow += 1
                fast += 1
        # 补零
        for i in range(slow, len(nums)):
            nums[i] = 0        
class Solution {
    public void moveZeroes(int[] nums) {
        int slow = 0;
        int fast = 0;
        while(fast < nums.length) {
            if(nums[fast] != 0) {
                nums[slow++] = nums[fast];
            }
            fast += 1;
        }
        while(slow < nums.length) {
            nums[slow++] = 0;
        }
    }
}

2.2.5 ⭐⭐数组中的最长山脉(845)

在这里插入图片描述
在这里插入图片描述

思路:
首先固定山峰值,然后分别寻找左、右半边山脉的长度。A[left] < A[left+1],继续向左寻找A[right] < A[right-1],继续向右寻找。如果以当前山峰的山脉长度比最长山脉长,更新最长山脉。

注意:我们可以在只有当前点为山峰的情况(即 A[i-1] < A[i] and A[i+1] < A[i]),才在左右寻找最长山峰,这样可以大大降低搜索的次数。

class Solution:
    def longestMountain(self, arr: List[int]) -> int:
        # 剪枝
        if len(arr) < 3:
            return 0
        
        # 初始化最长山脉长度
        length = 0
        
        # 先固定山峰
        for i in range(1, len(arr) - 1):
            # 剪枝,确保是山峰再去计算长度
            if arr[i - 1] >= arr[i] or arr[i] <= arr[i + 1]:
                continue
            # 寻找左右半边山脉的长度
            left, right = i - 1, i + 1
            while left >= 0 and arr[left + 1] > arr[left]:
                left -= 1
            while right < len(arr) and arr[right - 1] > arr[right]:
                right += 1

            length = max(right - left - 1, length)
        return length     
class Solution {
    public int longestMountain(int[] arr) {
        int n = arr.length;
        int max_length = 0;
        for(int i=1; i < n-1; i++){
            if(arr[i] > arr[i-1] && arr[i] > arr[i+1]){
                int left = i-1;
                int right = i+1;
                while(left > 0 && arr[left] > arr[left - 1]){
                    left--;
                }
                while(right < n - 1 && arr[right] > arr[right + 1]){
                    right++;
                }
                max_length = max_length > right - left + 1 ? max_length : right - left + 1;
            }
        }
        return max_length;
    }
}

2.3 同向双指针 - 滑动窗口

有些时候,我们需要获得数组或者字符串的连续子部分,这时候我们就可以考虑使用滑动窗口。 nums[left,right] 为滑动窗口,根据具体的要求,通过遍历的时候,来改变 left 和 right 的位置,从而完成任务。

滑动窗口主要用来处理连续问题,从类型上说主要有:
(1)固定窗口大小
(2)窗口大小不固定,求解最大的满足条件的窗口
(3)窗口大小不固定,求解最小的满足条件的窗口

(1)固定窗口大小
对于固定窗口,我们只需要固定初始化左右指针 l 和 r,分别表示窗口的左右顶点,并且保证:
l 初始化为 0,初始化 r,使得 r - l + 1 等于窗口大小,同时移动 l 和 r,判断窗口内的连续元素是否满足题目限定的条件。
如果满足,再判断是否需要更新最优解,如果需要则更新最优解
如果不满足,则继续。

2.3.1 滑动窗口最大值(239)

给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值所构成的数组。
输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7]

在这里插入图片描述

class Solution:
    def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
        # 初始化双指针
        left, right = 0, k - 1
        # 初始化输出数组
        res = []
        while right < len(nums):
            res.append(max(nums[left : right + 1]))
            left += 1
            right += 1
        return res

ps:这道题是Hard,这种方法会超出时间限制,应该后面有更好的方法。

⭐⭐补充:优先队列和单调队列

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        // //优先队列
        // int n = nums.length;
        // if(n == 0){
        //     return new int[0];
        // }
        // PriorityQueue<int[]> queue = new PriorityQueue<>(new Comparator<int[]>(){
        //     public int compare(int[] point1, int[] point2){
        //         return point1[0] != point2[0] ? point2[0] - point1[0] : point2[1] - point1[1];
        //     }
        // });
        // for(int i=0; i<k; i++){
        //     queue.offer(new int[]{nums[i], i});
        // }
        // int[] res = new int[n-k+1];
        // res[0] = queue.peek()[0];
        // for(int i=k; i<n; i++){
        //     queue.offer(new int[]{nums[i], i});
        //     while(queue.peek()[1] <= i-k){
        //         queue.poll();
        //     }
        //     res[i-k+1] = queue.peek()[0];
        // }
        // return res;

        //单调队列
        int n = nums.length;
        if(n == 0 || k == 0){
            return new int[0];
        }
        Deque<Integer> queue = new LinkedList<>();
        int[] res = new int[n-k+1];
        for(int right=0, left=1-k; right<n; left++, right++){
            if(left > 0 && queue.peekFirst() == nums[left-1]){
                queue.pollFirst();
            }
            while(!queue.isEmpty() && nums[right] > queue.peekLast()){
                queue.pollLast();
            }
            queue.offerLast(nums[right]);
            if(left >= 0){
                res[left] = queue.peekFirst();
            }
        }
        return res;
    }
}

(2)可变窗口大小
对于可变窗口,我们同样固定初始化左右指针 l 和 r,分别表示的窗口的左右顶点。后面有所不同,我们需要保证:
1、 l 和 r 都初始化为 0
2、 r 指针移动一步
3、判断窗口内的连续元素是否满足题目限定的条件
3.1 如果满足,再判断是否需要更新最优解,如果需要则更新最优解。
并尝试通过移动 l 指针缩小窗口大小。循环执行 3.1
3.2 如果不满足,则继续。

2.3.2 长度最小的子数组(209)

在这里插入图片描述
在这里插入图片描述

思路:

  1. 开始 right 向右滑动,使和变大。
  2. 当恰好 >=s 时,记录滑动窗口所包括的子数组长度 res,若 res 已有数值,需判断新值是否小于旧值,若是,更新 res; left 向右滑动。
  3. 判断是否仍 >=s,若是,重复步骤 2, 3。若否,转步骤 1。直到右边框到达最右边 。
class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
        # 初始化
        left, length = 0, len(nums) + 1
        # 右指针右移
        for right in range(len(nums)):
            while left <= right:
                # 求和
                sums = sum(nums[left:right+1])
                if sums >= target:
                    length = min(length, right - left + 1)
                    left += 1
                else:
                    break
        # 通过length是否改变来判断是否存在符合的数组
        if length == len(nums) + 1:
            return 0 
        else:
            return length     

在这里插入图片描述

执行时间太长了,应该是算了很多次sums = sum(nums[left:right+1])的原因,现在把求和改一下:

class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
        # 初始化
        left, length, sums = 0, len(nums) + 1, 0
        # 右指针右移
        for right in range(len(nums)):
            # 和加上右指针新指向的元素
            sums += nums[right]
            # sums大于等于target, 更新length,并将左指针右移
            while sums >= target: 
              length = min(length, right - left + 1)
                sums -= nums[left]
                left += 1
    # 根据length是否改变来判断是否存在符合的数组
        if length == len(nums) + 1:
            return 0 
        else:
            return length   

在这里插入图片描述

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        if(nums[0] >= target){
            return 1;
        }
        int n = nums.length;
        int left = 0;
        int right = 0;
        int res = 100001;
        int sum = nums[left];
        while(right < n){
            if(sum >= target){
                res = res < right - left + 1 ? res : right - left + 1;
                sum -= nums[left];
                left++;
            }else{
                right++;
                if(right < n){
                    sum += nums[right];
                }
            }
        }
        if(res == 100001){
            return 0;
        }
        return res;
    }
}

2.3.3 ⭐⭐乘积小于 K 的子数组(713)

在这里插入图片描述

思路:

  1. 当 left <= right 且滑动窗口内的乘积小于 k 时,我们可以知道 [left,right]、[left+1,right]…[right-1,right] 均满足条件,因此,计数加 right-left+1,然后移动右边界(滑动区间加大),看剩下的区间是否满足乘积小于 k,如果小于 k,重复步骤 1,否则进行步骤 2。
  2. 当滑动窗口内的乘积大于等于 k 时,右移左边界(滑动区间减小),如果这个区间内乘积小于 k,进入步骤 1,否则重复步骤 2。
class Solution:
    def numSubarrayProductLessThanK(self, nums: List[int], k: int) -> int:
        # 初始化
        left, count, multiply = 0, 0, 1
        
        # 右指针右移
        for right in range(len(nums)):
            multiply *= nums[right]
            while left <= right and multiply >= k:
                multiply /= nums[left]
                left += 1
            count += right - left + 1
        return count
class Solution {
    public int numSubarrayProductLessThanK(int[] nums, int k) {
        int n = nums.length;
        int left = 0;
        int count = 0;
        int mul = 1;

        for(int right = 0; right < n; right++){
            mul *= nums[right];
            while(mul >= k && left <= right){
                mul /= nums[left];
                left++;
            }
            count += right - left + 1;
        }
        return count;
    }
}

2.4 分离双指针

输入是两个数组 / 链表两个指针分别在两个容器中移动;根据问题的不同,初始位置可能都在头部,或者都在尾部,或一头一尾。

2.4.1 两个数组的交集(350)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

class Solution:
    def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
        # 先进行排序
        nums1.sort()
        nums2.sort()
        # 初始化
        one, two = 0, 0
        res = []
        # 开始遍历
        while one < len(nums1) and two < len(nums2):
            if nums1[one] == nums2[two]:
                res.append(nums1[one])
                one += 1
                two += 1
            elif nums1[one] > nums2[two]:
                two += 1
            else:
                one += 1
        return res
                
class Solution {
    public int[] intersect(int[] nums1, int[] nums2) {
        Arrays.sort(nums1);
        Arrays.sort(nums2);
        int length1 = nums1.length;
        int length2 = nums2.length;
        int[] res = new int[Math.min(length1, length2)];

        int head1 = 0;
        int head2 = 0;
        int index = 0;
        while(head1 < length1 && head2 < length2){
            if(nums1[head1] == nums2[head2]){
                res[index] = nums1[head1];
                index++;
                head1++;
                head2++;
            }else if(nums1[head1] > nums2[head2]){
                head2++;
            }else{
                head1++;
            }
        }
        return Arrays.copyOfRange(res, 0, index);
    }
}
class Solution {
    public int[] intersect(int[] nums1, int[] nums2) {
        int length1 = nums1.length;
        int length2 = nums2.length;
        Map<Integer, Integer> hashtable = new HashMap<Integer, Integer>();

        int[] res = new int[Math.min(length1, length2)];
        int index = 0;
        int count = 0;
        for(int i=0; i<length1; i++){
            count = hashtable.getOrDefault(nums1[i], 0) + 1;
            hashtable.put(nums1[i], count);
        }

        for(int i=0; i<length2; i++){
            count = hashtable.getOrDefault(nums2[i], 0);
            if(count > 0){
                res[index++] = nums2[i];
                count--;
            }
            if(count > 0){
                hashtable.put(nums2[i], count);
            }else{
                hashtable.remove(nums2[i]);
            }
        }
        return Arrays.copyOfRange(res, 0, index);
    }
}

2.4.2 两个数组的交集(349)

在这里插入图片描述

思路:

和上一题一样,只需要把存储的容器从列表改为集合即可。

class Solution:
    def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
        # 对数组进行排序
        nums1.sort()
        nums2.sort()

        # 初始化双指针
        i, j = 0, 0

        res = set()

        while i < len(nums1) and j < len(nums2):
            if nums1[i] == nums2[j]:
                res.add(nums1[i])
                i += 1
                j += 1
            elif nums1[i] < nums2[j]:
                i += 1
            else:
                j += 1
        return list(res)
class Solution {
    public int[] intersection(int[] nums1, int[] nums2) {
        Arrays.sort(nums1);
        Arrays.sort(nums2);
        int length1 = nums1.length;
        int length2 = nums2.length;
        int h1 = 0;
        int h2 = 0;
        int[] res = new int[Math.min(length1, length2)];
        int index = 0;

        while(h1 < length1 && h2 < length2){
            if(nums1[h1] == nums2[h2]){
                res[index++] = nums1[h1];
                h1++;
                h2++;
                //去重
                while(h1 < length1 && nums1[h1] == nums1[h1-1]){
                    h1++;
                }
                while(h2 < length2 && nums2[h2] == nums2[h2-1]){
                    h2++;
                }
            }else if(nums1[h1] > nums2[h2]){
                h2++;
            }else{
                h1++;
            }
        }
        return Arrays.copyOfRange(res, 0, index);
    }
}
  • 17
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值