刷题--双指针

Posted 2333wzl

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了刷题--双指针相关的知识,希望对你有一定的参考价值。

Two Sum类

首先是基本的Two Sum题解

用hashmap 时间复杂度O(n),空间复杂度O(n),每一次首先找hashmap中有没有target - nums[i], 如果没有将nums[i]入map

用双指针法,时间复杂度O(n + nlogn), 空间复杂度O(1) 首先要对数组进行排序,如果要求的是返回两个数的索引,那么就不能用这个方法

例 lintcode  56. Two Sum https://www.lintcode.com/problem/two-sum/description

 

所以当数组已经排好序了,two points的方法更优。

而涉及数据设计类问题,比如设计一个Two Sum 类,因为这是一个online algorithm,用户使用哪一个函数并不清楚,所以对时间复杂度的分析应该基于每一个小函数。

例 lintcode 607 Two Sum III - Data structure design  https://www.lintcode.com/problem/two-sum-iii-data-structure-design/description

这道题如果使用arraylist,那么add()操作可以到O(1)时间,直接加就行,但是find()就要先排序,时间复杂度O(nlogn),如何改善呢,在add的时候就进行插入排序,一次的时间复杂度是O(n),在查找的时候直接双指针查找,时间O(n),整体上在空间没有比hashmap更加优秀

    private ArrayList<Integer> array = new ArrayList<>();
    
    public void add(int number) {
        array.add(number);
        if(array.size() > 1)    
            insertSort(number);
    }
    
    public void insertSort(int number){
        int index = array.size() - 1;
        for(int i = array.size() - 2;i >= 0 ;i--){
            if(number < array.get(i)){
                int tmp = array.get(i);
                array.set(i, number);
                array.set(index, tmp);
                index = i;
            }
        }
    }

    /**
     * @param value: An integer
     * @return: Find if there exists any pair of numbers which sum is equal to the value.
     */
    public boolean find(int value) {
        if(array.size() == 0){
            return false;
        }
        
        int left = 0, right = array.size() - 1;
        while(left < right){
            int sum = array.get(left) + array.get(right);
            if(sum == value)return true;
            else if(sum < value){
                left++;
            }else{
                right --;
            }
        }
        
        return false;
    }
}

在lintcode上跑,直接超出内存了。

用hashmap,add的时间的是O(1),find的时间是O(n),注意的一个点是hashmap而不是hashset是为了记录数的出现次数。offline的two sum是将数字一个一个的和target做差,找不到再放到hashset里,所以同一的数不会计算两次。online的add 和 find 是分开的,hashset并不能知道同一个数字是否输入两次还是一次。

    HashMap<Integer, Integer> map = new HashMap<>();
    
    public void add(int number) {
        if(!map.containsKey(number)){
            map.put(number, 1);
        }else{
            map.put(number,map.get(number)+1);
        }
    }
    
    public boolean find(int value) {
        for(int key:map.keySet()){
            if(value == key * 2 && map.get(key) > 1){
                return true;
            }else if(value == key * 2 && map.get(key) == 1){
                continue;
            }
            
            if(map.containsKey(value - key)){
                return true;
            }
        }
        
        return false;
    }

 

follow up: 如果有大量的find操作,很少的add操作,如何优化?

改为两个hashset,里面存储两数之和。即, 每一次add操作的时间复杂度是On,每加入一个数,就将所有的和存储,find操作直接找和里有没有就行,O1的时间复杂度。

在lintcode上 超出了时间限制,因为这是特殊的follow up

    HashSet<Integer> nums = new HashSet<>();
    HashSet<Integer> sums = new HashSet<>();
    
    public void add(int number) {
        
        for(int num : nums){
            sums.add(number + num);
        }
        nums.add(number);
    }
    
    public boolean find(int value) {
        return sums.contains(value);
    }

 

two sum -- unique pair

lintcode 587. Two Sum - Unique pairs  https://www.lintcode.com/problem/two-sum-unique-pairs/description

two sum类问题的双指针解法,当找到一对的时候左右指针继续移动,直到跳过所有重复项。

    public int twoSum6(int[] nums, int target) {
        if(nums == null || nums.length == 0)return 0;
        
        int left = 0, right = nums.length - 1;
        
        int res = 0;
        Arrays.sort(nums);
        
        while(left < right){
            int sum = nums[left] + nums[right];
            if(sum == target){
                res++;
                while(left < right && nums[left] == nums[++left]);
                //System.out.println("left" + left);
                while(left < right && nums[right] == nums[--right]);
                //System.out.println("right" + right);
            }else if(sum < target){
                left++;
            }else{
                right--;
            }
        }
        
        return res;
    }

 

