数组

1、二分

9.1 35. 搜索插入位置

9.1 704. 二分查找

9.2 367. 有效的完全平方数

9.2 69. x 的平方根

2、移除元素

9.2 27. 移除元素 双指针—快慢指针

9.3 283. 移动零

9.3 844. 比较含退格的字符串

双指针

  1. class Solution {
  2. public boolean backspaceCompare(String s, String t) {
  3. int i = s.length() - 1, j = t.length() - 1;
  4. int skipS = 0, skipT = 0;
  5. while (i >= 0 || j >= 0) {
  6. while (i >= 0) {
  7. if (s.charAt(i) == '#') {
  8. skipS++;
  9. i--;
  10. }else if (skipS > 0) {
  11. skipS--;
  12. i--;
  13. }else {
  14. break;
  15. }
  16. }
  17. while (j >= 0) {
  18. if (t.charAt(j) == '#') {
  19. skipT++;
  20. j--;
  21. }else if (skipT > 0) {
  22. skipT--;
  23. j--;
  24. }else {
  25. break;
  26. }
  27. }
  28. if (i >= 0 && j >= 0) {
  29. if (s.charAt(i) != t.charAt(j)) {
  30. return false;
  31. }
  32. } else if (i >= 0 || j >= 0) {
  33. return false;
  34. }
  35. i--;
  36. j--;
  37. }
  38. return true;
  39. }
  40. }

3、有序数组的平方

9.5 977. 有序数组的平方 双指针 头尾

4、滑动窗口

M9.5-209. 长度最小的子数组

  1. class Solution {
  2. public int minSubArrayLen(int target, int[] nums) {
  3. int left = 0, sum = 0, res = Integer.MAX_VALUE;
  4. for (int j = 0; j < nums.length; j++) {
  5. sum += nums[j];
  6. while (sum >= target) {
  7. res = Math.min(res, j - left + 1);
  8. sum -= nums[left++];
  9. }
  10. }
  11. return res == Integer.MAX_VALUE ? 0 : res;
  12. }
  13. }

M9.5-904. 水果成篮

  1. class Solution {
  2. public int totalFruit(int[] fruits) {
  3. if (fruits == null || fruits.length == 0) return 0;
  4. int l = 0, ans = 0;
  5. HashMap<Integer, Integer> map = new HashMap<>();
  6. for (int i = 0; i < fruits.length; i++) {
  7. map.put(fruits[i], map.getOrDefault(fruits[i], 0) + 1);
  8. while (map.size() > 2) {
  9. map.put(fruits[l], map.get(fruits[l]) - 1);
  10. if (map.get(fruits[l]) == 0) map.remove(fruits[l]);
  11. l++;
  12. }
  13. ans = Math.max(ans, i - l + 1);
  14. }
  15. return ans;
  16. }
  17. }

H9.6-76. 最小覆盖子串

  1. class Solution {
  2. Map<Character, Integer> need = new HashMap<>();
  3. Map<Character, Integer> window = new HashMap<>();
  4. public String minWindow(String s, String t) {
  5. if (s == null || t == null || s.length() < t.length() ) return "";
  6. for (int i = 0; i < t.length(); i++) {
  7. char c = t.charAt(i);
  8. need.put(c, need.getOrDefault(c, 0) + 1);
  9. }
  10. int l = 0, r = 0;
  11. int len = Integer.MAX_VALUE, start = 0;
  12. //表示window窗口中匹配成功的字符种类数(非字符个数)
  13. //例如子串AABC字符种类是3,匹配了两个A,valid才会+1
  14. int valid = 0;
  15. //如果右指针超过了母串边界,则表示已经遍历完所有窗口组合
  16. while (r < s.length()) {
  17. char c = s.charAt(r);
  18. r++; //右指针向右滑动
  19. //如果右指针指向的字符是子串中的字符,那么更新窗口中的数据
  20. if (need.containsKey(c)) {
  21. window.put(c, window.getOrDefault(c, 0) + 1);
  22. if (window.get(c).equals(need.get(c))) {
  23. valid++;
  24. }
  25. }
  26. //窗口收缩
  27. while (valid == need.size()) {
  28. //len用来记录最小窗口长度
  29. //start用来记录最小窗口开始的索引
  30. if (r - l < len) {
  31. start = l;
  32. len = r - l;
  33. }
  34. char d = s.charAt(l);
  35. l++;
  36. if (need.containsKey(d)) {
  37. if (window.get(d).equals(need.get(d))) {
  38. valid--;
  39. }
  40. window.put(d, window.getOrDefault(d, 0) - 1);
  41. }
  42. }
  43. }
  44. return len == Integer.MAX_VALUE ? "" : s.substring(start, start + len);
  45. }
  46. }

螺旋矩阵

//M9.7-59. 螺旋矩阵 II

M9.7-54. 螺旋矩阵

错误版本

  1. List<Integer> res = new ArrayList<>();
  2. if (matrix == null || matrix.length == 0) return res;
  3. int l = 0, r = matrix[0].length - 1, top = 0, low = matrix.length - 1;
  4. // int count = 0;
  5. while (l <= r && top <= low) {
  6. for (int i = l; i <= r; i++) {
  7. res.add(matrix[top][i]);
  8. // count++;
  9. }
  10. top++;
  11. for (int i = top; i <= low; i++) {
  12. res.add(matrix[i][r]);
  13. // count++;
  14. }
  15. r--;
  16. for (int i = r; i >= l; i--) {
  17. res.add(matrix[low][i]);
  18. // count++;
  19. }
  20. low--;
  21. for (int i = low; i >= top; i--) {
  22. res.add(matrix[i][l]);
  23. // count++;
  24. }
  25. l++;
  26. }
  27. return res;
  28. }
  1. class Solution {
  2. public List<Integer> spiralOrder(int[][] matrix) {
  3. List<Integer> res = new ArrayList<>();
  4. if (matrix == null || matrix.length == 0) return res;
  5. int l = 0, r = matrix[0].length - 1, top = 0, low = matrix.length - 1;
  6. while (l <= r && top <= low) {
  7. for (int i = l; i <= r; i++) {
  8. res.add(matrix[top][i]);
  9. }
  10. top++;
  11. for (int i = top; i <= low; i++) {
  12. res.add(matrix[i][r]);
  13. }
  14. r--;
  15. if (l <= r && top <= low) {
  16. for (int i = r; i >= l; i--) {
  17. res.add(matrix[low][i]);
  18. }
  19. low--;
  20. for (int i = low; i >= top; i--) {
  21. res.add(matrix[i][l]);
  22. }
  23. l++;
  24. }
  25. }
  26. return res;
  27. }
  28. }

//S9.8-剑指 Offer 29. 顺时针打印矩阵

链表

//S9.8-203. 移除链表元素

M9.8-707. 设计链表

//S9.8-206. 反转链表

M9.9-24. 两两交换链表中的节点

  1. class Solution {
  2. public ListNode swapPairs(ListNode head) {
  3. if (head == null || head.next == null) return head;
  4. ListNode pre = head, cur = head.next, res = head.next;
  5. ListNode indicator = new ListNode(0);
  6. while (cur != null) {
  7. pre.next = cur.next;
  8. cur.next = pre;
  9. indicator.next = cur;
  10. indicator = pre;
  11. pre = pre.next;
  12. if (pre != null)
  13. cur = pre.next;
  14. else break;
  15. }
  16. return res;
  17. }
  18. }
  19. class Solution {
  20. public ListNode swapPairs(ListNode head) {
  21. ListNode indi = new ListNode(0);
  22. indi.next = head;
  23. ListNode pre = indi;
  24. while (head != null && head.next != null) {
  25. ListNode tmp = head.next.next;
  26. pre.next = head.next;
  27. head.next.next = head;
  28. head.next = tmp;
  29. pre = head;
  30. head = tmp;
  31. }
  32. return indi.next;
  33. }
  34. }

//M9.10-19. 删除链表的倒数第 N 个结点

S9.10-面试题 02.07. 链表相交

M9.10-142. 环形链表 II — 找环的入口

快慢指针,第一次相遇后,快指针从head开始, 然后快慢指针每次都只移动一步

  1. public class Solution {
  2. public ListNode detectCycle(ListNode head) {
  3. if (head == null || head.next == null) return null;
  4. ListNode fast = head, slow = head;
  5. while (fast != null && fast.next != null) {
  6. fast = fast.next.next;
  7. slow = slow.next;
  8. if (fast == slow) {
  9. break;
  10. }
  11. }
  12. if (fast == null || fast.next == null) return null;
  13. fast = head;
  14. while (fast != slow) {
  15. fast = fast.next;
  16. slow = slow.next;
  17. }
  18. return slow;
  19. }
  20. }

哈希表

有效的字母异位词

//S9.11-242. 有效的字母异位词

S9.12-383. 赎金信

M9.12-49. 字母异位词分组

  1. class Solution {
  2. public List<List<String>> groupAnagrams(String[] strs) {
  3. //创建一个map
  4. Map<String, List<String>> map = new HashMap<>();
  5. for (String str : strs) {
  6. char[] chars = str.toCharArray();
  7. //将字符排序
  8. Arrays.sort(chars);
  9. //排序后的字符作为key
  10. String key = String.valueOf(chars);
  11. if (!map.containsKey(key)) {
  12. //如果没有key那么新建一个分组
  13. map.put(key, new ArrayList<String>());
  14. }
  15. //根据key往分组中加入字符串
  16. map.get(key).add(str);
  17. }
  18. //返回分组后的结果
  19. return new ArrayList<List<String>>(map.values());
  20. }
  21. }
  22. class Solution {
  23. public List<List<String>> groupAnagrams(String[] strs) {
  24. Map<String, List<String>> map = new HashMap<>();
  25. for (String str : strs) {
  26. char[] chars = str.toCharArray();
  27. int[] count = new int[26];
  28. for (char c : chars) {
  29. count[c - 'a']++;
  30. }
  31. StringBuffer sb = new StringBuffer();
  32. for (int i = 0; i < 26; i++) {
  33. if (count[i] != 0) {
  34. sb.append((char)('a' + i));
  35. sb.append(count[i]);
  36. }
  37. }
  38. String key = sb.toString();
  39. List<String> list = map.getOrDefault(key, new ArrayList<String>());
  40. list.add(str);
  41. map.put(key, list);
  42. }
  43. return new ArrayList<List<String>>(map.values());
  44. }
  45. }

M9.13-438. 找到字符串中所有字母异位词

  1. class Solution {
  2. public List<Integer> findAnagrams(String s, String p) {
  3. List<Integer> res = new ArrayList<>();
  4. if (s.length() < p.length()) return res;
  5. //用来记录目标串各字母出现的个数
  6. int[] need = new int[26];
  7. for (char c : p.toCharArray()) {
  8. need[c - 'a']++;
  9. }
  10. //滑动窗口的左右边界
  11. int l = 0, r = 0;
  12. //用来存储当前窗口串各字母出现的个数
  13. int[] window = new int[26];
  14. char[] chars = s.toCharArray();
  15. while (r < s.length()) {
  16. //窗口扩大
  17. window[chars[r] - 'a']++;
  18. //如果窗口下字符串长度跟p一样判断是否相等
  19. if ((r - l + 1) == p.length()) {
  20. //如果相等,将左边界添加值结果中
  21. if (Arrays.equals(need, window)) res.add(l);
  22. //窗口缩小,去除相应的字符
  23. window[chars[l] - 'a']--;
  24. l++;
  25. }
  26. //窗口扩大
  27. r++;
  28. }
  29. return res;
  30. }
  31. }

两个数组的交集

S9.14-349. 两个数组的交集

这题重复的不算,求不重复的

S9.14-350. 两个数组的交集 II

这题重复的也要算

其他

S9.14-202. 快乐数

  1. ===================快慢指针=======================
  2. class Solution {
  3. public boolean isHappy(int n) {
  4. int slow = n;
  5. int fast = getNext(n);
  6. while (fast != 1 && slow != fast) {
  7. slow = getNext(slow);
  8. fast = getNext(getNext(fast));
  9. }
  10. return fast == 1;
  11. }
  12. public int getNext(int n) {
  13. int totalSum = 0;
  14. while (n > 0) {
  15. int d = n % 10;
  16. n = n / 10;
  17. totalSum += d * d;
  18. }
  19. return totalSum;
  20. }
  21. }

S9.14-1. 两数之和

M9.15-454. 四数相加 II

  1. class Solution {
  2. public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
  3. if (nums1 == null || nums2 == null || nums3 == null || nums4 == null) return 0;
  4. if (nums1.length == 0) return 0;
  5. Map<Integer, Integer> map = new HashMap<>();
  6. for (int i : nums1) {
  7. for (int j : nums2) {
  8. int temp = i + j;
  9. map.put(temp, map.getOrDefault(temp, 0) + 1);
  10. }
  11. }
  12. int res = 0;
  13. for (int i : nums3) {
  14. for (int j : nums4) {
  15. int sum = i + j;
  16. if (map.containsKey(0 - sum)) {
  17. res += map.get(0 - sum);
  18. }
  19. }
  20. }
  21. return res;
  22. }
  23. }

M9.16-15. 三数之和

  1. class Solution {
  2. public List<List<Integer>> threeSum(int[] nums) {
  3. List<List<Integer>> res = new ArrayList<>();
  4. if (nums.length < 3 || nums == null) return res;
  5. Arrays.sort(nums);
  6. for (int i = 0; i < nums.length - 2; i++) {
  7. //如果最小的数都大于0了,那么不可能存在三个数和为0
  8. if (nums[i] > 0) return res;
  9. //跳过重复项
  10. if (i > 0 && nums[i] == nums[i - 1]) continue;
  11. int left = i + 1, right = nums.length - 1;
  12. while (left < right) {
  13. int sum = nums[i] + nums[left] + nums[right];
  14. if (sum > 0) right--;
  15. else if (sum < 0) left++;
  16. else {
  17. //等于0
  18. res.add(Arrays.asList(nums[i], nums[left], nums[right]));
  19. //跳过右边重复项
  20. while (right > left && nums[right] == nums[right - 1]) right--;
  21. //跳过左边重复项
  22. while (right > left && nums[left] == nums[left + 1]) left++;
  23. //收紧,因为当前已经等于0了,而数组是排序的所以头尾指针需要同时向中间移动
  24. //left++相当于指向更大的数,相应的right--相当于指向更小的数,这样才可能出现和为0
  25. right--;
  26. left++;
  27. }
  28. }
  29. }
  30. return res;
  31. }
  32. }

M9.17-18. 四数之和

思路与三数之和类似,但是注意不要判断num[i] >target时直接return 例如存在负数的情况 -5 + -3 + -2 + - 1 = -11,-5 > -11,但是是存在解的

字符串

S9.18-541. 反转字符串 II

  1. class Solution {
  2. public String reverseStr(String s, int k) {
  3. if (s == null || s.length() == 1) return s;
  4. char[] ch = s.toCharArray();
  5. for (int i = 0; i < ch.length; i += 2*k) {
  6. int l = i, r = Math.min(ch.length - 1, l + k - 1);
  7. while (l < r) {
  8. ch[l] ^= ch[r];
  9. ch[r] ^= ch[l];
  10. ch[l] ^= ch[r];
  11. l++;
  12. r--;
  13. }
  14. }
  15. return new String(ch);
  16. }
  17. }

S9.18-剑指 Offer 58 - II. 左旋转字符串 不使用额外空间,在原基础上操作

  1. class Solution {
  2. public String reverseLeftWords(String s, int n) {
  3. int len=s.length();
  4. StringBuilder sb=new StringBuilder(s);
  5. reverseString(sb,0,n-1);
  6. reverseString(sb,n,len-1);
  7. return sb.reverse().toString();
  8. }
  9. public void reverseString(StringBuilder sb, int start, int end) {
  10. while (start < end) {
  11. char temp = sb.charAt(start);
  12. sb.setCharAt(start, sb.charAt(end));
  13. sb.setCharAt(end, temp);
  14. start++;
  15. end--;
  16. }
  17. }
  18. }

**S9.19-28. 实现 strStr()

KMP算法

  1. class Solution {
  2. public int strStr(String haystack, String needle) {
  3. if (needle.isEmpty()) return 0;
  4. char[] ss = haystack.toCharArray();
  5. char[] p = needle.toCharArray();
  6. // 构建 next 数组,数组长度为匹配串的长度(next 数组是和匹配串相关的)
  7. int[] next = new int[p.length];
  8. //初始化next数组
  9. for (int i = 1, j = 0; i < p.length; i++) {
  10. //匹配不成功,j回退到next[j-1]的位置
  11. while (j > 0 && p[i] != p[j]) j = next[j - 1];
  12. //匹配成功j+1;
  13. if (p[i] == p[j]) j++;
  14. //更新next[i],进入下一次循环
  15. next[i] = j;
  16. }
  17. //匹配模式串
  18. for (int i = 0, j = 0; i < ss.length; i++) {
  19. //匹配不成功,j回退到next[j-1]的位置
  20. while (j > 0 && p[j] != ss[i]) j = next[j - 1];
  21. //匹配成功j+1;
  22. if (ss[i] == p[j]) j++;
  23. //如果全部匹配完成返回下标
  24. if (j == p.length) return i - p.length + 1;
  25. }
  26. return -1;
  27. }
  28. }

**S9.20-459. 重复的子字符串

解法一: 效率更高

KMP算法,判断next[len - 1]是否有相同前后缀子串,如果有 判断len % (len - next[len -1]) == 0,成立即表示存在重复子串构成母串,其他情况均为false

  1. class Solution {
  2. public boolean repeatedSubstringPattern(String s) {
  3. if (s.length() < 2 || s == null) return false;
  4. int len = s.length();
  5. int[] next = new int[s.length()];
  6. for (int i = 1, j = 0; i < len; i++) {
  7. while (j > 0 && s.charAt(j) != s.charAt(i)) j = next[j - 1];
  8. if (s.charAt(j) == s.charAt(i)) j++;
  9. next[i] = j;
  10. }
  11. if (next[len - 1] == 0) return false;
  12. return len % (len - next[len - 1]) == 0 ? true : false;
  13. }
  14. }

解法二:

如果字符串可以通过子串重复组成,那么每次可以移动一位,比较移动后的字符串是否与原字符串匹配,如果匹配那么则存在重复子串可组成原字符串。移动的次数最多不超过length-1次,因为移动length次就变成了原字符串。这样的话对于很长的字符串效率会很低,为了避免这种无用的环绕,可以创建一个新的字符串 str,它等于原来的字符串 S 再加上 S 自身,这样其实就包含了所有移动的字符串。
反例:
比如字符串:S = acd,那么str = S + S = acdacd
acd移动的可能:daccda。其实都包含在了 str 中了。就像一个滑动窗口
一开始 acd (acd) ,移动一次 ac(dac)d,移动两次a(cda)cd。循环结束
所以可以直接判断 str 中去除首尾元素之后,是否包含自身元素。如果包含。则表明存在重复子串。
正例:
S=abab str=abababab
abab移动的可能:babaabab,一开始abab(abab),移动一次aba(baba)b,移动两次ab(abab)ab。找到了原字符串。

  1. class Solution {
  2. public boolean repeatedSubstringPattern(String s) {
  3. String str = s + s;
  4. return str.substring(1, str.length() - 1).contains(s);
  5. }
  6. }

双指针

M9.21-151. 翻转字符串里的单词

不使用split

  1. class Solution {
  2. public String reverseWords(String s) {
  3. //1.去除首尾以及中间多余的空格
  4. StringBuilder sb = removeSpace(s);
  5. //2.翻转整个字符串
  6. reverseString(sb, 0, sb.length() - 1);
  7. //3.翻转单个单词
  8. reverseEachWord(sb);
  9. return sb.toString();
  10. }
  11. private StringBuilder removeSpace(String s) {
  12. int start = 0, end = s.length() - 1;
  13. //去除头尾空格
  14. while (s.charAt(start) == ' ') start++;
  15. while (s.charAt(end) == ' ') end--;
  16. StringBuilder sb = new StringBuilder();
  17. while (start <= end) {
  18. char c = s.charAt(start);
  19. //只有在sb的最后一个字符是' '同时c也是' '时,表示单词中间出现了多余的空格
  20. //此时不添加字符到sb中,指针移动
  21. // if (!(c == ' ' && sb.charAt(sb.length() - 1) == ' ')) sb.append(c);
  22. if (c != ' ' || sb.charAt(sb.length() - 1) != ' ') sb.append(c);
  23. start++;
  24. }
  25. return sb;
  26. }
  27. private void reverseString(StringBuilder sb, int l, int r) {
  28. while (l < r) {
  29. char tmp = sb.charAt(l);
  30. sb.setCharAt(l, sb.charAt(r));
  31. sb.setCharAt(r, tmp);
  32. l++;
  33. r--;
  34. }
  35. }
  36. private void reverseEachWord(StringBuilder sb) {
  37. int l = 0, r = 1;
  38. int n = sb.length();
  39. while (l < n) {
  40. //找到单词中间的空格,r的位置即为空格的index
  41. while (r < n && sb.charAt(r) != ' ') r++;
  42. //[l, r - 1]位置上的字符就是一个单词,翻转
  43. reverseString(sb, l, r - 1);
  44. // 跳过空格
  45. l = r + 1;
  46. r = l + 1;
  47. }
  48. }
  49. }

S9.23-面试题 02.07. 链表相交

  1. public class Solution {
  2. public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
  3. ListNode a = headA, b = headB;
  4. while (a != b) {
  5. a = a == null ? headB : a.next;
  6. b = b == null ? headA : b.next;
  7. }
  8. return a;
  9. }
  10. }

M9.23-19. 删除链表的倒数第 N 个结点

  1. class Solution {
  2. public ListNode removeNthFromEnd(ListNode head, int n) {
  3. ListNode tmp = new ListNode(-1);
  4. tmp.next = head;
  5. ListNode cur = tmp, fast = tmp;
  6. while (n >= 0 && fast != null) {
  7. fast = fast.next;
  8. n--;
  9. }
  10. while (fast != null) {
  11. fast = fast.next;
  12. cur = cur.next;
  13. }
  14. cur.next = cur.next.next;
  15. return tmp.next;
  16. }
  17. }

M9.23-142. 环形链表 II

  1. public class Solution {
  2. public ListNode detectCycle(ListNode head) {
  3. ListNode slow = head, fast = head;
  4. while (fast != null && fast.next != null) {
  5. fast = fast.next.next;
  6. slow = slow.next;
  7. if (fast == slow) {
  8. fast = head;
  9. break;
  10. }
  11. }
  12. if (fast == null || fast.next == null) return null;
  13. while (fast != slow) {
  14. fast = fast.next;
  15. slow = slow.next;
  16. }
  17. return fast;
  18. }
  19. }

