前言
在此篇开始前已完成代码随想录 一刷,对于算法题算是初窥门径,但是一刷的周期很长,有很多东西已经记不清了,而且有很多对我于个人来说比较妙且有用的语法表达(基于Java)没有记录。故开此篇,总体上依照代码随想录的顺序开启二刷,并记录下遇到的难点和一些感想。
由衷感谢“卡哥”的开源精神!
代码随想录:https://programmercarl.com/
在每道题的标题后面会有一些符号:
无任何符号:没有思路 or 没有通过全部样例 or 解题过程中参考了一些题解思路
✔:Acess!
❗:在编写代码的过程中遇到一些语法上的问题,或者是一些巧妙的解题思路,需要留意
Leetcode
数组
【704】二分查找✔
题目链接:704. 二分查找 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public int search (int [] nums, int target) { int left = 0 ; int right = nums.length - 1 ; int mid = (left + right) / 2 ; while (left <= right) { mid = (left + right) / 2 ; if (nums[mid] == target) { return mid; } if (nums[mid] < target) { left = mid + 1 ; } if (nums[mid] > target) { right = mid - 1 ; } } return -1 ; } }
int mid = (right - left) / 2 + left;
是一种防溢出的表达。
注意区间的表达。
【35】搜索插入位置✔
题目链接:35. 搜索插入位置 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public int searchInsert (int [] nums, int target) { int left = 0 ; int right = nums.length - 1 ; int mid = (left + right) / 2 ; while (left <= right) { mid = (left + right) / 2 ; if (nums[mid] == target) { return mid; } if (nums[mid] < target) { left = mid + 1 ; } if (nums[mid] > target) { right = mid - 1 ; } } return left; } }
使用二分法。
注意返回值。
【34】在排序数组中查找元素的第一个和最后一个位置
题目链接:34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 class Solution { public int [] searchRange(int [] nums, int target) { int [] result = {-1 , -1 }; if (nums.length == 0 ) { return result; } int left1 = 0 ; int right1 = nums.length - 1 ; int mid1 = (left1 + right1) / 2 ; int left_board = -1 ; while (left1 <= right1) { mid1 = (left1 + right1) / 2 ; if (nums[mid1] == target) { left_board = mid1; right1 = mid1 - 1 ; } if (nums[mid1] > target) { right1 = mid1 - 1 ; } if (nums[mid1] < target) { left1 = mid1 + 1 ; } } int left2 = 0 ; int right2 = nums.length - 1 ; int mid2 = (left2 + right2) / 2 ; int right_board = -1 ; while (left2 <= right2) { mid2 = (left2 + right2) / 2 ; if (nums[mid2] == target) { right_board = mid2; left2 = mid2 + 1 ; } if (nums[mid2] > target) { right2 = mid2 - 1 ; } if (nums[mid2] < target) { left2 = mid2 + 1 ; } } result[0 ] = left_board; result[1 ] = right_board; return result; } }
思路:直接利用二分法分别找到左右边界。
与基础二分法题目不同的是,如果nums[mid] == target
,则需要根据查找的左、右目标边界,相对应地移动当前的右、左边界。目的是使当前的搜索区间更靠近目标方向。
【69】x的平方根
题目链接:69. x 的平方根 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public int mySqrt (int x) { int left = 0 ; int right = x; int mid = (right + left) / 2 ; while (left <= right) { mid = (right + left) / 2 ; if ((long ) mid * mid == x) { return mid; } if ((long ) mid * mid < x) { left = mid + 1 ; } if ((long ) mid * mid > x) { right = mid - 1 ; } } return left - 1 ; } }
由于返回的值只要求整数部分,可以使用二分法查找。
注意:在计算判断语句中(long) mid * mid
不可以写为(long) (mid * mid)
,后者如果当(mid * mid)
计算结果溢出,再转为long
类型没有意义,依旧会报错。
【367】有效的完全平方数✔
题目链接:367. 有效的完全平方数 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public boolean isPerfectSquare (int num) { int left = 0 ; int right = num; int mid = (left + right) / 2 ; while (left <= right) { mid = (left + right) / 2 ; if ((long ) mid * mid == num) { return true ; } if ((long ) mid * mid < num) { left = mid + 1 ; } if ((long ) mid * mid > num) { right = mid - 1 ; } } return false ; } }
正常使用二分法解决
【27】移除元素✔
题目链接:27. 移除元素 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 class Solution { public int removeElement (int [] nums, int val) { int slow = 0 ; for (int fast = 0 ; fast < nums.length; fast++) { if (nums[fast] != val) { nums[slow] = nums[fast]; slow++; } } return slow; } }
双指针的一种思路:将快指针定义在for循环条件处,并利用for循环控制快指针移动,覆盖前面的目标值。
【26】删除有序数组中的重复项✔
题目链接:26. 删除有序数组中的重复项 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 class Solution { public int removeDuplicates (int [] nums) { int slow = 0 ; for (int fast = 0 ; fast < nums.length; fast++) { if (nums[fast] > nums[slow]) { slow++; nums[slow] = nums[fast]; } } return slow + 1 ; } }
双指针解法,这里注意要先移动指针再替换。
【283】移动零✔
题目链接:283. 移动零 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public void moveZeroes (int [] nums) { int slow = 0 ; while (slow < nums.length && nums[slow] != 0 ) { slow++; } for (int fast = slow; fast < nums.length; fast++) { if (nums[fast] != 0 ) { nums[slow] = nums[fast]; nums[fast] = 0 ; slow++; } } } }
双指针思路:先将slow指针移动到第一个0处,注意不要让slow指针超出数组范围,再初始化fast指针,初始化和slow指针一致。
【844】比较含退格的字符串✔
题目链接:844. 比较含退格的字符串 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 class Solution { public boolean backspaceCompare (String s, String t) { char [] s_list = s.toCharArray(); char [] t_list = t.toCharArray(); Stack<Character> s_stack = new Stack<>(); Stack<Character> t_stack = new Stack<>(); for (int i = 0 ; i < s_list.length; i++) { if (s_list[i] != '#' ) { s_stack.push(s_list[i]); } else if (s_list[i] == '#' ) { if (s_stack.isEmpty()) { continue ; } else { s_stack.pop(); } } } for (int i = 0 ; i < t_list.length; i++) { if (t_list[i] != '#' ) { t_stack.push(t_list[i]); } else if (t_list[i] == '#' ) { if (t_stack.isEmpty()) { continue ; } else { t_stack.pop(); } } } if (s_stack.size() != t_stack.size()) { return false ; } while (!s_stack.isEmpty()) { if (s_stack.pop() != t_stack.pop()) { return false ; } } return true ; } }
很容易联想到栈,利用栈解决。
【977】有序数组的平方✔
题目链接:977. 有序数组的平方 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public int [] sortedSquares(int [] nums) { int [] result = new int [nums.length]; int left = 0 ; int right = nums.length - 1 ; for (int i = result.length - 1 ; i >= 0 ; i--) { if (Math.abs(nums[left]) > Math.abs(nums[right])) { result[i] = nums[left] * nums[left]; left++; } else { result[i] = nums[right] * nums[right]; right--; } } return result; } }
双指针解法:左右指针从数组两边往中间靠,result数组从后往前添加,依次添加绝对值最大的数字的平方。
【209】长度最小的子数组✔
题目链接:209. 长度最小的子数组 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public int minSubArrayLen (int target, int [] nums) { int left = 0 ; int sum = 0 ; int result = Integer.MAX_VALUE; for (int right = 0 ; right < nums.length; right++) { sum += nums[right]; while (sum >= target) { result = Math.min(result, right - left + 1 ); sum -= nums[left]; left++; } } return result == Integer.MAX_VALUE ? 0 : result; } }
利用双指针构成滑动区间解决。关键思路:for循环条件内定义右边界,每轮循环扩展右边界,一旦sum满足条件,就利用while循环缩小左边界。
【904】水果成篮
题目链接:904. 水果成篮 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class Solution { public int totalFruit (int [] fruits) { Map<Integer, Integer> map = new HashMap<>(); int left = 0 ; int result = 0 ; for (int right = 0 ; right < fruits.length; right++) { if (map.containsKey(fruits[right])) { map.put(fruits[right], map.get(fruits[right]) + 1 ); result = Math.max(result, right - left + 1 ); } else { if (map.size() < 2 ) { map.put(fruits[right], 1 ); result = Math.max(result, right - left + 1 ); } else { map.put(fruits[right], 1 ); while (map.size() > 2 ) { map.put(fruits[left], map.get(fruits[left]) - 1 ); if (map.get(fruits[left]) == 0 ) { map.remove(fruits[left]); } left++; } } } } return result; } }
利用滑动窗口的思想解题,用map
记录水果种类(篮子)。
做题踩坑点:一开始想到了解题思路,但是在删除包含fruits[left]
的篮子过程中没有逐步移动left,而是直接清空map
中的fruits[left]
,再重定位left
。这样可能导致区间[left, right-1]
中包含另一种水果的数量没有及时减去,导致最终结果偏大。
【76】最小覆盖子串✔❗
题目链接:76. 最小覆盖子串 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 class Solution { public String minWindow (String s, String t) { char [] s_list = s.toCharArray(); char [] t_list = t.toCharArray(); Map<Character, Integer> s_map = new HashMap<>(); Map<Character, Integer> t_map = new HashMap<>(); for (int i = 0 ; i < t_list.length; i++) { t_map.put(t_list[i], t_map.getOrDefault(t_list[i], 0 ) + 1 ); } int left = 0 ; StringBuilder sb = new StringBuilder(); String result = s + t; for (int right = 0 ; right < s_list.length; right++) { s_map.put(s_list[right], s_map.getOrDefault(s_list[right], 0 ) + 1 ); sb.append(s_list[right]); while (judge(s_map, t_map)) { s_map.put(s_list[left], s_map.get(s_list[left]) - 1 ); left++; result = result.length() < sb.toString().length() ? result : sb.toString(); sb.deleteCharAt(0 ); } } return result = result.equals(s + t) ? "" : result; } public Boolean judge (Map<Character, Integer> s_map, Map<Character, Integer> t_map) { for (Map.Entry<Character, Integer> entry : t_map.entrySet()) { if (!s_map.containsKey(entry.getKey())) { return false ; } else if (s_map.get(entry.getKey()) < entry.getValue()) { return false ; } } return true ; } }
利用滑动区间解决问题,复杂的地方在于对字符串操作。
一些语法需要注意❗
【59】螺旋矩阵Ⅱ✔
题目链接:59. 螺旋矩阵 II - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 class Solution { public int [][] generateMatrix(int n) { int [][] matrix = new int [n][n]; int number = 1 ; int offset = 1 ; int x = 0 , y = 0 ; int round = n / 2 ; while (round > 0 ) { for (; y < n - offset; y++) { matrix[x][y] = number; number++; } for (; x < n - offset; x++) { matrix[x][y] = number; number++; } for (; y > offset - 1 ; y--) { matrix[x][y] = number; number++; } for (; x > offset - 1 ; x--) { matrix[x][y] = number; number++; } x++; y++; offset++; round--; } if (n % 2 == 1 ) { int index = n / 2 ; matrix[index][index] = n * n; } return matrix; } }
解题思路就是模拟转圈填写数字,利用offset控制每一圈的起止位置。
【54】螺旋矩阵✔
题目链接:54. 螺旋矩阵 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 class Solution { public List<Integer> spiralOrder (int [][] matrix) { List<Integer> result = new ArrayList<>(); int x = 0 , y = 0 ; int offset = 1 ; int x_length = matrix.length; int y_length = matrix[0 ].length; int round = Math.min(x_length, y_length) / 2 ; while (round > 0 ) { for (; y < y_length - offset; y++) { result.add(matrix[x][y]); } for (; x < x_length - offset; x++) { result.add(matrix[x][y]); } for (; y > offset - 1 ; y--) { result.add(matrix[x][y]); } for (; x > offset - 1 ; x--) { result.add(matrix[x][y]); } x++; y++; offset++; round--; } if (Math.min(x_length, y_length) % 2 == 1 ) { if (x_length < y_length) { for (; y < y_length - offset + 1 ; y++) { result.add(matrix[x][y]); } } else { for (; x < x_length - offset + 1 ; x++) { result.add(matrix[x][y]); } } } return result; } }
解题思路:模拟转圈遍历数字。关键在于对中心剩余数字的判定和遍历。
链表
【203】移除链表元素✔
题目链接:203. 移除链表元素 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public ListNode removeElements (ListNode head, int val) { ListNode dummyHead = new ListNode(); dummyHead.next = head; ListNode temp = dummyHead; while (temp.next != null ) { if (temp.next.val == val) { temp.next = temp.next.next; } else { temp = temp.next; } } return dummyHead.next; } }
注意这里的一个细节:设置dummyHead节点链接在头节点之前,使得头节点和其他节点的处理方式一致,不必再单独处理头节点。
【707】设计链表❗
题目链接:707. 设计链表 - 力扣(LeetCode)
题解一:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 class ListNode { int val; ListNode next; ListNode() {}; ListNode(int val) { this .val = val; } } class MyLinkedList { int size; ListNode head; public MyLinkedList () { size = 0 ; head = new ListNode(0 ); } public int get (int index) { if (index < 0 || index >= size) { return -1 ; } ListNode cur = head; for (int i = 0 ; i <= index; i++) { cur = cur.next; } return cur.val; } public void addAtHead (int val) { addAtIndex(0 , val); } public void addAtTail (int val) { addAtIndex(size, val); } public void addAtIndex (int index, int val) { if (index > size) { return ; } if (index < 0 ) { index = 0 ; } ListNode pre = head; for (int i = 0 ; i < index; i++) { pre = pre.next; } ListNode newNode = new ListNode(val); newNode.next = pre.next; pre.next = newNode; size++; } public void deleteAtIndex (int index) { if (index < 0 || index >=size) { return ; } ListNode pre = head; for (int i = 0 ; i < index; i++) { pre = pre.next; } pre.next = pre.next.next; size--; } }
采用单链表的设计思路,需要注意以下几点:
定义单链表的ListNode结构❗
初始化的时候size为0(表示没有节点),但是会存在一个虚拟头节点。因此在get()
、addAtIndex()
、deleteAtIndex()
时需要注意for循环的条件。
题解二:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 class ListNode { int val; ListNode next; ListNode prev; ListNode() {}; ListNode(int val) { this .val = val; } } class MyLinkedList { int size; ListNode head; ListNode tail; public MyLinkedList () { size = 0 ; head = new ListNode(); tail = new ListNode(); head.next = tail; tail.prev = head; } public int get (int index) { if (index < 0 || index >= size) { return -1 ; } ListNode cur; if (index < size / 2 ) { cur = head; for (int i = 0 ; i <= index; i++) { cur = cur.next; } } else { cur = tail; for (int i = 0 ; i < size - index; i++) { cur = cur.prev; } } return cur.val; } public void addAtHead (int val) { addAtIndex(0 , val); } public void addAtTail (int val) { addAtIndex(size, val); } public void addAtIndex (int index, int val) { if (index < 0 || index > size) { return ; } ListNode newNode = new ListNode(val); if (index < size / 2 ) { ListNode preNode = head; for (int i = 0 ; i < index; i++) { preNode = preNode.next; } newNode.next = preNode.next; newNode.prev = preNode; preNode.next.prev = newNode; preNode.next = newNode; } else { ListNode postNode = tail; for (int i = 0 ; i < size - index; i++) { postNode = postNode.prev; } newNode.next = postNode; newNode.prev = postNode.prev; postNode.prev.next = newNode; postNode.prev = newNode; } size++; } public void deleteAtIndex (int index) { if (index < 0 || index >= size) { return ; } if (index < size / 2 ) { ListNode preNode = head; for (int i = 0 ; i < index; i++) { preNode = preNode.next; } preNode.next.next.prev = preNode; preNode.next = preNode.next.next; } else { ListNode postNode = tail; for (int i = 0 ; i < size - index - 1 ; i++) { postNode = postNode.prev; } postNode.prev.prev.next = postNode; postNode.prev = postNode.prev.prev; } size--; } }
采用双向链表的设计思路,需要注意以下几点:
定义双向链表的ListNode结构❗
在get()
、addAtIndex()
、deleteAtIndex()
时需要注意for循环的条件,以及首尾节点的初始化,还要注意操作节点时要同时改变前序指针和后序指针指向。
【206】反转链表
题目链接:206. 反转链表 - 力扣(LeetCode)
题解一:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public ListNode reverseList (ListNode head) { return reverse(null , head); } public ListNode reverse (ListNode prev, ListNode cur) { if (cur == null ) { return prev; } ListNode temp = cur.next; cur.next = prev; return reverse(cur, temp); } }
递归思路:(从前往后)定义一个递归函数,目的是把当前节点链接到前一个节点上。
注意返回条件和返回值。
题解二:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution { public ListNode reverseList (ListNode head) { ListNode prev = null ; ListNode cur = head; ListNode temp = null ; while (cur != null ) { temp = cur.next; cur.next = prev; prev = cur; cur = temp; } return prev; } }
双指针写法。注意初始化和循环条件
【24】两两交换链表中的节点
题目链接:24. 两两交换链表中的节点 - 力扣(LeetCode)
题解一:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution { public ListNode swapPairs (ListNode head) { if (head == null || head.next == null ) { return head; } ListNode first = head; ListNode second = first.next; ListNode third = second.next; second.next = first; first.next = swapPairs(third); return second; } }
递归思路:把当前节点和当前节点的下一个节点看成一对。要理解swapPairs()
的目的在于交换当前节点(first
)和当前节点的下一个节点(second
),返回下一个节点(second
)。
题解二:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public ListNode swapPairs (ListNode head) { ListNode dumyhead = new ListNode(-1 ); dumyhead.next = head; ListNode cur = dumyhead; ListNode temp; ListNode firstnode; ListNode secondnode; while (cur.next != null && cur.next.next != null ) { temp = cur.next.next.next; firstnode = cur.next; secondnode = cur.next.next; cur.next = secondnode; secondnode.next = firstnode; firstnode.next = temp; cur = firstnode; } return dumyhead.next; } }
迭代法。
【19】删除链表的倒数第N个节点✔❗
题目链接:19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)
解法一:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public ListNode removeNthFromEnd (ListNode head, int n) { int size = 0 ; ListNode cur = head; while (cur != null ) { cur = cur.next; size++; } ListNode dummyHead = new ListNode(); dummyHead.next = head; ListNode preNode = dummyHead; for (int i = 1 ; i <= size - n; i++) { preNode = preNode.next; } preNode.next = preNode.next.next; return dummyHead.next; } }
很自然地想到先遍历一遍链表获得长度,再删除第N个节点。
解法二:❗
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution { public ListNode removeNthFromEnd (ListNode head, int n) { ListNode dummyNode = new ListNode(0 ); dummyNode.next = head; ListNode fastIndex = dummyNode; ListNode slowIndex = dummyNode; for (int i = 0 ; i <= n; i++) { fastIndex = fastIndex.next; } while (fastIndex != null ) { fastIndex = fastIndex.next; slowIndex = slowIndex.next; } if (slowIndex.next != null ) { slowIndex.next = slowIndex.next.next; } return dummyNode.next; } }
双指针思路:由于需要找到倒数第N个节点,因此可以使用两个指针 fastIndex
和 slowIndex
同时对链表进行遍历,并且fastIndex
比slowIndex
超前 N个节点。当fastIndex
遍历到链表的末尾时,slowIndex
就恰好处于倒数第N个节点。
【160】相交链表✔
题目链接:160. 相交链表 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public class Solution { public ListNode getIntersectionNode (ListNode headA, ListNode headB) { int size_A = 0 ; int size_B = 0 ; ListNode cur_A = headA; ListNode cur_B = headB; while (cur_A != null ) { cur_A = cur_A.next; size_A++; } while (cur_B != null ) { cur_B = cur_B.next; size_B++; } ListNode cur_short; ListNode cur_long; if (size_A > size_B) { cur_short = headB; cur_long = headA; } else { cur_short = headA; cur_long = headB; } for (int i = 1 ; i <= Math.abs(size_A - size_B); i++) { cur_long = cur_long.next; } while (cur_long != null && cur_short != null ) { if (cur_long == cur_short) { return cur_long; } cur_long = cur_long.next; cur_short = cur_short.next; } return null ; } }
对齐两个链表的尾部,长链表需要提前移动和短链表的长度差,然后从两个链表同时出发进行比较。
【142】环形链表❗
题目链接:142. 环形链表 II - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class Solution { public ListNode detectCycle (ListNode head) { ListNode fast = head; ListNode slow = head; while (fast != null && fast.next != null ) { slow = slow.next; fast = fast.next.next; if (slow == fast) { ListNode index1 = head; ListNode index2 = slow; while (index1 != index2) { index1 = index1.next; index2 = index2.next; } return index1; } } return null ; } }
快慢指针思路。fast
快指针从头出发,每次移动两步,slow
慢指针从头出发,每次移动一步。直到快慢指针相遇,同时从相遇节点(index2
)和链表头节点(index1
)出发每次移动一步,直到相遇即为入环的第一个节点。
需要注意最外侧的循环条件。❗
哈希表
【242】有效的字母异位词✔❗
题目链接:242. 有效的字母异位词 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public boolean isAnagram (String s, String t) { int [] result = new int [26 ]; char [] s_list = s.toCharArray(); char [] t_list = t.toCharArray(); for (int i = 0 ; i < s_list.length; i++) { result[s_list[i] - 'a' ] += 1 ; } for (int i = 0 ; i < t_list.length; i++) { result[t_list[i] - 'a' ] -= 1 ; } for (int i = 0 ; i < result.length; i++) { if (result[i] != 0 ) { return false ; } } return true ; } }
哈希的思路,但是用数组解决。
注意[character - 'a']
的表达❗
【383】赎金信✔
题目链接:383. 赎金信 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public boolean canConstruct (String ransomNote, String magazine) { int [] result = new int [26 ]; char [] ransomNote_list = ransomNote.toCharArray(); char [] magazine_list = magazine.toCharArray(); for (int i = 0 ; i < magazine_list.length; i++) { result[magazine_list[i] - 'a' ] += 1 ; } for (int i = 0 ; i < ransomNote_list.length; i++) { result[ransomNote_list[i] - 'a' ] -= 1 ; } for (int i = 0 ; i < result.length; i++) { if (result[i] < 0 ) { return false ; } } return true ; } }
同【242】有效的字母异位词(242. 有效的字母异位词 - 力扣(LeetCode) )解题思路一致。哈希的思路,但是用数组解决。
【49】字母异位词分组✔❗
题目链接:49. 字母异位词分组 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution { public List<List<String>> groupAnagrams(String[] strs) { Map<String, List<String>> map = new HashMap<String, List<String>>(); for (String str : strs) { char [] array = str.toCharArray(); Arrays.sort(array); String key = new String(array); List<String> list = map.getOrDefault(key, new ArrayList<String>()); list.add(str); map.put(key, list); } List<List<String>> result = new ArrayList<List<String>>(map.values()); return result; } }
利用哈希的解题关键:由于是字母异位词,那么每个字母异位词排序后是相同的。
创建Map,每一个entry的Key
是排序后的字符串str
,其值是一个List<String>
,用于存储Key
的字母异位词,最后用一个结果接收。
注意:❗
String key = new String(array)
将字符数组array
转化为字符串
map.values()
可以返回所有value值构成的数组
【438】找到字符串中所有字母异位词✔❗
题目链接:438. 找到字符串中所有字母异位词 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Solution { public List<Integer> findAnagrams (String s, String p) { int s_len = s.length(); int p_len = p.length(); List<Integer> result = new ArrayList<>(); if (s_len < p_len) { return result; } int [] s_count = new int [26 ]; int [] p_count = new int [26 ]; for (int i = 0 ; i < p_len; i++) { s_count[s.charAt(i) - 'a' ] += 1 ; p_count[p.charAt(i) - 'a' ] += 1 ; } if (Arrays.equals(s_count, p_count)) { result.add(0 ); } for (int i = 0 ; i < s_len - p_len; i++) { s_count[s.charAt(i) - 'a' ] -= 1 ; s_count[s.charAt(i + p_len) - 'a' ] += 1 ; if (Arrays.equals(s_count, p_count)) { result.add(i + 1 ); } } return result; } }
利用滑动窗口依次进行判断,用字符数组代替哈希记录字符串中字母出现的词频,进行比较。
注意:❗
快速比较两个数组是否相同:Arrays.equals(s_count, p_count)
获取字符串中指定下标的字母:s.charAt(i)
【349】两个数组的交集✔❗
题目链接:349. 两个数组的交集 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public int [] intersection(int [] nums1, int [] nums2) { Set<Integer> record = new HashSet<>(); Set<Integer> res_set = new HashSet<>(); for (int i : nums1) { record.add(i); } for (int i : nums2) { if (record.contains(i)) { res_set.add(i); } } return res_set.stream().mapToInt(x -> x).toArray(); } }
由于返回的结果是去重的,因此考虑Set()
。
注意:❗
stream()的快速返回写法:res_set.stream().mapToInt(x -> x).toArray()
【350】两个数组的交集Ⅱ✔
题目链接:350. 两个数组的交集 II - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Solution { public int [] intersect(int [] nums1, int [] nums2) { Map<Integer, Integer> record_num1 = new HashMap<>(); Map<Integer, Integer> record_num2 = new HashMap<>(); for (int i : nums1) { record_num1.put(i, record_num1.getOrDefault(i, 0 ) + 1 ); } for (int i : nums2) { record_num2.put(i, record_num2.getOrDefault(i, 0 ) + 1 ); } List<Integer> list = new ArrayList<>(); for (Map.Entry<Integer, Integer> entry : record_num1.entrySet()) { if (record_num2.containsKey(entry.getKey())) { int count = Math.min(entry.getValue(), record_num2.get(entry.getKey())); for (int i = 0 ; i < count; i++) { list.add(entry.getKey()); } } } return list.stream().mapToInt(x -> x).toArray(); } }
由于涉及公共元素出现的最小次数,考虑使用Map()
【202】快乐数✔❗
题目链接:202. 快乐数 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public boolean isHappy (int n) { Set<Integer> record = new HashSet<>(); while (n != 1 ) { record.add(n); n = sum(n); if (record.contains(n)) { return false ; } } return true ; } public int sum (int n) { int sum = 0 ; while (n > 0 ) { int ge = n % 10 ; sum += ge * ge; n = n / 10 ; } return sum; } }
解题关键:题目中说了会无限循环,那么也就是说求和的过程中,sum会重复出现❗
【1】两数之和✔
题目链接:1. 两数之和 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class Solution { public int [] twoSum(int [] nums, int target) { int [] res = new int [2 ]; if (nums == null || nums.length == 0 ){ return res; } Map<Integer, Integer> map = new HashMap<>(); for (int i = 0 ; i < nums.length; i++){ int temp = target - nums[i]; if (map.containsKey(temp)){ res[1 ] = i; res[0 ] = map.get(temp); break ; } map.put(nums[i], i); } return res; } }
用哈希的方法要快一些。
【454】四数相加Ⅱ
题目链接:454. 四数相加 II - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public int fourSumCount (int [] nums1, int [] nums2, int [] nums3, int [] nums4) { int result = 0 ; Map<Integer, Integer> map = new HashMap<Integer, Integer>(); for (int i : nums1) { for (int j : nums2) { int sum = i + j; map.put(sum, map.getOrDefault(sum, 0 ) + 1 ); } } for (int i : nums3) { for (int j : nums4) { int sum = i + j; result += map.getOrDefault(0 - sum, 0 ); } } return result; } }
利用哈希表,先统计前两个数组的排列组合的和,并统计出现的次数,再统计后两个数组的排列组合的和,如果四数之和为0,那么更新result
【15】三数之和❗
题目链接:15. 三数之和 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Solution { public List<List<Integer>> threeSum(int [] nums) { Set<List<Integer>> set = new HashSet<>(); Arrays.sort(nums); for (int i = 0 ; i < nums.length - 2 ; i++) { int left = i + 1 ; int right = nums.length - 1 ; while (left < right) { int sum = nums[i] + nums[left] + nums[right]; if (sum < 0 ) { left++; } else if (sum > 0 ) { right--; } else { List<Integer> res = Arrays.asList(nums[i], nums[left], nums[right]); set.add(res); left++; right--; } } } List<List<Integer>> result = new ArrayList<>(); for (List<Integer> item : set) { result.add(item); } return result; } }
解题思路:双指针 + 哈希去重。
首先对原始数组排序(关键),然后在for循环定义最左侧指针i
,在循环内部:定义指针left = i + 1
和指针right = nums.length - 1
,随着sum和目标结果(0)的关系移动left
和right
指针。利用set集合对结果进行去重。
注意:❗
对数组排序:Arrays.sort(nums)
对集合排序:Collections.sort(res)
【18】四数之和✔
题目链接:18. 四数之和 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Solution { public List<List<Integer>> fourSum(int [] nums, int target) { Set<List<Integer>> set = new HashSet<>(); Arrays.sort(nums); for (int i = 0 ; i < nums.length - 3 ; i++) { for (int j = i + 1 ; j < nums.length - 2 ; j++) { int left = j + 1 ; int right = nums.length - 1 ; while (left < right) { long sum = (long ) nums[i] + nums[j] + nums[left] + nums[right]; if (sum < target) { left++; } else if (sum > target) { right--; } else { List<Integer> res = Arrays.asList(nums[i], nums[j], nums[left], nums[right]); set.add(res); left++; right--; } } } } List<List<Integer>> result = new ArrayList<>(); for (List<Integer> item : set) { result.add(item); } return result; } }
解题思路:双指针 + 哈希去重。(同【15】三数之和(15. 三数之和 - 力扣(LeetCode) )一样)
字符串
【344】反转字符串✔❗
题目链接:344. 反转字符串 - 力扣(LeetCode)
题解一:
1 2 3 4 5 6 7 8 9 10 class Solution { public void reverseString (char [] s) { int count = s.length / 2 ; for (int i = 0 ; i < count; i++) { char temp = s[i]; s[i] = s[s.length - i - 1 ]; s[s.length - i - 1 ] = temp; } } }
很简单,两两交换位置。注意下标。
题解二:
1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution { public void reverseString (char [] s) { int left = 0 ; int right = s.length - 1 ; while (left < right) { char temp = s[left]; s[left] = s[right]; s[right] = temp; left++; right--; } } }
个人更推荐这种方式,下标不容易出错。❗
【541】反转字符串Ⅱ❗
题目链接:541. 反转字符串 II - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public String reverseStr (String s, int k) { char [] s_list = s.toCharArray(); for (int i = 0 ; i < s.length(); i += 2 * k) { reverse(s_list, i, Math.min(i + k, s.length()) - 1 ); } return new String(s_list); } public void reverse (char [] arr, int left, int right) { while (left < right) { char temp = arr[left]; arr[left] = arr[right]; arr[right] = temp; left++; right--; } } }
思路❗:反转每个下标从 2k 的倍数开始的,长度为 k 的子串。若该子串长度不足 k ,则反转整个子串。
【151】反转字符串中的单词✔❗
题目链接:151. 反转字符串中的单词 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Solution { public String reverseWords (String s) { Stack<Character> stack_str = new Stack<>(); Stack<Character> stack_word = new Stack<>(); String new_s = s.trim(); char [] s_list = new_s.toCharArray(); StringBuilder sb = new StringBuilder(); for (int i = 0 ; i < s_list.length; i++) { if (s_list[i] == ' ' && s_list[i + 1 ] == ' ' ) { continue ; } stack_str.push(s_list[i]); } while (!stack_str.isEmpty()) { if (stack_str.peek() != ' ' ) { stack_word.push(stack_str.pop()); } else { while (!stack_word.isEmpty()) { sb.append(stack_word.pop()); } sb.append(' ' ); stack_str.pop(); } } while (!stack_word.isEmpty()) { sb.append(stack_word.pop()); } return sb.toString(); } }
思路:利用两个栈处理,一个栈stack_str
用于添加整个字符串,另一个栈stack_word
用于处理单词。
关键在于字符串空格的处理❗:
去除字符串首尾空格trim()
:String new_s = s.trim();
处理字符串中间连续的空格。
【28】找出字符串中第一个匹配项的下标✔
题目链接:28. 找出字符串中第一个匹配项的下标 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public int strStr (String haystack, String needle) { char [] haystack_list = haystack.toCharArray(); char [] needle_list = needle.toCharArray(); for (int i = 0 ; i <= haystack_list.length - needle_list.length; i++) { int index = i; boolean flag = true ; for (int j = 0 ; j < needle_list.length; j++) { if (needle_list[j] != haystack_list[index]) { flag = false ; break ; } index++; } if (flag == true ) { return i; } } return -1 ; } }
暴力解法。还可以使用KMP算法。
【459】重复的子字符串
题目链接:459. 重复的子字符串 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Solution { public boolean repeatedSubstringPattern (String s) { char [] s_list = s.toCharArray(); for (int i = 0 ; i < s_list.length / 2 ; i++) { if (judge(s_list, i)) { return true ; } } return false ; } public boolean judge (char [] s_list, int sub_end) { if (s_list.length % (sub_end + 1 ) != 0 ) { return false ; } int sub_start = 0 ; for (int i = sub_end + 1 ; i < s_list.length; i++) { if (s_list[sub_start % (sub_end + 1 )] != s_list[i]) { return false ; } sub_start++; } return true ; } }
暴力解法,也可以使用KMP算法。
栈与队列
【232】用栈实现队列✔
题目链接:232. 用栈实现队列 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 class MyQueue { Stack<Integer> stack_in; Stack<Integer> stack_out; public MyQueue () { stack_in = new Stack<>(); stack_out = new Stack<>(); } public void push (int x) { stack_in.push(x); } public int pop () { while (!stack_in.isEmpty()) { stack_out.push(stack_in.pop()); } int result = stack_out.pop(); while (!stack_out.isEmpty()) { stack_in.push(stack_out.pop()); } return result; } public int peek () { while (!stack_in.isEmpty()) { stack_out.push(stack_in.pop()); } int result = stack_out.peek(); while (!stack_out.isEmpty()) { stack_in.push(stack_out.pop()); } return result; } public boolean empty () { return stack_in.isEmpty(); } }
利用两个栈结构实现
【225】用队列实现栈✔❗
题目链接:225. 用队列实现栈 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 class MyStack { int size; Queue<Integer> queue_in; Queue<Integer> queue_out; public MyStack () { queue_in = new LinkedList<>(); queue_out = new LinkedList<>(); } public void push (int x) { queue_in.add(x); size++; } public int pop () { for (int i = 0 ; i < size - 1 ; i++) { queue_out.add(queue_in.poll()); } int result = queue_in.poll(); while (!queue_out.isEmpty()) { queue_in.add(queue_out.poll()); } size--; return result; } public int top () { for (int i = 0 ; i < size - 1 ; i++) { queue_out.add(queue_in.poll()); } int result = queue_in.peek(); queue_out.add(queue_in.poll()); while (!queue_out.isEmpty()) { queue_in.add(queue_out.poll()); } return result; } public boolean empty () { return queue_in.isEmpty(); } }
注意❗:
需要有一个size
记录数量,方便取队列的最后一个值
队列的初始化:Queue<Integer> queue_in = new LinkedList<>();
LinkedList
结构:添加元素方法:add()
;弹出元素方法:poll()
【20】有效的括号✔
题目链接:20. 有效的括号 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Solution { public boolean isValid (String s) { Stack<Character> stack = new Stack<>(); char [] s_list = s.toCharArray(); for (int i = 0 ; i < s_list.length; i++) { if (s_list[i] == '(' || s_list[i] == '[' || s_list[i] == '{' ) { stack.push(s_list[i]); } else { if (!stack.isEmpty() && s_list[i] == ')' && stack.pop() == '(' ) { continue ; } else if (!stack.isEmpty() && s_list[i] == ']' && stack.pop() == '[' ) { continue ; } else if (!stack.isEmpty() && s_list[i] == '}' && stack.pop() == '{' ) { continue ; } else { return false ; } } } return stack.isEmpty(); } }
利用栈解决,遍历字符串,如果是左括号,直接入栈;如果是右括号,需要弹栈判断。
【1047】删除字符串中的所有相邻重复项✔❗
题目链接:1047. 删除字符串中的所有相邻重复项 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution { public String removeDuplicates (String s) { Stack<Character> stack = new Stack<>(); char [] s_list = s.toCharArray(); for (int i = 0 ; i < s_list.length; i++) { if (stack.isEmpty()) { stack.push(s_list[i]); } else { if (s_list[i] == stack.peek()) { stack.pop(); continue ; } else { stack.push(s_list[i]); } } } StringBuilder sb = new StringBuilder(); while (!stack.isEmpty()) { sb.insert(0 , stack.pop()); } return sb.toString(); } }
注意❗:StringBuilder
的插入方法:sb.insert(0, stack.pop());
【150】逆波兰表达式求值✔❗
题目链接:150. 逆波兰表达式求值 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution { public int evalRPN (String[] tokens) { Stack<String> stack = new Stack<>(); for (int i = 0 ; i < tokens.length; i++) { if (!tokens[i].equals("+" ) && !tokens[i].equals("-" ) && !tokens[i].equals("*" ) && !tokens[i].equals("/" )) { stack.push(tokens[i]); } else if (tokens[i].equals("+" )) { int x = Integer.parseInt(stack.pop()); int y = Integer.parseInt(stack.pop()); stack.push((y + x) + "" ); } else if (tokens[i].equals("-" )) { int x = Integer.parseInt(stack.pop()); int y = Integer.parseInt(stack.pop()); stack.push((y - x) + "" ); } else if (tokens[i].equals("*" )) { int x = Integer.parseInt(stack.pop()); int y = Integer.parseInt(stack.pop()); stack.push((y * x) + "" ); } else if (tokens[i].equals("/" )) { int x = Integer.parseInt(stack.pop()); int y = Integer.parseInt(stack.pop()); stack.push((y / x) + "" ); } } return Integer.parseInt(stack.pop()); } }
利用栈解决。
注意❗:
字符串比较:tokens[i].equals("+")
字符串转化为数字:Integer.parseInt(stack.pop());
【239】滑动窗口的最大值❗
题目链接:239. 滑动窗口最大值 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution { public int [] maxSlidingWindow(int [] nums, int k) { Deque<Integer> deque = new LinkedList<>(); int [] result = new int [nums.length - k + 1 ]; for (int i = 0 ; i < k; i++) { while (!deque.isEmpty() && deque.peekLast() < nums[i]) { deque.removeLast(); } deque.addLast(nums[i]); } result[0 ] = deque.getFirst(); for (int i = k; i < nums.length; i++) { if (deque.peekFirst() == nums[i - k]) { deque.removeFirst(); } while (!deque.isEmpty() && deque.peekLast() < nums[i]) { deque.removeLast(); } deque.addLast(nums[i]); result[i - k + 1 ] = deque.getFirst(); } return result; } }
暴力解法超时
思路:需要利用双端队列结构维持一个单调减的滑动窗口,队首始终为滑动窗口的最大值。关键点在于,如果当前添加的元素大于滑动窗口内队首方向的其他元素,则需要将其他元素删除(没有维护的必要)。更详细的思路参考:代码随想录
注意❗:
双端队列结构Deque
:Deque<Integer> deque = new LinkedList<>();
Deque
的常用方法:
addFirst(E e)
:在队列头部插入元素
addLast(E e)
:在队列尾部插入元素
removeFirst()
:移除并返回队列头部的元素
removeLast()
:移除并返回队列尾部的元素
getFirst()
:获取但不移除队列头部的元素
getLast()
:获取但不移除队列尾部的元素
【347】前K个高频元素❗
题目链接:347. 前 K 个高频元素 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public int [] topKFrequent(int [] nums, int k) { Map<Integer, Integer> map = new HashMap<>(); for (int i = 0 ; i < nums.length; i++) { map.put(nums[i], map.getOrDefault(nums[i], 0 ) + 1 ); } PriorityQueue<int []> pq = new PriorityQueue<>((o1, o2) -> o2[1 ] - o1[1 ]); for (Map.Entry<Integer, Integer> entry : map.entrySet()) { int [] temp = new int [2 ]; temp[0 ] = entry.getKey(); temp[1 ] = entry.getValue(); pq.add(temp); } int [] result = new int [k]; for (int i = 0 ; i < result.length; i++) { result[i] = pq.poll()[0 ]; } return result; } }
思路:利用大根堆解决。首先创建Map集合用于记录nums中出现的元素及其频率。利用优先级队列实现大根堆,将Map中的entry转化为int数组进行排序存储,最后前k个元素就是目标结果。
注意❗:利用优先级队列实现堆:
大根堆:PriorityQueue<int[]> pq = new PriorityQueue<>((o1, o2) -> o1 - o2);
小根堆:PriorityQueue<int[]> pq = new PriorityQueue<>((o1, o2) -> o2 - o1);
二叉树
【144】二叉树的前序遍历✔❗
题目链接:144. 二叉树的前序遍历 - 力扣(LeetCode)
题解(递归):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { List<Integer> result = new ArrayList<>(); public List<Integer> preorderTraversal (TreeNode root) { recur(root); return result; } public void recur (TreeNode root) { if (root == null ) { return ; } result.add(root.val); recur(root.left); recur(root.right); } }
递归法
题解(迭代)❗:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution { public List<Integer> preorderTraversal (TreeNode root) { List<Integer> result = new ArrayList<>(); if (root == null ) { return result; } Stack<TreeNode> stack = new Stack<>(); stack.push(root); while (!stack.isEmpty()) { TreeNode node = stack.pop(); result.add(node.val); if (node.right != null ) { stack.push(node.right); } if (node.left != null ) { stack.push(node.left); } } return result; } }
需要使用栈结构来实现迭代法。需要注意的是二叉树的前序遍历在入栈操作时要先处理右节点❗
【94】二叉树的中序遍历✔❗
题目链接:94. 二叉树的中序遍历 - 力扣(LeetCode)
题解(递归):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { List<Integer> result = new ArrayList<>(); public List<Integer> inorderTraversal (TreeNode root) { recur(root); return result; } public void recur (TreeNode root) { if (root == null ) { return ; } recur(root.left); result.add(root.val); recur(root.right); } }
题解(迭代)❗:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution { public List<Integer> inorderTraversal (TreeNode root) { List<Integer> result = new ArrayList<>(); if (root == null ){ return result; } Stack<TreeNode> stack = new Stack<>(); TreeNode cur = root; while (cur != null || !stack.isEmpty()){ if (cur != null ){ stack.push(cur); cur = cur.left; } else { cur = stack.pop(); result.add(cur.val); cur = cur.right; } } return result; } }
注意中序遍历的迭代法和前序以及后序遍历的区别❗ 需要利用指针控制节点访问顺序。
【145】二叉树的后序遍历✔❗
题目链接:145. 二叉树的后序遍历 - 力扣(LeetCode)
题解(递归):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { List<Integer> result = new ArrayList<>(); public List<Integer> postorderTraversal (TreeNode root) { recur(root); return result; } public void recur (TreeNode root) { if (root == null ) { return ; } recur(root.left); recur(root.right); result.add(root.val); } }
题解(迭代)❗:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { public List<Integer> postorderTraversal (TreeNode root) { List<Integer> result = new ArrayList<>(); if (root == null ) { return result; } Stack<TreeNode> stack = new Stack<>(); stack.push(root); while (!stack.isEmpty()) { TreeNode node = stack.pop(); result.add(node.val); if (node.left != null ) { stack.push(node.left); } if (node.right != null ) { stack.push(node.right); } } Collections.reverse(result); return result; } }
后序遍历的入栈顺序和前序遍历相反,最后还需要反转数组。
注意❗:利用Collections
翻转数组:Collections.reverse(result);
【102】二叉树的层序遍历❗
题目链接:102. 二叉树的层序遍历 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution { public List<List<Integer>> levelOrder(TreeNode root) { List<List<Integer>> result = new ArrayList<>(); if (root == null ) { return result; } Queue<TreeNode> queue = new LinkedList<>(); queue.add(root); while (!queue.isEmpty()) { int count = queue.size(); List<Integer> list = new ArrayList<>(); while (count > 0 ) { TreeNode node = queue.poll(); list.add(node.val); if (node.left != null ) { queue.add(node.left); } if (node.right != null ) { queue.add(node.right); } count--; } result.add(list); } return result; } }
以上是层序遍历的标准模板❗(利用队列实现)
【107】二叉树的层序遍历Ⅱ✔❗
题目链接:107. 二叉树的层序遍历 II - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution { public List<List<Integer>> levelOrderBottom(TreeNode root) { List<List<Integer>> result = new ArrayList<>(); if (root == null ) { return result; } Queue<TreeNode> queue = new LinkedList<>(); queue.add(root); while (!queue.isEmpty()) { int count = queue.size(); List<Integer> list = new ArrayList<>(); while (count > 0 ) { TreeNode node = queue.poll(); list.add(node.val); if (node.left != null ) { queue.add(node.left); } if (node.right != null ) { queue.add(node.right); } count--; } result.add(0 , list); } return result; } }
整体思路同基础的层序遍历一样,需要注意的是最后收集结果。
注意:ArrayList
插入方法:result.add(0, list);
【199】二叉树的右视图✔
题目链接:199. 二叉树的右视图 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution { public List<Integer> rightSideView (TreeNode root) { List<Integer> result = new ArrayList<>(); if (root == null ) { return result; } Queue<TreeNode> queue = new LinkedList<>(); queue.add(root); while (!queue.isEmpty()) { int count = queue.size(); while (count > 0 ) { TreeNode node = queue.poll(); if (count == 1 ) { result.add(node.val); } if (node.left != null ) { queue.add(node.left); } if (node.right != null ) { queue.add(node.right); } count--; } } return result; } }
整体思路同基础的层序遍历一样,就是需要在内层循环中,判断当前弹出节点是否是该层的最后一个节点,如果是,则收集结果;如果不是则略过。
【637】二叉树的层平均值✔
题目连接:637. 二叉树的层平均值 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution { public List<Double> averageOfLevels (TreeNode root) { List<Double> result = new ArrayList<>(); if (root == null ) { return result; } Queue<TreeNode> queue = new LinkedList<>(); queue.add(root); while (!queue.isEmpty()) { int count = queue.size(); double layer_sum = 0.0 ; for (int i = 0 ; i < count; i++) { TreeNode node = queue.poll(); layer_sum += node.val; if (node.left != null ) { queue.add(node.left); } if (node.right != null ) { queue.add(node.right); } } result.add(layer_sum / count); } return result; } }
整体思路同基础的层序遍历一样,需要注意在计算平均值时继续使用count,内层循环用for循环
【429】N叉树的层序遍历✔
题目链接:429. N 叉树的层序遍历 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution { public List<List<Integer>> levelOrder(Node root) { List<List<Integer>> result = new ArrayList<>(); if (root == null ) { return result; } Queue<Node> queue = new LinkedList<>(); queue.add(root); while (!queue.isEmpty()) { int count = queue.size(); List<Integer> list = new ArrayList<>(); while (count > 0 ) { Node node = queue.poll(); list.add(node.val); if (node.children != null ) { List<Node> children_list = node.children; for (Node children_node : children_list) { queue.add(children_node); } } count--; } result.add(list); } return result; } }
整体思路同基础的层序遍历一样,就是在处理子节点时为一个集合。
【515】在每个树行中找最大值✔
题目链接:515. 在每个树行中找最大值 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution { public List<Integer> largestValues (TreeNode root) { List<Integer> result = new ArrayList<>(); if (root == null ) { return result; } Queue<TreeNode> queue = new LinkedList<>(); queue.add(root); while (!queue.isEmpty()) { int count = queue.size(); int max = Integer.MIN_VALUE; while (count > 0 ) { TreeNode node = queue.poll(); max = max < node.val ? node.val : max; if (node.left != null ) { queue.add(node.left); } if (node.right != null ) { queue.add(node.right); } count--; } result.add(max); } return result; } }
整体思路同基础的层序遍历一样,就是在处理每层时取最大值。
【116】填充每个节点的下一个右侧节点指针✔
题目链接:116. 填充每个节点的下一个右侧节点指针 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Solution { public Node connect (Node root) { if (root == null ) { return null ; } Queue<Node> queue = new LinkedList<>(); queue.add(root); while (!queue.isEmpty()) { int count = queue.size(); List<Node> list = new ArrayList<>(); for (int i = 0 ; i < count; i++) { Node node = queue.poll(); list.add(node); if (node.left != null ) { queue.add(node.left); } if (node.right != null ) { queue.add(node.right); } } for (int i = 0 ; i < count - 1 ; i++) { list.get(i).next = list.get(i + 1 ); } list.get(count - 1 ).next = null ; } return root; } }
整体思路同基础的层序遍历一样。需要用一个集合来临时存储每一层节点,然后遍历该集合使得前一个节点的指针指向后一个节点,最后单独处理每层的最后一个节点。
【117】填充每个节点的下一个右侧节点指针Ⅱ✔
题目链接:117. 填充每个节点的下一个右侧节点指针 II - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Solution { public Node connect (Node root) { if (root == null ) { return null ; } Queue<Node> queue = new LinkedList<>(); queue.add(root); while (!queue.isEmpty()) { int count = queue.size(); List<Node> list = new ArrayList<>(); for (int i = 0 ; i < count; i++) { Node node = queue.poll(); list.add(node); if (node.left != null ) { queue.add(node.left); } if (node.right != null ) { queue.add(node.right); } } for (int i = 0 ; i < count - 1 ; i++) { list.get(i).next = list.get(i + 1 ); } list.get(count - 1 ).next = null ; } return root; } }
同116. 填充每个节点的下一个右侧节点指针 - 力扣(LeetCode) 解法一样。
【104】二叉树的最大深度✔
题目链接:104. 二叉树的最大深度 - 力扣(LeetCode)
题解(BFS):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { public int maxDepth (TreeNode root) { int depth = 0 ; if (root == null ) { return depth; } Queue<TreeNode> queue = new LinkedList<>(); queue.add(root); while (!queue.isEmpty()) { int count = queue.size(); depth++; while (count > 0 ) { TreeNode node = queue.poll(); if (node.left != null ) { queue.add(node.left); } if (node.right != null ) { queue.add(node.right); } count--; } } return depth; } }
整体思路同基础的层序遍历一样。最外侧的循环,每循环一次,表示深一层。
题解(DFS):
1 2 3 4 5 6 7 8 9 10 class Solution { public int maxDepth (TreeNode root) { if (root == null ) { return 0 ; } int left_depth = maxDepth(root.left); int right_depth = maxDepth(root.right); return Math.max(left_depth, right_depth) + 1 ; } }
利用递归实现深度优先搜索。
【111】二叉树的最小深度✔❗
题目链接:111. 二叉树的最小深度 - 力扣(LeetCode)
题解(BFS):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Solution { public int minDepth (TreeNode root) { int depth = 0 ; if (root == null ) { return depth; } Queue<TreeNode> queue = new LinkedList<>(); queue.add(root); while (!queue.isEmpty()) { int count = queue.size(); depth++; while (count > 0 ) { TreeNode node = queue.poll(); if (node.left == null && node.right == null ) { return depth; } if (node.left != null ) { queue.add(node.left); } if (node.right != null ) { queue.add(node.right); } count--; } } return depth; } }
整体思路同基础的层序遍历一样。如果遇到一个节点无左右子节点,则该层就是最小深度,直接返回。
题解(BFS)❗:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public int minDepth (TreeNode root) { if (root == null ) { return 0 ; } int left_depth = minDepth(root.left); int right_depth = minDepth(root.right); if (left_depth == 0 || right_depth == 0 ) { return left_depth + right_depth + 1 ; } else { return Math.min(left_depth, right_depth) + 1 ; } } }
利用递归实现深度优先搜索。注意返回的条件❗
【226】翻转二叉树✔
题目链接:226. 翻转二叉树 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution { public TreeNode invertTree (TreeNode root) { if (root == null ) { return null ; } TreeNode temp = root.left; root.left = root.right; root.right = temp; invertTree(root.left); invertTree(root.right); return root; } }
递归实现。
【101】对称二叉树
题目链接:101. 对称二叉树 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public boolean isSymmetric (TreeNode root) { return compare(root.left, root.right); } public boolean compare (TreeNode left, TreeNode right) { if (left == null && right != null ) { return false ; } if (left != null && right == null ) { return false ; } if (left == null && right == null ) { return true ; } if (left.val != right.val) { return false ; } boolean compare_outside = compare(left.left, right.right); boolean compare_inside = compare(left.right, right.left); return compare_outside && compare_inside; } }
递归解法:要注意对称判断,即左节点的左子节点和右节点的右子节点是否相等(外侧),以及左节点的右子节点和右节点的左子节点是否相等(内侧)。
【100】相同的树✔
题目链接:100. 相同的树 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public boolean isSameTree (TreeNode p, TreeNode q) { if (p == null && q != null ) { return false ; } if (p != null && q == null ) { return false ; } if (p == null && q == null ) { return true ; } if (p.val != q.val) { return false ; } else { boolean compare_left = isSameTree(p.left, q.left); boolean compare_right = isSameTree(p.right, q.right); return compare_left && compare_right; } } }
递归判断左右节点。
【572】另一颗树的子树✔
题目链接:572. 另一棵树的子树 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Solution { public boolean isSubtree (TreeNode root, TreeNode subRoot) { boolean compare_cur = judge(root, subRoot); boolean compare_left = false ; boolean compare_right = false ; if (root.left != null ) { compare_left = isSubtree(root.left, subRoot); } if (root.right != null ) { compare_right = isSubtree(root.right, subRoot); } return compare_cur || compare_left || compare_right; } public boolean judge (TreeNode root, TreeNode subRoot) { if (root == null && subRoot != null ) { return false ; } if (root != null && subRoot == null ) { return false ; } if (root == null && subRoot == null ) { return true ; } if (root.val != subRoot.val) { return false ; } else { boolean compare_left = judge(root.left, subRoot.left); boolean compare_right = judge(root.right, subRoot.right); return compare_left && compare_right; } } }
利用100. 相同的树 - 力扣(LeetCode) 的解题方法判断两棵树是否是同一棵树,主函数递归判断以root为根节点的树是否包含subRoot子树。
【222】完全二叉树的节点个数✔
题目链接:222. 完全二叉树的节点个数 - 力扣(LeetCode)
题解(BFS):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { public int countNodes (TreeNode root) { int number = 0 ; if (root == null ) { return number; } Queue<TreeNode> queue = new LinkedList<>(); queue.add(root); while (!queue.isEmpty()) { int count = queue.size(); while (count > 0 ) { TreeNode node = queue.poll(); number++; if (node.left != null ) { queue.add(node.left); } if (node.right != null ) { queue.add(node.right); } count--; } } return number; } }
整体思路同基础的层序遍历一样。
题解(DFS):
1 2 3 4 5 6 7 8 class Solution { public int countNodes (TreeNode root) { if (root == null ) { return 0 ; } return countNodes(root.left) + countNodes(root.right) + 1 ; } }
递归实现DFS
【110】平衡二叉树✔
题目链接:110. 平衡二叉树 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution { public boolean isBalanced (TreeNode root) { if (root == null ) { return true ; } int left_depth = depth(root.left); int right_depth = depth(root.right); if (Math.abs(left_depth - right_depth) <= 1 ) { boolean judge_left = isBalanced(root.left); boolean judge_right = isBalanced(root.right); return judge_left && judge_right; } return false ; } public int depth (TreeNode root) { if (root == null ) { return 0 ; } return Math.max(depth(root.left), depth(root.right)) + 1 ; } }
编写递归函数用于计算二叉树的深度,然后递归判断左右子树是否为平衡二叉树。
【257】二叉树的所有路径✔❗
题目链接:257. 二叉树的所有路径 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution { List<String> result = new ArrayList<>(); List<String> path = new ArrayList<>(); public List<String> binaryTreePaths (TreeNode root) { findPath(root); return result; } public void findPath (TreeNode root) { path.add(root.val + "" ); if (root.left == null && root.right == null ) { String res = String.join("->" , path); result.add(res); return ; } if (root.left != null ) { findPath(root.left); path.remove(path.size() - 1 ); } if (root.right != null ) { findPath(root.right); path.remove(path.size() - 1 ); } } }
使用回溯法。
注意❗:
指定连接符将字符串集合的元素拼接为一个完整的字符串:String res = String.join("->", path);
【404】左叶子之和
题目链接:404. 左叶子之和 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution { public int sumOfLeftLeaves (TreeNode root) { if (root == null ) { return 0 ; } int left_sum = sumOfLeftLeaves(root.left); int right_sum = sumOfLeftLeaves(root.right); int value = 0 ; if (root.left != null && root.left.left == null && root.left.right == null ) { value = root.left.val; } return left_sum + right_sum + value; } }
递归需采用后序遍历,因为结果需要逐层向上传递。
【513】找树左下角的值✔
题目链接:513. 找树左下角的值 - 力扣(LeetCode)
题解(BFS):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public int findBottomLeftValue (TreeNode root) { int result = 0 ; Queue<TreeNode> queue = new LinkedList<>(); queue.add(root); while (!queue.isEmpty()) { TreeNode node = queue.poll(); if (node.right != null ) { queue.add(node.right); } if (node.left != null ) { queue.add(node.left); } result = node.val; } return result; } }
整体思路同基础的层序遍历一样,但是在遍历每一层的时候先将右子节点添加至队列,再添加左子节点到队列。
【112】路径总和❗
题目链接:112. 路径总和 - 力扣(LeetCode)
题解(DFS):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public boolean hasPathSum (TreeNode root, int targetSum) { if (root == null ) { return false ; } if (root.left == null && root.right == null ) { return root.val == targetSum ? true : false ; } boolean left_result = false ; boolean right_result = false ; if (root.left != null ) { left_result = hasPathSum(root.left, targetSum - root.val); } if (root.right != null ) { right_result = hasPathSum(root.right, targetSum - root.val); } return left_result || right_result; } }
注意❗:本题一定不是回溯。DFS遍历到叶子节点直接将结果返回。
【113】路径总和Ⅱ✔❗
题目链接:113. 路径总和 II - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Solution { List<List<Integer>> result = new ArrayList<>(); List<Integer> path = new ArrayList<>(); public List<List<Integer>> pathSum(TreeNode root, int targetSum) { if (root == null ) { return result; } findPath(root, targetSum); return result; } public void findPath (TreeNode root, int targetSum) { path.add(root.val); if (root.left == null && root.right == null && root.val == targetSum) { result.add(new ArrayList<>(path)); return ; } if (root.left != null ) { findPath(root.left, targetSum - root.val); path.remove(path.size() - 1 ); } if (root.right != null ) { findPath(root.right, targetSum - root.val); path.remove(path.size() - 1 ); } } }
使用回溯法。
注意❗:在收获结果时必须新建一个路径对象,否则传递的path为引用。
【106】从中序与后序遍历序列构造二叉树✔❗
题目链接:106. 从中序与后序遍历序列构造二叉树 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 class Solution { public TreeNode buildTree (int [] inorder, int [] postorder) { if (inorder.length == 0 || postorder.length == 0 ) { return null ; } int root_value = postorder[postorder.length - 1 ]; TreeNode root = new TreeNode(root_value); int index = 0 ; List<Integer> left_inorder = new ArrayList<>(); for (int i = 0 ; i < inorder.length; i++) { if (inorder[i] == root_value) { index = i; break ; } left_inorder.add(inorder[i]); } int [] LEFT_inorder = left_inorder.stream().mapToInt(Integer::intValue).toArray(); List<Integer> right_inorder = new ArrayList<>(); for (int i = index + 1 ; i < inorder.length; i++) { right_inorder.add(inorder[i]); } int [] RIGHT_inorder = right_inorder.stream().mapToInt(Integer::intValue).toArray(); List<Integer> left_postorder = new ArrayList<>(); for (int i = 0 ; i < index; i++) { left_postorder.add(postorder[i]); } int [] LEFT_postorder = left_postorder.stream().mapToInt(Integer::intValue).toArray(); List<Integer> right_postorder = new ArrayList<>(); for (int i = index; i < postorder.length - 1 ; i++) { right_postorder.add(postorder[i]); } int [] RIGHT_postorder = right_postorder.stream().mapToInt(Integer::intValue).toArray(); root.left = buildTree(LEFT_inorder, LEFT_postorder); root.right = buildTree(RIGHT_inorder, RIGHT_postorder); return root; } }
注意index的记录。
记忆❗:
利用stream()
流将List<Integer>
集合转化为int[]
数组:int[] LEFT_inorder = left_inorder.stream().mapToInt(Integer::intValue).toArray();
【105】从前序与中序遍历序列构造二叉树✔
题目链接:105. 从前序与中序遍历序列构造二叉树 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 class Solution { public TreeNode buildTree (int [] preorder, int [] inorder) { if (preorder.length == 0 || inorder.length == 0 ) { return null ; } int root_val = preorder[0 ]; TreeNode root = new TreeNode(root_val); int index = 0 ; for (int i = 0 ; i < inorder.length; i++) { if (inorder[i] == root_val) { index = i; break ; } } List<Integer> left_preorder = new ArrayList<>(); for (int i = 1 ; i <= index; i++) { left_preorder.add(preorder[i]); } int [] LEFT_preorder = left_preorder.stream().mapToInt(Integer::intValue).toArray(); List<Integer> right_preorder = new ArrayList<>(); for (int i = index + 1 ; i < preorder.length; i++) { right_preorder.add(preorder[i]); } int [] RIGHT_preorder = right_preorder.stream().mapToInt(Integer::intValue).toArray(); List<Integer> left_inorder = new ArrayList<>(); for (int i = 0 ; i < index; i++) { left_inorder.add(inorder[i]); } int [] LEFT_inorder = left_inorder.stream().mapToInt(Integer::intValue).toArray(); List<Integer> right_inorder = new ArrayList<>(); for (int i = index + 1 ; i < inorder.length; i++) { right_inorder.add(inorder[i]); } int [] RIGHT_inorder = right_inorder.stream().mapToInt(Integer::intValue).toArray(); root.left = buildTree(LEFT_preorder, LEFT_inorder); root.right = buildTree(RIGHT_preorder, RIGHT_inorder); return root; } }
注意index的记录。
【654】最大二叉树✔
题目链接:654. 最大二叉树 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 class Solution { public TreeNode constructMaximumBinaryTree (int [] nums) { if (nums.length == 0 ) { return null ; } int max = Integer.MIN_VALUE; for (int i = 0 ; i < nums.length; i++) { max = max < nums[i] ? nums[i] : max; } TreeNode root = new TreeNode(max); int index = 0 ; for (int i = 0 ; i < nums.length; i++) { if (nums[i] == max) { index = i; break ; } } List<Integer> left_nums = new ArrayList<>(); for (int i = 0 ; i < index; i++) { left_nums.add(nums[i]); } int [] LEFT_nums = left_nums.stream().mapToInt(Integer::intValue).toArray(); List<Integer> right_nums = new ArrayList<>(); for (int i = index + 1 ; i < nums.length; i++) { right_nums.add(nums[i]); } int [] RIGHT_nums = right_nums.stream().mapToInt(Integer::intValue).toArray(); root.left = constructMaximumBinaryTree(LEFT_nums); root.right = constructMaximumBinaryTree(RIGHT_nums); return root; } }
注意index的记录。
【617】合并二叉树✔
题目链接:617. 合并二叉树 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution { public TreeNode mergeTrees (TreeNode root1, TreeNode root2) { if (root1 == null ) return root2; if (root2 == null ) return root1; root1.val += root2.val; root1.left = mergeTrees(root1.left,root2.left); root1.right = mergeTrees(root1.right,root2.right); return root1; } }
以root1为主要的“根”进行操作。
【700】二叉搜索树中的搜索✔
题目链接:700. 二叉搜索树中的搜索 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 class Solution { public TreeNode searchBST (TreeNode root, int val) { if (root == null || root.val == val) { return root; } return searchBST(val < root.val ? root.left : root.right, val); } }
如果当前值小于val,递归查找左子树;如果当前值大于val,递归查找右子树。
【98】验证二叉搜索树❗
题目链接:98. 验证二叉搜索树 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { TreeNode max; public boolean isValidBST (TreeNode root) { if (root == null ) { return true ; } boolean left_judge = isValidBST(root.left); if (max != null && root.val <= max.val) { return false ; } max = root; boolean right_judge = isValidBST(root.right); return left_judge && right_judge; } }
关键❗:二叉搜索树的中序遍历是非严格递增的,所以采用中序遍历。
思路:需要有一个额外的节点记录当前节点的前一个节点。首先递归判断左子树,然后判断当前节点是否大于前一个节点的值,最后递归遍历右子树。
【530】二叉搜索树的最小绝对差✔
题目链接:530. 二叉搜索树的最小绝对差 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { int min = Integer.MAX_VALUE; TreeNode pre_node; public int getMinimumDifference (TreeNode root) { if (root == null ) { return Integer.MAX_VALUE; } getMinimumDifference(root.left); if (pre_node != null ) { min = min < (root.val - pre_node.val) ? min : (root.val - pre_node.val); } pre_node = root; getMinimumDifference(root.right); return min; } }
使用中序遍历。去不断地更新差值。
【501】二叉搜索树中的众数✔❗
题目链接:501. 二叉搜索树中的众数 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 class Solution { List<Integer> result = new ArrayList<>(); int maxCount = 0 ; int count = 0 ; TreeNode pre_node; public int [] findMode(TreeNode root) { inorder(root); return result.stream().mapToInt(Integer::intValue).toArray(); } public void inorder (TreeNode root) { if (root == null ) { return ; } inorder(root.left); if (pre_node == null || root.val != pre_node.val) { count = 1 ; } else { count++; } if (count > maxCount) { result.clear(); result.add(root.val); maxCount = count; } else if (count == maxCount) { result.add(root.val); } pre_node = root; inorder(root.right); } }
使用中序遍历。
注意❗:
【236】二叉树的最近公共祖先
题目链接:236. 二叉树的最近公共祖先 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Solution { public TreeNode lowestCommonAncestor (TreeNode root, TreeNode p, TreeNode q) { if (root == null || root == p || root == q) { return root; } TreeNode left = lowestCommonAncestor(root.left, p, q); TreeNode right = lowestCommonAncestor(root.right, p, q); if (left != null && right != null ) { return root; } else if (left != null && right == null ) { return left; } else if (left == null && right != null ) { return right; } else { return null ; } } }
后序遍历,递归返回公共祖先。注意向上传递结果的关键是返回左或右节点。
【235】二叉搜索树的最近公共祖先✔
题目链接:235. 二叉搜索树的最近公共祖先 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Solution { public TreeNode lowestCommonAncestor (TreeNode root, TreeNode p, TreeNode q) { if (root == null || root == p || root == q) { return root; } TreeNode left = lowestCommonAncestor(root.left, p, q); TreeNode right = lowestCommonAncestor(root.right, p, q); if (left != null && right != null ) { return root; } else if (left != null && right == null ) { return left; } else if (left == null && right != null ) { return right; } else { return null ; } } }
同236. 二叉树的最近公共祖先 - 力扣(LeetCode) 解法一样。
【701】二叉搜索树中的插入操作
题目链接:701. 二叉搜索树中的插入操作 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public TreeNode insertIntoBST (TreeNode root, int val) { if (root == null ) { TreeNode rootNode = new TreeNode(val); return rootNode; } if (root.val < val) { root.right = insertIntoBST(root.right, val); } if (root.val > val) { root.left = insertIntoBST(root.left, val); } return root; } }
该方法的策略是将要插入的节点链接到叶子节点上。所以根据二叉搜索树的条件递归遍历左右子树,一旦遇到叶子节点就插入。
【450】删除二叉搜索树中的节点
题目链接:450. 删除二叉搜索树中的节点 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 class Solution { public TreeNode deleteNode (TreeNode root, int key) { if (root == null ) { return root; } if (root.val == key && root.left == null && root.right == null ) { return null ; } if (root.val == key && root.left != null && root.right == null ) { return root.left; } if (root.val == key && root.left == null && root.right != null ) { return root.right; } if (root.val == key && root.left != null && root.right != null ) { TreeNode cur = root.right; while (cur.left != null ) { cur = cur.left; } cur.left = root.left; return root.right; } if (key < root.val) { root.left = deleteNode(root.left, key); } if (key > root.val) { root.right = deleteNode(root.right, key); } return root; } }
如果待删除节点的左、右节点都不为空的删除策略:直接返回右节点,而左子树则挂到右子树的最底最左的叶子节点的下方。(二叉搜索树的特性)
【669】修建二叉搜索树✔
题目链接:669. 修剪二叉搜索树 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public TreeNode trimBST (TreeNode root, int low, int high) { if (root == null ) { return null ; } if (root.val < low) { return trimBST(root.right, low, high); } if (root.val > high) { return trimBST(root.left, low, high); } root.left = trimBST(root.left, low, high); root.right = trimBST(root.right, low, high); return root; } }
思路,通过递归逐步删除[low,high]范围外的叶子节点。
【108】将有序数组转化为二叉搜索树✔
题目链接:108. 将有序数组转换为二叉搜索树 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution { public TreeNode sortedArrayToBST (int [] nums) { if (nums.length == 0 ) { return null ; } int index = nums.length / 2 ; TreeNode root = new TreeNode(nums[index]); List<Integer> left_nums = new ArrayList<>(); for (int i = 0 ; i < index; i++) { left_nums.add(nums[i]); } int [] LEFT_nums = left_nums.stream().mapToInt(Integer::intValue).toArray(); List<Integer> right_nums = new ArrayList<>(); for (int i = index + 1 ; i < nums.length; i++) { right_nums.add(nums[i]); } int [] RIGHT_nums = right_nums.stream().mapToInt(Integer::intValue).toArray(); root.left = sortedArrayToBST(LEFT_nums); root.right = sortedArrayToBST(RIGHT_nums); return root; } }
思路,将有序数组构建为二叉搜索树,树的根节点在有序数组的中间,找到根节点再分别定义左右子树的有序数组,然后递归构建左右子树。
【538】把二叉搜索树转换为累加树✔
题目链接:538. 把二叉搜索树转换为累加树 - 力扣(LeetCode)
题解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution { TreeNode pre = new TreeNode(0 ); public TreeNode convertBST (TreeNode root) { if (root == null ) { return null ; } convertBST(root.right); root.val = root.val + pre.val; pre = root; convertBST(root.left); return root; } }
思路:更新完的累加树按照右中左的顺序遍历其实也是递增的。考虑用一个指针记录前一个节点的值,并不断迭代进行更新。
回溯算法
贪心算法
动态规划
单调栈
图论
后记
To be continued…