3sum 找三数之和为0的对。比如a+b+c = 0, 那就有-a = b + c,将最小的值放到等式的一边,进行n次循环,对最小值的右边进行双指针的2sum查找。同样的,也要去重,第一层循环和内层循环都要去重。

lintcode 57 3sum   https://www.lintcode.com/problem/3sum/description

    public List<List<Integer>> threeSum(int[] numbers) {
        List<List<Integer>> res = new LinkedList<>();
        if(numbers == null || numbers.length <= 2) return res;
        
        Arrays.sort(numbers);
        
        for(int i = 0;i <= numbers.length - 3;i++){
            int left = i + 1, right = numbers.length - 1;
            int target = -numbers[i];
            while(left < right){
                //System.out.println(target);
                int sum = numbers[left] + numbers[right];
                if(sum == target){
                    List<Integer> ans = Arrays.asList(numbers[i], numbers[left], numbers[right]);
                    res.add(ans);
                    while(left < right && numbers[left] == numbers[++left]);
                    while(left < right && numbers[right] == numbers[--right]);
                }else if(sum < target){
                    left++;
                }else{
                    right--;
                }
            }
            while(i <= numbers.length - 3 && numbers[i] == numbers[++i]);
            //System.out.println(i);
            i--;
        }
        
        return res;
    }

 

 

triangle count 找数组中有没有能够构成三角形的三条边,返回可以的构成的数量。a+b > c 两条小边之和大于第三边,所以可以从数组中最大的开始循环找。比如 0 2 2 3 4,如果2,3之和大于4,那么2与三之间的所有数和3的和都大于4。即从最大边开始,每条最大边都要只走一次On的循环,在每一次On的循环中,双指针总是要移动一个时间是On^2。如果是输出所有的三条边,比如 1 1 1 1 1 1 1 1 1,那么时间复杂度就是On^3。因为每一个可能性都要走一遍。

lintcode 382. Triangle Count       https://www.lintcode.com/problem/triangle-count/description

    public int triangleCount(int[] S) {
        if(S == null || S.length < 3) return 0;
        
        Arrays.sort(S);
        int res = 0;
        
        for(int i = S.length - 1;i > 1;i--){
            int left = 0, right = i - 1;
            while(left < right){
                int sum = S[left] + S[right];
                if(sum > S[i]){
                    res += right - left;
                    right--;
                }else{
                    left++;
                }
            }
        }
        
        return res;
    }

 

对找两数之和大于每个数或者小于某个数是一样的方法和思路

lintcode 609 Two Sum - Less than or equal to target https://www.lintcode.com/problem/two-sum-less-than-or-equal-to-target/description

lintcode 443 Two Sum - Greater than target   https://www.lintcode.com/problem/two-sum-greater-than-target/description

 

两数之和最接近目标数

lintcode 533. Two Sum - Closest to target  https://www.lintcode.com/problem/two-sum-closest-to-target/description

和普通的两数之和的思想是一样的,双指针,每次找到一个和值就和目标数target做差,一个变量用来记录每次的最小值。最后返回的是最小的差

public int twoSumClosest(int[] nums, int target) {
        if(nums == null || nums.length == 0){
            return 0;
        }
        
        Arrays.sort(nums);
        int res = Integer.MAX_VALUE;
        int left = 0, right = nums.length - 1;
        
        while(left < right){
            int sum = nums[left] + nums[right];
            if(sum < target){
                res = Math.min(res, target - sum);
                left++;
            }else if(sum > target){
                res = Math.min(res, sum - target);
                right--;
            }else{
                return 0;
            }
        }
        
        return res;
        
    }

 

三数之和最接近target

lintcode 59. 3Sum Closest  https://www.lintcode.com/problem/3sum-closest/description

和之前三数的思路一致,从其中一个数开始循环,对另外两个数进行双指针,返回值是最接近target的和值。注意点是和上一题不一样,这里的变量记录的是和值,所以最开始的时候必须使其等于第一个sum。

    public int threeSumClosest(int[] numbers, int target) {
        if(numbers == null || numbers.length == 0)return 0;
        
        Arrays.sort(numbers);
        int res = Integer.MAX_VALUE;
        
        for(int i = 0;i < numbers.length - 2;i++){
        
            int left = i + 1, right = numbers.length - 1;
            while(left < right){
                int sum = numbers[i] + numbers[left] + numbers[right];
            
                if(sum == target){
                    return sum;
                }
            
                if(res == Integer.MAX_VALUE || Math.abs(sum - target) <     Math.abs(res - target)){
                    res = sum;
                }
            
                if(sum < target){
                    left ++;
                }else{
                    right --;
                }
            }
        }
        
        return res;
    }
}

 

