【LeetCode】双指针

双指针

  • 双指针场景
  • 例题
    • k次问题
      • 80. 删除有序数组中的重复项
      • 26. 删除有序数组中的重复项
    • 求和
      • 剑指 Offer 57. 和为s的两个数字
      • 15. 三数之和
      • 16. 最接近的三数之和
    • 用左右指针维护子数组
      • 209. 长度最小的子数组
      • 713. 乘积小于K的子数组*
      • 845.数组中的最长山脉
    • 两个数组
      • 88. 合并两个有序数组
      • 349. 两个数组的交集
    • 一个指针为当前遍历的位置,一个指针为结果的右边界
      • 26. 删除有序数组中的重复项
    • 922. 按奇偶排序数组 II

双指针场景

例题

k次问题

数据有序,相同元素保留k次问题

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

给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使得出现次数超过两次的元素只出现两次 ,返回删除后数组的新长度。

不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:

// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);

// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}

示例 1:

输入:nums = [1,1,1,2,2,3]
输出:5, nums = [1,1,2,2,3]
解释:函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3。 不需要考虑数组中超出新长度后面的元素。

示例 2:

输入:nums = [0,0,1,1,1,1,2,3,3]
输出:7, nums = [0,0,1,1,2,3,3]
解释:函数应返回新长度 length = 7, 并且原数组的前五个元素被修改为 0, 0, 1, 1, 2, 3, 3。不需要考虑数组中超出新长度后面的元素

提示:

1 <= nums.length <= 3 * 10⁴
-10⁴ <= nums[i] <= 10⁴
nums 已按升序排列


思路:
i是保留的数组的位置,j是遍历nums数组的指针,判断当前j是否能保留就是看nums[j]和nums[i-k]是否相等

    public int removeDuplicates(int[] nums) {
        int n = nums.length;
        if (n<=2){
            return n;
        }
        int i = 2;
        for (int j = 2;j<n;j++){
            if (nums[j]!=nums[i-2]){
                nums[i] = nums[j];
                i++;
            }

        }
        return i;
    }

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

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

    public int removeDuplicates(int[] nums) {
        int i=1;
        int n = nums.length;
        for (int j=1;j<n;j++){
            if (nums[j]!=nums[i-1]){
                nums[i++] = nums[j];
            }
        }
        return i;
    }

求和

这种求和问题 就是先排序,然后通过左右指针(left=0,right=n-1)移动 得到解
注意的就是去重复值

剑指 Offer 57. 和为s的两个数字

输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。

示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[2,7] 或者 [7,2]

示例 2:
输入:nums = [10,26,30,31,47,60], target = 40
输出:[10,30] 或者 [30,10]

限制:
1 <= nums.length <= 10^5
1 <= nums[i] <= 10^6


思路: 注意做这种求和的 为了防止溢出应该用减的方式nums[l] == target-nums[r] 而不是加的方式

class Solution {
    public int[] twoSum(int[] nums, int target) {

        int l = 0,r = nums.length-1;
        while(l<r){
            if(nums[l]==target-nums[r]){
                return new int[]{nums[l],nums[r]};
            }
            else if(nums[l]<target-nums[r]){
                l++;
            }
            else{
                r--;
            }
        }
        return new int[0];
    }
}

15. 三数之和

15. 三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例 1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
示例 2:

输入:nums = []
输出:[]
示例 3:

输入:nums = [0]
输出:[]

提示:

0 <= nums.length <= 3000
-105 <= nums[i] <= 105


思路:
先排序 ,然后就可以用双指针
这个题要注意的是去重, 由于我们的数组是排好序的,所以我们可以在指针移动的时候判断和下一个值是否相等

    public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);
        List<List<Integer>> ans = new ArrayList<>();
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            int left = i+1;
            int right = n-1;
            while(left<right){
                // 三数之和
                int sum = nums[i]+nums[left]+nums[right];
                // 左指针和右指针对应的值
                int lValue = nums[left];
                int rValue = nums[right];
                if(sum==0){
                    ArrayList<Integer> lst = new ArrayList<>();
                    lst.add(nums[i]);
                    lst.add(nums[left]);
                    lst.add(nums[right]);
                    ans.add(lst);
                    // 去重
                    while(left<right && lValue==nums[left]){
                        left++;
                    }
                    while(left<right && rValue==nums[right]){
                        right--;
                    }
                }
                else if(sum>0){
                    // 去重
                    while(left<right && rValue==nums[right]){
                        right--;
                    }
                }
                else{
                    // 去重
                    while(left<right && lValue==nums[left]){
                        left++;
                    }
                }
            }
            while(i<n-1 && nums[i]==nums[i+1]){
                i++;
            }
        }
        return ans;

    }

