双指针的一些总结

导言

最近又再次开始刷leetcode题目,做了一些关于双指针的题目,这一篇来稍微总结一下,在数组题目中利用双指针的一些想法和题目的坑,日后方便复习

一.关于双指针

1.什么是双指针

双指针,用两个指针去遍历有序数组

2.双指针的类型

常用的有,对撞指针(一个在头,一个在尾),有的是快慢指针等等

3.使用的场景

  • 多数求和这样的题目

几个数字的和为一个特定的值,这种题目一般用两个指针,一个在头,一个在尾,两边向中间遍历,找到合适的数字

  • 数字交换

数组中元素的交换一般都用双指针,不然需要移动大量元素,用双指针找到要交换的数字来交换

Tips:

1.当题目说到数组是有序数组时,就该想到对撞指针

2.题目需要移动数组里元素时,尝试使用用快慢指针

二.LeetCode例题

283.移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。


示例:

输入: [0,1,0,3,12]
输出: [1,3,12,0,0]


说明:

必须在原数组上操作,不能拷贝额外的数组。
尽量减少操作次数。


思路:看到题目是移动元素,可以尝试用双指针解法,i指针来遍历数组,j指针来指向存放非0元素的位置。这样我们在循环开始的时候,如果i指针指向的元素为0,那么我们就让i指针继续往后走,找到元素不为0的地方。如果i指针的元素不为0,那么我们就把i指针的元素移到j指针那里去,j指针往后走一个,指向下一个存放非0元素的位置,此时若i指针和j指针不在同一个位置,就令i位置的元素为0,为了数组后面的补0

代码:

//c++
class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int j = 0;
        for(int i = 0;i < nums.size(); i++){
            if(nums[i] != 0){
                swap(nums[i],nums[j]);
                j++;
            }
        }
    }
};
//java
class Solution {
    public void moveZeroes(int[] nums) {
        int j = 0;
        for (int i = 0;i < nums.length; i++) {
            if (nums[i] != 0) {
                nums[j] = nums[i];
                if (i != j) {
                    nums[i] = 0;
                }
                j++;
            }
        }
    }
}
//Go
func moveZeroes(nums []int)  {
    j := 0
    for i := 0;i < len(nums); i++ {
        if nums[i] != 0 {
            nums[j] = nums[i]
            if i != j {
                nums[i] = 0
            }
            j++
        }
    }
}

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

给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。

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


示例 1:

给定数组 nums = [1,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。

你不需要考虑数组中超出新长度后面的元素。


思路:当我们看到排序数组时,就想到利用双指针来解决,由于是使得每个元素都只出现一次,所以我们的思路是,用i指针在前,j指针在i指针后一个,往后遍历,如果两个指针指向的数字相等,那么在后面的j指针就继续往后跑,直到遇到不等于i指针指向的数字,此时i指针先往后走一个,前面空出的那个元素就是我们要唯一保留的那个元素,然后把j指针指向的值赋给i指针,之后j指针完后走一个,直到j指针走到尾,返回新数组的长度,就是i+1

代码:

//c++
class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int len = nums.size();
        if (len == 0 || len ==1 ) return len;
        int i = 0, j = 1;
        while (j < len) {
            if (nums[i] == nums[j]) {
                j++;
            }else {
                nums[++i] = nums[j++];
            }
        }
        return i+1;
    }
};
//java
class Solution {
    public int removeDuplicates(int[] nums) {
        int len = nums.length;
        if (len  == 0 || len == 1) return len;
        int i = 0,j = 1;
        while (j < len) {
            if (nums[i] == nums[j]) {
                j++;
            }else {
                nums[++i] = nums[j++];
            }
        }
        return i+1;
    }
}
//go
//go语言写这道题目有个更便捷的方法,从尾往头遍历,两个相邻的元素要是想等的话,直接利用切片把它删除了,最后直接返回数组长度
func removeDuplicates(nums []int) int {
    for i := len(nums)-1;i > 0; i-- {
        if nums[i] == nums[i-1] {
            nums = append(nums[:i],nums[i+1:]...)
        }
    }
    return len(nums)
}

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

给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素最多出现两次,返回移除后数组的新长度。

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


示例 1:

给定 nums = [1,1,1,2,2,3],

函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3 。

你不需要考虑数组中超出新长度后面的元素。


示例 2:

给定 nums = [0,0,1,1,1,1,2,3,3],

函数应返回新长度 length = 7, 并且原数组的前五个元素被修改为 0, 0, 1, 1, 2, 3, 3 。

你不需要考虑数组中超出新长度后面的元素。


思路:这个题目和上个题目类似,只是这个题目的要求是,可以有两个相同的元素,判断快指针指向的元素和慢指针指向的元素的前一个元素是否相等,相等则只更新快指针,不相等的话,先将慢指针后移一位,再将快指针指向的元素覆写入慢指针指向的单元,最后更新快指针。

另一种思路:利用for each循环来进行判断,巧妙的用i-2判断了是否要替换,我们用count指针来指向有效位置,从第三个位置开始判断,因为前两个位置元素都是有效的,当我们i位置的元素和count-2处的元素相等的话,此时count位置还是无效的,不更新,继续循环,当i位置的元素和count-2的元素相等时,我们就把i位置的值赋给count,然后count指针后移。