M9.24-15. 三数之和

  1. class Solution {
  2. public List<List<Integer>> threeSum(int[] nums) {
  3. List<List<Integer>> res = new ArrayList<>();
  4. if (nums.length < 3 || nums == null) return res;
  5. Arrays.sort(nums);
  6. for (int i = 0; i < nums.length - 2; i++) {
  7. //第一个数大于0就不用找了
  8. if (nums[i] > 0) return res;
  9. //第一个数去重
  10. if (i > 0 && nums[i] == nums[i - 1]) continue;
  11. int l = i + 1, r = nums.length - 1;
  12. if (nums[i] + nums[l] > 0) return res;
  13. while (l < r) {
  14. int sum = nums[l] + nums[r] + nums[i];
  15. if (sum > 0) r--;
  16. else if (sum < 0) l++;
  17. else {
  18. res.add(Arrays.asList(nums[l], nums[r], nums[i]));
  19. while (l < r && nums[l] == nums[l + 1]) l++;
  20. while (l < r && nums[r - 1] == nums[r]) r--;
  21. l++;
  22. r--;
  23. }
  24. }
  25. }
  26. return res;
  27. }
  28. }

M9.24-18. 四数之和

  1. class Solution {
  2. public List<List<Integer>> fourSum(int[] nums, int target) {
  3. List<List<Integer>> res = new ArrayList<>();
  4. if (nums.length < 4 || nums == null) return res;
  5. Arrays.sort(nums);
  6. for (int i = 0; i < nums.length - 3; i++) {
  7. if (i > 0 && nums[i] == nums[i - 1]) continue;
  8. for (int j = i + 1; j < nums.length - 2; j++) {
  9. if (j > i + 1 && nums[j] == nums[j - 1]) continue;
  10. int l = j + 1, r = nums.length - 1;
  11. while (l < r) {
  12. int sum = nums[i] + nums[j] + nums[l] + nums[r];
  13. if (sum > target) r--;
  14. else if (sum < target) l++;
  15. else {
  16. res.add(Arrays.asList(nums[i], nums[j], nums[l], nums[r]));
  17. while (l < r && nums[l] == nums[l + 1]) l++;
  18. while (l < r && nums[r] == nums[r - 1]) r--;
  19. l++;
  20. r--;
  21. }
  22. }
  23. }
  24. }
  25. return res;
  26. }
  27. }

栈与队列

S9.25-225. 用队列实现栈

解法一:用两个队列que1和que2实现队列的功能,que2其实完全就是一个备份的作用,把que1最后面的元素以外的元素都备份到que2,然后弹出最后面的元素,再把其他元素从que2导回que1。

  1. class MyStack {
  2. Queue<Integer> que1;
  3. Queue<Integer> que2;
  4. public MyStack() {
  5. que1 = new LinkedList<>();
  6. que2 = new LinkedList<>();
  7. }
  8. public void push(int x) {
  9. que1.offer(x);
  10. }
  11. public int pop() {
  12. int ans = Integer.MIN_VALUE;
  13. int len = que1.size();
  14. for (int i = 0; i < len; i++) {
  15. if (i == len - 1) ans = que1.poll();
  16. else {
  17. que2.offer(que1.poll());
  18. }
  19. }
  20. while (!que2.isEmpty()) {
  21. que1.offer(que2.poll());
  22. }
  23. return ans;
  24. }
  25. public int top() {
  26. int a = pop();
  27. que1.offer(a);
  28. return a;
  29. }
  30. public boolean empty() {
  31. return que1.isEmpty();
  32. }
  33. }

优化解法:其实这道题目就是用一个队里就够了。
一个队列在模拟栈弹出元素的时候只要将队列头部的元素(除了最后一个元素外) 重新添加到队列尾部,此时在去弹出元素就是栈的顺序了。

  1. class MyStack {
  2. Queue<Integer> que1;
  3. public MyStack() {
  4. que1 = new LinkedList<>();
  5. }
  6. public void push(int x) {
  7. que1.offer(x);
  8. }
  9. public int pop() {
  10. int ans = Integer.MIN_VALUE;
  11. int len = que1.size();
  12. for (int i = 0; i < len; i++) {
  13. if (i == len - 1) ans = que1.poll();
  14. else {
  15. que1.offer(que1.poll());
  16. }
  17. }
  18. return ans;
  19. }
  20. public int top() {
  21. int a = pop();
  22. que1.offer(a);
  23. return a;
  24. }
  25. public boolean empty() {
  26. return que1.isEmpty();
  27. }
  28. }

M9.25-150. 逆波兰表达式求值

  1. class Solution {
  2. public int evalRPN(String[] tokens) {
  3. Stack<Integer> stack = new Stack<>();
  4. for (String s : tokens) {
  5. if (s.equals("+") || s.equals("-") || s.equals("/") || s.equals("*")) {
  6. int a = stack.pop();
  7. int b = stack.pop();
  8. switch(s) {
  9. case "+" :
  10. stack.push(a + b);
  11. break;
  12. case "-" :
  13. stack.push(b - a);
  14. break;
  15. case "*" :
  16. stack.push(b * a);
  17. break;
  18. default : stack.push(b / a);
  19. }
  20. }
  21. else stack.push(Integer.parseInt(s));
  22. }
  23. return stack.pop();
  24. }
  25. }

*H9.26-239. 滑动窗口最大值

  1. class Solution {
  2. public int[] maxSlidingWindow(int[] nums, int k) {
  3. int[] res = new int[nums.length - k + 1];
  4. if (k > nums.length) return new int[0];
  5. int j = 0;
  6. int index = 0;
  7. Deque<Integer> deque = new LinkedList<>();
  8. for (int i = 0; i < nums.length; i++) {
  9. while (!deque.isEmpty() && deque.peekLast() < nums[i]) {
  10. deque.pollLast();
  11. }
  12. deque.offer(nums[i]);
  13. if (i - j + 1 == k) {
  14. res[index++] = deque.peek();
  15. if (nums[j] == deque.peek())
  16. deque.pollFirst();
  17. j++;
  18. }
  19. }
  20. return res;
  21. }
  22. }

M9.27-347. 前 K 个高频元素

  • 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的

优先队列

  1. class Solution {
  2. public int[] topKFrequent(int[] nums, int k) {
  3. PriorityQueue<Map.Entry<Integer, Integer>> queue = new PriorityQueue<>((o1, o2) -> o1.getValue() - o2.getValue());
  4. Map<Integer, Integer> map = new HashMap<>();
  5. for (int num : nums) {
  6. map.put(num, map.getOrDefault(num, 0) + 1);
  7. }
  8. for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
  9. queue.offer(entry);
  10. if (queue.size() > k) queue.poll();
  11. }
  12. int[] res = new int[k];
  13. for (int i = 0; i < k; i++) {
  14. res[i] = queue.poll().getKey();
  15. }
  16. return res;
  17. }
  18. }

桶排序

  1. //桶排序
  2. public int[] topKFrequent(int[] nums, int k) {
  3. //map用来存储每个数字出现的次数
  4. Map<Integer, Integer> map = new HashMap<>();
  5. for (int num : nums) {
  6. map.put(num, map.getOrDefault(num, 0) + 1);
  7. }
  8. //桶排序,数字出现的次数对应桶的索引
  9. List<Integer>[] list = new List[nums.length + 1];
  10. for (int key : map.keySet()) {
  11. //获取数字出现的次数,作为桶list的下标
  12. int i = map.get(key);
  13. if (list[i] == null) {
  14. list[i] = new ArrayList<>();
  15. }
  16. //存储出现i次的数字,可能有多个数字出现次数相同
  17. list[i].add(key);
  18. }
  19. //存储结果,k个出现频率最高的数
  20. int[] res = new int[k];
  21. int index = 0;
  22. for (int i = list.length - 1; i > 0 && index < k; i--) {
  23. if (list[i] == null) continue;
  24. for (Integer num : list[i]) {
  25. res[index++] = num;
  26. }
  27. }
  28. return res;
  29. }

二叉树

S9.28-144. 二叉树的前序遍历

递归:

  1. class Solution {
  2. List<Integer> res = new ArrayList<>();
  3. public List<Integer> inorderTraversal(TreeNode root) {
  4. preOrder(root);
  5. return res;
  6. }
  7. public void preOrder(TreeNode root) {
  8. if(root == null) return;
  9. res.add(root.val);
  10. preOrder(root.left);
  11. preOrder(root.right);
  12. }
  13. }

非递归:

  1. class Solution {
  2. public List<Integer> preorderTraversal(TreeNode root) {
  3. if (root == null) return new ArrayList();
  4. List<Integer> res = new ArrayList<>();
  5. Stack<TreeNode> stack = new Stack<TreeNode>();
  6. stack.push(root);
  7. while (!stack.isEmpty()) {
  8. TreeNode node = stack.pop();
  9. res.add(node.val);
  10. if (node.right != null)
  11. stack.push(node.right);
  12. if (node.left != null)
  13. stack.push(node.left);
  14. }
  15. return res;
  16. }
  17. }

S9.28-145. 二叉树的后序遍历

递归:

  1. class Solution {
  2. List<Integer> res = new ArrayList<>();
  3. public List<Integer> postorderTraversal(TreeNode root) {
  4. postOrder(root);
  5. return res;
  6. }
  7. public void postOrder(TreeNode root) {
  8. if(root == null) return;
  9. postOrder(root.left);
  10. postOrder(root.right);
  11. res.add(root.val);
  12. }
  13. }

非递归:

  1. class Solution {
  2. public List<Integer> postorderTraversal(TreeNode root) {
  3. List<Integer> res = new ArrayList<>();
  4. if (root == null) return res;
  5. Stack<TreeNode> stack = new Stack<>();
  6. stack.push(root);
  7. while (!stack.isEmpty()) {
  8. TreeNode node = stack.pop();
  9. res.add(node.val);
  10. if (node.left != null)
  11. stack.push(node.left);
  12. if (node.right != null)
  13. stack.push(node.right);
  14. }
  15. Collections.reverse(res);
  16. return res;
  17. }
  18. }

非递归统一写法:

  1. class Solution {
  2. public List<Integer> postorderTraversal(TreeNode root) {
  3. List<Integer> res = new ArrayList<>();
  4. if (root == null) return res;
  5. Stack<TreeNode> stack = new Stack<>();
  6. stack.push(root);
  7. while (!stack.isEmpty()) {
  8. TreeNode node = stack.pop();
  9. // 通过null标记下一个节点为需要处理的节点
  10. // 如果当前节点不为null,则表示该节点只是访问而不处理
  11. if (node != null) {
  12. stack.push(node); // 添加中间节点
  13. stack.push(null); // 中间节点已访问,但是没有处理通过null标记
  14. // 添加右节点,空节点不入栈
  15. if (node.right != null) stack.push(node.right);
  16. // 添加左节点,空节点不入栈
  17. if (node.left != null) stack.push(node.left);
  18. }
  19. else { // 如果当前节点为null,这表示下一个节点是需要处理的节点
  20. node = stack.pop();
  21. res.add(node.val);
  22. }
  23. }
  24. return res;
  25. }
  26. }

S9.28-94. 二叉树的中序遍历

递归:

  1. class Solution {
  2. List<Integer> res = new ArrayList<>();
  3. public List<Integer> inorderTraversal(TreeNode root) {
  4. inOrder(root);
  5. return res;
  6. }
  7. public void inOrder(TreeNode root) {
  8. if(root == null) return;
  9. inOrder(root.left);
  10. res.add(root.val);
  11. inOrder(root.right);
  12. }
  13. }

非递归:

  1. class Solution {
  2. List<Integer> res = new ArrayList<>();
  3. public List<Integer> inorderTraversal(TreeNode root) {
  4. Stack<TreeNode> stack = new Stack<>();
  5. while(root != null || !stack.isEmpty()) {
  6. //将当前节点,以及其左孩子全部入栈
  7. while (root != null) { //访问结点,只到最底层
  8. stack.push(root); //将访问的节点入栈
  9. root = root.left; //左子树
  10. }
  11. //从栈里面弹出的节点就是要遍历的节点
  12. TreeNode node = stack.pop();
  13. res.add(node.val);
  14. // 指向右子树
  15. root = node.right;
  16. }
  17. return res;
  18. }
  19. }

M9.29-102. 二叉树的层序遍历

递归: DFS

  1. class Solution {
  2. List<List<Integer>> res = new ArrayList<>();
  3. public List<List<Integer>> levelOrder(TreeNode root) {
  4. DFS(root, 0);
  5. return res;
  6. }
  7. public void DFS(TreeNode node, int deep) {
  8. if (node == null) return;
  9. deep++; //当前在第几层
  10. if (res.size() < deep) {
  11. //当层级增加时,res中的Item也增加,用Item的数量来表示层级,
  12. //也就是通过res的索引来表示二叉树的第几层
  13. res.add(new ArrayList<Integer>());
  14. }
  15. res.get(deep - 1).add(node.val);
  16. DFS(node.left, deep);
  17. DFS(node.right, deep);
  18. }
  19. }

非递归:利用队列 BFS

  1. class Solution {
  2. List<List<Integer>> res = new ArrayList<>();
  3. public List<List<Integer>> levelOrder(TreeNode root) {
  4. BFS(root);
  5. return res;
  6. }
  7. public void BFS(TreeNode node) {
  8. if (node == null) return;
  9. Queue<TreeNode> que = new LinkedList<>();
  10. que.offer(node);
  11. while (!que.isEmpty()) {
  12. int len = que.size();
  13. List<Integer> arr = new ArrayList<>();
  14. for (int i = 0; i < len; i++) {
  15. TreeNode tmp = que.poll();
  16. arr.add(tmp.val);
  17. if (tmp.left != null) que.offer(tmp.left);
  18. if (tmp.right != null) que.offer(tmp.right);
  19. }
  20. res.add(arr);
  21. }
  22. }
  23. }

M9.29-107. 二叉树的层序遍历 II

递归:

  1. class Solution {
  2. List<List<Integer>> res = new ArrayList<>();
  3. public List<List<Integer>> levelOrderBottom(TreeNode root) {
  4. if (root == null) return res;
  5. DFS(root, 0);
  6. Collections.reverse(res);
  7. return res;
  8. }
  9. public void DFS(TreeNode root, int deep) {
  10. if (root == null) return;
  11. if (res.size() < deep + 1) {
  12. List<Integer> tmp = new ArrayList<>();
  13. res.add(new ArrayList<Integer>());
  14. }
  15. res.get(deep).add(root.val);
  16. DFS(root.left, deep + 1);
  17. DFS(root.right, deep + 1);
  18. }
  19. }

非递归:

  1. class Solution {
  2. public List<List<Integer>> levelOrderBottom(TreeNode root) {
  3. List<List<Integer>> res = new ArrayList<>();
  4. if (root == null) return res;
  5. Queue<TreeNode> que = new LinkedList<>();
  6. que.offer(root);
  7. while (!que.isEmpty()) {
  8. int len = que.size();
  9. List<Integer> tmp = new ArrayList<>();
  10. for (int i = 0; i < len; i++) {
  11. TreeNode node = que.poll();
  12. tmp.add(node.val);
  13. if (node.left != null) que.offer(node.left);
  14. if (node.right != null) que.offer(node.right);
  15. }
  16. res.add(tmp);
  17. }
  18. Collections.reverse(res);
  19. return res;
  20. }
  21. }

M10.2-199. 二叉树的右视图

  1. class Solution {
  2. public List<Integer> rightSideView(TreeNode root) {
  3. List<Integer> res = new ArrayList<>();
  4. if (root == null) return res;
  5. Queue<TreeNode> que = new LinkedList<>();
  6. que.offer(root);
  7. while (!que.isEmpty()) {
  8. int len = que.size();
  9. for (int i = 0; i < len; i++) {
  10. TreeNode node = que.poll();
  11. if (i == len - 1) res.add(node.val);
  12. if (node.left != null) que.offer(node.left);
  13. if (node.right != null) que.offer(node.right);
  14. }
  15. }
  16. return res;
  17. }
  18. }

S10.6-226. 翻转二叉树

S10.6-101. 对称二叉树

S10.7-104. 二叉树的最大深度

层序遍历:最大层数就是二叉树的最大深度
递归法:DFS

  1. //后序遍历
  2. class Solution {
  3. public int maxDepth(TreeNode root) {
  4. if (root == null) return 0;
  5. //详细写法,分别求左右子树的深度,找到最大的那个
  6. // int leftDeep = maxDepth(root.left);
  7. // int rightDeep = maxDepth(root.right);
  8. // return (leftDeep < rightDeep ? rightDeep : leftDeep) + 1;
  9. //简洁写法
  10. return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
  11. }
  12. }
  13. //前序遍历
  14. class Solution {
  15. int res = 0;
  16. public int maxDepth(TreeNode root) {
  17. preOrder(root, 0);
  18. return res;
  19. }
  20. public void preOrder(TreeNode node, int deep) {
  21. if (node == null) return;
  22. deep++;
  23. res = res < deep ? deep : res;
  24. preOrder(node.left, deep);
  25. preOrder(node.right, deep);
  26. return;
  27. }
  28. }

S10.9-559. N 叉树的最大深度

DFS

  1. class Solution {
  2. public int maxDepth(Node root) {
  3. if (root == null) return 0;
  4. int deep = 0;
  5. for (int i = 0; i < root.children.size(); i++) {
  6. deep = Math.max(deep, maxDepth(root.children.get(i)));
  7. }
  8. return deep + 1;
  9. }
  10. }

S10.9-111. 二叉树的最小深度

  1. //BFS
  2. class Solution {
  3. public int minDepth(TreeNode root) {
  4. if (root == null) return 0;
  5. Queue<TreeNode> que = new LinkedList<>();
  6. que.offer(root);
  7. int deep = 0;
  8. // int res = 0;
  9. while (!que.isEmpty()) {
  10. int len = que.size();
  11. for (int i = 0; i < len; i++) {
  12. TreeNode node = que.poll();
  13. if (node.left == null && node.right == null) return deep + 1;
  14. if (node.left != null) que.offer(node.left);
  15. if (node.right != null) que.offer(node.right);
  16. }
  17. deep++;
  18. }
  19. return deep;
  20. // return res < deep ? res : deep;
  21. }
  22. }
  23. //DFS
  24. class Solution {
  25. public int minDepth(TreeNode root) {
  26. if (root == null) return 0;
  27. int lDeep = minDepth(root.left);
  28. int rDeep = minDepth(root.right);
  29. if (root.left == null) return 1 + rDeep;
  30. if (root.right == null) return 1 + lDeep;
  31. return Math.min(lDeep, rDeep) + 1;
  32. }
  33. }

M10.10-222. 完全二叉树的节点个数

  1. //DFS postOrder 遍历节点
  2. class Solution {
  3. public int countNodes(TreeNode root) {
  4. if (root == null) return 0;
  5. // int lNum = countNodes(root.left); //左
  6. // int rNum = countNodes(root.right); //右
  7. // int treeNum = lNum + rNum + 1; //中
  8. // return treeNum;
  9. return countNodes(root.left) + countNodes(root.right) + 1;
  10. }
  11. }
  12. // 使用完全二叉树的性质
  13. class Solution {
  14. public int countNodes(TreeNode root) {
  15. if (root == null) return 0;
  16. TreeNode l = root.left;
  17. TreeNode r = root.right;
  18. int ld = 0, rd = 0; // 这里初始为0是有目的的,为了下面求指数方便
  19. while (l != null) { // 求左子树深度
  20. l = l.left;
  21. ld++;
  22. }
  23. while (r != null) { // 求右子树深度
  24. r = r.right;
  25. rd++;
  26. }
  27. //如果左右子树深度相等,说明是满二叉树
  28. if (rd == ld) return (2 << ld) - 1; // 注意(2<<1) 相当于2^2,所以ld初始为0
  29. return countNodes(root.left) + countNodes(root.right) + 1;
  30. }
  31. }

S10.12-110. 平衡二叉树

  1. ===================递归==========================
  2. class Solution {
  3. public boolean isBalanced(TreeNode root) {
  4. if (root == null) return true;
  5. int lDeep = deep(root.left);
  6. int rDeep = deep(root.right);
  7. if (lDeep == -1 || rDeep == -1) return false;
  8. return Math.abs(lDeep - rDeep) > 1 ? false : true;
  9. }
  10. // 返回以该节点为根节点的二叉树的高度,如果不是二叉平衡树则返回-1
  11. public int deep(TreeNode root) {
  12. if (root == null) return 0;
  13. int ld = deep(root.left); //左
  14. if (ld == -1) return -1;
  15. int rd = deep(root.right); //右
  16. if (rd == -1) return -1;
  17. //中
  18. int res = 0;
  19. if (Math.abs(ld - rd) > 1) res = -1;
  20. else res = 1 + Math.max(ld, rd); //以当前节点为根节点的二叉平衡树的高度
  21. return res;
  22. }
  23. }
  24. ==========================迭代========================

S10.13-257. 二叉树的所有路径

  1. ================================递归+回溯===============================
  2. class Solution {
  3. List<String> res = new ArrayList<>();
  4. List<Integer> path = new ArrayList<>();
  5. public List<String> binaryTreePaths(TreeNode root) {
  6. if (root == null) return res;
  7. getPath(root);
  8. return res;
  9. }
  10. public void getPath(TreeNode node) {
  11. path.add(node.val);
  12. //叶子结点,递归出口,输出当前路径
  13. if (node.left == null && node.right == null) {
  14. StringBuilder sb = new StringBuilder();
  15. for (int i = 0; i < path.size() - 1; i++) {
  16. sb.append(path.get(i)).append("->");
  17. }
  18. sb.append(path.get(path.size() - 1));
  19. res.add(sb.toString());
  20. return;
  21. }
  22. //左子树
  23. if (node.left != null) {
  24. getPath(node.left);
  25. path.remove(path.size() - 1); //回溯
  26. }
  27. //右子树
  28. if (node.right != null) {
  29. getPath(node.right);
  30. path.remove(path.size() - 1); //回溯
  31. }
  32. }
  33. }
  34. ======================迭代,无回溯============================
  35. class Solution {
  36. public List<String> binaryTreePaths(TreeNode root) {
  37. List<String> res = new ArrayList<>();
  38. if (root == null) return res;
  39. Stack<TreeNode> stack = new Stack<>();
  40. Stack<String> path = new Stack<>();
  41. stack.push(root);
  42. path.push("" + root.val);
  43. while (!stack.isEmpty()) {
  44. TreeNode node = stack.pop();
  45. String s = path.pop();
  46. if (node.left == null && node.right == null) res.add(s);
  47. if (node.right != null) {
  48. stack.push(node.right);
  49. path.push(s + "->" + node.right.val);
  50. }
  51. if (node.left != null) {
  52. stack.push(node.left);
  53. path.push(s + "->" + node.left.val);
  54. }
  55. }
  56. return res;
  57. }
  58. }