16. 最接近的三数之和

16. 最接近的三数之和

给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。

示例:

输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。

提示:

3 <= nums.length <= 10^3
-10^3 <= nums[i] <= 10^3
-10^4 <= target <= 10^4


思路:
同样是求三数之和,只是变成了最接近的,所以就算一下ans和sum 减target
得到最接近的
注意这个也是有重复值的,也要注意去重

    public int threeSumClosest(int[] nums, int target) {
        Arrays.sort(nums);
        int n = nums.length;
        int ans = 10000; // 注意不能写成MAX.Integer 因为这样-负的target会越界
        for(int i = 0;i<n;i++){

            int left =i+1;
            int right = n-1;

            while(left<right){
                int sum = nums[i]+nums[left]+nums[right];
                int leftValue = nums[left];
                int rightValue = nums[right];
                if(Math.abs(ans-target)>Math.abs(sum-target)){
                    ans = sum;
                }
                if(sum==target){
                    return ans;
                }
                else if(sum>target){
                    while(right>left && nums[right]==rightValue){
                        right--;
                    }

                }
                else{
                    while(right>left && nums[left]==leftValue){
                        left++;
                    }
                }
            }
            // 重复元素
            while(i<n-1 && nums[i]==nums[i+1]){
                i++;
            }
        }
        return ans;
    }

用左右指针维护子数组

这部分题目都是通过left和right来维护一个子数组,可行解有很多,下一个解可以在上一个可行解的基础上继续移动

209. 长度最小的子数组

209. 长度最小的子数组

给定一个含有 n 个正整数的数组和一个正整数 target 。

找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。

示例 1:
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
示例 2:

输入:target = 4, nums = [1,4,4]
输出:1
示例 3:

输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0


思路:
通过left和right维护子数组的左右边界,没有达到条件右指针右移,达到条件后,左指针右移收缩

class Solution {
    public int minSubArrayLen(int target, int[] nums) {

        int n = nums.length;
        // left和right就是当前子数组的边界

        int ans = Integer.MAX_VALUE;

        
        int left = 0;
        int right = 0;
        // 当前窗口内的和
        int sum = nums[left];
        
        while(left<=right & right<n){

            // 没有达到条件,窗口右移
            if(sum<target){
                right++;
                if(right<=n-1)
                    sum+=nums[right];

            }else{ // 找到可行解
                ans = Math.min(ans,right-left+1);
                // 窗口左边界右移 缩小窗口
                sum-=nums[left];
                left++;
            }
        }

        return ans==Integer.MAX_VALUE?0:ans;
    }
}

题解的:

    class Solution {
        public int minSubArrayLen(int s, int[] nums) {
            int n = nums.length;
            if (n == 0) {
                return 0;
            }
            int ans = Integer.MAX_VALUE;
            int start = 0, end = 0;
            int sum = 0;
            while (end < n) {
                sum += nums[end];
                while (sum >= s) {
                    ans = Math.min(ans, end - start + 1);
                    sum -= nums[start];
                    start++;
                }
                end++;
            }
            return ans == Integer.MAX_VALUE ? 0 : ans;
        }
    }

713. 乘积小于K的子数组*

给定一个正整数数组 nums。

找出该数组内乘积小于 k 的连续的子数组的个数。

示例 1:

输入: nums = [10,5,2,6], k = 100
输出: 8
解释: 8个乘积小于100的子数组分别为: [10], [5], [2], [6], [10,5], [5,2], [2,6], [5,2,6]。
需要注意的是 [10,5,2] 并不是乘积小于100的子数组。
说明:

0 < nums.length <= 50000
0 < nums[i] < 1000
0 <= k < 10^6


