1. Two Sum
最基础的一道题,方法很多,用HashMap存pair是一种(HashSet和HashMap的方法在这里原理是一样的)。也可以sort the whole array first,then use two pointers, one start from the left side, the other from the right side. if array[left]+array[right]>target, move the right index to the left by 1, 小于的话同理. 这里给一个HashMap的方法。
1 public int[] twoSum(int[] numbers, int target) { 2 HashMap<Integer, Integer> map = new HashMap<Integer, Integer>(); 3 int[] result = new int[2]; 4 for(int i = 0; i < numbers.length; i++) { 5 if(map.containsKey(numbers[i])) { 6 int index = map.get(numbers[i]); 7 result[0] = index + 1; 8 result[1] = i + 1; 9 break; 10 } else { 11 map.put(target - numbers[i], i); 12 } 13 } 14 return result; 15 }
2. Median of Two sorted arrays
首先,比较暴力的方法是统计两个数组的长度(奇数长度有一个median,偶数长度是最中间的两个数的均值),然后两个数组各设置一个index, 谁小,谁的index++,直到两个index走的总长度达到了median所需的长度。这个是O(n)的复杂度。进一步想,数组里找某个数的问题很容易往binary search上面考。这题也不例外。只不过这道题对于边界的控制需要小心,一下写出bug free的代码还是挺有难度的。
关键的是把这个问题转化为第k小的问题。只不过最终找的这个k其实是median。转化为第k小的问题有一个好处,比较好设计成一个recursion的函数。recursion,第一步想清楚base case, 这个recursion里面,第k小的数、这个k是可能会变化的。变化到最后,剩下的某个数组为空,或者k==1的时候,就找到了我们想要的结果,recursion就走到头了。第二步想清楚recursion rule。每次判断剩余的A和B两个数组各自的median,通过比较他们的大小,我们要么舍弃A的前半段和B的后半段,要么舍弃A的后半段和B的前半段。每一个情况又分为两种,如果舍弃的是某个数组的后半段,我们要找的仍然是剩下的元素中间的第k小的数,k不变。如果舍弃的是某个数组的前半段,那么我们的k就要发生变化了,传进下一层recursion的k参数就要发生变化。综上,我们实际上每层recursion会分四种情况讨论。具体的我在下面的代码里做了注释。
1 public double findMedianSortedArrays(int[] A, int[] B) { 2 int lengthA = A.length; 3 int lengthB = B.length; 4 if((lengthA + lengthB) % 2 == 0) { 5 double r1 = (double)findM(A, 0, lengthA, B, 0, lengthB, (lengthA + lengthB) / 2); 6 double r2 = (double)findM(A, 0, lengthA, B, 0, lengthB, (lengthA + lengthB) / 2 + 1); 7 return (r1 + r2) / 2; 8 } else { 9 return findM(A, 0, lengthA, B, 0, lengthB, (lengthA + lengthB) / 2 + 1); 10 } 11 } 12 13 public int findM(int[] A, int startA, int endA, int[] B, int startB, int endB, int k) { 14 //转换为第k小数的问题 15 int n = endA - startA; 16 int m = endB - startB; 17 if(n <= 0) {//corner case & base case of recursion 18 return B[startB + k - 1]; 19 } 20 if(m <= 0) {//corner case & base case of recursion 21 return A[startA + k - 1] 22 } 23 if(k == 1) {//corner case & recursion case of recursion 24 return A[startA] < B[startB] ? A[startA] : B[startB]; 25 } 26 int midA = (startA + endA) / 2; 27 int midB = (startB + endB) / 2; 28 if(A[midA] <= B[midB]) {// the target will appear at the first half of A, or the second half of B 29 if(n / 2 + m / 2 + 1 >= k) {// if in second half of B, k will not change, because we will not cut any number smaller than target 30 return findM(A, startA, endA, B, startB, midB, k); 31 } else {// if in the first half of A, we will cut all the number in the first half, the next step is: find the (k-n/2-1)th smallest number 32 return findM(A, mid + 1, endA, B, startB, endB, k - n / 2 - 1); 33 } else { 34 if(n / 2 + m / 2 + 1 >= k) { 35 return findM(A, startA, midA, B, startB, endB, k); 36 } else { 37 return findM(A, startA, endA, B, midB + 1, endB, k - m / 2 -1); 38 } 39 } 40 }
3. Longest Substring Without Repeating Characters