刷题--双指针(2)

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;
    }
}

原文地址:https://www.cnblogs.com/2333wzl/p/12283149.html

时间: 2024-10-11 10:43:16

刷题--双指针(2)的相关文章

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

本文总结LeetCode上有关双指针.位运算和分治法的算法题,推荐刷题总数14道.具体考点分析如下图: 一.双指针 1.字符串和数组问题 题号:424. 替换后的最长重复字符,难度中等 题号:828. 独特字符串,难度困难 题号:923. 三数之和的多种可能,难度中等 2.实际场景应用问题 题号:826. 安排工作以达到最大收益,难度中等 3.元素对问题 题号:986. 区间列表的交集,难度中等 二.位运算 1.字符串和数组问题 题号:137. 只出现一次的数字 II,难度中等 题号:318.

DP刷题记录

目录 dp刷题记录 codeforces 706C codeforces 940E BZOJ3997 POJ2279 GYM102082B GYM102082D codeforces132C L3-020 至多删三个字符 牛客 553C Chino with Queue POJ3260 The Fewest Coins Codeforces 372C dp刷题记录 codeforces 706C 题意:给出n个字符串,可以对每个字符串进行翻转操作, 每个操作对应一个消耗c[i],问经过操作后是否

LeetCode刷题总结之双指针法

Leetcode刷题总结 目前已经刷了50道题,从零开始刷题学到了很多精妙的解法和深刻的思想,因此想按方法对写过的题做一个总结 双指针法 双指针法有时也叫快慢指针,在数组里是用两个整型值代表下标,在链表里是两个指针,一般能实现O(n)的时间解决问题,两个指针的位置一般在第一个元素和第二个元素或者第一个元素和最后一个元素,快指针在前“探路”,当符合某种条件时慢指针向前挪 盛最多水的容器 这道题其实是求最大面积,最大面积取决于较小值.初始时两指针分别位于第一和最后一个元素处,那么明确指针应该向什么方

用js刷题的一些坑

leecode可以用js刷题了,我大js越来越被认可了是吧.但是刷题中会因为忽略js的一些特性掉入坑里.我这里总结一下我掉过的坑. 坑1:js中数组对象是引用对象 js中除了object还有数组对象也是引用对象,这点常常被忽视,所以在递归的时候传递数组要用arr.slice(0)这样复制一个一样的新数组,不然会出现你传入的数组会被同级的递归改变,结果就不对了. 所以只要数组复制的地方最好都要这么写,除非你真的想引用.而且注意是slice不是splice这两个方法差别很大,你如果用splice(0

LeetCode刷题之一:寻找只出现一次的数字

投简历的时候看到了个刷题网站,http://www.nowcoder.com/527604,就做了一套题,现记录下来. 题目为: Given an array of integers, every element appears twice except for one. Find that single one. Note: Your algorithm should have a linear runtime complexity. Could you implement it withou

【leetcode刷题笔记】Sum Root to Leaf Numbers

Given a binary tree containing digits from 0-9 only, each root-to-leaf path could represent a number. An example is the root-to-leaf path 1->2->3 which represents the number 123. Find the total sum of all root-to-leaf numbers. For example, 1 / 2 3 T

BZOJ第一页刷题计划

BZOJ第一页刷题计划 已完成:1 / 100 BZOJ1000:A+B

刷题记录

刷题啦,刷题啦,咱也刷算法题. 先从牛客网的JS方面刷起,接着刷数据结构和算法,然后去刷leetcode,这儿记载自己从出错的地方. 1.题目描述 移除数组 arr 中的所有值与 item 相等的元素,直接在给定的 arr 数组上进行操作,并将结果返回 . 没有认真思考,写下了如下的答案 function removeWithoutCopy(arr, item) { for(i = 0; i < arr.length; i++) { if( arr[i] === item) { arr.spli

停课刷题总结-给自己一点鼓励吧

嗯,我已经停了四五天课在家刷BZOJ准备复赛了,感觉压力好大.但是,实际上感觉效率并不高,每天就是7-8题的样子,而且并不是每题都有质量.而且这几天刷下来,我貌似因为刷了太多水题的关系,打字写题的速度变慢了,有一点悠闲没有紧迫感了,要赶快把这个习惯给改掉!今天去学校做题被虐了,竟然一个简单的Hash没有调对[虽然我现在还是不知道为什么会死循环QAQ.]感觉吧,可能因为刷题有点不在状态了.[其实也因为刷题的间隙玩了几盘LOL,游戏这东西QAQ]被虐了,感觉很不爽,有点难受,毕竟我付出了那么多努力,