思路:
自己做的时候没有想到这样
遍历right,找到满足的左边界。这样下一个right就可以用前一个right
【LeetCode】双指针_第1张图片

    public int numSubarrayProductLessThanK(int[] nums, int k) {
        if(k<=1){
            return 0;
        }
        int left = 0;
        int mul = 1;
        int ans = 0;
        int n = nums.length;
        // 以right 为右边界,找到最小的left
        for (int right=0; right<n;right++){
            mul*=nums[right];
            // 向右移动left,得到满足条件的left
            while(mul>=k){
                mul /= nums[left++];
            }
            // 得到以当前right为右边界的 子数组的个数
            ans += right-left+1;
        }
        return ans;
    }

845.数组中的最长山脉

我们把数组 A 中符合下列属性的任意连续子数组 B 称为 “山脉”:

B.length >= 3
存在 0 < i < B.length - 1 使得 B[0] < B[1] < … B[i-1] < B[i] > B[i+1] > … > B[B.length - 1]
(注意:B 可以是 A 的任意子数组,包括整个数组 A。)

给出一个整数数组 A,返回最长 “山脉” 的长度。

如果不含有 “山脉” 则返回 0。

示例 1:

输入:[2,1,4,7,3,2,5]
输出:5
解释:最长的 “山脉” 是 [1,4,7,3,2],长度为 5。
示例 2:

输入:[2,2,2]
输出:0
解释:不含 “山脉”。

提示:

0 <= A.length <= 10000
0 <= A[i] <= 10000


思路:从右向左,先找单调递增,找到后再找单调递减

    public int longestMountain(int[] arr) {
        int n = arr.length;
        // 当前窗口的右边界
        int right =n-1;
        int ans = 0;

        while(right>=0){

            int cur = right;
            while(cur>0 && arr[cur-1]>arr[cur]){
                cur--;
            }
            // 已找到山脉右半部分
            if(cur<right){
                int mid = cur;
                // 找山脉的左半部分
                while(cur>0 && arr[cur-1]<arr[cur]){
                    cur--;
                }
                // 找到可行解
                if(cur<mid){
                    ans = Math.max(ans,right-cur+1);
                }
                right=cur;
            }
            else{ // 以此right为右边界的没有可行解
                right--;
            }
        }
        return ans;
    }

两个数组

对于两个有序数组,一人一个指针 进行比较

88. 合并两个有序数组

给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。

初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。你可以假设 nums1 的空间大小等于 m + n,这样它就有足够的空间保存来自 nums2 的元素。

示例 1:
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]

示例 2:
输入:nums1 = [1], m = 1, nums2 = [], n = 0
输出:[1]

提示:
nums1.length == m + n
nums2.length == n
0 <= m, n <= 200
1 <= m + n <= 200
-109 <= nums1[i], nums2[i] <= 109


i: 从后往前遍历nums1,作为当前要填充的位置
l1,l2 分别指向各自的数组, 谁大就填谁
对于l2<0,说明l2已经都移到了nums1中 直接可以返回;当l1<0,要把nums2全部移过来。

    public void merge(int[] nums1, int m, int[] nums2, int n) {

        int l2 = n-1;
        int l1 = m-1;
        // 从后往前遍历,i指向当前要插入的位置
        for (int i = m+n-1; i >= 0; i--) {
            // nums2已经全部填到nums1中
            if(l2<0){
                return ;
            }
            // 谁大谁填进去
            if((l1>=0 && nums2[l2]>nums1[l1])||l1<0){
                nums1[i] = nums2[l2];
                l2--;
            }else{
                nums1[i] = nums1[l1];
                l1--;
            }
        }
    }

349. 两个数组的交集

给定两个数组,编写一个函数来计算它们的交集。

示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]
示例 2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]

说明:
输出结果中的每个元素一定是唯一的。
我们可以不考虑输出结果的顺序。


思路:
由于不考虑输出结果的顺序,所以就把两个数组都排序,然后用分别用l1,l2对应两个数组 进行比较。 此外还要当前指针和前一个指针是否相同进行去重。

    public int[] intersection(int[] nums1, int[] nums2) {
        int n1 = nums1.length;
        int n2 = nums2.length;

        Arrays.sort(nums1);
        Arrays.sort(nums2);
        List<Integer> list = new ArrayList<>();

        int l1=0,l2=0;
        while(l1<n1 && l2<n2){
            if(nums1[l1]==nums2[l2]){
                list.add(nums1[l1]);
                l1++;
                l2++;
            }
            else if(nums1[l1]>nums2[l2]){
                l2++;
            }
            else{
                l1++;
            }


            while(l1>0 && l1<n1 &&nums1[l1]==nums1[l1-1]){
                l1++;
            }
            while(l2>0 && l2<n2 &&nums2[l2]==nums2[l2-1]){
                l2++;
            }
        }

        int n = list.size();
        int[] ans = new int[n];
        for (int i = 0; i < n; i++) {
            ans[i] = list.get(i);
        }
        return ans;
    }