两数之差

lintcode 610. Two Sum - Difference equals to target  https://www.lintcode.com/problem/two-sum-difference-equals-to-target/my-submissions?_from=ladder&&fromId=1

首先是hashmap的解决方法,思想和两数之和一样,先找map中有没有这个target - nums[i]值,有的话直接返回,没有的话将nums[i],i放到map中。不同的一点是两数之差有两种情况,找target + nums[i] 和 nums[i] - target。

双指针算法。因为最终需要返回索引,而输入有时无序数组,为了保证排序之后得到原来的索引,需要新建pair类,同时重写comparator接口(两个坑,一是java 自己的Pair类为什么不能建数组,二是重写比较接口),之后的思想和双指针一样。

class Pair{
    int value;
    int index;
    public Pair(int v, int i){
        this.value = v;
        this.index = i;
    }
}


public class Solution {
    /**
     * @param nums: an array of Integer
     * @param target: an integer
     * @return: [index1 + 1, index2 + 1] (index1 < index2)
     */
    /*
    hashmap. In the two sum question, we just check int diff = target - nums[i], if we get the the ans, return it, otherwise, we put (nums[i], i) in the map;
    In this question, we need to consider two conditions because it is difference, otherthings are same.
    */ 
     
    // public int[] twoSum7(int[] nums, int target) {
    //     int[] res = new int[2];
    //     if(nums == null || nums.length == 0)return res;
        
    //     HashMap<Integer, Integer> map = new HashMap<>();
        
    //     for(int i = 0;i < nums.length;i++){
    //         int sum = nums[i] + target;
    //         if(map.containsKey(sum)){
    //             int index1 = map.get(sum);
    //             int index2 = i;
    //             if(index2 < index1){
    //                 int temp = index1;
    //                 index1 = index2;
    //                 index2 = index1;
    //             }
    //             res[0] = index1 + 1;
    //             res[1] = index2 + 1;
    //             return res;
    //         }
            
    //         int diff = nums[i] - target;
    //         if(map.containsKey(diff)){
    //             int index1 = map.get(diff);
    //             int index2 = i;
    //             if(index2 < index1){
    //                 int temp = index1;
    //                 index1 = index2;
    //                 index2 = index1;
    //             }
    //             res[0] = index1 + 1;
    //             res[1] = index2 + 1;
    //             return res;
    //         }
            
    //         map.put(nums[i], i);
    //     }
        
    //     return res;
    // }
    
    
    public int[] twoSum7(int[] nums, int target) {
        int[] res = new int[2];
        if(nums == null || nums.length == 0)return res;
        
        Pair[] pairs = new Pair[nums.length]; 
        
        if(target < 0) target = -target;
        
        for(int i = 0;i < nums.length;i++){
            pairs[i] = new Pair(nums[i], i);
        }
        
        Arrays.sort(pairs, new Comparator<Pair>(){
            public int compare(Pair p1, Pair p2){
                return p1.value - p2.value;
            }
        }
        );
        
        int j = 0;
        for(int i = 0;i < nums.length - 1;i++){
            if(i == j){
                j++;
            }
            
            while(j < nums.length && pairs[j].value - pairs[i].value < target){
                j++;
            }
            
            if(pairs[j].value - pairs[i].value == target){
                int index1 = pairs[i].index + 1;
                int index2 = pairs[j].index + 1;
                if(index2 < index1){
                    int tmp = index1;
                    index1 = index2;
                    index2 = tmp;
                }
                res[0] = index1;
                res[1] = index2;
                return res;
            }
        }
        
        return res;
    }
}

 

以上是关于刷题--双指针的主要内容,如果未能解决你的问题,请参考以下文章

Leetcode刷题100天—1221. 分割平衡字符串( 双指针或贪心)—day31

刷题--双指针

力扣刷题之双指针(C++)

LeetCode刷题总结-双指针位运算和分治法篇

LeetCode刷题 -- 双指针篇 -- 三数之和

LeetCode刷题 -- 双指针篇 -- 三数之和