S10.17-100. 相同的树

  1. ====================递归========================
  2. class Solution {
  3. public boolean isSameTree(TreeNode p, TreeNode q) {
  4. if (p == null && q == null) return true;
  5. if (p == null || q == null) return false;
  6. if (p.val != q.val) return false;
  7. // boolean compareLeft = isSameTree(p.left, q.left);
  8. // boolean compareRight = isSameTree(p.right, q.right);
  9. // return compareLeft && compareRight;
  10. return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
  11. }
  12. }
  13. ====================迭代========================
  14. class Solution {
  15. public boolean isSameTree(TreeNode p, TreeNode q) {
  16. Stack<TreeNode> s1 = new Stack<>();
  17. Stack<TreeNode> s2 = new Stack<>();
  18. s1.push(p);
  19. s2.push(q);
  20. while (!s1.isEmpty() && !s2.isEmpty()) {
  21. TreeNode node1 = s1.pop();
  22. TreeNode node2 = s2.pop();
  23. if (node1 == null && node2 == null) continue;
  24. if (node1 == null || node2 == null) return false;
  25. if (node2.val != node1.val) return false;
  26. s1.push(node1.right);
  27. s1.push(node1.left);
  28. s2.push(node2.right);
  29. s2.push(node2.left);
  30. }
  31. return s1.isEmpty() && s2.isEmpty();
  32. }
  33. }
  1. class Solution { // 递归
  2. int res = 0;
  3. public int sumOfLeftLeaves(TreeNode root) {
  4. calculateLeftSum(root);
  5. return res;
  6. }
  7. public void calculateLeftSum(TreeNode root) {
  8. if (root == null) return;
  9. if (root.left != null) {
  10. if (root.left.left == null && root.left.right == null)
  11. res += root.left.val;
  12. calculateLeftSum(root.left);
  13. }
  14. if (root.right != null) {
  15. calculateLeftSum(root.right);
  16. }
  17. return;
  18. }
  19. }
  20. class Solution { //迭代
  21. public int sumOfLeftLeaves(TreeNode root) {
  22. int res = 0;
  23. if (root == null) return res;
  24. Stack<TreeNode> stack = new Stack<>();
  25. stack.push(root);
  26. while (!stack.isEmpty()) {
  27. TreeNode node = stack.pop();
  28. if (node.right != null) {
  29. stack.push(node.right);
  30. }
  31. if (node.left != null) {
  32. stack.push(node.left);
  33. if (node.left.left == null && node.left.right == null) {
  34. res += node.left.val;
  35. }
  36. }
  37. }
  38. return res;
  39. }
  40. }

S10.17-404. 左叶子之和

  1. class Solution { //递归
  2. int res = 0;
  3. public int sumOfLeftLeaves(TreeNode root) {
  4. calculateLeftSum(root);
  5. return res;
  6. }
  7. public void calculateLeftSum(TreeNode root) {
  8. if (root == null) return;
  9. if (root.left != null) {
  10. if (root.left.left == null && root.left.right == null)
  11. res += root.left.val;
  12. calculateLeftSum(root.left);
  13. }
  14. if (root.right != null) {
  15. calculateLeftSum(root.right);
  16. }
  17. return;
  18. }
  19. }
  20. class Solution { //迭代
  21. public int sumOfLeftLeaves(TreeNode root) {
  22. int res = 0;
  23. if (root == null) return res;
  24. Stack<TreeNode> stack = new Stack<>();
  25. stack.push(root);
  26. while (!stack.isEmpty()) {
  27. TreeNode node = stack.pop();
  28. if (node.right != null) {
  29. stack.push(node.right);
  30. }
  31. if (node.left != null) {
  32. stack.push(node.left);
  33. if (node.left.left == null && node.left.right == null) {
  34. res += node.left.val;
  35. }
  36. }
  37. }
  38. return res;
  39. }
  40. }

M10.17-513. 找树左下角的值

  1. class Solution { //层序遍历
  2. public int findBottomLeftValue(TreeNode root) {
  3. Queue<TreeNode> que = new LinkedList<>();
  4. que.offer(root);
  5. int res = root.val;
  6. while (!que.isEmpty()) {
  7. int len = que.size();
  8. for (int i = 0; i < len; i++) {
  9. TreeNode node = que.poll();
  10. if (i == 0) res = node.val;
  11. if (node.left != null) que.offer(node.left);
  12. if (node.right != null) que.offer(node.right);
  13. }
  14. }
  15. return res;
  16. }
  17. }
  18. class Solution { //递归+回溯
  19. int minDepth = -1;
  20. int res;
  21. public int findBottomLeftValue(TreeNode root) {
  22. // res = root.val;
  23. preOrder(root, 0);
  24. return res;
  25. }
  26. public void preOrder(TreeNode root, int leftDepth) {
  27. if (root == null) return;
  28. if (root.left == null && root.right == null) {
  29. if (leftDepth > minDepth) {
  30. minDepth = leftDepth;
  31. res = root.val;
  32. }
  33. }
  34. if (root.left != null) {
  35. leftDepth++;
  36. preOrder(root.left, leftDepth);
  37. leftDepth--; //回溯
  38. // preOrder(root.left, leftDepth + 1); //简洁写法
  39. }
  40. if (root.right != null) {
  41. leftDepth++;
  42. preOrder(root.right, leftDepth);
  43. leftDepth--;
  44. // preOrder(root.right, leftDepth + 1); //简洁写法
  45. }
  46. }
  47. }

S10.18-112. 路径总和

  1. class Solution { //递归
  2. boolean res;
  3. public boolean hasPathSum(TreeNode root, int targetSum) {
  4. if (root == null) return false;
  5. findSum(root, targetSum);
  6. return res;
  7. }
  8. public void findSum(TreeNode root, int targetSum) {
  9. //找到叶子节点了
  10. if (root.left == null && root.right == null) {
  11. if (root.val == targetSum) { //满足条件
  12. res = true;
  13. return;
  14. }
  15. }
  16. if (root.left != null) { //左孩子
  17. findSum(root.left, targetSum - root.val);
  18. }
  19. if (root.right != null) { //右孩子
  20. findSum(root.right, targetSum - root.val);
  21. }
  22. }
  23. }
  24. class Solution { //迭代
  25. public boolean hasPathSum(TreeNode root, int targetSum) {
  26. if (root == null) return false;
  27. Stack<TreeNode> stack = new Stack<>();
  28. Stack<Integer> sum = new Stack<>();
  29. stack.push(root);
  30. sum.push(root.val);
  31. while (!stack.isEmpty()) {
  32. TreeNode node = stack.pop();
  33. int total = sum.pop();
  34. // 如果该节点是叶子节点了,同时该节点的路径数值等于sum,那么就返回true
  35. if (node.left == null && node.right == null && total == targetSum) return true;
  36. // 右节点,压进去一个节点的时候,将该节点的路径数值也记录下来
  37. if (node.right != null) {
  38. stack.push(node.right);
  39. sum.push(total + node.right.val);
  40. }
  41. // 左节点,压进去一个节点的时候,将该节点的路径数值也记录下来
  42. if (node.left != null) {
  43. stack.push(node.left);
  44. sum.push(total + node.left.val);
  45. }
  46. }
  47. return false;
  48. }
  49. }

M10.18-113. 路径总和 II

  1. class Solution {
  2. List<List<Integer>> res = new ArrayList<>();
  3. List<Integer> path = new ArrayList<>();
  4. public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
  5. if (root == null) return res;
  6. findPath(root, targetSum);
  7. return res;
  8. }
  9. public void findPath(TreeNode root, int targetSum) {
  10. path.add(root.val);
  11. //叶子节点
  12. if (root.left == null && root.right == null) {
  13. //找到了和为targetSum的路径
  14. if (root.val == targetSum) {
  15. res.add(new ArrayList<>(path));
  16. }
  17. }
  18. if (root.left != null) {
  19. findPath(root.left, targetSum - root.val);
  20. }
  21. if (root.right != null) {
  22. findPath(root.right, targetSum - root.val);
  23. }
  24. path.remove(path.size() - 1); //回溯
  25. }
  26. }

M10.26-106. 从中序与后序遍历序列构造二叉树

  1. class Solution {
  2. public TreeNode buildTree(int[] inorder, int[] postorder) {
  3. if (inorder == null || postorder == null) return null;
  4. if (inorder.length == 0 || postorder.length == 0) return null;
  5. return build(inorder, 0, inorder.length, postorder, 0, postorder.length);
  6. }
  7. public TreeNode build(int[] inorder, int inLeft, int inRight, int[] postorder, int posLeft, int posRight) {
  8. //没有元素了
  9. if (inRight <= inLeft) return null;
  10. // 根节点了,只有一个元素
  11. if (inRight - inLeft == 1) return new TreeNode(inorder[inLeft]);
  12. // 根节点的值,后序遍历最后一个节点为根节点
  13. int rootVal = postorder[posRight - 1];
  14. TreeNode root = new TreeNode(rootVal);
  15. int index = 0;
  16. // 找根节点在中序遍历中的位置,以划分左右子树
  17. for (int i = inLeft; i < inRight; i++) {
  18. if (inorder[i] == rootVal) index = i;
  19. }
  20. //根据root划分左子树
  21. root.left = build(inorder, inLeft, index, postorder, posLeft, posLeft + (index - inLeft));
  22. //根据root划分右子树
  23. root.right = build(inorder, index + 1, inRight, postorder, posLeft + (index - inLeft), posRight - 1);
  24. return root;
  25. }
  26. }
  27. ======================使用map优化,但是不能存在重复元素==============================
  28. class Solution {
  29. //用map有个前提条件,就是不能有重复元素
  30. Map<Integer, Integer> map; //map的key存中序遍历的val,value存index,方便定位
  31. int[] inOrder; //中序遍历
  32. int[] postOrder; //后序遍历
  33. public TreeNode buildTree(int[] inorder, int[] postorder) {
  34. if (inorder == null || postorder == null) return null;
  35. if (inorder.length == 0 || postorder.length == 0) return null;
  36. map = new HashMap<>();
  37. inOrder = inorder;
  38. postOrder = postorder;
  39. for (int i = 0; i < inorder.length; i++) map.put(inorder[i], i);
  40. return build(0, inorder.length, 0, postorder.length);
  41. }
  42. public TreeNode build(int inLeft, int inRight, int posLeft, int posRight) {
  43. //没有元素了
  44. if (inRight <= inLeft) return null;
  45. // 根节点了,只有一个元素
  46. if (inRight - inLeft == 1) return new TreeNode(inOrder[inLeft]);
  47. // 根节点的值,后序遍历最后一个节点为根节点
  48. int rootVal = postOrder[posRight - 1];
  49. TreeNode root = new TreeNode(rootVal);
  50. // 找根节点在中序遍历中的位置,以划分左右子树
  51. int index = map.get(rootVal);
  52. //根据root划分左子树
  53. root.left = build(inLeft, index, posLeft, posLeft + (index - inLeft));
  54. //根据root划分右子树
  55. root.right = build(index + 1, inRight, posLeft + (index - inLeft), posRight - 1);
  56. return root;
  57. }
  58. }

M10.26-105. 从前序与中序遍历序列构造二叉树

  1. class Solution {
  2. Map<Integer, Integer> map;
  3. int[] pre;
  4. int[] in;
  5. public TreeNode buildTree(int[] preorder, int[] inorder) {
  6. if (preorder == null || inorder == null || preorder.length == 0 || inorder.length == 0) return null;
  7. map = new HashMap<>();
  8. pre = preorder;
  9. in = inorder;
  10. for (int i = 0; i < in.length; i++) map.put(in[i], i);
  11. return build(0, pre.length, 0, in.length);
  12. }
  13. public TreeNode build (int preLeft, int preRight, int inLeft, int inRight) {
  14. //没有元素了
  15. if (preLeft >= preRight) return null;
  16. //只有一个元素
  17. if (preRight - preLeft == 1) return new TreeNode(pre[preLeft]);
  18. //根节点
  19. int rootVal = pre[preLeft];
  20. TreeNode root = new TreeNode(rootVal);
  21. //找根节点在中序遍历中的index
  22. int index = map.get(rootVal);
  23. //划分左子树
  24. root.left = build(preLeft + 1, preLeft + 1 + index - inLeft, inLeft, index);
  25. //划分右子树
  26. root.right = build(preLeft + 1 + index - inLeft, preRight, index + 1, inRight);
  27. return root;
  28. }
  29. }

M10.27-654. 最大二叉树

  1. class Solution {
  2. int[] nodes;
  3. public TreeNode constructMaximumBinaryTree(int[] nums) {
  4. if (nums == null || nums.length == 0) return null;
  5. nodes = nums;
  6. return buildTree(0, nums.length);
  7. }
  8. public TreeNode buildTree(int left, int right) {
  9. if (right <= left) return null;
  10. int index = left;
  11. for (int i = left; i < right; i++) {
  12. if (nodes[index] < nodes[i]) index = i;
  13. }
  14. int rootVal = nodes[index];
  15. TreeNode root = new TreeNode(rootVal);
  16. root.left = buildTree(left, index);
  17. root.right = buildTree(index + 1, right);
  18. return root;
  19. }
  20. }

S10.27-617. 合并二叉树

  1. =====================详细写法,不改变原始树=========================
  2. class Solution {
  3. public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
  4. if (root1 == null && root2 == null) return null;
  5. TreeNode root;
  6. if (root1 == null || root2 == null) {
  7. root = root1 == null ? new TreeNode(root2.val) : new TreeNode(root1.val);
  8. root.left = root1 == null ? mergeTrees(null, root2.left) : mergeTrees(root1.left, null);
  9. root.right = root1 == null ? mergeTrees(null, root2.right) : mergeTrees(root1.right, null);
  10. }
  11. else {
  12. root = new TreeNode(root1.val + root2.val);
  13. root.left = mergeTrees(root1.left, root2.left);
  14. root.right = mergeTrees(root1.right, root2.right);
  15. }
  16. return root;
  17. }
  18. }
  19. ==============递归优化,改变原来的树===============
  20. class Solution {
  21. public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
  22. if(root1 == null || root2 == null) return root1 == null ? root2 : root1;
  23. root1.val += root2.val;
  24. root1.left = mergeTrees(root1.left, root2.left);
  25. root1.right = mergeTrees(root1.right, root2.right);
  26. return root1;
  27. }
  28. }
  29. ====================迭代法============================
  30. //层序遍历合并
  31. class Solution {
  32. public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
  33. if(root1 == null || root2 == null) return root1 == null ? root2 : root1;
  34. Queue<TreeNode> que = new LinkedList<>();
  35. que.offer(root1);
  36. que.offer(root2);
  37. while (!que.isEmpty()) {
  38. TreeNode node1 = que.poll();
  39. TreeNode node2 = que.poll();
  40. // 此时两个节点一定不为空,val相加
  41. node1.val += node2.val;
  42. // 如果两棵树左节点都不为空,加入队列
  43. if (node1.left != null && node2.left != null) {
  44. que.offer(node1.left);
  45. que.offer(node2.left);
  46. }
  47. // 如果两棵树右节点都不为空,加入队列
  48. if (node1.right != null && node2.right != null) {
  49. que.offer(node1.right);
  50. que.offer(node2.right);
  51. }
  52. //当node1的左节点为空,node2的左节点不为空,就幅值过去
  53. if (node1.left == null && node2.left != null) {
  54. node1.left = node2.left;
  55. }
  56. //当node1的右节点为空,node2的右节点不为空,就幅值过去
  57. if (node1.right == null && node2.right != null) {
  58. node1.right = node2.right;
  59. }
  60. }
  61. return root1;
  62. }
  63. }

S10.28-700. 二叉搜索树中的搜索

  1. ================递归=================
  2. class Solution {
  3. public TreeNode searchBST(TreeNode root, int val) {
  4. if (root == null || root.val == val) return root;
  5. if (root.val < val) {
  6. return searchBST(root.right, val);
  7. }
  8. else {
  9. return searchBST(root.left, val);
  10. }
  11. }
  12. }
  13. ============迭代====================
  14. class Solution {
  15. public TreeNode searchBST(TreeNode root, int val) {
  16. while (root != null) {
  17. if (root.val < val) root = root.right;
  18. else if (root.val > val) root = root.left;
  19. else return root;
  20. }
  21. return root;
  22. }
  23. }

M10.29-98. 验证二叉搜索树

思路:中序遍历,判断是否为递增序列

  1. =======================递归========================
  2. class Solution {
  3. TreeNode pre = null; //用来记录前一个节点
  4. public boolean isValidBST(TreeNode root) {
  5. if (root == null) return true;
  6. boolean left = isValidBST(root.left);
  7. //如果前一个节点的值比当前大,表示不是二叉搜索树
  8. if (pre != null && pre.val >= root.val) return false;
  9. pre = root; // 记录前一个节点
  10. boolean right = isValidBST(root.right);
  11. return left && right;
  12. }
  13. }
  14. =====================迭代=====================
  15. class Solution {
  16. TreeNode pre = null; //用来记录前一个节点
  17. public boolean isValidBST(TreeNode root) {
  18. if (root == null) return true;
  19. Stack<TreeNode> stack = new Stack<>();
  20. // stack.push(root);
  21. while (root != null || !stack.isEmpty()) {
  22. while (root != null) {
  23. stack.push(root);
  24. root = root.left;
  25. }
  26. TreeNode node = stack.pop();
  27. if (pre != null && pre.val >= node.val) return false;
  28. pre = node;
  29. root = node.right;
  30. }
  31. return true;
  32. }
  33. }

S10.30-530. 二叉搜索树的最小绝对差

注意是二叉搜索树,二叉搜索树可是有序的。遇到在二叉搜索树上求什么最值啊,差值之类的,就把它想成在一个有序数组上求最值,求差值,这样就简单多了。那么二叉搜索树采用中序遍历,其实就是一个有序数组。

  1. //递归
  2. class Solution {
  3. int min = Integer.MAX_VALUE;
  4. TreeNode pre = null;
  5. public int getMinimumDifference(TreeNode root) {
  6. getMin(root);
  7. return min;
  8. }
  9. public void getMin(TreeNode root) {
  10. if (root == null) return;
  11. getMin(root.left);
  12. if (pre != null) {
  13. int dif = Math.abs(root.val - pre.val);
  14. min = dif < min ? dif : min;
  15. }
  16. pre = root;
  17. getMin(root.right);
  18. }
  19. }
  20. //非递归
  21. class Solution {
  22. public int getMinimumDifference(TreeNode root) {
  23. Stack<TreeNode> st = new Stack<>();
  24. int min = Integer.MAX_VALUE;
  25. TreeNode pre = null;
  26. while (root != null || !st.isEmpty()) {
  27. while (root != null) {
  28. st.push(root);
  29. root = root.left;
  30. }
  31. TreeNode node = st.pop();
  32. if (pre != null) {
  33. int dif = Math.abs(node.val - pre.val);
  34. min = dif < min ? dif : min;
  35. }
  36. pre = node;
  37. if (node.right != null) root = node.right;
  38. }
  39. return min;
  40. }
  41. }

S10.31-501. 二叉搜索树中的众数

  1. //递归
  2. class Solution {
  3. List<Integer> res;
  4. int count, maxCount;
  5. TreeNode pre;
  6. public int[] findMode(TreeNode root) {
  7. res = new ArrayList<>();
  8. count = 0;
  9. maxCount = 0;
  10. pre = null;
  11. find(root);
  12. int[] ans = new int[res.size()];
  13. for (int i = 0; i < res.size(); i++) {
  14. ans[i] = res.get(i);
  15. }
  16. return ans;
  17. }
  18. public void find(TreeNode root) {
  19. if (root == null) return;
  20. find(root.left);
  21. int rootVal = root.val;
  22. //初始化,或pre的值跟root值不同时,root从1开始重新计数
  23. if (pre == null || rootVal != pre.val) {
  24. count = 1;
  25. } else { //相同 count增加
  26. count++;
  27. }
  28. if (count > maxCount) { //count最大
  29. res.clear(); // 清空结果集
  30. res.add(rootVal); // 加入当前众数
  31. maxCount = count; // 更新众数出现的次数
  32. } else if (count == maxCount){ //出现了多个众数
  33. res.add(rootVal);
  34. }
  35. pre = root;
  36. find(root.right);
  37. }
  38. }
  39. //迭代
  40. class Solution {
  41. List<Integer> res;
  42. int count, maxCount;
  43. TreeNode pre;
  44. public int[] findMode(TreeNode root) {
  45. res = new ArrayList<>();
  46. count = 0;
  47. maxCount = 0;
  48. pre = null;
  49. find(root);
  50. int[] ans = new int[res.size()];
  51. for (int i = 0; i < res.size(); i++) {
  52. ans[i] = res.get(i);
  53. }
  54. return ans;
  55. }
  56. public void find(TreeNode root) {
  57. Stack<TreeNode> st = new Stack<>();
  58. while (root != null || !st.isEmpty()) {
  59. while (root != null) {
  60. st.push(root);
  61. root = root.left;
  62. }
  63. TreeNode node = st.pop();
  64. int curVal = node.val;
  65. if (pre == null || pre.val != curVal) {
  66. count = 1;
  67. } else {
  68. count++;
  69. }
  70. if (count > maxCount) {
  71. res.clear();
  72. res.add(curVal);
  73. maxCount = count;
  74. } else if (count == maxCount) {
  75. res.add(curVal);
  76. }
  77. pre = node;
  78. root = node.right;
  79. }
  80. }
  81. }

M11.1-236. 二叉树的最近公共祖先

后序遍历,回溯

  1. class Solution {
  2. public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
  3. if (root == null || root == p || root == q) return root;
  4. TreeNode left = lowestCommonAncestor(root.left, p, q);
  5. TreeNode right = lowestCommonAncestor(root.right, p, q);
  6. //左右子树分别找到了,说明当前节点就是最近公共祖先
  7. if (left != null && right != null) return root;
  8. // left为空,right不为空,说明目标节点通过right返回,反之亦然
  9. if (left == null && right != null) return right;
  10. // if (left != null && right == null) return left;
  11. // left 跟right 都没找到返回null
  12. // return null;
  13. // 上面两种情况可以整合
  14. return left;
  15. }
  16. }

S11.3-235. 二叉搜索树的最近公共祖先

利用二叉搜索树的性质,cur节点的值处于[p,q]区间内,cur就是最近公共祖先了。

  1. class Solution {
  2. public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
  3. if (root == null) return root;
  4. if (root.val >= p.val && root.val <= q.val) return root;
  5. if (root.val <= p.val && root.val >= q.val) return root;
  6. TreeNode left = lowestCommonAncestor(root.left, p, q);
  7. TreeNode right = lowestCommonAncestor(root.right, p, q);
  8. if (left != null) return left;
  9. return right;
  10. }
  11. }

M11.3-701. 二叉搜索树中的插入操作

