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 }
来源:https://www.cnblogs.com/jianghewade/p/4290007.html