1. Two Sum
leetcode链接: https://oj.leetcode.com/problems/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
leetcode链接: https://oj.leetcode.com/problems/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
leetcode链接: https://oj.leetcode.com/problems/longest-substring-without-repeating-characters/
首先能直观的想到暴力的方法,两个指针标记子串的头和尾,hashset来检查是否有重复,两层for循环头和尾,hashset检测到重复以后退出尾部指针的循环,比较与global的大小并更新。但是这里时间复杂度是O(n^2)。一个更好的办法是开辟一个256的数组,作用其实就是个字典表
1 public int lengthOfLongestSubstring(String s) { 2 int[] charMap = new int[256]; 3 Arrays.fill(charMap, -1); 4 int i = 0; 5 int maxLen = 0; 6 for(int j = 0; j < s.length(); j++) { 7 if(charMap[s.charAt(j)] >= i) { 8 i = charMap[s.charAt(j)] + 1; 9 } 10 charMap[s.charAt(j)] = i; 11 maxLen = Math.max(j - i + 1, maxLen); 12 } 13 return maxLen; 14 }
4. Add Two Numbers
leetcode链接: https://oj.leetcode.com/problems/add-two-numbers/
这个题思路很直观,两个链表各一个指针用来读值。另外需要考虑几件事,1. 当某个链表读到头的时候怎么办,2. 这种合并链表的题最好设置dummyHead,3. 最后的最后,如果依然有进位,别把它忘了。其他倒是没啥了。O(m+n)的复杂度
public ListNode addTwoNumbers(ListNode l1, ListNode l2) { int carry = 0; ListNode newHead = new ListNode(0); ListNode p1 = l1; ListNode p2 = l2; ListNode p3 = newHead; while(p1 != null || p2 != null) { if(p1 != null) { carry += p1.val; p1 = p1.next; } if(p2 != null) { carry += p2.val; p2 = p2.next; } p3.next = new ListNode(carry % 10); p3 = p3.next; carry /= 10; } if(carry == 1) { p3.next = new ListNode(1); } return newHead.next; }
5. Longest Palindromic Substring
leetcode链接: https://oj.leetcode.com/problems/longest-palindromic-substring/
这道题思路仍然很直接,循环扫描每个字符,每次从中心开花,找palindrome。但是有几点需要注意:
1. palindrome分为两种,一种是中心有一个元素,然后两边元素满足palindrome,另一种是中心有两个元素,这两个元素相等,然后以他俩为中心两边palindrome
2. corner case:写辅助函数的时候,是否在palindrome为1的时候合法?len2里面,(s, i, i+1),i+1这个参数貌似越界了,真的越界了么,会throw exception么?可以自己把特殊情况带进去,在演草纸上划拉一下,验证下。
3. 审题。题目需要返回的结果是个最长palindrome的substring,而不是int length。所以我们需要在找到更大长度的时候更新start和end这两个index。
public String longestPalindrome(String s) { int start = 0; int end = 0; for(int i = 0; i < s.length(); i++) { int len1 = expandAroundCenter(s, i, i); int len2 = expandAroundCenter(s, i, i + 1); int len = Math.max(len1, len2); if(len > end - start) { start = i - (len - 1) / 2; end = i + len / 2; } } return s.substring(start, end + 1); } public int expandAroundCenter(String s, int left, int right) { int L = left; int R = right; while(L >= 0 && R < s.length() && s.charAt(L) == s.charAt(R)) { L--; R++; } return R - L - 1; }
6. ZigZag Conversion
leetcode链接: https://oj.leetcode.com/problems/zigzag-conversion/
其实是挺没意思的一道题。纯math。在纸上画一个5 row的情况。找到规律即可。注意数组下标的操作不要越界,算清楚。
public String convert(String s, int nRows) { if(s == null || s.length() == 0 || nRows <= 0) { return ""; } if(nRows == 1) { return s; } StringBuilder result = new StringBuilder(); int size = 2 * nRows - 2; for(int i = 0; i < nRows; i++) { for(int j = i; j < s.length(); j += size) { result.append(s.charAt(j)); if(i != 0 && i != nRows - 1) { int temp = j + size - 2 * i; if(temp < s.length()) { result.append(s.charAt(temp)); } } } } return result.toString(); }
7. Reverse Integer
leetcode链接: https://oj.leetcode.com/problems/reverse-integer/
最直观的方法是把integer变成charArray,然后把charArray给reverse。需要的额外空间会多一些。下面的方法是直接对数字进行处理。注意会有很多corner case,都是计算机语言对于数字存储的细节方面的知识了。
public int reverse(int x) { if(x == Integer.MIN_VALUE) { return 0; } int num = Math.abs(x); int result = 0; while(num != 0) { if(result > (Integer.MAX_VALUE - num % 10) / 10) { return 0; } result = result * 10 + num % 10; num /= 10; } return x > 0 ? result : -result; }
8. String to Integer(atoi)
leetcode链接:https://oj.leetcode.com/problems/string-to-integer-atoi/
没什么具体的算法思路,考的仍然是对语言细节的掌握和corner case的敏感度。
public static final int maxDivBy10 = Integer.MAX_VALUE / 10; public int atoi(String str) { int i = 0; int n = str.length(); while(i < n && Character.isWhitespace(str.charAt(i))) { i++; //regardless of the heading white space } int sigh = 1; if(i < n && str.charAt(i) == '+') { i++; } else if(i < n && str.charAt(i) == '-') { sign = -1; i++; } int num = 0; while(i < n && Character.isDigit(str.charAt(i))) { int digit = Character.getNumericValue(str.charAt(i)); if(num > maxDivBy10 || num == maxDivBy10 && digit >= 8) { return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE; } num = num * 10 + digit; i++; } return sign * num; }
9. Palindrome Number
leetcode链接: https://oj.leetcode.com/problems/palindrome-number/
这个题要求不要有额外的空间。所以转化成字符串比较就行不同了。那就只能每次剥离这个数当前的首尾然后比较。为了取到第一位,我们需要首先生成一个大除数。然后开始循环判断是否palindrome,不要忘了每次大除数要自除100,因为剥离了两个数字。
public boolean isPalindrome(int x) { if(x < 0) { return false; } int div = 1; while(x / div >= 10) { div *= 10; } while(x != 0) { int left = x / div; int right = x % 10; if(left != right) { return false; } x = (x % div) / 10; div /= 100; } return true; }
10. Regular Expression Matching
leetcode链接: https://oj.leetcode.com/problems/regular-expression-matching/
recursion的方法一层一层判断。需要注意的是base case比较多。这也是由于regular expression匹配的情况比较多导致的。
public boolean isMatch(String s, String p) { if(p.length() == 0) { return s.length() == 0; } if(p.length() == 1) { return (s.length() == 1) && (p.charAt(0) == s.charAt(0) || p.charAt(0) == '.'); } if(p.charAt(1) != '*') { if(s.length() == 0) { return false; } else { return (s.charAt(0) == p.charAt(0) || p.charAt(0) == '.') && isMatch(s.substring(1), p.substring(1)); } } else { while(s.length() > 0 && (p.charAt(0) == s.charAt(0)) || p.charAt(0) == '.') { if(isMatch(s, p.substring(2))) { return true; } s = s.substring(1); } return isMatch(s, p.substring(2)); } }
总结leetcode的前十道题,个人感觉比较好,值得回味的应该是第2,3,4,10题。
11. Container with most water
leetcode链接:https://leetcode.com/problems/container-with-most-water/
这道题需要审题。和trap water是有区别的。trap water是高低不平的一排区间,问能存多少水。而这道题只需要考虑两条边界,中间可以认为是空的,可以存水。实际上难度大大降低。只需要左右两个pointer即可。
1 public int maxArea(int[] height) { 2 if(height.length <= 1) { 3 return 0; 4 } 5 int left = 0; 6 int right = height.length - 1; 7 int max = 0; 8 while(left < right) { 9 max = Math.max(max, (right - left) * Math.min(height[left], height[right])); 10 if(height[left] < height[right]) { 11 left++; 12 } else { 13 right++; 14 } 15 } 16 return max; 17 }
14. Longest common prefix
leetcode链接: https://leetcode.com/problems/longest-common-prefix/
public String longestCommonPrefix(String[] strs) { if(strs == null || strs.length == 0) { return ""; } String prefix = strs[0]; for(int i = 1; i < strs.length; i++) { int j = 0; for(; j < strs[i].length() && j < prefix.length(); j++) { if(prefix.charAt(j) != strs[i].charAt(j)) { break; } } prefix = prefix.substring(0, j); if(prefix.length() == 0) { break; } } return prefix; }
15. 3 Sum
leetcode链接: https://leetcode.com/problems/3sum/
其实这个3Sum的解法很直接,用3个指针追踪三个数凑出所需的数。唯一需要注意的是这道题需要一个deduplication的过程。
这个deduplication用hashset同样可以解决,不过这个解法更精炼一些。
public List<List<Integer>> threeSum(int[] num) { List<List<Integer>> result = new ArrayList<List<Integer>>(); if(num.length < 3 || num == null) { return result; } Arrays.sort(num); for(int i = 0; i < num.length - 2; i++) { if(i == 0 || num[i] > num > num[i - 1]) { int j = i + 1; int k = num.length - 1; while(j < k) { if(num[j] + num[k] == -num[i]) { ArrayList<Integer> list = new ArrayList<Integer>(); list.add(num[i]); list.add(num[j]); list.add(num[k]); k--; j++; while(j < k && num[k] == num[k + 1]) { k--; } while(j < k && num[j] == num[j - 1]) { j++; } } else if(num[j] + num[k] > -num[i]) { k--; } else { j++; } } } } }
16. 3Sum Closest
leetcode链接: https://leetcode.com/problems/3sum-closest/
先排序(很重要), 然后循环三个指针, 如果diff出现比min还小, 更新min。
1 public int threeSumClosest(int[] num, int target) { 2 int result = 0; 3 int min = Integer.MAX_VALUE; 4 Arrays.sort(num); 5 for(int i = 0; i < num.length; i++) { 6 int j = i + 1; 7 int k = num.length - 1; 8 while (j < k) { 9 int sum = num[i] + num[j] + num[k]; 10 int diff = Math.abs(target - sum); 11 if(diff == 0) { 12 return sum; 13 } 14 if(diff < min) { 15 min = diff; 16 result = sum; 17 } 18 if(sum <= target) { 19 j++; 20 } else { 21 k--; 22 } 23 } 24 } 25 return result; 26 }