友情提示:篇幅较长,找题目的话,右边有目录,幸亏我会MarkDown语法。
改为了系列模式,由于相似的题很多,本质上都是换壳,因此在同一篇文章里面把这类问题汇总一下。先说2 Sum。这道题很是出名,由于这是leetcode的第一道题。不少人说一些公司招聘的时候,这道题专门出给他们想招进来的人,由于这不是放水,简直就是洪水。要作的就是已知一个数组,和一个target值。返回两个目标的index,这两个目标求和就是target值。编程
这题不难,只须要熟悉hashtable便可。在hashtable里面,key是差,value是index。好比例子中的[2,7,11,15]
,target是9。那么在2的时候就存入7 0
,下一位找到7的时候,以前有个差值是7,那么就返回7对应的index,0,以及当前这个7的index,就是1。数组
public class Solution { public int[] twoSum(int[] nums, int target) { //建立一下数组,要存两个index的数组。 int[] result = new int[2]; //这里用hashtable也行,看心情。 Map<Integer, Integer> map = new HashMap<Integer, Integer>(); //扫一遍数组,一边扫一边存 for(int i = 0; i < nums.length; i++){ int cur = nums[i]; //这里搞出来个差值,其实差值是在没找到以后添加到map里面的。 int toFind = target - cur; //若是发现以前须要这个差值,那就找index。 if(map.containsKey(cur)){ result[0] = map.get(cur); result[1] = i; return result; } //若是没有,就put到map里面 else{ map.put(toFind, i); } } return result; } }
就是O(n),由于只扫了一遍数组。less
虽然这题很是简单,可是14年秋天第一次看到这题的时候感受仍是难到爆炸,无从下手。由于一丝编程基础都没有,如今两年过去了,用脚趾都能敲出来。其实行业之间努力其次,路径很是重要,在这里感谢一下点拨个人老乡和兄弟们。并且从新写了几回,连变量命名都是同样的。测试
这题用sorted
当作题目,比如路边的一些职业勾引男性行人,很是直接的就意味着二分搜索。一次查一半,因此刚开始只用到了二分搜索。可是有个问题,二分搜索的步子太大,可能把目标值跳过,那么还要借鉴双指针的全盘扫描的特色。线程
public class Two_Sum_II { public int[] twoSum(int[] numbers, int target) { int[] result = new int[2]; //这里用二分搜索,我经常使用start和end来命名两头,middle是中间。 int start = 0; int end = numbers.length-1; //这个while循环条件很巧妙,二分搜索建议固定一个模板,这个就挺好固定的。 while (start + 1 < end) { //看,我刚说的是实话,并且这里middle的计算方法是防止越界。 int middle = start + (end-start)/2; if (numbers[start] + numbers[end] < target) { //这里须要判断,究竟是跳一半仍是走一步,就再加个判断。 if (numbers[middle] + numbers[end] < target) { start = middle; } else { start++; } } else if(numbers[start] + numbers[end] > target) { if (numbers[middle] + numbers[start] > target) { end = middle; } else { end--; } } else { break; } } //题目中保证了有结果,还不是zero-based,那么就把result两项都续一秒。 result[0] = start+1; result[1] = end+1; return result; } }
固然就是最坏状况O(n)了,也是标准的双指针复杂度。不过二分搜索方法是它最优状况是O(nlgn)。翻译
不得不说,这个题目把二分搜索和双指针拼在一块儿很是有创意。只用二分搜索让我多交了一个submit,好题一个。指针
Design and implement a TwoSum class. It should support the following operations: add and find. add - Add the number to an internal data structure. find - Find if there exists any pair of numbers which sum is equal to the value. For example, add(1); add(3); add(5); find(4) -> true find(7) -> false
锁住的题,罕见的一道design题目和Google不要紧,tag只有Linkedin,怪不得被收购了。code
这是一道入门级别的design题目,固然第一次遇到design这个词我就像脑血栓般浑身发抖。不过好在它脱胎于Two Sum,本质上仍是不难的,咱们要作的就是套上design的外壳,解决掉它。值得注意的是,一个数字只能用1次,因此仍是要记录一下数字出现的次数的。排序
public class TwoSumIII { //用一个List当容器装数字,用Map来记录数字出现的次数 List<Integer> list = new ArrayList<>(); Map<Integer, Integer> map = new HashMap<Integer, Integer>(); // Add the number to an internal data structure. public void add(int number) { list.add(number); //很是常规的往map里记录出现个数的写法 if (map.containsKey(number)) { map.put(number, map.get(number)+1); } else { map.put(number,1); } } // Find if there exists any pair of numbers which sum is equal to the value. public boolean find(int value) { for (int i = 0; i < list.size(); i++) { int cur = list.get(i); int toFind = value - cur; //这里仍是Two Sum的求法,取一个,找另外一个。值得注意的是须要看求和的两个数是否是相等。 if (cur != toFind) { //根据leetcode测试,在map里面找比在list找目标数字更快一些。 if (map.containsKey(toFind)) { return true; } } else { if (map.get(cur) > 1) { return true; } } } return false; } }
这种题应该不太须要分析复杂度吧,能实现就行。每次都是遍历一遍List,因此就是O(n)。three
写的时候发现其实遍历一下Map也行,能够省去一个list。但我恰恰不省,由于List不要钱,能加就加,并且看着也方便,一个map用于不一样的用途,可能会引发线程冲突。出来混,多一事不如少一事。
这题用脚后跟看都是2Sum的follow up。就是在一个数组里面挑3个数字,这三个数字的和为0就行。须要注意的是triplet这个单词的拼写和发音,还有不能有重复的triplet,不能重复这一点仍是有点儿小麻烦的
既然是follow up,解决思路也就是follow up。follow up是什么意思呢,咱们翻译一下,follow就是跟随,up就是上面。就是跟随上面的意思,咱们往上看,上面只有2Sum一题,那咱们就跟随一下。A+B
是2Sum,A+B+C
是3Sum,那么稍加修改A+(B+C)就成了这两道题链接的桥梁。因此这题的基本思路就是套了个壳子而已。
值得一提的是,此题可能有重复数字,并且要求不能有重复结果,因此使用双指针法。前面这句的不是很理所固然,在这里就当经验记录一下了,强行解释就是指针能够跳太重复的数字,并且求和也很容易。
public class ThreeSum { public List<List<Integer>> threeSum(int[] nums) { //首先把输出写出来 List<List<Integer>> result = new ArrayList<>(); //双指针出马以前数组一般须要排序 Arrays.sort(nums); for (int i = 0; i < nums.length; i++) { int cur = nums[i]; //这个本意是重复数字的话能够跳过。由于以前的数字已经打头过了,重复的就不必打头了。 if (i > 0 && cur == nums[i-1]) { continue; } //双指针出马,这里注意了我通常命名成left和right。 int left = i+1; int right = nums.length-1; //这里注意了开始2Sum过程。 while (left < right) { int two_sum = nums[left] + nums[right]; if (two_sum < -1*cur) { //说明加和小了,那把左指针往右移动 left++; } else if (two_sum > -1 * cur) { //大了那就往左移动 right--; } else { List<Integer> list = new ArrayList<>(); list.add(cur); list.add(nums[left]); list.add(nums[right]); //把此次记录的结果加到result里面 result.add(list); //下回测试corner case的时候就一群0,此次4个0就吃亏了,忘了这个while循环,因此要跳太重复数字。 while(left+1 < right && nums[left] == nums[left+1]){ left++; } while (right-1 > left && nums[right] == nums[right-1]) { right--; } //跳过以后,继续挪动一下。 left++; right--; } } } return result; } }
这个排序的复杂度是O(nlgn),循环的复杂度就是O(n^2),因此就是循环的复杂度n方。
其实这种数组题,不管多么的熟练,都要在纸上先勾画一下思路,尤为是这道题里面重复数字的处理,其实也能够用个set来去重,但那样毕竟颜值不行,不符合我自拍的一向水准。跳过相等数字,最后再挪动一下,code里面很差分析,在纸上画画一目了然。
Given an array of n integers nums and a target, find the number of index triplets i, j, k with 0 <= i < j < k < n that satisfy the condition nums[i] + nums[j] + nums[k] < target. For example, given nums = [-2, 0, 1, 3], and target = 2. Return 2. Because there are two triplets which sums are less than 2: [-2, 0, 1] [-2, 0, 3] Follow up: Could you solve it in O(n2) runtime?
这题竟然是锁住的,company tag只有一个Google,因此把题目内容贴上来。
这题说老实话让我很困惑,为啥这题都能当一道题出了。其实就是3Sum稍微变一下,而后返回个个数而不是一群triplet。并且要求是O(n2),那类比3Sum的双指针方法能够知足。
public class Three_Sum_Smaller {
public int threeSumSmaller(int[] nums, int target) { //双指针是必定要排序的,不然后面根据大小挪动指针就没有意义了。 Arrays.sort(nums); int result = 0; for (int i = 0; i < nums.length-2; i++) { int left = i+1; int right = nums.length-1; int cur = nums[i]; while (left < right) { int two_sum = nums[left] + nums[right]; //这里须要注意若是知足条件,接下来不须要移动指针,直接把中间全部的可能性都加起来就能够 if (cur + two_sum < target) { result += right - left; left++; } else { //只有和大了,才要让右边指针左移,让总体小一些。 right--; } } } return result; }
}
直接O(n2)了,就是两重循环,多说一句,双指针就是把O(n2)降成O(n)的,外面再套一层循环,就是O(n2)了。
这题会作了,Google是否是能过一轮了啊。就注意小于的状况直接求result就行。
仍是一个数组,还有个目标数。返回距离目标最近的一个和,这个和是3个数的和。
和上面同样,双指针,看大小。
public class ThreeSumClosest { public int threeSumClosest(int[] nums, int target) { if(nums == null || nums.length < 3){ return 0; } int res = 0; int diff = Integer.MAX_VALUE; Arrays.sort(nums); for(int cur = 0; cur < nums.length-2; cur++){ int left = cur+1; int right = nums.length-1; int tempTar = target-nums[cur]; while(left < right){ int sum = nums[left] + nums[right]; int value = Math.abs(sum-tempTar); //找到更小的就更新。 if(value <= diff){ diff = value; res = nums[cur] + nums[left] + nums[right]; } if(sum < tempTar){ left++; } else if(sum > tempTar){ right--; } else{ return target; } } } return res; } }
仍是O(n2).
因此能够看出,不少题目思路一致,换汤不换药。都是双指针扫数组,很是容易。
此次是4个,就是找四个数,它们的和是目标数。
此次就是3 Sum套了个壳而已,方法都是同样的。
public class FourSum { public List<List<Integer>> fourSum(int[] nums, int target) { List<List<Integer>> res = new ArrayList<>(); //象征性的说,若是没有4个数,那还玩个球啊 if(nums.length < 4) return res; //双指针排序,都看腻了吧 Arrays.sort(nums); for(int i = 0; i < nums.length-3; i++){ //去掉重复的数字,就是打头不能相同 if(i > 0 && nums[i] == nums[i-1]) continue; for(int j = i+1; j < nums.length-2; j++){ //再去掉一遍 if(j > i+1 && nums[j] == nums[j-1]) continue; //实力打脸,之后仍是要left和right,low和high太low了。 int low = j+1, high = nums.length-1; while(low < high){ int sum = nums[i] + nums[j] + nums[low] + nums[high]; if(sum == target){ //这里新建一个list也行。 res.add(Arrays.asList(nums[i],nums[j], nums[low], nums[high])); while(low+1 < high && nums[low+1] == nums[low]){ low++; } while(high-1 > low && nums[high-1] == nums[high]){ high--; } low++; high--; } else if(sum < target){ low++; } else{ high--; } } } } return res; } }
O(n3),由于是三重循环。
这个系列结束了,没想到从2 Sum能够延伸这么长。不过核心思想都差很少,一些细节处,好比去掉重复数字这种手法须要多加熟练。这个9月加油找了。