代码:

//c++
class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int len = nums.size();
        if (len <= 2) {
            return len;
        }
        int j = 1;
        for (int t = 2;t < len; t++) {
            if (nums[t] != nums[j-1]) {
                nums[++j] = nums[t];
            }
        }
        return j+1;
    }
};
//java
class Solution {
    public int removeDuplicates(int[] nums) {
        int count = 0;
        for (int n : nums) {
            if (count < 2 || n != nums[count-2]) {
                nums[count++] = n;
            }
        }
        return count;
    }
}
//go
func removeElement(nums []int, val int) int {
    count := 0
    for i:=2;i < len(nums); i++ {
        if nums[i] != val {
            nums[count] = nums[i]
            count++
        }
    }
    return count
}

15.三数之和

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

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


示例:

给定数组 nums = [-1, 0, 1, 2, -1, -4],

满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]


思路:这个题目是三指针,首先将数组排序,这样是为了后面指针移动提供依据,我们首先定下来一个元素当作target,然后用两个指针来遍历数组,一个指向target后面的一个,一个指向尾部,判断两个指针指向的元素相加是否为target的相反数,如果是,压倒二维数组里,不是的话就判断两数之和是大于target还是小于target,然后移动相应的指针,最主要的是去重,有两个方向需要去重,一个是for循环开始的时候,我们已经压入了一组数据,但是这个时候新的target和老的target相同时,我们刚刚计算的就是这个数据,就会重复,所以需要去重,还有在把一组数据压入到答案后,我们需要判断左指针的右侧和右指针的左侧是否相同,如果相同的话,我们则需要移动指针,因为我们刚刚计算的就是这个数据,要不然元素会重复压入一组数据内。最后还有优化的地方,因为数组经过排序后,当我们的target > 0时,后面加正数永远不会小于0,剪枝,数组长度小于3的,直接返回,剪枝。

代码:

//c++
class Solution {
public:
    int n;
    vector<vector<int>> ans;
    vector<vector<int>> threeSum(vector<int>& nums) {
        n=nums.size();
        if(n < 3) return ans;
        sort(nums.begin(),nums.end());
        for(int i=0;i<n;i++){
            int j=i+1;
            int r=n-1;
            //第一步去重
            if(i!=0&&nums[i-1]==nums[i]) continue;
            while(j<r){
                if(nums[i]+nums[j]+nums[r]==0){
                    vector<int> a;
                    a.push_back(nums[i]);
                    a.push_back(nums[j]);
                    a.push_back(nums[r]);
                    ans.push_back(a);
                    //第二步去重
                    while (j < r && nums[j] == nums[j+1]) j++;
                    while (j < r && nums[r] == nums[r-1]) r--;
                    j++;
                    r--;
                }
                else if(nums[i]+nums[j]+nums[r]>0){
                    r--;
                }
                else{
                    j++;
                }
            }
        }
        return ans;
    }
};
//java
class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        int len = nums.length;
        Arrays.sort(nums);
        if (len < 3) return res;
        for (int i = 0;i < len - 2; i++) {
            if (nums[i] > 0) break;
            if (i != 0 && nums[i] == nums[i-1]) continue;
            int l = i + 1,r = len - 1;
            int target = -nums[i];
            while (l < r) {
                List<Integer> ans = new ArrayList<>();
                if (nums[l] + nums[r] == target) {
                    ans.add(nums[l]);
                    ans.add(nums[r]);
                    ans.add(nums[i]);
                    res.add(ans);
                    while (l < r && nums[l] == nums[l+1]) l++;
                    while (l < r && nums[r] == nums[r-1]) r--;
                    l++;
                    r--;
                }else if (nums[l] + nums[r] > target) {
                    r--;
                }else {
                    l++;
                }
            }
        }
        return res;
    }
}
//go
func threeSum(nums []int) [][]int {
    L := len(nums)
    sort.Ints(nums)
    var ans [][]int
    if L < 3 {
        return ans
    }
    for i:=0;i < L;i++ {
        if nums[i] > 0 {
            break
        }
        if i != 0 && nums[i] == nums[i-1] {
            continue;
        }
        target := -nums[i]
        l,r := i+1,L-1
        for l < r {
            if nums[l] + nums[r] == target {
                ans = append(ans, []int{nums[i] , nums[l], nums[r]})
                for l < r && nums[l] == nums[l+1] {
                    l++
                }
                for l < r && nums[r] == nums[r-1] {
                    r--
                }
                l++
                r--
            }else if nums[l] + nums[r] < target {
                l++
            }else {
                r--
            }
        }
    }
    return ans;
}

三.总结

双指针问题感觉没有那种模版,但是在数组中很常见,每次做数组题的时候都可以思考一下,双指针可以进行判断,移动元素等等,当然需要判断特殊情况和重复情况,多剪枝才可以使得程序更快,日后会出一期链表中双指针的用法。

  • 题目来源于:LeetCode网站
posted @ 2020-04-23 17:27  阿-栋  阅读(593)  评论(0编辑  收藏  举报