使用前序遍历,利用BST的性质,如果当前节点的值小于目标值,那么直接遍历当前节点的右子树,反之遍历左子树;当遍历到的节点为空为时,那么就代表此节点就是插入是节点 点位置(递归出口)。

  1. //有返回值的递归
  2. class Solution {
  3. public TreeNode insertIntoBST(TreeNode root, int val) {
  4. //递归出口,遍历到的节点为null的时候,就是要插入的位置
  5. if (root == null) {
  6. TreeNode node = new TreeNode(val);
  7. return node;
  8. }
  9. // 下面两步操作保证了插入的位置一定满足BST
  10. // 利用BST的性质,左子树的值一定比根节点小
  11. if (root.val > val) root.left = insertIntoBST(root.left, val);
  12. // 右子树的值一定比根节点大
  13. if (root.val < val) root.right = insertIntoBST(root.right, val);
  14. return root;
  15. }
  16. }
  17. //无返回值递归
  18. class Solution {
  19. TreeNode parent; //用来记录父节点
  20. public TreeNode insertIntoBST(TreeNode root, int val) {
  21. if (root == null) return new TreeNode(val);
  22. insert(root, val);
  23. return root;
  24. }
  25. public void insert(TreeNode root, int val) {
  26. if (root == null) {
  27. if (parent.val < val) parent.right = new TreeNode(val);
  28. else parent.left = new TreeNode(val);
  29. return;
  30. }
  31. parent = root; //必须在遍历左右子树前更新父节点
  32. if (root.val > val) insert(root.left, val);
  33. if (root.val < val) insert(root.right, val);
  34. }
  35. }
  36. //迭代
  37. class Solution {
  38. TreeNode parent;
  39. public TreeNode insertIntoBST(TreeNode root, int val) {
  40. if (root == null) return new TreeNode(val);
  41. Stack<TreeNode> st = new Stack<>();
  42. st.push(root);
  43. // TreeNode pre;
  44. while (!st.isEmpty()) {
  45. TreeNode node = st.pop();
  46. if (node.val < val) {
  47. if (node.right == null) {
  48. node.right = new TreeNode(val);
  49. break;
  50. }
  51. st.push(node.right);
  52. }
  53. if (node.val > val) {
  54. if (node.left == null) {
  55. node.left = new TreeNode(val);
  56. break;
  57. }
  58. st.push(node.left);
  59. }
  60. }
  61. return root;
  62. }
  63. }

M11.04-450. 删除二叉搜索树中的节点

  1. class Solution {
  2. TreeNode pre;
  3. public TreeNode deleteNode(TreeNode root, int key) {
  4. // 情况1:没找到要删除的节点,遍历到空节点直接返回
  5. if (root == null) return root;
  6. //找到了删除节点
  7. if (root.val == key) {
  8. //情况2:左右孩子都为空,返回空节点
  9. //情况3:右孩子为空,返回左孩子
  10. if (root.right == null) return root.left;
  11. //情况4:左孩子为空,返回右孩子
  12. if (root.left == null) return root.right;
  13. //情况5:左右孩子均不为空,要把子树接到右子树最左边的节点
  14. else {
  15. TreeNode cur = root.right;
  16. while (cur.left != null) {
  17. cur = cur.left;
  18. }
  19. cur.left = root.left; //把删除节点(root)的左子树挂在右子树最左边的节点(cur)的左子树上
  20. root = root.right; //删除root;
  21. return root;
  22. }
  23. }
  24. if (root.val > key) root.left = deleteNode(root.left, key);
  25. if (root.val < key) root.right = deleteNode(root.right, key);
  26. return root;
  27. }
  28. }

M11.05-669. 修剪二叉搜索树

  1. //递归
  2. class Solution {
  3. public TreeNode trimBST(TreeNode root, int low, int high) {
  4. // 空节点,说明满足区间
  5. if (root == null) return root;
  6. // 当前节点的值小于low,那么就要对右子树进行修剪
  7. if (root.val < low) {
  8. TreeNode right = trimBST(root.right, low, high); //修剪右子树
  9. return right; //返回修剪的结果给上一层
  10. //return trimBST(root.right, low, high);
  11. }
  12. // 当前节点的值大于high,那么就要对左子树进行修剪
  13. if (root.val > high) {
  14. TreeNode left = trimBST(root.left, low, high); //修剪左子树
  15. return left; //返回修剪的结果给上一层
  16. //return trimBST(root.left, low, high);
  17. }
  18. // 当前节点的值处于区间内,分别修剪左右子树
  19. root.left = trimBST(root.left, low, high);
  20. root.right = trimBST(root.right, low, high);
  21. return root;
  22. }
  23. }
  24. //迭代
  25. class Solution {
  26. public TreeNode trimBST(TreeNode root, int low, int high) {
  27. if (root == null) return null;
  28. // 处理头结点,让root移动到[low, high] 范围内,注意是左闭右闭
  29. while (root != null && (root.val < low || root.val > high)) {
  30. if (root.val < low) root = root.right;
  31. else root = root.left;
  32. }
  33. //此时root处于[low, high]区间内,处理左孩子小于low的情况
  34. TreeNode cur = root;
  35. while (cur != null) {
  36. while (cur.left != null && cur.left.val < low) {
  37. cur.left = cur.left.right;
  38. }
  39. cur = cur.left;
  40. }
  41. cur = root;
  42. while (cur != null) {
  43. while (cur.right != null && cur.right.val > high) {
  44. cur.right = cur.right.left;
  45. }
  46. cur = cur.right;
  47. }
  48. return root;
  49. }
  50. }

S11.06-108. 将有序数组转换为二叉搜索树

数组构造二叉树,构成平衡树是自然而然的事情,因为大家默认都是从数组中间位置取值作为节点元素,一般不会随机取,所以想构成不平衡的二叉树是自找麻烦

  1. //递归
  2. class Solution {
  3. int[] num;
  4. public TreeNode sortedArrayToBST(int[] nums) {
  5. if (nums == null) return null;
  6. num = nums;
  7. return build(0, nums.length);
  8. }
  9. public TreeNode build(int l, int r) {
  10. if (l >= r) return null;
  11. int index = (l + r) / 2;
  12. TreeNode node = new TreeNode(num[index]);
  13. node.left = build(l, index);
  14. node.right = build(index + 1, r);
  15. return node;
  16. }
  17. }
  18. //迭代
  19. class Solution {
  20. public TreeNode sortedArrayToBST(int[] nums) {
  21. if (nums == null || nums.length == 0) return null;
  22. TreeNode root = new TreeNode(-1); //初始化根节点
  23. Queue<TreeNode> nodeQue = new LinkedList<>(); //放遍历的节点
  24. Queue<Integer> leftQue = new LinkedList<>(); //保存区间左下标
  25. Queue<Integer> rightQue = new LinkedList<>(); //保存区间右下标
  26. nodeQue.offer(root); //根节点入队
  27. //存入初始区间下标,[0, nums.length)
  28. leftQue.offer(0); //0是区间左下标的初始位置
  29. rightQue.offer(nums.length); //nums.length是区间右下标的初始位置
  30. //开始构造树
  31. while (!nodeQue.isEmpty()) {
  32. TreeNode curNode = nodeQue.poll();
  33. //取当前区间的左右下标
  34. int left = leftQue.poll();
  35. int right = rightQue.poll();
  36. int mid = left + ((right - left) >> 1); //确定根节点的下标
  37. curNode.val = nums[mid]; //给根节点赋值
  38. //处理左区间 [left, mid)
  39. if (left < mid) {
  40. curNode.left = new TreeNode(-1);
  41. nodeQue.offer(curNode.left);
  42. leftQue.offer(left);
  43. rightQue.offer(mid);
  44. }
  45. //处理右区间 [mid + 1, right)
  46. if (mid + 1 < right) {
  47. curNode.right = new TreeNode(-1);
  48. nodeQue.offer(curNode.right);
  49. leftQue.offer(mid + 1);
  50. rightQue.offer(right);
  51. }
  52. }
  53. return root;
  54. }
  55. }

M11.07-538. 把二叉搜索树转换为累加树

BST的中序遍历有序,题目要求当前节点的值等与此节点到比他大的节点的累加,所以可以反向中序遍历的顺序就是由大到小,用一个变量记录之前的和即可:

  1. //递归
  2. class Solution {
  3. int total = 0;
  4. public TreeNode convertBST(TreeNode root) {
  5. if (root == null) return null;
  6. convertBST(root.right);
  7. root.val += total;
  8. total = root.val;
  9. convertBST(root.left);
  10. return root;
  11. }
  12. }
  13. //迭代
  14. class Solution {
  15. int total = 0;
  16. public TreeNode convertBST(TreeNode root) {
  17. if (root == null) return null;
  18. Stack<TreeNode> st = new Stack<>();
  19. int total = 0;
  20. TreeNode node = root;
  21. while (node != null || !st.isEmpty()) {
  22. while (node != null) {
  23. st.push(node);
  24. node = node.right;
  25. }
  26. TreeNode cur = st.pop();
  27. cur.val += total;
  28. total = cur.val;
  29. node = cur.left;
  30. }
  31. return root;
  32. }
  33. }

回溯

回溯模板框架伪代码

  1. void backtracking(参数) {
  2. if (终止条件) {
  3. 存放结果;
  4. return;
  5. }
  6. for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
  7. 处理节点;
  8. backtracking(路径,选择列表); // 递归
  9. 回溯,撤销处理结果
  10. }
  11. }

M11.07-77. 组合

  1. class Solution {
  2. List<List<Integer>> res = new ArrayList<>();
  3. LinkedList<Integer> path = new LinkedList<>();
  4. public List<List<Integer>> combine(int n, int k) {
  5. combine1(n, k, 1);
  6. return res;
  7. }
  8. /**
  9. * 每次从集合中选取元素,可选择的范围随着选择的进行而收缩,调整可选择的范围,就是要靠cur
  10. * @param cur 用来记录本层递归的中,集合从哪里开始遍历(集合就是[1,...,n] )。
  11. */
  12. public void combine1(int n, int k, int cur) {
  13. if (path.size() == k) {
  14. res.add(new ArrayList<>(path));
  15. return;
  16. }
  17. // n-(k-path.size())+1 剪枝操作
  18. //path.size() 已经选择的元素, k-path.size() 剩余需要选择的元素
  19. //那么i至多从 n-(k-path.size())+1开始才能保证有足够多的元素
  20. for (int i = cur; i <= n - (k - path.size()) + 1; i++) {
  21. path.add(i);
  22. combine1(n, k, i + 1);
  23. path.removeLast();
  24. }
  25. }
  26. }

M11.08-216. 组合总和 III

  1. class Solution {
  2. List<List<Integer>> res = new ArrayList<>();
  3. LinkedList<Integer> path = new LinkedList<>();
  4. public List<List<Integer>> combinationSum3(int k, int n) {
  5. //k最多为9,判断k个数的和能否达到n,达不到直接返回
  6. if (k > 9 || ((19 - k) * k) >> 1 < n) return res;
  7. getC(k, n, 0, 1);
  8. return res;
  9. }
  10. //sum表示当前的和,cur表示当前到哪个数了
  11. public void getC(int k, int n, int sum, int cur) {
  12. if (path.size() == k && sum == n) {
  13. res.add(new ArrayList<>(path));
  14. return;
  15. }
  16. // 同时满足 i<=9 以及集合中的数没有达到k个才行
  17. // 过滤掉k个数小于n的情况。
  18. for (int i = cur; i <= 9 && path.size() < k; i++) {
  19. //剪枝,如果当前的和加上即将加入到集合的数的和大于n,那么后面的数都不用找了,因为是升序的。
  20. if (sum + i > n) return;
  21. sum += i;
  22. path.add(i);
  23. getC(k, n, sum, i + 1);
  24. sum -= i;
  25. path.removeLast();
  26. }
  27. }
  28. }

M11.09-17. 电话号码的字母组合

  1. class Solution {
  2. List<String> res = new ArrayList<>();
  3. StringBuilder st = new StringBuilder();
  4. String[] map = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
  5. public List<String> letterCombinations(String digits) {
  6. if (digits.length() == 0) return res;
  7. bt(0, digits);
  8. return res;
  9. }
  10. public void bt(int i, String digits) {
  11. if (i == digits.length()) {
  12. res.add(st.toString());
  13. return;
  14. }
  15. String s = map[digits.charAt(i) - '0']; //取第i个数字对应的字母组成的字符串
  16. char[] chars = s.toCharArray();
  17. for (char c : chars) {
  18. st.append(c);
  19. bt(i + 1, digits);
  20. st.deleteCharAt(st.length() - 1);
  21. }
  22. }
  23. }

M11.10-39. 组合总和

  1. class Solution {
  2. List<List<Integer>> res = new ArrayList<>();
  3. List<Integer> path = new ArrayList<>();
  4. public List<List<Integer>> combinationSum(int[] candidates, int target) {
  5. if (candidates == null || candidates.length == 0) return res;
  6. Arrays.sort(candidates);
  7. getSum(candidates, target, 0);
  8. return res;
  9. }
  10. public void getSum(int[] candidates, int target, int index) {
  11. // 找到了数字和为 target 的组合
  12. if (target == 0) {
  13. res.add(new ArrayList(path));
  14. return;
  15. }
  16. for (int i = index; i < candidates.length; i++) {
  17. //如果要加入的数,超过了目标值,直接跳出
  18. if (candidates[i] > target) break;
  19. path.add(candidates[i]);
  20. //选定一个元素后,下一个元素从当前位置开始选(只会出现223 不会出现232这样就避免了重复)
  21. getSum(candidates, target - candidates[i], i);
  22. path.remove(path.size() - 1); //回溯,移除最后一个元素
  23. }
  24. }
  25. }

M11.11-40. 组合总和 II

  1. class Solution {
  2. List<List<Integer>> res = new ArrayList<>();
  3. List<Integer> path = new ArrayList<>();
  4. public List<List<Integer>> combinationSum2(int[] candidates, int target) {
  5. if (candidates == null || candidates.length == 0) return res;
  6. Arrays.sort(candidates);
  7. bk(candidates, target, 0);
  8. return res;
  9. }
  10. public void bk (int[] candidates, int target, int index) {
  11. if (target == 0) {
  12. res.add(new ArrayList(path));
  13. return;
  14. }
  15. for (int i = index; i < candidates.length; i++) {
  16. if (candidates[i] > target) break;
  17. //去重,如果之前已经求过该数字的解,跳过。
  18. //i > index 是保证搜索树在同一枝上,而不是同一层
  19. //如果不加i > index [1,1,2,3] 4 的结果 1 1 2会被剪枝掉
  20. if (i > 0 && candidates[i] == candidates[i - 1] && i > index) continue;
  21. path.add(candidates[i]);
  22. bk(candidates, target - candidates[i], i + 1);
  23. path.remove(path.size() - 1);
  24. }
  25. }
  26. }

M11.12-131. 分割回文串

  1. class Solution {
  2. List<List<String>> res = new ArrayList<>();
  3. List<String> path = new ArrayList<>();
  4. public List<List<String>> partition(String s) {
  5. if (s == null || s.length() == 0) return res;
  6. bk(s, 0);
  7. return res;
  8. }
  9. public void bk(String s, int index) {
  10. if (index >= s.length()) {
  11. res.add(new ArrayList(path));
  12. return;
  13. }
  14. for (int i = index; i < s.length(); i++) {
  15. if (isPalindrome(s, index, i)) {
  16. String str = s.substring(index, i + 1);
  17. path.add(str);
  18. } else continue;
  19. bk(s, i + 1);
  20. path.remove(path.size() - 1);
  21. }
  22. }
  23. public boolean isPalindrome(String str, int l, int r) {
  24. while (l < r) {
  25. if (str.charAt(l) != str.charAt(r)) return false;
  26. l++;
  27. r--;
  28. }
  29. return true;
  30. }
  31. }

M11.13-93. 复原 IP 地址

  1. class Solution {
  2. List<String> res = new ArrayList<>();
  3. List<Integer> path = new ArrayList<>();
  4. public List<String> restoreIpAddresses(String s) {
  5. if (s == null || s.length() < 4) return res;
  6. bk(s, 0);
  7. return res;
  8. }
  9. public void bk (String s, int index) {
  10. //如果分割点位置超过字符串的长度,表示分割完毕,同时需要满足分割了4个数出来,IP的每位最多是个三位数。比如2551分割成了255.1这样就不符合要求
  11. if (index >= s.length() && path.size() == 4) {
  12. StringBuilder ip = new StringBuilder();
  13. for (int j = 0; j < 4; j++) {
  14. if (j == 3) ip.append(path.get(j));
  15. else ip.append(path.get(j)).append(".");
  16. }
  17. res.add(ip.toString());
  18. return;
  19. }
  20. //IP的某一位上最多是个三位数,所以下标最多往后挪三位,同时需要保证下标没有越界
  21. for (int i = index; i < index + 3 && i < s.length(); i++) {
  22. //剪枝操作,IP的一个位置至少需要1位数
  23. //判断当前子串的长度/剩余IP的位置是否大于1,大于一则表示IP剩余位置上至少能分到一位数,反之不能够构成IP直接结束循环。
  24. if (path.size() != 4 && ((s.length() - i) / (4 - path.size()) < 1)) break;
  25. //如果子串第一位是0,那么分割方式只有一种
  26. if (i == index && s.charAt(index) == '0') {
  27. path.add(0);
  28. bk(s, i + 1);
  29. path.remove(path.size() - 1);
  30. break;
  31. }
  32. String num = s.substring(index, i + 1);
  33. //确保分割出来的数小于等于255
  34. if (Integer.parseInt(num) > 255) break;
  35. path.add(Integer.parseInt(num));
  36. bk(s, i + 1);
  37. path.remove(path.size() - 1);
  38. }
  39. }
  40. }

M11.14-78. 子集

  1. class Solution {
  2. List<List<Integer>> res = new ArrayList<>();
  3. List<Integer> path = new ArrayList<>();
  4. public List<List<Integer>> subsets(int[] nums) {
  5. if (nums == null || nums.length < 1) return res;
  6. bk(nums, 0);
  7. return res;
  8. }
  9. public void bk(int[] nums, int index) {
  10. res.add(new ArrayList(path));
  11. //if (index >= nums.length) return; //终止条件可以不加
  12. for (int i = index; i < nums.length; i++) {
  13. path.add(nums[i]);
  14. bk(nums, i + 1);
  15. path.remove(path.size() - 1);
  16. }
  17. }
  18. }

M11.14-90. 子集 II

  1. class Solution {
  2. List<List<Integer>> res = new ArrayList<>();
  3. List<Integer> path = new ArrayList<>();
  4. public List<List<Integer>> subsetsWithDup(int[] nums) {
  5. if (nums == null || nums.length == 0) return res;
  6. Arrays.sort(nums);
  7. bk(nums, 0);
  8. return res;
  9. }
  10. public void bk(int[] nums, int index) {
  11. res.add(new ArrayList(path));
  12. for (int i = index; i < nums.length; i++) {
  13. if (i > index && nums[i] == nums[i - 1]) continue;
  14. path.add(nums[i]);
  15. bk(nums, i + 1);
  16. path.remove(path.size() - 1);
  17. }
  18. }
  19. }

M11.14-491. 递增子序列

  1. class Solution {
  2. List<List<Integer>> res = new ArrayList<>();
  3. List<Integer> path = new ArrayList<>();
  4. // 需要一个used数组,来记录当前数字是否已选择
  5. boolean[] used;
  6. public List<List<Integer>> permute(int[] nums) {
  7. if (nums == null || nums.length == 0) return res;
  8. used = new boolean[nums.length];
  9. bk(nums);
  10. return res;
  11. }
  12. public void bk(int[] nums) {
  13. if (path.size() == nums.length) {
  14. res.add(new ArrayList(path));
  15. return;
  16. }
  17. //每次都从头开始
  18. for (int i = 0; i < nums.length; i++) {
  19. if (used[i]) continue; //如果已经选择过 跳过。
  20. used[i] = true;
  21. path.add(nums[i]);
  22. bk(nums);
  23. path.remove(path.size() - 1);
  24. used[i] = false;
  25. }
  26. }
  27. }

M11.14-46. 全排列

  1. class Solution {
  2. List<List<Integer>> res = new ArrayList<>();
  3. List<Integer> path = new ArrayList<>();
  4. // 需要一个used数组,来记录当前数字是否已选择
  5. boolean[] used;
  6. public List<List<Integer>> permute(int[] nums) {
  7. if (nums == null || nums.length == 0) return res;
  8. used = new boolean[nums.length];
  9. bk(nums);
  10. return res;
  11. }
  12. public void bk(int[] nums) {
  13. if (path.size() == nums.length) {
  14. res.add(new ArrayList(path));
  15. return;
  16. }
  17. //每次都从头开始
  18. for (int i = 0; i < nums.length; i++) {
  19. if (used[i]) continue; //如果已经选择过 跳过。
  20. used[i] = true;
  21. path.add(nums[i]);
  22. bk(nums);
  23. path.remove(path.size() - 1);
  24. used[i] = false;
  25. }
  26. }
  27. }

M11.14-47. 全排列 II

  1. class Solution {
  2. List<List<Integer>> res = new ArrayList<>();
  3. List<Integer> path = new ArrayList<>();
  4. // 需要一个used数组,来记录当前数字是否已选择
  5. boolean[] used;
  6. public List<List<Integer>> permuteUnique(int[] nums) {
  7. if (nums == null || nums.length == 0) return res;
  8. used = new boolean[nums.length];
  9. Arrays.sort(nums); // 因为要去重,所以需要数组有序
  10. bk(nums);
  11. return res;
  12. }
  13. public void bk(int[] nums) {
  14. //说明找到了一组解
  15. if (path.size() == nums.length) {
  16. res.add(new ArrayList(path));
  17. return;
  18. }
  19. //每次都从头开始
  20. for (int i = 0; i < nums.length; i++) {
  21. if (used[i]) continue; //如果已经选择过 跳过。
  22. // used[i - 1] == true,则表示已经使用,可以重复选取
  23. // used[i - 1] == false,则表示没有使用,说明已经回溯过了,需要去重,直接跳过
  24. if (i > 0 && nums[i] == nums[i -1] && !used[i - 1]) continue;
  25. used[i] = true;
  26. path.add(nums[i]);
  27. bk(nums);
  28. path.remove(path.size() - 1);
  29. used[i] = false;
  30. }
  31. }
  32. }