题解的思路:
去重部分不一样,就是在往ans 添加的时候判断是否重复,否则就不添加

    public int[] intersection(int[] nums1, int[] nums2) {
        int n1 = nums1.length;
        int n2 = nums2.length;

        Arrays.sort(nums1);
        Arrays.sort(nums2);
        int[] ans = new int[n1+n2];

        int l1=0,l2=0,l=0;
        while(l1<n1 && l2<n2){
            if(nums1[l1]==nums2[l2]){
                // 保证元素唯一性
                if(l==0 || nums1[l1]!=ans[l-1]){
                    ans[l++] = nums1[l1];
                }
                l1++;
                l2++;
            }
            else if(nums1[l1]>nums2[l2]){
                l2++;
            }
            else{
                l1++;
            }
        }

        return Arrays.copyOfRange(ans,0,l);
    }

一个指针为当前遍历的位置,一个指针为结果的右边界

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

给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。

不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

说明:

为什么返回数值是整数,但输出的答案是数组呢?

请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。

你可以想象内部操作如下:

// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);

// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}

示例 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 。不需要考虑数组中超出新长度后面的元素。

提示:

0 <= nums.length <= 3 * 104
-104 <= nums[i] <= 104
nums 已按升序排列


思路:
因为是有序数组去重复元素,我们应该想到用两个同向的双指针 fast和slow,slow用来指向放置不重复元素的位置,fast指向当前遍历到的位置。
当fast指向的位置和前一个元素不相同时,就可以将此数复制到slow指向位置,slow后移。

class Solution {
    public int removeDuplicates(int[] nums) {

        // 不重复元素插入的位置
        int slow = 1;
        // 遍历到的位置
        int fast = 1;
        int n = nums.length;
        if (n == 0) {
            return 0;
        }
        while(fast<n){
            // fast和前一个元素不同,则移到slow处
            if(nums[fast]!=nums[fast-1]){
                nums[slow] = nums[fast];
                slow++;
            }
            // fast和前一个相同, 则继续向后走 即忽略
            fast++;
        }
        return slow;

    }
}

922. 按奇偶排序数组 II

给定一个非负整数数组 A, A 中一半整数是奇数,一半整数是偶数。
对数组进行排序,以便当 A[i] 为奇数时,i 也是奇数;当 A[i] 为偶数时, i 也是偶数。
你可以返回任何满足上述条件的数组作为答案。

示例:

输入:[4,2,5,7]
输出:[4,5,2,7]
解释:[4,7,2,5],[2,5,4,7],[2,7,4,5] 也会被接受。

提示:

2 <= A.length <= 20000
A.length % 2 == 0
0 <= A[i] <= 1000


思路:

方法一:
用一个新数组,奇偶两个指针指向即将插入的位置

    public int[] sortArrayByParityII(int[] nums) {
        int n = nums.length;
        int[] ans = new int[n];
        int j = 1,o = 0;
        for (int i = 0; i < n; i++) {
            if(nums[i]%2!=0){
                ans[j] = nums[i];
                j+=2;
            }
            else{
                ans[o] = nums[i];
                o+=2;
            }
        }
        return ans;
    }

方法二:
原地,用奇偶两个指针,从前往后遍历偶数指针,如果当前数为奇数,则开始遍历奇数,直到奇数位置为偶数,就交换

    public int[] sortArrayByParityII(int[] nums) {

        int n = nums.length;
        int[] ans = new int[n];
        int j = 1;
        for (int i = 0; i < n; i=i+2) {
            // 是奇数就交换
            if(nums[i]%2==1){
                // 找到放置的奇数的位置
                while(nums[j]%2==1){
                    j+=2;
                }
                swap(nums,i,j);
            }
        }

        return nums;

    }


    public void swap(int[] nums, int i, int j){

        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;

    }

你可能感兴趣的:(LeetCode,双指针)