刷题--双指针
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; } }
以上是关于刷题--双指针的主要内容,如果未能解决你的问题,请参考以下文章