*H11.18-332. 重新安排行程

  1. class Solution {
  2. List<String> res = new ArrayList<>();
  3. Map<String, Map<String, Integer>> map = new HashMap<>(); //用map存储出发到达机场,因为一个机场可以有多个到达机场,而且当前航线可能不仅仅飞一次,所以需要记录次数
  4. public List<String> findItinerary(List<List<String>> tickets) {
  5. if (tickets == null || tickets.size() < 1) return res;
  6. //初始化map
  7. for (List<String> list : tickets) {
  8. Map<String, Integer> temp;
  9. // 如果当前出发机场已经在map中了,我们只需要更新到达机场以及到达机场的次数
  10. if (map.containsKey(list.get(0))) {
  11. temp = map.get(list.get(0));
  12. //到达机场可能是第一次到达,所以需要判断,到达机场在不在到达map中
  13. temp.put(list.get(1), temp.getOrDefault(list.get(1), 0) + 1);
  14. } else { //当前出发机场不在map中,就需要初始化
  15. temp = new TreeMap<>(); //升序Map,这样到达机场在遍历时就是按题目要求顺序
  16. temp.put(list.get(1), 1);
  17. }
  18. map.put(list.get(0), temp); //更新当前出发、到达信息
  19. }
  20. res.add("JFK"); //从JFK出发
  21. bk(tickets.size());
  22. return res;
  23. }
  24. public boolean bk(int ticNum) {
  25. if (res.size() == ticNum + 1) return true;
  26. String last = res.get(res.size() - 1);
  27. if (map.containsKey(last)) { //防止出现null,即当前机场只有到达,没有出发,但是他又是最小的到达机场 比如[["JFK","KUL"],["JFK","NRT"],["NRT","JFK"]] 这里的["JFK","KUL"]
  28. for (Map.Entry<String, Integer> target : map.get(last).entrySet()) {//遍历起飞机场为last的到达机场
  29. int count = target.getValue(); //到达机场是否还有次数
  30. if (count > 0) { //有次数就从当前机场起飞,没有就继续遍历
  31. res.add(target.getKey()); //将当前机场加入路径
  32. target.setValue(count - 1); //较少当前机场到达次数
  33. if (bk(ticNum)) return true; //如果已经找到解了,一定是最优解,因为TreeMap是升序
  34. res.remove(res.size() - 1); //没找到解,回溯
  35. target.setValue(count); //到达次数也要一起回溯
  36. }
  37. }
  38. }
  39. return false;
  40. }
  41. }

*H11.19-51. N 皇后

  1. class Solution {
  2. List<List<String>> res = new ArrayList<>();
  3. char[][] chessBoard;
  4. public List<List<String>> solveNQueens(int n) {
  5. chessBoard = new char[n][n];
  6. for (char[] c :chessBoard) {
  7. Arrays.fill(c, '.');
  8. }
  9. bk(n, 0);
  10. return res;
  11. }
  12. /**
  13. * 回溯的主体
  14. */
  15. public void bk(int n, int row) {
  16. if (row == n) {
  17. res.add(array2List(chessBoard));
  18. return;
  19. }
  20. for (int col = 0; col < n; col++) {
  21. if (isValid(col, row, chessBoard, n)) {
  22. chessBoard[row][col] = 'Q'; //放置皇后
  23. bk(n, row + 1);
  24. chessBoard[row][col] = '.'; //回溯,撤销
  25. }
  26. }
  27. }
  28. /**
  29. * 用于判断当前位置放皇后是否合法
  30. */
  31. public boolean isValid(int col, int row, char[][] chessBoard, int n) {
  32. // 检查当前列的前几行上是否存在皇后
  33. for (int i = 0; i < row; i++) { //这里其实是一个剪枝,检查当前列上有没有皇后,有的话直接退出
  34. if (chessBoard[i][col] == 'Q') return false;
  35. }
  36. //检查左上角斜线上是否有皇后
  37. for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
  38. if (chessBoard[i][j] == 'Q') return false;
  39. }
  40. //检查右上角斜线上是否有皇后
  41. for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
  42. if (chessBoard[i][j] == 'Q') return false;
  43. }
  44. return true;
  45. }
  46. /**
  47. * 将二维字符数组转换为 List
  48. */
  49. public List array2List(char[][] chessBoard) {
  50. List<String> list = new ArrayList<>();
  51. for (char[] c : chessBoard) {
  52. list.add(String.copyValueOf(c));
  53. }
  54. return list;
  55. }
  56. }

*H11.20-37. 解数独

  1. class Solution {
  2. public void solveSudoku(char[][] board) {
  3. bk(board);
  4. }
  5. public boolean bk(char[][] board) {
  6. for (int i = 0; i < board.length; i++) { //遍历行
  7. for (int j = 0; j < board[0].length; j++) { //遍历列
  8. if (board[i][j] != '.') continue;
  9. for (char k = '1'; k <= '9'; k++) {
  10. if (isValid(i, j, k, board)) { //[i, j]这个位置能否放置k
  11. board[i][j] = k; //放置k
  12. if (bk(board)) return true; //如果找到合适的解,立即返回
  13. board[i][j] = '.'; //回溯,撤销k
  14. }
  15. }
  16. return false; //如果1-9这个位置都不能放,直接返回false
  17. // 因为如果一行一列确定下来了,这里尝试了9个数都不行,说明这个棋盘找不到解决数独问题的解!
  18. // 那么会直接返回, 「这也就是为什么没有终止条件也不会永远填不满棋盘而无限递归下去!」
  19. }
  20. }
  21. return true; //遍历完没有返回false,说明找到了解,返回true
  22. }
  23. /**
  24. * 判断棋盘是否合法有如下三个维度:
  25. * 同行是否重复
  26. * 同列是否重复
  27. * 9宫格里是否重复
  28. */
  29. public boolean isValid(int row, int col, char k, char[][] board) {
  30. //检查行
  31. for (int i = 0; i < board.length; i++) {
  32. if (board[row][i] == k) return false;
  33. }
  34. //检查列
  35. for (int i = 0; i < board.length; i++) {
  36. if (board[i][col] == k) return false;
  37. }
  38. // 检查当前3*3内有没有重复
  39. int startRow = (row / 3) * 3;
  40. int startCol = (col / 3) * 3;
  41. for (int i = startRow; i < startRow + 3; i++) {
  42. for (int j = startCol; j < startCol + 3; j++) {
  43. if (board[i][j] == k) return false;
  44. }
  45. }
  46. return true;
  47. }
  48. }

贪心

//S11.21-455. 分发饼干

M11.21-376. 摆动序列

  1. class Solution {
  2. public int wiggleMaxLength(int[] nums) {
  3. if (nums == null) return 0;
  4. if (nums.length <= 1) return 1;
  5. int curDif = 0;
  6. int preDif = 0;
  7. int count = 1;
  8. for (int i = 1; i < nums.length; i++) {
  9. curDif = nums[i] - nums[i - 1]; //计算当前差值
  10. if ((curDif > 0 && preDif <= 0) || (curDif < 0 && preDif >= 0)) {
  11. count++;
  12. preDif = curDif;
  13. }
  14. }
  15. return count;
  16. }
  17. }

S11.22-53. 最大子序和

  1. class Solution {
  2. public int maxSubArray(int[] nums) {
  3. int res = nums[0];
  4. for(int i = 1; i < nums.length; i++){
  5. // 计算前面的子序列最大和跟当前值相加 有没有比 当前单独值大
  6. //如果没有,那么当前值组成的子序列就是最大
  7. nums[i] = Math.max(nums[i], nums[i] + nums[i - 1]);
  8. // 更新最大和
  9. res = nums[i] > res ? nums[i] : res;
  10. }
  11. return res;
  12. }
  13. }

M11.22-122. 买卖股票的最佳时机 II

  1. // 贪心算法
  2. class Solution {
  3. public int maxProfit(int[] prices) {
  4. int profit = 0;
  5. for(int i = 0; i < prices.length - 1; i++){
  6. int temp = prices[i + 1] - prices[i];
  7. if(temp > 0) profit += temp;
  8. }
  9. return profit;
  10. }
  11. }
  12. // 动态规划
  13. class Solution {
  14. public int maxProfit(int[] prices) {
  15. if (prices.length <= 1) return 0;
  16. // [天数][是否持股]
  17. int[][] dp = new int[prices.length][2];
  18. dp[0][0] = 0;
  19. dp[0][1] = -prices[0];
  20. for (int i = 1; i < prices.length; i++) {
  21. //下面的dp更新公式是基于不能同时参与多笔交易,也就是说今天如果持股,就不能再买进股票
  22. //第i天不持股,要么今天没有交易,延续昨天不持股状态dp[i-1][0],要么今天将股票卖出dp[i-1][1] + prices[i]
  23. dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
  24. //第i天持股,要么今天买入dp[i-1][0] - prices[i],要么今天没有交易dp[i-1][1],维持昨天的状态
  25. dp[i][1] = Math.max(dp[i - 1][0] - prices[i], dp[i - 1][1]);
  26. }
  27. return dp[prices.length - 1][0];
  28. }
  29. }

M11.23-55. 跳跃游戏

  1. class Solution {
  2. public boolean canJump(int[] nums) {
  3. int count = 0; // 用来记录最远可以跳到哪里
  4. // 这里判断条件是 i <= count,在当前覆盖范围下,不管哪里都可以跳到
  5. for (int i = 0; i <= count; i++) {
  6. //每跳到一个位置,更新最大访问范围
  7. count = Math.max(i + nums[i], count);
  8. //最大访问范围达到(大于等于)数组最大下标,就表示可以跳到,直接返回
  9. if (count >= nums.length - 1) return true;
  10. }
  11. // for循环执行完,则表示不能跳到最后一个位置
  12. return false;
  13. }
  14. }

*M11.24-45. 跳跃游戏 II

  1. class Solution {
  2. public int jump(int[] nums) {
  3. if (nums.length == 1) return 0;
  4. int count = 0; //记录最小跳跃数
  5. int max = 0; //记录下一步覆盖的最远距离下标
  6. int end = 0; //记录当前覆盖范围内的最远位置
  7. // 这里i < nums.length - 1 很关键 到终点前一个位置,就不用遍历了
  8. // 在遍历数组时,我们不访问最后一个元素,这是因为在访问最后一个元素之前,我们的边界一定大于等于最后一个位置,否则就无法跳到最后一个位置了。
  9. for (int i = 0; i < nums.length - 1; i++) {
  10. max = Math.max(i + nums[i], max); //更新下一步覆盖的最远距离下标
  11. if (i == end) { // 已经跳到当前覆盖的最远位置了,相当于把当前能跳到的位置都遍历了一遍,要跳下一步了
  12. end = max; // 跳了一步后,最多可以到哪里,更新
  13. count ++; // 跳跃步数+1
  14. }
  15. }
  16. return count;
  17. }
  18. }

S11.25-1005. K 次取反后最大化的数组和

  1. class Solution {
  2. public int largestSumAfterKNegations(int[] nums, int k) {
  3. int min = Integer.MAX_VALUE;
  4. int count = 0;
  5. Arrays.sort(nums); //排序
  6. for (int i = 0; i < nums.length; i++) {
  7. // 记录数组中绝对值最小的那个数
  8. min = Math.abs(nums[i]) < min ? Math.abs(nums[i]) : min;
  9. // 碰到负数了,同时可以取反,那么就取反
  10. // 因为数组排序了,所以一定是绝对值最大的负数先取反
  11. if ((k > 0) && (nums[i] < 0)) {
  12. count -= nums[i];
  13. k--;
  14. }
  15. else count += nums[i];
  16. }
  17. if ((k % 2) == 1 && min != 0) { //k如果没用完,剩余奇数次,那么就把绝对值最小的那个数减掉两次。因为加的时候是加的正数,而实际上应该加负数。
  18. count = count - 2 * min;
  19. }
  20. return count;
  21. }
  22. }

*M11.26-134. 加油站

134代码随想录题解

  1. //暴力解
  2. class Solution {
  3. public int canCompleteCircuit(int[] gas, int[] cost) {
  4. for (int i = 0; i < gas.length; i++) {
  5. int rest = gas[i] - cost[i];
  6. int index = (i + 1) % gas.length;
  7. while (rest > 0 && index != i) {
  8. rest = rest + gas[index] - cost[index];
  9. index = (index + 1) % gas.length;
  10. }
  11. if (rest >= 0 && index == i) return i;
  12. }
  13. return -1;
  14. }
  15. }
  16. // 解法1
  17. class Solution {
  18. public int canCompleteCircuit(int[] gas, int[] cost) {
  19. int curSum = 0;
  20. int min = Integer.MAX_VALUE; //计算从起点出发,油箱里的油量最小值
  21. for (int i = 0; i < gas.length; i++) {
  22. int rest = gas[i] - cost[i];
  23. curSum += rest;
  24. if (curSum < min) min = curSum;
  25. }
  26. if (curSum < 0) return -1; // 情况1,加油站的总油量比消耗的少,无法跑完
  27. if (min >= 0) return 0; //qk2,从第0天开始 每天的油都够,那么0就是起点
  28. //情况3,从0开始出现了油不够的情况
  29. //从后向遍历,看哪个节点能这个负数填平,能把这个负数填平的节点就是出发节点。
  30. for (int i = gas.length - 1; i >= 0; i--) {
  31. int rest = gas[i] - cost[i];
  32. min += rest;
  33. if (min >= 0) {
  34. return i;
  35. }
  36. }
  37. return -1;
  38. }
  39. }
  40. // 解法2
  41. class Solution {
  42. public int canCompleteCircuit(int[] gas, int[] cost) {
  43. int index = 0;
  44. int min = Integer.MAX_VALUE;
  45. int total = 0;
  46. for (int i = 0; i < gas.length; i++) {
  47. int cur = gas[i] - cost[i];
  48. total += cur;
  49. if (total < min) { // 找到遍历过程中最小的油耗差
  50. min = total;
  51. index = i;
  52. }
  53. }
  54. // 如果total < 0 那么怎么走都不行,反之从最小的油耗差后一个位置开始
  55. return total < 0 ? -1 : (index + 1) % gas.length;
  56. }
  57. }
  58. //解法3-贪心
  59. class Solution {
  60. public int canCompleteCircuit(int[] gas, int[] cost) {
  61. int index = 0;
  62. int curSum = 0; //用来记录从i开始,往后走的过程中的油耗差
  63. int total = 0; //用来记录整个过程的油耗差,判断能否走完
  64. for (int i = 0; i < gas.length; i++) {
  65. curSum += gas[i] - cost[i];
  66. total += gas[i] - cost[i];
  67. if (curSum < 0) { //当前路线油耗差一旦小于0,则表示当前路段上的任一节点出发都不行,只能从下一个节点出发
  68. index = (i + 1) % gas.length; //更新出发节点,i+1
  69. curSum = 0; //当前路线油耗差清零
  70. }
  71. }
  72. return total < 0 ? -1 : index;
  73. }
  74. }

*H11.27-135. 分发糖果

  1. class Solution {
  2. public int candy(int[] ratings) {
  3. int res = 0;
  4. int[] candy = new int[ratings.length];
  5. candy[0] = 1;
  6. // 从左往右判断,只要当前的比左边的大,当前的就在左边的基础上+1,否则就是1
  7. for (int i = 1; i < ratings.length; i++) {
  8. if (ratings[i] > ratings[i - 1]) candy[i] = candy[i - 1] + 1;
  9. else candy[i] = 1;
  10. }
  11. // 从右往左判断
  12. for (int i = ratings.length - 2; i >= 0; i--) {
  13. // 当前的比右边的大
  14. if (ratings[i] > ratings[i + 1]) {
  15. // 为了保证比两边都大
  16. candy[i] = Math.max(candy[i], candy[i + 1] + 1);
  17. }
  18. }
  19. for (int num : candy) res += num;
  20. return res;
  21. }
  22. }

S11.29-860. 柠檬水找零

  1. class Solution {
  2. public boolean lemonadeChange(int[] bills) {
  3. int cash_5 = 0; //记录5块的张数
  4. int cash_10 = 0; //记录10块的张数
  5. for (int i = 0; i < bills.length; i++) {
  6. if (bills[i] == 5) cash_5++;
  7. else if (bills[i] == 10) {
  8. cash_5--;
  9. cash_10++;
  10. }
  11. else if (bills[i] == 20) {
  12. if (cash_10 > 0) {
  13. cash_10--;
  14. cash_5--;
  15. }
  16. else cash_5 -= 3;
  17. }
  18. if (cash_5 < 0 || cash_10 < 0) return false;
  19. }
  20. return true;
  21. }
  22. }

*M11.29-406. 根据身高重建队列

  1. class Solution {
  2. public int[][] reconstructQueue(int[][] people) {
  3. // 身高从大到小排(身高相同k小的站前面)
  4. Arrays.sort(people, (o1, o2) -> {
  5. //如果身高h相同,k小的放前面
  6. if (o1[0] == o2[0]) return o1[1] - o2[1];
  7. //身高从大到小排序
  8. return o2[0] - o1[0];
  9. });
  10. // Arrays.sort(people, new Comparator<int[]>() {
  11. // @Override
  12. // public int compare(int[] o1, int[] o2) {
  13. // if (o1[0] == o2[0]) return o1[1] - o2[1];
  14. // return o2[0] - o1[0];
  15. // }
  16. // });
  17. LinkedList<int[]> que = new LinkedList<>();
  18. for (int[] p : people) {
  19. que.add(p[1],p); // 直接根据k的位置插入
  20. }
  21. return que.toArray(new int[people.length][]);
  22. }
  23. }

*M11.29-452. 用最少数量的箭引爆气球

  1. // 左边界排序
  2. class Solution {
  3. public int findMinArrowShots(int[][] points) {
  4. if (points.length == 0) return 0;
  5. Arrays.sort(points, (o1, o2) -> Integer.compare(o1[0], o2[0]));
  6. int count = 1;
  7. for (int i = 1; i < points.length; i++) {
  8. // 如果两个气球不重合,那么就需要多一根箭
  9. if (points[i][0] > points[i -1][1]) count++;
  10. // 如果两个气球重合,一根箭就可以引爆。
  11. // 同时需要更新重叠气球的最小右边界。
  12. else points[i][1] = Math.min(points[i - 1][1], points[i][1]);
  13. }
  14. return count;
  15. }
  16. }
  17. // 右边界排序
  18. class Solution {
  19. public int findMinArrowShots(int[][] points) {
  20. if (points.length == 0) return 0;
  21. // 这里是按右边界排序
  22. Arrays.sort(points, (o1, o2) -> o1[1] < o2[1] ? -1 : 1);
  23. int count = 1;
  24. int pre = points[0][1];
  25. for (int i = 1; i < points.length; i++) {
  26. // 如果两个气球不重合,那么就需要多一根箭
  27. if (points[i][0] > pre) {
  28. count++;
  29. //更新最左右边界
  30. pre = points[i][1];
  31. }
  32. }
  33. return count;
  34. }
  35. }

*M11.30-435. 无重叠区间

  1. //按右边界排序,不用考虑左边界
  2. class Solution {
  3. public int eraseOverlapIntervals(int[][] intervals) {
  4. if (intervals == null || intervals.length == 0) return 0;
  5. int res = 0;
  6. //排序按照右边界排,小的放前面。如果右边界相同,长度短的放前面
  7. Arrays.sort(intervals, (o1, o2) -> {
  8. // if (o1[1] == o2[1]) {
  9. // return Integer.compare(o1[0],o2[0]);
  10. // }
  11. return Integer.compare(o1[1],o2[1]);
  12. });
  13. //记录最小右边界
  14. int flag = intervals[0][1];
  15. for (int i = 1; i < intervals.length; i++) {
  16. // 如果左边界小于了最小右边界,则表示重合
  17. if (intervals[i][0] < flag) res++;
  18. // 更新最小右边界
  19. else flag = intervals[i][1];
  20. }
  21. return res;
  22. }
  23. }

*M12.1-763. 划分字母区间

image.png

  1. class Solution {
  2. List<Integer> res = new ArrayList<>();
  3. public List<Integer> partitionLabels(String s) {
  4. char[] str = s.toCharArray();
  5. int[] edge = new int[26]; //记录每个字符的最远边界
  6. for (int i = 0; i < str.length; i++) {
  7. edge[str[i] - 'a'] = i;
  8. }
  9. int idx = 0; //记录目前最远边界
  10. int last = -1; //用来记录上一个边界的位置
  11. for (int i = 0; i < str.length; i++) {
  12. //idx记录当前字符的最远边界,如果当前字符最远边界比idx大,则更新最远边界
  13. idx = Math.max(idx, edge[str[i] - 'a']);
  14. //下标跟之前出现过的字符最大下标相等,则表示需要分割
  15. if (i == idx) {
  16. res.add(i - last);
  17. last = i;
  18. }
  19. }
  20. return res;
  21. }
  22. }

M12.02-56. 合并区间

  1. class Solution {
  2. public int[][] merge(int[][] intervals) {
  3. // 如果只有一个区间,直接返回
  4. if (intervals.length == 1) return intervals;
  5. // 按左边界排序,如果左边界相等,右边界小的放前面
  6. Arrays.sort(intervals, (o1, o2) -> {
  7. if (o1[0] == o2[0]) return Integer.compare(o1[1], o2[1]);
  8. return Integer.compare(o1[0], o2[0]);
  9. });
  10. List<int[]> res = new ArrayList<>();
  11. int head = intervals[0][0]; //记录当前区间的头
  12. int tail = intervals[0][1]; //记录当前区间的尾
  13. for (int i = 1; i < intervals.length; i++) {
  14. // 出现重合就合并
  15. if (intervals[i][0] <= tail) tail = Math.max(tail, intervals[i][1]);
  16. else { //否则将当前区间加入到结果中,更新当前区间头尾
  17. res.add(new int[]{head, tail});
  18. head = intervals[i][0];
  19. tail = intervals[i][1];
  20. }
  21. // 上面的逻辑最后一个区间无法加入结果,需要额外处理
  22. if (i == intervals.length - 1) res.add(new int[]{head, tail});
  23. }
  24. return res.toArray(new int[res.size()][]);
  25. }
  26. }

M12.02-738. 单调递增的数字

  1. // 从前往后遍历
  2. class Solution {
  3. public int monotoneIncreasingDigits(int n) {
  4. if (n / 10 == 0) return n;
  5. char[] c = Integer.toString(n).toCharArray();
  6. int res = 0;
  7. if (c[0] > c[1]) { // 如果最高位大于第二位,最高位-1,后面全是9
  8. res += c[0] - '0' - 1;
  9. for (int i = 1; i < c.length; i++) {
  10. res = res * 10 + 9;
  11. }
  12. }
  13. else { //否则,找出不满足单调递增的位置
  14. int idx = 0; //记录最大单增长度的下标
  15. for (int i = 1; i < c.length; i++) {
  16. if (c[i - 1] <= c[i]) idx = i;
  17. else break;
  18. }
  19. if (idx != c.length - 1) {
  20. //单增序列最后一个数字开始,后面全是9,相应的该位上数要-1,不用担心是0
  21. c[idx] = (char) (c[idx] - 1);
  22. }
  23. for (int i = idx - 1; i >= 0; i--) { //遍历修改后的单增序列
  24. // 如果最后一位(最大位)-1了还满足单增,直接跳出
  25. if (c[i] <= c[i + 1]) break;
  26. c[i + 1] = '9'; // 否则该位变为9
  27. c[i] = (char) (c[i] - 1); //前一位-1
  28. }
  29. for (int i = 0; i < c.length; i++) { //计算最终结果
  30. if (i <= idx) res = res * 10 + c[i] - '0';
  31. else res = res * 10 + 9;
  32. }
  33. }
  34. return res;
  35. }
  36. }
  37. //优化版 从后往前遍历
  38. class Solution {
  39. public int monotoneIncreasingDigits(int n) {
  40. if (n / 10 == 0) return n;
  41. char[] c = Integer.toString(n).toCharArray();
  42. int idx = c.length;
  43. // 从后向前遍历,找不满足单增的最左断点
  44. for (int i = idx - 1; i > 0; i--) {
  45. // 如果当前位置比前一个小,那么前一个数-1
  46. if (c[i] < c[i - 1]) {
  47. c[i - 1] = (char) (c[i - 1] - 1);
  48. idx = i; //更新断点从idx到最后全是9
  49. }
  50. }
  51. int res = 0;
  52. for (int i = 0; i < c.length; i++) {
  53. if (i < idx) res = res * 10 + c[i] - '0';
  54. else res = res * 10 + 9;
  55. }
  56. return res;
  57. }
  58. }

*M12.03-714. 买卖股票的最佳时机含手续费

  1. // 贪心
  2. class Solution {
  3. public int maxProfit(int[] prices, int fee) {
  4. int res = 0;
  5. int low = prices[0];
  6. for (int i = 1; i < prices.length; i++) {
  7. //
  8. if (prices[i] < low) low = prices[i];
  9. // 保持原有状态,不进行操作
  10. if (prices[i] > low && prices[i] <= low + fee) continue;
  11. if (prices[i] > low + fee) {
  12. res += prices[i] - low - fee;
  13. low = prices[i] - fee;
  14. }
  15. }
  16. return res;
  17. }
  18. }
  19. // DP
  20. class Solution {
  21. public int maxProfit(int[] prices, int fee) {
  22. //dp[i][j] 表示第i+1天的持股、不持股分别有多少现金
  23. int[][] dp = new int[prices.length][2];
  24. dp[0][0] = 0;
  25. dp[0][1] = -prices[0];
  26. for (int i = 1; i < prices.length; i++) {
  27. dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i] - fee);
  28. dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
  29. }
  30. return dp[prices.length - 1][0];
  31. }
  32. }

*H12.05-968. 监控二叉树

https://leetcode-cn.com/problems/binary-tree-cameras/

  1. class Solution {
  2. int res = 0;
  3. public int minCameraCover(TreeNode root) {
  4. if (trval(root) == 0) res++;
  5. return res;
  6. }
  7. private int trval(TreeNode root) {
  8. // 空节点,该节点有覆盖
  9. if (root == null) return 2;
  10. int left = trval(root.left);
  11. int right = trval(root.right);
  12. // 情况1
  13. // 左右节点都有覆盖
  14. if (left == 2 && right == 2) return 0;
  15. // 情况2 只要左右有一个没有覆盖为0,那么这个节点必须得有摄像头
  16. // left == 0 && right == 0 左右节点无覆盖
  17. // left == 1 && right == 0 左节点有摄像头,右节点无覆盖
  18. // left == 0 && right == 1 左节点有无覆盖,右节点摄像头
  19. // left == 0 && right == 2 左节点无覆盖,右节点覆盖
  20. // left == 2 && right == 0 左节点覆盖,右节点无覆盖
  21. if (left == 0 || right == 0) {
  22. res++;
  23. return 1;
  24. }
  25. // 情况3 左右节点有一个有摄像头,那么该节点就被覆盖
  26. // left == 1 && right == 2 左节点有摄像头,右节点有覆盖
  27. // left == 2 && right == 1 左节点有覆盖,右节点有摄像头
  28. // left == 1 && right == 1 左右节点都有摄像头
  29. // 其他情况前段代码均已覆盖
  30. if (left == 1 || right == 1) return 2;
  31. return -1;
  32. }
  33. }

动态规划

动态规划五部曲:

  1. 确定dp数组(dp table)以及下标的含义
  2. 确定递推公式
  3. dp数组如何初始化
  4. 确定遍历顺序
  5. 举例推导dp数组

    S12.06-746. 使用最小花费爬楼梯

    1. class Solution {
    2. public int minCostClimbingStairs(int[] cost) {
    3. if (cost == null || cost.length == 0) return 0;
    4. //dp表示爬到第i阶楼梯需要的最小体力,dp长度比cost多1,因为要爬完cost所有的楼梯
    5. int[] dp = new int[cost.length + 1];
    6. //爬到第0和第1阶楼梯都不耗费体力
    7. dp[0] = 0;
    8. dp[1] = 0;
    9. for (int i = 2; i <= cost.length; i++) {
    10. // 爬到i有两条路,从前一阶爬或者从前两阶爬,所以最小消耗就是
    11. // 爬到前面位置耗费的体力+从前面位置爬过来要耗费的体力
    12. dp[i] = Math.min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
    13. }
    14. return dp[cost.length];
    15. }
    16. }

    M12.7-62. 不同路径

    1. //计算组合数
    2. class Solution {
    3. public int uniquePaths(int m, int n) {
    4. int t = m + n - 2;
    5. int count = m - 1;
    6. int denominator = m - 1; //分子
    7. long res = 1L; //分母
    8. while (count-- > 0) {
    9. res *= (t--);
    10. while (denominator != 0 && res % denominator == 0) {
    11. res /= denominator;
    12. denominator--;
    13. }
    14. }
    15. return (int)res;
    16. }
    17. }
    18. //动态规划
    19. class Solution {
    20. public int uniquePaths(int m, int n) {
    21. int[][] dp = new int[m][n];
    22. for (int i = 0; i < n; i++) {
    23. dp[0][i] = 1;
    24. }
    25. for (int i = 0; i < m; i++) {
    26. dp[i][0] = 1;
    27. }
    28. for (int i = 1; i < n; i++) {
    29. for (int j = 1; j < m; j++) {
    30. dp[j][i] = dp[j][i - 1] + dp[j - 1][i];
    31. }
    32. }
    33. return dp[m - 1][n - 1];
    34. }
    35. }
    36. //图 深度优先,超时
    37. class Solution {
    38. public int uniquePaths(int m, int n) {
    39. return dfs(1, 1, m , n);
    40. }
    41. public int dfs (int i, int j, int m, int n) {
    42. if (i > m || j > n) return 0; //越界了
    43. if (i == m && j == n) return 1; //找到了一种方法
    44. return dfs(i + 1, j, m , n) + dfs(i, j + 1, m ,n);
    45. }
    46. }

    M12.7-63. 不同路径 II

    1. // 动态规划
    2. class Solution {
    3. public int uniquePathsWithObstacles(int[][] obstacleGrid) {
    4. if (obstacleGrid == null || obstacleGrid.length < 1) return 0;
    5. int m = obstacleGrid.length;
    6. int n = obstacleGrid[0].length;
    7. int[][] dp = new int[m][n];
    8. // 初始化第一列
    9. for (int i = 0; i < m; i++) {
    10. // 如果当前位置有障碍物,那么从这个位置到最下面的位置都到不了
    11. if (obstacleGrid[i][0] == 1) break;
    12. dp[i][0] = 1;
    13. }
    14. //初始化第一行
    15. for (int i = 0; i < n; i++) {
    16. // 如果当前位置有障碍物,那么从这个位置到最后面的位置都到不了
    17. if (obstacleGrid[0][i] == 1) break;
    18. dp[0][i] = 1;
    19. }
    20. for (int i = 1; i < m; i++) {
    21. for (int j = 1; j < n; j++) {
    22. // 如果当前位置没有障碍物,就更新路径,有障碍物直接跳过
    23. if (obstacleGrid[i][j] == 0)
    24. dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
    25. }
    26. }
    27. return dp[m - 1][n - 1];
    28. }
    29. }

    M12.8-343. 整数拆分

    ```java // 数学 class Solution { public int integerBreak(int n) {

    1. if (n <= 2) return 1;
    2. if (n == 3) return 2;
    3. // 3越多越好
    4. int a = n / 3;
    5. int b = n % 3;
    6. if (b == 1) {
    7. a--;
    8. return (int)(Math.pow(3, a) * 4);
    9. }
    10. if (b == 2) return (int)(Math.pow(3, a) * 2);
    11. return (int) Math.pow(3, a);

    } } // 数学优化版 class Solution { public int integerBreak(int n) {

    1. if (n <= 2) return 1;
    2. if (n == 3) return 2;
    3. if (n == 4) return 4;
    4. // 3越多越好
    5. int res = 1;
    6. // 如果最后拆的只剩4了就不用拆了,因为2+2=2*2,这样就杜绝了出现1,然后用一个3和一个1拆成两个2的情况
    7. while (n > 4) {
    8. res *= 3;
    9. n -= 3;
    10. }
    11. return res *= n;

    } }

//动态规划 class Solution { public int integerBreak(int n) { //dp[n]表示数字n拆分得到的最大乘积,n应该从2开始 int[] dp = new int[n + 1]; dp[2] = 1; //dp[2]就一种拆法 // i从3开始 for (int i = 3; i <= n; i++) { for (int j = 1; j < i - 1; j++) { //j最大为i - 2 //j (i - j) 是单纯的把整数拆分为两个数相乘,而j dp[i - j]是拆分成两个以及两个以上的个数相乘。 dp[i] = Math.max(dp[i], Math.max(j (i - j), j dp[i - j])); } } return dp[n]; } }

  1. <a name="sQk1F"></a>
  2. #### M12.09-[96. 不同的二叉搜索树](https://leetcode-cn.com/problems/unique-binary-search-trees/)
  3. 这题要这么理解,n个数(1-n)组成的二叉搜索树的种树,需要考虑从1到n分别作为根节点的情况。<br />当j作为根节点时,左子树的节点有j-1个,右子树节点有n-j个。(比如1、2、3,2作为时左子树有1个节点,右子树也只有一个节点)<br />那么dp数组的含义为:dp[i]表示i个节点构成的二叉搜索树的个数。那么dp[3] = dp[0]*dp[2] (根节点为1) + dp[1]*dp[1] (根节点为2) + dp[2]*dp[0] (节点为3)
  4. ```java
  5. class Solution {
  6. public int numTrees(int n) {
  7. //初始化 dp 数组
  8. int[] dp = new int[n + 1];
  9. //初始化0个节点和1个节点的情况
  10. dp[0] = 1;
  11. dp[1] = 1;
  12. for (int i = 2; i <= n; i++) {
  13. for (int j = 1; j <= i; j++) {
  14. //对于第i个节点,需要考虑1作为根节点直到i作为根节点的情况,所以需要累加
  15. //一共i个节点,对于根节点j时,左子树的节点个数为j-1,右子树的节点个数为i-j
  16. dp[i] += dp[j - 1] * dp[i - j];
  17. }
  18. }
  19. return dp[n];
  20. }
  21. }

*M12.12-416. 分割等和子集

把题目转变为0-1背包问题,分成两个集合也就是说背包容量是所有数的和的一半,每个物品的重量和价值都是value[i]。那么题目就转换为,背包中能装的最大价值是否等于所有物品价值总和的一半

  1. //优化背包
  2. class Solution {
  3. public boolean canPartition(int[] nums) {
  4. if (nums == null || nums.length < 2) return false;
  5. int n = nums.length;
  6. int sum = 0;
  7. for (int num : nums) sum += num;
  8. //如果集合总和为奇数,那么就分不了
  9. if (sum % 2 != 0) return false;
  10. sum /= 2;
  11. int[] dp = new int[sum + 1];
  12. for (int i = 0; i < n; i++) {
  13. for (int j = sum; j >= nums[i]; j--) {
  14. //物品 i 的重量是 nums[i],其价值也是 nums[i]
  15. dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);
  16. }
  17. }
  18. return sum == dp[sum];
  19. }
  20. }
  21. //未优化背包
  22. class Solution {
  23. public boolean canPartition(int[] nums) {
  24. if (nums == null || nums.length < 2) return false;
  25. int n = nums.length;
  26. int sum = 0;
  27. for (int num : nums) sum += num;
  28. //如果集合总和为奇数,那么就分不了
  29. if (sum % 2 != 0) return false;
  30. sum /= 2;
  31. int[][] dp = new int[n][sum + 1];
  32. for (int i = 0; i <= sum; i++) {
  33. if (i >= nums[0]) dp[0][i] = nums[0];
  34. }
  35. for (int i = 1; i < n; i++) {
  36. for (int j = 1; j <= sum; j++) {
  37. if (j < nums[i]) dp[i][j] = dp[i - 1][j];
  38. else {
  39. //物品 i 的重量是 nums[i],其价值也是 nums[i]
  40. dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - nums[i]] + nums[i]);
  41. }
  42. }
  43. }
  44. return sum == dp[n - 1][sum];
  45. }
  46. }

*M12.13-1049. 最后一块石头的重量 II

该问题可以转换为0-1背包问题,把石头分成两份,尽量的使两份石头重量相同。这里每块石头的重量stones[i]等于其价值value[i]。
动态规划五部曲:

  • dp[j]表示容量为j的背包,最多可以背dp[j]这么重的石头;
  • 递推公式:dp[j]=max(dp[j], dp[j - stones[i]] + stones[i])
  • 怎么初始化:dp[j]表示容量,那么最大容量是所有石头重量的一半向下取整;初始化为0
  • 确定遍历顺序:物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒叙遍历!
  • 举例推导dp
    1. //压缩后的dp
    2. class Solution {
    3. // 这题的关键在于,将问题转化为尽量把石头分成重量相同的两堆
    4. public int lastStoneWeightII(int[] stones) {
    5. if (stones.length == 1) return stones[0];
    6. int sum = 0;
    7. for (int s : stones) sum += s;
    8. int target = sum / 2; //背包的最大容量就是所有石头重量和的一半
    9. int[] dp = new int[target + 1];
    10. //遍历,尽可能的装最重的石头
    11. for (int i = 0; i < stones.length; i++) {
    12. for (int j = target; j >= stones[i]; j--) {
    13. dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]);
    14. }
    15. }
    16. // 两堆的重量分别为sum - dp[target]和dp[target],他们的差就是最后剩下的石头的重量
    17. return sum - dp[target] - dp[target];
    18. }
    19. }

    *M12.13-494. 目标和

    sum表示所有数的和,left-(sum-left)=target -> left=(target+sum)/2,转为0-1背包问题
    dp[j]表示装满j重量的背包有dp[j]种方法
    1. //0-1背包 压缩
    2. class Solution {
    3. public int findTargetSumWays(int[] nums, int target) {
    4. int sum = 0;
    5. for (int num : nums) sum += num;
    6. if (Math.abs(target) > sum) return 0;
    7. if ((target + sum) % 2 != 0) return 0;
    8. int size = (target + sum) / 2;
    9. //这里把二维dp数组压缩了
    10. int[] dp = new int[size + 1]; //dp[j]表示,容量为j时,使用[0,i]共有多少种方法
    11. dp[0] = 1; //初始化dp数组,dp[0] = 1 容量为0的时候只有一种方法,就是不放
    12. for (int i = 0; i < nums.length; i++) {
    13. for (int j = size; j >= nums[i]; j--) {
    14. // 这里理解成遍历到第i个数,那么第i个数使用,就有dp[j - nums[i]]种方法,
    15. // 如果第i个数不使用,则有上一轮dp[j]种方法,将这两种情况相加,那么就是
    16. // 当前dp[j]的值。
    17. dp[j] = dp[j] + dp[j - nums[i]];
    18. }
    19. }
    20. return dp[size];
    21. }
    22. }
    23. // 0-1背包未压缩
    24. class Solution {
    25. public int findTargetSumWays(int[] nums, int target) {
    26. int sum = 0;
    27. for (int num : nums) sum += num;
    28. if (Math.abs(target) > sum) return 0;
    29. if ((target + sum) % 2 != 0) return 0;
    30. int size = (target + sum) / 2;
    31. //dp[i][j]数组表示,放下标0-i这nums[i]的时候,和为j的放法
    32. int[][] dp = new int[nums.length][size + 1];
    33. dp[0][0] = 1; // 放入第0个数的时候,和为0的方法。为1,就是不放
    34. if (nums[0] == 0) dp[0][0] = 2; //如果第一个数是0,那么+-0都可以满足,所以是2
    35. for (int i = 1; i <= size; i++) { //初始化第一行
    36. if (i == nums[0]) dp[0][i] = 1; // 只有当i=nums[0]的时候,放入nums[0]重量才能为i,此时只有一种放法
    37. }
    38. for (int i = 1; i < nums.length; i++) dp[i][0] = 1;
    39. for (int i = 1; i < nums.length; i++) {
    40. for (int j = 0; j <= size; j++) {
    41. if (j < nums[i]) dp[i][j] = dp[i - 1][j];
    42. else dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i]];
    43. }
    44. }
    45. return dp[nums.length - 1][size];
    46. }
    47. }

    *M12.18-474. 一和零

    1. class Solution {
    2. public int findMaxForm(String[] strs, int m, int n) {
    3. // dp[i][j]表示包含i个0,j个1的子集的个数为dp[i][j];
    4. int[][] dp = new int[m + 1][n + 1];
    5. for (String s : strs) {
    6. int one = 0;
    7. int zero = 0;
    8. // 这个for循环用来统计当前字符窜中0和1的个数
    9. for (char c : s.toCharArray()) {
    10. if (c == '1') one++;
    11. else zero++;
    12. }
    13. // 这里遍历要从后往前,自己推一下就知道不能从前往后遍历
    14. for (int i = m; i >= zero; i--) {
    15. for (int j = n; j >= one; j--) {
    16. dp[i][j] = Math.max(dp[i][j], dp[i - zero][j - one] + 1);
    17. }
    18. }
    19. }
    20. return dp[m][n];
    21. }
    22. }

    M12.20-518. 零钱兑换 II

    ```java // 二维dp数组 class Solution { public int change(int amount, int[] coins) {
    1. // dp[i][j] 表示用[0,i]内的coins凑成j共有dp[i][j]种凑法
    2. int[][] dp = new int[coins.length][amount + 1];
    3. dp[0][0] = 1;
    4. for (int i = 1; i <= amount; i++) {
    5. if (i % coins[0] == 0) dp[0][i] = 1;
    6. }
    7. for (int coin : coins) {
    8. for (int i = 1; i < coins.length; i++) {
    9. for (int j = 0; j <= amount; j++) {
    10. if (j < coins[i]) dp[i][j] = dp[i - 1][j];
    11. else {
    12. // 主要是理解这句话的含义,画画图推一下
    13. dp[i][j] = dp[i - 1][j] + dp[i][j - coins[i]];
    14. }
    15. }
    16. }
    17. }
    18. return dp[coins.length - 1][amount];
    } }

// 滚动数组 class Solution { public int change(int amount, int[] coins) { int[] dp = new int[amount + 1]; dp[0] = 1; //凑成0只有一种方法,那就是都不放 for (int i = 0; i < coins.length; i++) { for (int j = coins[i]; j <= amount; j++) { //滚动数组 对应二维dp数组 // dp[i][j] = dp[i - 1][j] + dp[i][j - coins[i]]; dp[j] += dp[j - coins[i]]; } } return dp[amount]; } }

  1. <a name="fbkRO"></a>
  2. #### *M12.20-[377. 组合总和 Ⅳ](https://leetcode-cn.com/problems/combination-sum-iv/)
  3. ```java
  4. //回溯,超时
  5. class Solution {
  6. int res = 0;
  7. public int combinationSum4(int[] nums, int target) {
  8. if (nums == null || nums.length == 0) return 0;
  9. bk(nums, 0, target);
  10. return res;
  11. }
  12. public void bk(int[] nums, int sum, int target) {
  13. if (sum == target) {
  14. res++;
  15. return;
  16. }
  17. for (int i = 0; i < nums.length; i++) {
  18. if (nums[i] > (target - sum)) continue;
  19. bk(nums, sum + nums[i], target);
  20. }
  21. }
  22. }
  23. // dp 滚动数组
  24. class Solution {
  25. public int combinationSum4(int[] nums, int target) {
  26. int[] dp = new int[target + 1];
  27. dp[0] = 1;
  28. for (int j = 1; j <= target; j++) { //先遍历容量
  29. for (int i = 0; i < nums.length; i++) { // 再遍历物品
  30. if (j >= nums[i]) dp[j] += dp[j - nums[i]];
  31. }
  32. }
  33. return dp[target];
  34. }
  35. }

*M12.22-322. 零钱兑换

  1. //二维dp数组
  2. class Solution {
  3. public int coinChange(int[] coins, int amount) {
  4. //dp[i][j]表示使用0-i的conis[i]凑满j需要的最少硬币个数
  5. int[][] dp = new int[coins.length][amount + 1];
  6. for (int i = 0; i < coins.length; i++) {
  7. Arrays.fill(dp[i], Integer.MAX_VALUE);
  8. }
  9. //初始化dp数组第一行
  10. for (int i = 0; i <= amount; i++) {
  11. int n = i % coins[0];
  12. if (n == 0) dp[0][i] = i / coins[0];
  13. }
  14. //求的是钱币个数,钱币顺序并不影响个数,所以先遍历谁都行
  15. for (int i = 1; i < coins.length; i++) {
  16. for (int j = 0; j <= amount; j++) {
  17. //**这里要判断一下dp[i][j - coins[i]]防止数据溢出**
  18. //dp[i - 1][j]表示不使用coins[i]凑到j所需最少硬币
  19. //使用coins[i]凑足总额为j - coins[i]的最少个数为dp[i][j - coins[i]],那么只需要加上一个钱币coins[i]即dp[i][j - coins[i]] + 1
  20. if (j >= coins[i] && dp[i][j - coins[i]] < Integer.MAX_VALUE) dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - coins[i]] + 1);
  21. else dp[i][j] = dp[i - 1][j];
  22. }
  23. }
  24. return dp[coins.length - 1][amount] == Integer.MAX_VALUE ? -1 : dp[coins.length - 1][amount];
  25. }
  26. }
  27. //dp滚动数组
  28. class Solution {
  29. public int coinChange(int[] coins, int amount) {
  30. int[] dp = new int[amount + 1];
  31. Arrays.fill(dp, Integer.MAX_VALUE);
  32. dp[0] = 0; //金额为0的时候硬币数量为0
  33. for (int i = 0; i < coins.length; i++) {
  34. for (int j = coins[i]; j <= amount; j++) {
  35. //凑足总额为j - coins[i]的最少个数为dp[j - coins[i]],那么只需要加上一个钱币coins[i]即dp[j - coins[i]] + 1
  36. if (dp[j - coins[i]] < Integer.MAX_VALUE) dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1);
  37. }
  38. }
  39. return dp[amount] == Integer.MAX_VALUE ? -1 : dp[amount];
  40. }
  41. }

M12.23-279. 完全平方数

  1. //滚动数组,先遍历物品
  2. class Solution {
  3. public int numSquares(int n) {
  4. if (n % Math.sqrt(n) == 0) return 1;
  5. int[] dp = new int[n + 1];
  6. Arrays.fill(dp, Integer.MAX_VALUE);
  7. dp[0] = 0;
  8. for (int i = 1; i <= Math.sqrt(n); i++) {
  9. int num = i * i;
  10. for (int j = num; j <= n; j++) {
  11. if (dp[j - num] < Integer.MAX_VALUE) dp[j] = Math.min(dp[j], dp[j - num] + 1);
  12. }
  13. }
  14. return dp[n];
  15. }
  16. }
  17. //滚动数组,先遍历容量
  18. class Solution {
  19. public int numSquares(int n) {
  20. if (n % Math.sqrt(n) == 0) return 1;
  21. int[] dp = new int[n + 1];
  22. Arrays.fill(dp, Integer.MAX_VALUE);
  23. dp[0] = 0;
  24. for (int j = 1; j <= n; j++) {
  25. for (int i = 1; i * i <= j; i++) {
  26. dp[j] = Math.min(dp[j], dp[j - i * i] + 1);
  27. }
  28. }
  29. return dp[n];
  30. }
  31. }

*M12.27-139. 单词拆分

  1. // dp完全背包问题
  2. class Solution {
  3. public boolean wordBreak(String s, List<String> wordDict) {
  4. // dp[i]数组表示[0, i)组成的字符串能否被字段中的单词表示
  5. boolean[] dp = new boolean[s.length() + 1];
  6. dp[0] = true;
  7. int start = 0;
  8. for (int i = 1; i <= s.length(); i++) { //遍历背包
  9. for (int j = 0; j < i; j++) { // 遍历物品(子串)
  10. // 如果[j,i)子串包含在字典中,且dp[j]为true,那么就表示当前[0,i)的
  11. //子串能够被拆分成字典中的字符串表示
  12. if (wordDict.contains(s.substring(j, i)) && dp[j]) {
  13. dp[i] = true;
  14. }
  15. }
  16. }
  17. return dp[s.length()];
  18. }
  19. }
  20. //回溯,超时
  21. class Solution {
  22. public boolean wordBreak(String s, List<String> wordDict) {
  23. if (s.length() == 0) return false;
  24. return bk(s, wordDict, 0);
  25. }
  26. public boolean bk(String s, List<String> wordDict, int index) {
  27. if (index >= s.length()) return true;
  28. for (int i = index; i < s.length(); i++) {
  29. String sub = s.substring(index, i + 1);
  30. if (wordDict.contains(sub) && bk(s, wordDict, i + 1)) {
  31. return true;
  32. }
  33. }
  34. return false;
  35. }
  36. }

M12.29-198. 打家劫舍

  1. // 我自己的思路
  2. class Solution {
  3. public int rob(int[] nums) {
  4. if (nums.length == 1) return nums[0];
  5. if (nums.length == 2) return nums[0] > nums[1] ? nums[0] : nums[1];
  6. //dp[i]表示偷第i家能够获取的最大金额
  7. int[] dp = new int[nums.length];
  8. dp[0] = nums[0]; //第一家最大金额就是其本身
  9. dp[1] = nums[1]; //第二家最大金额就是其本身
  10. dp[2] = nums[0] + nums[2]; //第三家最大金额为第一家跟第三家的和
  11. for (int i = 3; i < nums.length; i++) {
  12. // 因为不能偷相邻的,所以可以偷前一个或者前两个
  13. dp[i] = Math.max(dp[i - 2], dp[i - 3]) + nums[i];
  14. }
  15. int l = dp.length;
  16. // 最后需要判断一下最后两家哪个大,有可能偷倒数第二家更大
  17. // 比如[1,100,1]
  18. return dp[l - 1] > dp[l - 2] ? dp[l - 1] : dp[l - 2];
  19. }
  20. }
  21. // 代码随想录思路,这个写法更简洁
  22. class Solution {
  23. public int rob(int[] nums) {
  24. if (nums == null || nums.length == 0) return 0;
  25. if (nums.length == 1) return nums[0];
  26. //dp[i]考虑下标i(包括i)以内的房屋,最多可以偷窃的金额为dp[i]。
  27. int[] dp = new int[nums.length];
  28. dp[0] = nums[0]; //第一家最大金额就是其本身
  29. dp[1] = Math.max(nums[0], nums[1]);
  30. for (int i = 2; i < dp.length; i++) {
  31. // 如果偷第i间房,那么dp[i] = dp[i - 2] + nums[i]
  32. // 即:第i-1房一定是不考虑的,只需要找到i-2以内的房屋获得的最大金额+第i间房屋的金额
  33. // 如果不偷第i间房,那么dp[i] = dp[i - 1],即考虑i-1间房的最大金额
  34. // 比较上述两种情况的最大金额,即可获得全局最大金额
  35. dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);
  36. }
  37. return dp[dp.length - 1];
  38. }
  39. }

M12.30-213. 打家劫舍 II

这题跟19题差不多,唯一的区别是成环了,成环的话主要有三种情况:

  • 情况一:不考虑首尾元素
  • 情况二:不考虑首元素
  • 情况三:不考虑尾元素

注意,这里是考虑,但不一定使用,所以情况2跟3包含了1,只需要考虑情况2跟3得到的即可。

  1. class Solution {
  2. public int rob(int[] nums) {
  3. if (nums == null || nums.length == 0) return 0;
  4. if (nums.length == 1) return nums[0];
  5. //
  6. int result1 = robRange(nums, 0, nums.length - 2);
  7. int result2 = robRange(nums, 1, nums.length - 1);
  8. return result1 > result2 ? result1 : result2;
  9. }
  10. public int robRange(int[] nums, int start, int end) {
  11. if (end == start) return nums[start];
  12. int[] dp = new int[nums.length];
  13. dp[start] = nums[start];
  14. dp[start + 1] = Math.max(nums[start + 1], nums[start]);
  15. for (int i = start + 2; i <= end; i++) {
  16. dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);
  17. }
  18. return dp[end];
  19. }
  20. }

*M1.1-337. 打家劫舍 III

  1. class Solution {
  2. public int rob(TreeNode root) {
  3. if (root == null) return 0;
  4. int[] res = robAction(root);
  5. return Math.max(res[0], res[1]);
  6. }
  7. public int[] robAction(TreeNode root) {
  8. // dp数组,dp[0]表示不偷当前节点能获得的最大值
  9. //dp[1] 表示偷当前节点能获得的最大值
  10. int[] dp = new int[2];
  11. if (root == null) return dp;
  12. // 后续遍历
  13. int[] left = robAction(root.left);
  14. int[] right = robAction(root.right);
  15. // 如果不偷当前节点,那么最大值为左右子树(不用考虑左右子树偷还是不偷)的最大值总和。
  16. dp[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
  17. // 如果偷当前节点,那么最大值为不偷左右子树+当前节点值的和。
  18. dp[1] = root.val + left[0] + right[0];
  19. return dp;
  20. }
  21. }

S1.2-121. 买卖股票的最佳时机

  1. // 贪心算法
  2. class Solution {
  3. public int maxProfit(int[] prices) {
  4. int low = Integer.MAX_VALUE;
  5. int res = 0;
  6. for (int i = 0; i < prices.length; i++) {
  7. low = Math.min(prices[i], low); // 取最左最小价格
  8. res = Math.max(prices[i] - low, res); // 直接取最大区间利润
  9. }
  10. return res;
  11. }
  12. }
  13. // 动态规划
  14. class Solution {
  15. public int maxProfit(int[] prices) {
  16. // dp[i][0]表示第i天持股最大利润,dp[i][1]表示第i天不持股最大利润
  17. int[][] dp = new int[prices.length][2];
  18. dp[0][0] = -prices[0];
  19. for (int i = 1; i < prices.length; i++) {
  20. // 第i天持股,分两种情况:延续前一天的状态或者今天买入
  21. // 因为只能买一次,所以如果是第i天买入,那么dp[i][0] = -prices[i]
  22. dp[i][0] = Math.max(dp[i - 1][0], -prices[i]);
  23. // 第i天不持股,分两种情况:延续前一天的状态或者今天卖出
  24. dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + prices[i]);
  25. }
  26. return dp[prices.length - 1][1];
  27. }
  28. }

M1.2-122. 买卖股票的最佳时机 II

  1. // 贪心 只要利润是正的就买
  2. class Solution {
  3. public int maxProfit(int[] prices) {
  4. int res = 0;
  5. for (int i = 1; i < prices.length; i++) {
  6. int profit = prices[i] - prices[i - 1];
  7. if (profit > 0) res += profit;
  8. }
  9. return res;
  10. }
  11. }
  12. // 动态规划
  13. class Solution {
  14. public int maxProfit(int[] prices) {
  15. int len = prices.length;
  16. //dp[i][0] 表示第i天持股所获最大利润
  17. //dp[i][1] 表示第i天不持股所获最大利润
  18. int[][] dp = new int[len][2];
  19. dp[0][0] = -prices[0];
  20. for (int i = 1; i < len; i++) {
  21. // 第i天持股,有两种情况:
  22. // 1. 延续前一天的状态dp[i - 1][0]
  23. // 2. 今天买入 dp[i - 1][1] - prices[i]
  24. // 这里要与只能买一次区分开,如果只能买一次那么dp[i][1] = -prices[i]
  25. dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
  26. // 第i天不持股,有两种情况:
  27. // 1. 延续前一天的状态
  28. // 2. 今天卖出:dp[i - 1][0] + prices[i]
  29. dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + prices[i]);
  30. }
  31. return dp[len - 1][1];
  32. }
  33. }

H1.04-123. 买卖股票的最佳时机 III

  1. // 动态规划1
  2. class Solution {
  3. public int maxProfit(int[] prices) {
  4. int len = prices.length;
  5. if (len <= 1) return 0;
  6. // dp[i][j]表示第i天持股状态j所获最大利润
  7. // j有5个取值:0表示不做任何操作,1表示第一次买入,2表示第一次卖出
  8. // 3表示第二次买入,4表示第二次卖出
  9. int[][] dp = new int[len][5];
  10. dp[0][1] = -prices[0];
  11. dp[0][3] = -prices[0];
  12. for (int i = 1; i < len; i++) {
  13. dp[i][0] = dp[i - 1][0]; // 不做任何操作直接延续前一天的状态
  14. // 第i天持股状态是第一次买入状态有两种情况:
  15. // 1. 直接延续前一天的状态 dp[i - 1][1]
  16. // 2. 第i天买入 dp[i - 1][0] - prices[i]
  17. dp[i][1] = Math.max(dp[i - 1][0] - prices[i], dp[i - 1][1]);
  18. // 第i天持股状态是第一次卖出状态有两种情况:
  19. // 1. 直接延续前一天的状态 dp[i - 1][2]
  20. // 2. 第i天卖出 dp[i - 1][1] + prices[i]
  21. dp[i][2] = Math.max(dp[i - 1][1] + prices[i], dp[i - 1][2]);
  22. // 第i天持股状态是第二次买入状态有两种情况:
  23. // 1. 直接延续前一天的状态 dp[i - 1][3]
  24. // 2. 第i天买入 dp[i - 1][2] - prices[i]
  25. dp[i][3] = Math.max(dp[i - 1][2] - prices[i], dp[i - 1][3]);
  26. // 第i天持股状态是第二次卖出状态有两种情况:
  27. // 1. 直接延续前一天的状态 dp[i - 1][4]
  28. // 2. 第i天买入 dp[i - 1][3] + prices[i]
  29. dp[i][4] = Math.max(dp[i - 1][3] + prices[i], dp[i - 1][4]);
  30. }
  31. return dp[len - 1][4];
  32. }
  33. }

H1.04-188. 买卖股票的最佳时机 IV

这一题跟上一题其实差不多,只不过交易次数是不固定的。这里我没有设置无操作的状态,因为无操作的状态都是0,可以节省部分内存。

  1. class Solution {
  2. public int maxProfit(int k, int[] prices) {
  3. int len = prices.length;
  4. if (k == 0 || len <= 1) return 0;
  5. // dp[i][j] 表示第i天持股状态j下,获取的最大收益为dp[i][j]
  6. int[][] dp = new int[len][k * 2];
  7. for (int i = 0; i < 2 * k; i += 2) {
  8. dp[0][i] = -prices[0];
  9. }
  10. for (int i = 1; i < len; i++) {
  11. // 第j + 1次买入
  12. for (int j = 0; j < 2 * k; j += 2) {
  13. if (j == 0) {
  14. // 第i天处于第一次买入状态,有两种情况
  15. // 1. 维持前一天的第一次买入状态dp[i - 1][0]
  16. // 2. 第i天买入,-prices[i]
  17. dp[i][0] = Math.max(dp[i - 1][0], -prices[i]);
  18. }
  19. else {
  20. // 第i天处于非第一次买入状态,有两种情况
  21. // 1. 维持前一天的第一次买入状态dp[i - 1][j]
  22. // 2. 第i天买入,前一天处于第一次卖出状态
  23. // dp[i - 1][j - 1] - prices[i]
  24. dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - 1] - prices[i]);
  25. }
  26. }
  27. // 第j次卖出
  28. for (int j = 1; j < 2 * k; j += 2) {
  29. // 卖出状态不需要分第一次还是非第一次
  30. // 第i天处于第j次卖出状态有两种情况:
  31. // 1. 维持前一天第j次卖出状态 dp[i - 1][j]
  32. // 2. 第i天卖出股票,前一天第j次买入收益+当前股价
  33. // dp[i - 1][j - 1] + prices[i]
  34. dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - 1] + prices[i]);
  35. }
  36. }
  37. return dp[len - 1][2 * k - 1];
  38. }
  39. }

*M1.05-309. 最佳买卖股票时机含冷冻期

这一题一定要弄清楚股票的四个状态:
状态0:今天买入 dp[i][0]
状态1:前两天卖出,度过冻结期的卖出状态 dp[i][1]
状态2:今天刚好卖出 dp[i][2]
状态3:今天是冻结期 dp[i][3]
然后一定要弄清楚每个状态的递推关系,dp数组怎么初始化!

  1. class Solution {
  2. public int maxProfit(int[] prices) {
  3. if (prices == null || prices.length <= 1) return 0;
  4. int len = prices.length;
  5. // dp[i][j]表示在第i天持股状态为j下获得的最大利润
  6. // 持股状态有四种:
  7. // 0. 表示买入状态
  8. // 卖出状态: 1. 前两天卖出了股票,度过了冷冻期 2. 今天卖出了股票
  9. // 3. 冷冻期
  10. int[][] dp = new int[len][4];
  11. dp[0][0] = -prices[0];
  12. for (int i = 1; i < len; i++) {
  13. // 第i天为买入状态的,有三种情况:
  14. // 1. 维持前一天的买入状态 dp[i - 1][0]
  15. // 2. 前一天为非冻结期的卖出状态: dp[i - 1][1] - prices[i]
  16. // 3. 前一天为冻结期: dp[i - 1][3] - proces[i]
  17. dp[i][0] = Math.max(dp[i - 1][0], Math.max(dp[i - 1][1], dp[i - 1][3]) - prices[i]);
  18. // 第i天为卖出状态
  19. // I、第i天为前两天卖出股票,度过冷冻期状态,有两种情况:
  20. // 1. 维持前一天的度过冷冻期卖出状态 dp[i - 1][1]
  21. // 2. 前一天为冷冻期 dp[i - 1][3]
  22. dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][3]);
  23. // II、第i天为今天卖出状态,只有一种情况
  24. // 前一天为买入,今天卖出: dp[i - 1][0] + prices[i]
  25. dp[i][2] = dp[i - 1][0] + prices[i];
  26. // 第i天为冻结期,只有一种情况,前一天卖股票
  27. dp[i][3] = dp[i - 1][2];
  28. }
  29. // 最后最大利润的状态可能分别是状态1、状态2和状态3
  30. // 状态1:前两天卖出,度过冻结期卖出状态
  31. // 状态2:今天卖出股票
  32. // 状态3:今天是冻结期
  33. return Math.max(dp[len - 1][3], Math.max(dp[len - 1][1], dp[len - 1][2]));
  34. }
  35. }

M1.06-714. 买卖股票的最佳时机含手续费

  1. class Solution {
  2. public int maxProfit(int[] prices, int fee) {
  3. //dp[i][j] 表示第i+1天的持股、不持股分别有多少现金
  4. int[][] dp = new int[prices.length][2];
  5. dp[0][0] = - prices[0];
  6. for (int i = 1; i < prices.length; i++) {
  7. // 第i+1天持股状态分两种情况:
  8. // 1. 维持前一天的持股状态 dp[i - 1][0]
  9. // 2. 今天买入 dp[i - 1][0] - prices[i]
  10. dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
  11. // 第i+1天不持股状态 分两种情况:
  12. // 1. 维持前一天的不持股状态 dp[i - 1][1]
  13. // 2. 今天卖出股票 dp[i - 1][0] + prices[i] - fee
  14. dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + prices[i] - fee);
  15. }
  16. return dp[prices.length - 1][1];
  17. }
  18. }

M1.07-300. 最长递增子序列

这题看看力扣的题解,有加入二分查找时间复杂度更低的方法

  1. // 动态规划
  2. class Solution {
  3. public int lengthOfLIS(int[] nums) {
  4. if (nums.length == 1) return 1;
  5. // dp[i] 表示0-i构成的数组的最长递增子序列为dp[i]
  6. int[] dp = new int[nums.length];
  7. Arrays.fill(dp, 1); // 不管数组元素是什么顺序,最长子序列最少是1
  8. int result = 1;
  9. for (int i = 1; i < nums.length; i++) {
  10. for (int j = 0; j < i; j++) {
  11. if (nums[i] > nums[j]) dp[i] = Math.max(dp[j] + 1, dp[i]);
  12. }
  13. // 更新最长子序列
  14. result = result > dp[i] ? result : dp[i];
  15. }
  16. return result;
  17. }
  18. }

//S1.08-674. 最长连续递增序列

*M1.08-718. 最长重复子数组

解题思路:主要是dp数组的定义以及递推公式,dp[i][j]的状态只能通过dp[i-1][j-1]来推导,这一点的理解极为重要

  1. class Solution {
  2. public int findLength(int[] nums1, int[] nums2) {
  3. // dp[i][j] 表示nums1中0到i-1元素组成的子数组
  4. // 和nums2中0到j-1元素组成的子数组的最长公共子数组为dp[i][j]
  5. // dp[0][j]和dp[i][0]都是没有意义的,均为0
  6. int[][] dp = new int[nums1.length + 1][nums2.length + 1];
  7. int result = 0;
  8. // 遍历顺序对最终结果不产生影响
  9. for (int i = 1; i <= nums1.length; i++) {
  10. for (int j = 1; j <= nums2.length; j++) {
  11. // 如果nums1[i - 1]和nums2[j - 1]相等那么两个数组的当前元素
  12. // 可以组成一个长度为1的子序列
  13. // 根据dp[i][j]的定义,dp[i][j]的状态只能由dp[i - 1][j - 1]推导出来。
  14. // 即当A[i - 1] 和B[j - 1]相等的时候,dp[i][j] = dp[i - 1][j - 1] + 1;
  15. if (nums1[i - 1] == nums2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
  16. if (dp[i][j] > result) result = dp[i][j];
  17. }
  18. }
  19. return result;
  20. }
  21. }

*M1.09-1143. 最长公共子序列

题解

  1. class Solution {
  2. public int longestCommonSubsequence(String text1, String text2) {
  3. // dp[i][j]表示长度为[0,i-1]的字符串text1和长度为[0,j-1]的字符串text2的最长公共子序列长度为dp[i][j]
  4. int[][] dp = new int[text1.length() + 1][text2.length() + 1];
  5. for (int i = 1; i < dp.length; i++) {
  6. for (int j = 1; j < dp[0].length; j++) {
  7. if (text1.charAt(i - 1) == text2.charAt(j - 1)) dp[i][j] = dp[i - 1][j - 1] + 1;
  8. else {
  9. dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
  10. }
  11. }
  12. }
  13. return dp[text1.length()][text2.length()];
  14. }
  15. }

M1.11-1035. 不相交的线

这题跟上一题1143. 最长公共子序列一样的思路

  1. class Solution {
  2. public int maxUncrossedLines(int[] nums1, int[] nums2) {
  3. // dp[i][j] 表示nums1 [0,i-1]和nums2 [0,j-1]能构成的最多直线为dp[i][j]
  4. int[][] dp = new int[nums1.length + 1][nums2.length + 1];
  5. for (int i = 1; i <= nums1.length; i++) {
  6. for (int j = 1; j <= nums2.length; j++) {
  7. // 当nums1[i]与nums2[j]相等时,可以直接连一条线
  8. // 不会与前面的线交叉
  9. if (nums1[i - 1] == nums2[j - 1]) {
  10. dp[i][j] = dp[i - 1][j - 1] + 1;
  11. }
  12. else {
  13. // 不相等时,选择dp[i-1][j]和dp[i][j-1]中大的一个
  14. dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
  15. }
  16. }
  17. }
  18. return dp[nums1.length][nums2.length];
  19. }
  20. }

S1.12-53. 最大子数组和

  1. // 动态规划
  2. class Solution {
  3. public int maxSubArray(int[] nums) {
  4. //dp[i]表示nums中[0,i]中构成的连续子数组最大和为dp[i]
  5. int[] dp = new int[nums.length];
  6. dp[0] = nums[0];
  7. int res = dp[0]; // 用于保存dp[i]的最大值
  8. for (int i = 1; i < nums.length; i++) {
  9. //dp[i]只有两个方向可以推出来:
  10. //1. dp[i - 1] + nums[i],即:nums[i]加入当前连续子序列和
  11. //2. nums[i],即:从头开始计算当前连续子序列和
  12. dp[i] = Math.max(dp[i - 1] + nums[i], nums[i]);
  13. res = res > dp[i] ? res : dp[i];
  14. }
  15. return res;
  16. }
  17. }
  18. // 贪心算法
  19. class Solution {
  20. public int maxSubArray(int[] nums) {
  21. int res = nums[0];
  22. for (int i = 1; i < nums.length; i++) {
  23. // 计算前面的子序列最大和跟当前值相加 有没有比 当前单独值大
  24. // 如果没有,那么当前值组成的子序列就是最大
  25. nums[i] = Math.max(nums[i], nums[i] + nums[i - 1]);
  26. // 更新最大和
  27. res = nums[i] > res ? nums[i] : res;
  28. }
  29. return res;
  30. }
  31. }

S1.12-392. 判断子序列

这题用双指针更好,dp题解

  1. // 动态规划
  2. class Solution {
  3. public boolean isSubsequence(String s, String t) {
  4. if (s.length() == 0) return true;
  5. if (t.length() == 0) return false;
  6. // dp[i][j]表示表示以下标i-1为结尾的字符串s,和以下标j-1为结尾的字符串t,相同子序列的长度为dp[i][j]
  7. int[][] dp = new int[s.length() + 1][t.length() + 1];
  8. int res = 0;
  9. for (int i = 1; i <= s.length(); i++) {
  10. for (int j = 1; j <= t.length(); j++) {
  11. // 当前两字符相等,相同子序列长度在dp[i - 1][j - 1]上增加1
  12. if (s.charAt(i - 1) == t.charAt(j - 1)) {
  13. dp[i][j] = dp[i - 1][j - 1] + 1;
  14. }
  15. // 如果不相等,需要跳过t的第j-1个字符,因此dp[i][j]=dp[i][j-1]
  16. else {
  17. dp[i][j] = dp[i][j - 1];
  18. }
  19. res = res > dp[i][j] ? res : dp[i][j];
  20. if (res == s.length()) return true;
  21. }
  22. }
  23. return false;
  24. }
  25. }
  26. // 双指针
  27. class Solution {
  28. public boolean isSubsequence(String s, String t) {
  29. if (s.length() == 0) return true;
  30. if (t.length() == 0) return false;
  31. int index = 0; // 指向s中某个字符的指针
  32. // i表示指向t中某个字符的指针
  33. for (int i = 0; i < t.length(); i++) {
  34. // 如果i指向的字符与index指向的字符相等
  35. // 那么index向后挪一位
  36. if (t.charAt(i) == s.charAt(index)) {
  37. index++;
  38. }
  39. // 一旦index与s的长度相等,则表示匹配完毕
  40. if (index == s.length()) return true;
  41. }
  42. // 否则表示不能匹配
  43. return false;
  44. }
  45. }

*H1.18-115. 不同的子序列

题解

  1. class Solution {
  2. public int numDistinct(String s, String t) {
  3. // dp数组表示以i-1为结尾的s子序列中出现以j-1为结尾的t的个数为dp[i][j]。
  4. int[][] dp = new int[s.length() + 1][t.length() + 1];
  5. // t为空串的时候跟任意s都可以匹配
  6. for (int i = 0; i <= s.length(); i++) dp[i][0] = 1;
  7. for (int i = 1; i <= s.length(); i++) {
  8. for (int j = 1; j <= t.length(); j++) {
  9. // 当指向字符相等,dp[i][j]可以有两部分组成。
  10. // 1. 用s中下标为i-1的字符来匹配,即dp[i - 1][j - 1]
  11. // 2. 不用s中下标为i-1的字符来匹配,即dp[i - 1][j];
  12. if (s.charAt(i - 1) == t.charAt(j - 1)) {
  13. dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
  14. }
  15. else { // 指向字符不相等
  16. // 不相等时,只存在一种情况,即不用s中下标为i-1的字符匹配
  17. dp[i][j] = dp[i - 1][j];
  18. }
  19. }
  20. }
  21. return dp[s.length()][t.length()];
  22. }
  23. }

*M1.19-583. 两个字符串的删除操作

题解

  1. class Solution {
  2. public int minDistance(String word1, String word2) {
  3. // dp[i][j]表示word1以索引i-1结尾的字符串和word2以j-1结尾的字符串达到相等
  4. // 需要删除的元素最少次数为dp[i][j]
  5. int[][] dp = new int[word1.length() + 1][word2.length() + 1];
  6. // 初始化dp,当其中一个字符串为空时,剩下一个需要删除所有字符
  7. for (int i = 0; i <= word1.length(); i++) dp[i][0] = i;
  8. for (int j = 0; j <= word2.length(); j++) dp[0][j] = j;
  9. for (int i = 1; i <= word1.length(); i++) {
  10. for (int j = 1; j <= word2.length(); j++) {
  11. // 当word1[i-1]与word2[j-1]相等时,不需要进行删除操作
  12. // 直接延续word1[i-2]与word2[j-2]所需要删除的次数
  13. if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
  14. dp[i][j] = dp[i - 1][j - 1];
  15. }
  16. else {
  17. // 当word1[i-1]与word2[j-1]不相等时,存在三种情况
  18. // 1. 删除word1[i - 1]: dp[i - 1][j] + 1
  19. // 2. 删除word2[j - 1]: dp[i][j - 1] + 1
  20. // 3. 同时删除word1[i - 1]和word2[j - 1]: dp[i - 1][j - 1] + 2
  21. // 选其中最小的一个
  22. dp[i][j] = Math.min(dp[i - 1][j - 1] + 2, Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1));
  23. }
  24. }
  25. }
  26. return dp[word1.length()][word2.length()];
  27. }
  28. }

*H1.21-72. 编辑距离

  1. class Solution {
  2. public int minDistance(String word1, String word2) {
  3. // dp[i][j] 表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j]
  4. int[][] dp = new int[word1.length() + 1][word2.length() + 1];
  5. // 当word2为空串时,word1[i-1]需要进行i次删除操作
  6. for (int i = 1; i <= word1.length(); i++) dp[i][0] = i;
  7. // 当word1为空串时,word1[i-1]需要进行i次插入操作
  8. for (int j = 1; j <= word2.length(); j++) dp[0][j] = j;
  9. for (int i = 1; i <= word1.length(); i++) {
  10. for (int j = 1; j <=word2.length(); j++) {
  11. // 如果word1[i-1]跟word2[j-1]相等,不进行操作
  12. if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
  13. dp[i][j] = dp[i - 1][j - 1];
  14. }
  15. else {
  16. // 如果word1[i-1]跟word2[j-1]不相等,需要进行增、删或者换
  17. // 增:word1以下标i-1结尾的字符串可以跟word2以下标j-2结尾的字符串匹配;可以理解为wrod2删除一个元素(因为word2删除一个元素相当于word1增加一个元素) dp[i][j - 1] + 1
  18. // 删:word1以下标i-2结尾的字符串已经可以跟word2以下标j-1的匹配 dp[i - 1][j] + 1
  19. // 换:dp[i - 1][j - 1] + 1
  20. dp[i][j] = Math.min(Math.min(dp[i - 1][j - 1], dp[i - 1][j]), dp[i][j - 1]) + 1;
  21. }
  22. }
  23. }
  24. return dp[word1.length()][word2.length()];
  25. }
  26. }

*M2.3-647. 回文子串

题解(动态规划和双指针)
动态规划一定要注意遍历的顺序;
双指针每次都以一个点和两点个为中心,像两边拓展遍历。

  1. // 动态规划
  2. class Solution {
  3. public int countSubstrings(String s) {
  4. int res = 0;
  5. int len = s.length();
  6. if (s == null || len < 1) return res;
  7. //dp[i][j]:s字符串下标i到下标j的字串是否是一个回文串,即s[i, j]
  8. boolean dp[][] = new boolean[len][len];
  9. // 注意遍历顺序,因为在计算dp[i][j]的时候需要用到dp[i + 1][j - 1]
  10. // 所以需要保证i+1跟j-1在之前已经遍历过了
  11. for (int j = 0; j < len; j++) {
  12. for (int i = 0; i <= j; i++) {
  13. // 如果s[i]与s[j]相等,分三种情况
  14. // 1. i=j,即一个字符,那么就直接是true
  15. // 2. i和j之间相差1,那么也是true
  16. // 3. i和j之间相差大于1的时候,那么dp[i][j] = dp[i+1][j-1]
  17. if (s.charAt(i) == s.charAt(j)) {
  18. if (j - i <= 1) { // 情况1和情况2可以合并
  19. dp[i][j] = true;
  20. res++;
  21. }
  22. else if (dp[i + 1][j - 1]) { // 情况3
  23. dp[i][j] = true;
  24. res++;
  25. }
  26. }
  27. }
  28. }
  29. return res;
  30. }
  31. }
  32. // 双指针
  33. class Solution {
  34. public int countSubstrings(String s) {
  35. int len = s.length();
  36. int res = 0;
  37. if (s == null || len < 0) return 0;
  38. for (int i = 0; i < len; i++) {
  39. res += extend(s, i, i, len); // 以i为中心
  40. res += extend(s, i, i + 1, len); // 以i和i+1为中心
  41. }
  42. return res;
  43. }
  44. public int extend(String s, int i, int j, int n) {
  45. int res = 0;
  46. while (i >= 0 && j < n && s.charAt(i) == s.charAt(j)) {
  47. i--;
  48. j++;
  49. res++;
  50. }
  51. return res;
  52. }
  53. }

*M2.4-516. 最长回文子序列

  1. class Solution {
  2. public int longestPalindromeSubseq(String s) {
  3. if (s == null || s.length() < 1) return 0;
  4. int len = s.length();
  5. // dp[i][j]表示s[i,j]所包含的最长回文子序列为dp[i][j]
  6. int dp[][] = new int[len][len];
  7. // 当i=j时,回文子序列长度为1
  8. for (int i = 0; i < len; i++) dp[i][i] = 1;
  9. // 注意遍历顺序,因为dp[i][j]需要dp[i+1][j-1]、dp[i+1][j]
  10. // 和dp[i][j-1]来计算,所以i需要从后向前遍历,j需要从前往后遍历,
  11. // 同时j>i,因为i指向的是子序列的头,j指向子序列的尾
  12. for (int i = len - 1; i >= 0; i--) {
  13. for (int j = i + 1; j < len; j++) {
  14. // dp[i][j]递推分为两种情况
  15. // 1. s[i]=s[j] dp[i][j]=dp[i+1][j-1]+2;
  16. // 2. s[i]!=s[j] 取只加入左边或者右边大的那一个
  17. // dp[i][j]=max(dp[i+1][j],dp[i][j-1])
  18. if (s.charAt(i) == s.charAt(j)) {
  19. dp[i][j] = dp[i + 1][j - 1] + 2;
  20. }
  21. else {
  22. dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]);
  23. }
  24. }
  25. }
  26. return dp[0][len - 1];
  27. }
  28. }

单调栈

什么时候用到单调栈? 通常是一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置,此时我们就要想到可以用单调栈了

*M2.24-739. 每日温度

  1. // 详细写法
  2. class Solution {
  3. public int[] dailyTemperatures(int[] temperatures) {
  4. if (temperatures == null || temperatures.length == 0) return new int[0];
  5. int[] res = new int[temperatures.length];
  6. Stack<Integer> s = new Stack<>(); // 栈里面放的是下标,温度从栈顶到栈底为递增
  7. s.push(0);
  8. for (int i = 1; i < res.length; i++) {
  9. // 情况1,下标为i的温度小于栈顶下标温度
  10. // 情况2,下标为i的温度等与栈顶下标温度
  11. if (temperatures[i] <= temperatures[s.peek()]) {
  12. s.push(i);
  13. }
  14. else { //情况3,下标为i的温度大于栈顶下标温度
  15. // 先将栈顶下标温度小的全部出栈,同时更新结果
  16. while (!s.isEmpty() && temperatures[i] > temperatures[s.peek()]) {
  17. int preIndex = s.pop();
  18. res[preIndex] = i - preIndex;
  19. }
  20. s.push(i);
  21. }
  22. }
  23. return res;
  24. }
  25. }
  26. // 精简写法
  27. class Solution {
  28. public int[] dailyTemperatures(int[] temperatures) {
  29. if (temperatures == null || temperatures.length == 0) return new int[0];
  30. int[] res = new int[temperatures.length];
  31. Stack<Integer> s = new Stack<>(); // 栈里面放的是下标
  32. for (int i = 0; i < res.length; i++) {
  33. while (!s.isEmpty() && temperatures[i] > temperatures[s.peek()]) {
  34. int preIndex = s.pop();
  35. res[preIndex] = i - preIndex;
  36. }
  37. s.push(i);
  38. }
  39. return res;
  40. }
  41. }

S2.25-496. 下一个更大元素 I

  1. class Solution {
  2. public int[] nextGreaterElement(int[] nums1, int[] nums2) {
  3. int[] res = new int[nums1.length];
  4. // map用来存储nums2中每个元素第一个比他大的值
  5. // key表示当前元素值,value表示nums2中下一个比他大的值
  6. Map<Integer, Integer> map = new HashMap<>();
  7. // s为单调栈,用来找出第一个比当前元素大的元素,存储的是元素值(不是下标)
  8. // 栈顶到栈底为递增
  9. Stack<Integer> s = new Stack<>();
  10. for (int i = 0; i < nums2.length; i++) {
  11. map.put(nums2[i], -1); // 初始化,全为-1
  12. }
  13. // 单调栈,找nums2中元素右侧第一个比当前元素大的元素
  14. for (int i = 0; i < nums2.length; i++) {
  15. // 如果栈顶元素小于入栈元素,则找到了第一个比他大的元素
  16. // 出栈,然后更新map中的记录
  17. while (!s.isEmpty() && s.peek() < nums2[i]) {
  18. map.put(s.pop(), nums2[i]);
  19. }
  20. s.push(nums2[i]);
  21. }
  22. // 因为nums1是nums2的子集,所指直接从map中取结果即可
  23. for (int i = 0; i < nums1.length; i++) {
  24. res[i] = map.get(nums1[i]);
  25. }
  26. return res;
  27. }
  28. }

M2.26-503. 下一个更大元素 II

  1. // 遍历两遍数组
  2. class Solution {
  3. public int[] nextGreaterElements(int[] nums) {
  4. if (nums == null || nums.length == 0) return null;
  5. int size = nums.length;
  6. int[] res = new int[size];
  7. Arrays.fill(res, -1);
  8. Stack<Integer> s = new Stack<>(); // 单调栈,存储下标,栈顶到栈底递增
  9. // 直接遍历两遍数组即可
  10. for (int i = 0; i < size * 2; i++) {
  11. while (!s.isEmpty() && nums[i % size] > nums[s.peek()]) {
  12. res[s.pop()] = nums[i % size];
  13. }
  14. s.push(i % size);
  15. }
  16. return res;
  17. }
  18. }
  19. // 先遍历数组 再遍历栈
  20. class Solution {
  21. public int[] nextGreaterElements(int[] nums) {
  22. if (nums == null || nums.length == 0) return null;
  23. int[] res = new int[nums.length];
  24. Arrays.fill(res, -1);
  25. Stack<Integer> s = new Stack<>(); // 单调栈,存储下标,栈顶到栈底递增
  26. // 首先找右边第一个比当前值大的,普通单调栈的找法就可以
  27. for (int i = 0; i < nums.length; i++) {
  28. while (!s.isEmpty() && nums[i] > nums[s.peek()]) {
  29. res[s.pop()] = nums[i];
  30. }
  31. s.push(i);
  32. }
  33. // 如果找完右边大的元素,栈中元素个数大于1,则表示需要循环搜索
  34. if (s.size() > 1) {
  35. for (int i = 0; i < nums.length; i++) {
  36. // 找到比栈顶元素大的元素,同时要保证栈中的元素必须大于1
  37. while (s.size() > 1 && nums[i] > nums[s.peek()]) {
  38. res[s.pop()] = nums[i];
  39. }
  40. // 因为没有比最大元素大的元素,所以栈的长度至少为1,此时搜寻完毕
  41. if (s.size() <= 1) break;
  42. }
  43. }
  44. return res;
  45. }
  46. }

*H2.26-42. 接雨水

三种解法,双指针、动态规划、单调栈;其中双指针跟动态规划的思想是一样的,都是按列来计算水量;单调栈是通过来计算水量。

  1. // 双指针 时间n^2 空间1
  2. class Solution {
  3. public int trap(int[] height) {
  4. if (height == null || height.length <= 1) return 0;
  5. int res = 0;
  6. for (int i = 0; i < height.length; i++) {
  7. // 第一根柱子跟最后一根柱子不接雨水
  8. if (i == 0 || i == height.length - 1) continue;
  9. int rH = height[i]; // 记录右边柱子的最高高度
  10. int lH = height[i]; // 记录左边柱子的最高高度
  11. for (int r = i + 1; r < height.length; r++) {
  12. if (height[r] > rH) rH = height[r];
  13. }
  14. for (int l = i - 1; l >= 0; l--) {
  15. if (height[l] > lH) lH = height[l];
  16. }
  17. // 当前柱子能接的雨水高度=min(左边最高柱子,右边最高柱子)-当前柱子高度
  18. int cur = Math.min(rH, lH) - height[i];
  19. res += cur;
  20. }
  21. return res;
  22. }
  23. }
  24. // 动态规划 时间n 空间n
  25. class Solution {
  26. public int trap(int[] height) {
  27. if (height == null || height.length <= 1) return 0;
  28. int res = 0;
  29. // 为了避免重复计算左边与右边最高柱子的高度
  30. // 使用maxRight和maxLeft数组来存储当前柱子左右侧的最高高度
  31. int[] maxRight = new int[height.length];
  32. int[] maxLeft = new int[height.length];
  33. int len = height.length;
  34. // 记录每个柱子左边柱子最大高度
  35. maxLeft[0] = height[0];
  36. for (int i = 1; i < len; i++) {
  37. // 动态规划的思想
  38. maxLeft[i] = Math.max(maxLeft[i - 1], height[i]);
  39. }
  40. // 记录每个柱子右边柱子最大高度
  41. maxRight[len - 1] = height[len - 1];
  42. for (int i = len - 2; i >= 0; i--) {
  43. // 动态规划的思想
  44. maxRight[i] = Math.max(maxRight[i + 1], height[i]);
  45. }
  46. for (int i = 0; i < len; i++) {
  47. // 第一根柱子跟最后一根柱子不接雨水
  48. if (i == 0 || i == len - 1) continue;
  49. // 当前柱子能接的雨水高度=min(左边最高柱子,右边最高柱子)-当前柱子高度
  50. int cur = Math.min(maxRight[i], maxLeft[i]) - height[i];
  51. res += cur;
  52. }
  53. return res;
  54. }
  55. }
  56. // 单调栈
  57. class Solution {
  58. public int trap(int[] height) {
  59. if (height == null || height.length <= 2) return 0;
  60. int res = 0;
  61. // 存着下标,计算的时候用下标对应的柱子高度,栈顶到栈底是单调递增
  62. Stack<Integer> s = new Stack<>();
  63. s.push(0);
  64. for (int i = 1; i < height.length; i++) {
  65. // 情况1:当前柱子高度小于栈顶柱子高度,直接入栈
  66. if (height[i] < height[s.peek()]) {
  67. s.push(i);
  68. }
  69. // 情况2:当前柱子高度等于栈顶柱子高度,要跟更新栈顶元素,
  70. // 因为遇到相相同高度的柱子,需要使用最右边的柱子来计算宽度。
  71. else if (height[i] == height[s.peek()]) {
  72. s.pop();
  73. s.push(i);
  74. }
  75. // 情况3:当前柱子高度大于栈顶柱子高度,此时会形成凹槽
  76. else {
  77. while (!s.isEmpty() && height[i] > height[s.peek()]) {
  78. int mid = s.pop(); // 记录凹槽柱子的下标
  79. if (!s.isEmpty()) {
  80. // 计算高度 min(凹槽左边高度, 凹槽右边高度) - 凹槽底部高度
  81. int h = Math.min(height[i], height[s.peek()]) - height[mid];
  82. // 计算宽度 雨水的宽度是 凹槽右边的下标 - 凹槽左边的下标 - 1
  83. // 因为只求中间的宽度
  84. int w = i - s.peek() - 1;
  85. res += h * w;
  86. }
  87. }
  88. s.push(i);
  89. }
  90. }
  91. return res;
  92. }
  93. }

*H2.27-84. 柱状图中最大的矩形

这题跟接雨水类似。
单调栈的解法,相当于以每根柱子为高,计算能得到的最大面积,类似于一个穷举?

  1. // 动态规划
  2. class Solution {
  3. public int largestRectangleArea(int[] heights) {
  4. if (heights == null || heights.length == 0) return 0;
  5. if (heights.length == 1) return heights[0];
  6. int res = 0;
  7. int size = heights.length;
  8. int[] minRight = new int[size]; // 记录每个柱子 右边第一个小于该柱子的下标
  9. int[] minLeft = new int[size]; // 记录每个柱子 左边第一个小于该柱子的下标
  10. minLeft[0] = -1; // 第一个柱子左边没有
  11. for (int i = 1; i < size; i++) {
  12. int t = i - 1; // 从左边第一个柱子开始
  13. // 动态规划,寻找左侧第一个小于该柱子的下标
  14. // 解释一下t=minLeft[t]: 当第t跟柱子高于该柱子的时候,那么直接找第t根柱子
  15. // 左侧的第一根比第t跟柱子小的柱子即可。然后再与该柱子比较
  16. while (t >= 0 && heights[t] >= heights[i]) t = minLeft[t];
  17. minLeft[i] = t;
  18. }
  19. // 记录每个柱子 右边第一个小于该柱子的下标
  20. minRight[size - 1] = size; // 注意这里初始化,防止下面while死循环
  21. for (int i = size - 2; i >= 0; i--) {
  22. int t = i + 1;
  23. // 动态规划,寻找右侧第一个小于该柱子的下标
  24. // t=minRigth[i]的思想跟找左侧的一样
  25. while (t < size && heights[t] >= heights[i]) t = minRight[t];
  26. minRight[i] = t;
  27. }
  28. // 求解
  29. for (int i = 0; i < size; i++) {
  30. int sum = (minRight[i] - minLeft[i] - 1) * heights[i];
  31. res = Math.max(sum, res);
  32. }
  33. return res;
  34. }
  35. }
  36. // 单调栈
  37. class Solution {
  38. public int largestRectangleArea(int[] heights) {
  39. if (heights == null || heights.length == 0) return 0;
  40. // 首先需要将原数组扩容,因为最左侧跟最右侧的柱子两侧没有柱子,所以应该是0
  41. int[] newHeigths = new int[heights.length + 2];
  42. for (int i = 0; i < heights.length; i++) {
  43. newHeigths[i + 1] = heights[i];
  44. }
  45. heights = newHeigths;
  46. // 栈顶到栈底的顺序应该是从大到小,跟接雨水不同,接雨水是从小到大
  47. // 本题是要找每个柱子左右两边第一个小于该柱子的柱子
  48. Stack<Integer> s = new Stack<>();
  49. s.push(0);
  50. int res = 0;
  51. for (int i = 1; i < heights.length; i++) {
  52. // 情况1:当前遍历的元素heights[i]大于栈顶元素heights[st.top()]的情况
  53. if (heights[i] > heights[s.peek()]) s.push(i);
  54. // 情况2:当前遍历的元素heights[i]等于栈顶元素heights[st.top()]的情况
  55. else if (heights[i] == heights[s.peek()]) {
  56. s.pop();
  57. s.push(i);
  58. }
  59. else {
  60. while (heights[i] < heights[s.peek()]) {
  61. int mid = s.pop();
  62. int left = s.peek(); // 左边第一个比当前元素小的柱子
  63. int right = i;
  64. int w = right - left - 1;
  65. int h = heights[mid];
  66. res = Math.max(res, w * h);
  67. }
  68. s.push(i);
  69. }
  70. }
  71. return res;
  72. }
  73. }