代码随想录刷题笔记
代码随想录
数组
704. 二分查找
class Solution { public int search(int[] nums, int target) { int left = 0, right = nums.length-1; while (left <= right) { int mid = (left + right) / 2; if (nums[mid] == target) { return mid; } else if (nums[mid] < target) { left = mid + 1; } else { right = mid - 1; } } return -1; } }
27. 移除元素
class Solution { public int removeElement(int[] nums, int val) { // 双指针 快慢指针 int slow = 0; for (int fast = 0; fast < nums.length; fast++) { if (val != nums[fast]) { nums[slow++] = nums[fast]; } } return slow; // slow恰好是最后得到的数组长度 } }
977. 有序数组的平方
class Solution { public int[] sortedSquares(int[] nums) { // 双指针法 // 数组平方的最大值一定在两端 int[] ans = new int[nums.length]; int left = 0, right = nums.length - 1; int i = nums.length - 1; while (left <= right) { if (nums[left] * nums[left] <= nums[right] * nums[right]) { ans[i--] = nums[right] * nums[right]; right--; } else{ ans[i--] = nums[left] * nums[left]; left++; } } return ans; } }
209. 长度最小的子数组
class Solution { public int minSubArrayLen(int target, int[] nums) { int ans = Integer.MAX_VALUE; // 要返回的最短子数组长度 int subLength = 0; // 子数组的长度 int subSum = 0; // 滑动窗口的和 int i = 0; // 滑动窗口左边界 for (int j = 0; j < nums.length; j++) { subSum += nums[j]; while (subSum >= target) { // 滑窗左边界右移的条件是子数组的和大于target subLength = j - i + 1; // 子数组长度 ans = subLength < ans ? subLength : ans; subSum -= nums[i++]; // 左边界右移 } } return ans == Integer.MAX_VALUE ? 0 : ans; } }
59.螺旋矩阵II
class Solution { public int[][] generateMatrix(int n) { int[][] ans = new int[n][n]; int num = 1; int target = n * n; int i = 0, j = 0; while(num <= target){ for(; j < n && ans[i][j] == 0; j++) ans[i][j] = num++; i++; j--; for(; i < n && ans[i][j] == 0; i++) ans[i][j] = num++; i--; j--; for(; j >= 0 && ans[i][j] == 0; j--) ans[i][j] = num++; i--; j++; for(; i >= 0 && ans[i][j] == 0; i--) ans[i][j] = num++; i++; j++; } return ans; } }
总结
五道题分别是:二分法、双指针法、双指针法、滑动窗口、模拟
链表
203. 移除链表元素
class Solution { public ListNode removeElements(ListNode head, int val) { ListNode preHead = new ListNode(); // 定义一个指向头结点的结点 preHead.next = head; ListNode p = head, q = preHead; // p 指向当前遍历的结点,q指向前一个结点 while (p != null) { if (p.val == val) { p = p.next; q.next = p; } else { p = p.next; q = q.next; } } return preHead.next; } }
707. 设计链表
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(); // 虚拟的头结点,并不是首元结点 } public int get(int index) { // 若链表是1 2 3,get(0)得到1 if (index < 0 || index >= size) return -1; ListNode p = head; // p需要向前走index+1步 for (int i = 0; i < index + 1; i++) { p = p.next; } return p.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; ListNode p = head; for (int i = 0; i < index; i++) { p = p.next; } // 此时p指向的是要插入位置的前一个结点 ListNode p_next = p.next; ListNode insertNode = new ListNode(val); // 要插入的结点 p.next = insertNode; insertNode.next = p_next; size++; } public void deleteAtIndex(int index) { if (index < 0 || index >= size) return; ListNode p = head; for (int i = 0; i < index; i++) { p = p.next; } // 此时p指向的是要删除位置的前一个结点 ListNode p_next_next = p.next.next; p.next = p_next_next; size--; } }
206. 反转链表
class Solution { public ListNode reverseList(ListNode head) { ListNode curr = head; // 指向当前遍历结点 ListNode prev = null; // 指向cur之前的结点 ListNode next; // 用来指向curr的下一个结点 while (curr != null) { next = curr.next; curr.next = prev; prev = curr; curr = next; } return prev; } }
24. 两两交换链表中的节点
class Solution { public ListNode swapPairs(ListNode head) { ListNode preHead = new ListNode(); preHead.next = head; ListNode curr = preHead; while (curr.next != null && curr.next.next != null) { ListNode temp = curr.next; // 这一组相邻结点的第一个结点 ListNode temp2 = curr.next.next.next; // 下一组的第一个结点 curr.next = temp.next; temp.next.next = temp; temp.next = temp2; curr = temp; } return preHead.next; } }
19. 删除链表的倒数第 N 个结点
class Solution { public ListNode removeNthFromEnd(ListNode head, int n) { ListNode fast = head; ListNode slow = new ListNode(0, head); ListNode preHead = slow; for(int i = 0; i < n; i++) { fast = fast.next; } while (fast != null) { fast = fast.next; slow = slow.next; } // 删除slow.next结点 slow.next = slow.next.next; return preHead.next; } }
面试题 02.07. 链表相交
public class Solution { public ListNode getIntersectionNode(ListNode headA, ListNode headB) { ListNode p = headA, q = headB; while (p != q) { p = (p != null) ? p.next : headB; q = (q != null) ? q.next : headA; } return p; } }
142. 环形链表 II
题目:给定一个链表,如果有环,返回环的起始点,如果没有环,返回null
public class Solution { public ListNode detectCycle(ListNode head) { ListNode fast = head, slow = head; while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; if (slow == fast) { // 快慢指针相遇了,说明有环 ListNode p = fast; // 快慢指针相遇处 ListNode q = head; // 头结点和相遇处一起走 while (p != q) { p = p.next; q = q.next; } return p; } } return null; } }
哈希表
242. 有效的字母异位词
class Solution { public boolean isAnagram(String s, String t) { int[] record = new int[26]; for (int i = 0; i < s.length(); i++) { record[s.charAt(i) - 'a']++; } for (int i = 0; i < t.length(); i++) { record[t.charAt(i) - 'a']--; } for (int i = 0; i < 26; i++) { if (record[i] != 0) return false; } return true; } }
349. 两个数组的交集
202. 快乐数
https://leetcode.cn/problems/happy-number/
class Solution { public boolean isHappy(int n) { Set<Integer> set = new HashSet<>(); while (n != 1 && !set.contains(n)) { // 如果集合中包含了当前数,那么就出现循环了 set.add(n); n = getNext(n); } return n == 1; } private int getNext(int n) { int res = 0; while (n > 0) { int temp = n % 10; n = n / 10; res += temp * temp; } return res; } }
1. 两数之和
https://leetcode.cn/problems/two-sum/
class Solution { public int[] twoSum(int[] nums, int target) { int[] res = new int[2]; Map<Integer, Integer> map = new HashMap<>(); for (int i = 0; i < nums.length; i++) { if (map.containsKey(target - nums[i])) { res[0] = map.get(target - nums[i]); res[1] = i; break; } else { map.put(nums[i], i); } } return res; } }
454. 四数相加 II
https://leetcode.cn/problems/4sum-ii/
class Solution { public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) { Map<Integer, Integer> map = new HashMap<>(); // 存放数组1和2的和及其出现的次数 int res = 0; // 遍历数组1和2 for (int i: nums1) { for (int j: nums2) { int sum = i + j; map.put(sum, map.getOrDefault(sum, 0) + 1); } } // 遍历数组3和4 for (int i: nums3) { for (int j: nums4) { int sum = i + j; res += map.getOrDefault(-sum, 0); } } return res; } }
383. 赎金信
https://leetcode.cn/problems/ransom-note
class Solution { public boolean canConstruct(String ransomNote, String magazine) { int[] map = new int[26]; for (char c: magazine.toCharArray()) { map[c - 'a'] += 1; } for (char c: ransomNote.toCharArray()) { map[c - 'a'] -= 1; } for (int i: map) { if (i < 0) { return false; } } return true; } }
15. 三数之和
https://leetcode.cn/problems/3sum/
class Solution { public List<List<Integer>> threeSum(int[] nums) { List<List<Integer>> res = new ArrayList<>(); int n = nums.length; // 先排序 Arrays.sort(nums); for (int i = 0; i < n - 2; i++) { if (i > 0 && nums[i] == nums[i-1]) continue; // 去重 if (nums[i] + nums[i+1] + nums[i+2] > 0) break; if (nums[i] + nums[n-1] + nums[n-2] < 0) continue; int j = i + 1, k = n - 1; while (j < k) { int sum = nums[i] + nums[j] + nums[k]; if (sum > 0) { k--; } else if (sum < 0) { j++; } else { res.add(Arrays.asList(nums[i], nums[j], nums[k])); for (j++; j < k && nums[j] == nums[j-1]; j++); // 去重 for (k--; j < k && nums[k] == nums[k+1]; k--); // 去重 } } } return res; } }
18. 四数之和
https://leetcode.cn/problems/4sum/
class Solution { public List<List<Integer>> fourSum(int[] nums, int target) { List<List<Integer>> res = new ArrayList<>(); Arrays.sort(nums); int n = nums.length; for (int a = 0; a < n - 3; a++) { // 枚举第一个数 if (a > 0 && nums[a] == nums[a-1]) continue; // 去重 if ((long) nums[a] + nums[a+1] + nums[a+2] + nums[a+3] > target) break; if ((long) nums[a] + nums[n-1] + nums[n-2] + nums[n-3] < target) continue; for (int b = a + 1; b < n - 2; b++) { if (b > a + 1 && nums[b] == nums[b-1]) continue; // 去重 if ((long) nums[a] + nums[b] + nums[b+1] + nums[b+2] > target) break; if ((long) nums[a] + nums[b] + nums[n-1] + nums[n-2] < target) continue; int c = b + 1, d = n - 1; while (c < d) { long sum = nums[a] + nums[b] + nums[c] + nums[d]; if (sum > target) d--; else if (sum < target) c++; else { res.add(List.of(nums[a], nums[b], nums[c], nums[d])); for (c++; c < d && nums[c] == nums[c-1]; c++); // 去重 for (d--; c < d && nums[d] == nums[d+1]; d--); // 去重 } } } } return res; } }
字符串
344. 反转字符串
https://leetcode.cn/problems/reverse-string
class Solution { public void reverseString(char[] s) { for (int i = 0, j = s.length; i < j; i++, j--) { char temp = s[i]; s[i] = s[j]; s[j] = temp; } } }
541. 反转字符串 II
https://leetcode.cn/problems/reverse-string-ii/
class Solution { public String reverseStr(String s, int k) { int n = s.length(); char[] ch = s.toCharArray(); for (int i = 0; i < n; i += (2 * k)) { if (n - i < k) { // 剩余字符的数量为n - start reverseString(ch, i, n - 1); } else { reverseString(ch, i, i + k - 1); } } return new String(ch); } public void reverseString(char[] s, int start, int end) { // 反正s字符串中从start到end的 for (int i = start, j = end; i < j; i++, j--) { char temp = s[i]; s[i] = s[j]; s[j] = temp; } } }
剑指 Offer 05. 替换空格
https://leetcode.cn/problems/ti-huan-kong-ge-lcof/
class Solution { public String replaceSpace(String s) { int count = 0; // 统计空格数 for (int i = 0; i < s.length(); i++) { if (s.charAt(i) == ' ') { count++; } } char[] ch = new char[s.length() + count * 2]; // 新数组长度 for (int i = 0, j = 0; i < s.length(); i++) { // i是旧数组的,j是新数组的 if (s.charAt(i) == ' ') { ch[j++] = '%'; ch[j++] = '2'; ch[j++] = '0'; } else { ch[j++] = s.charAt(i); } } return new String(ch); } }
151. 反转字符串中的单词
https://leetcode.cn/problems/reverse-words-in-a-string/
class Solution { public String reverseWords(String s) { // 1、移除多余空格(移除首位空格,单词间只保留一个空格) StringBuilder sb = removeExtraSpace(s); // 2、字符串反转 reverseString(sb, 0, sb.length() - 1); // 3、单词反转 reverseEachWord(sb); return sb.toString(); } private StringBuilder removeExtraSpace(String s) { int start = 0, end = s.length() - 1; // 去除首尾空格 while (s.charAt(start) == ' ') start++; while (s.charAt(end) == ' ') end--; StringBuilder sb = new StringBuilder(); while (start <= end) { if (s.charAt(start) != ' ' || s.charAt(start - 1) != ' ') { // 如果不是空格或者是单词后的第一个空格,就添加进去,这样就去除掉了单词间多余空格 sb.append(s.charAt(start)); } start++; } return sb; } private void reverseString(StringBuilder sb, int start, int end) { while (start <= end) { char temp = sb.charAt(start); sb.setCharAt(start, sb.charAt(end)); sb.setCharAt(end, temp); start++; end--; } } private void reverseEachWord(StringBuilder sb) { int n = sb.length(); int start = 0; while (start < n) { int end = start; while (end < n && sb.charAt(end) != ' ') end++; reverseString(sb, start, end - 1); start = end + 1; } } }
剑指 Offer 58 - II. 左旋转字符串
https://leetcode.cn/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof/
class Solution { public String reverseLeftWords(String s, int n) { char[] ch = s.toCharArray(); reverseString(ch, 0, n - 1); reverseString(ch, n, s.length() - 1); reverseString(ch, 0, s.length() - 1); return new String(ch); } private void reverseString(char[] ch, int start, int end) { while (start < end) { char temp = ch[start]; ch[start] = ch[end]; ch[end] = temp; start++; end--; } } }
28. 找出字符串中第一个匹配项的下标
https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string
这是经典的KMP算法
栈与队列
232. 用栈实现队列
https://leetcode.cn/problems/implement-queue-using-stacks/
class MyQueue { Stack<Integer> inStack; Stack<Integer> outStack; public MyQueue() { inStack = new Stack<>(); outStack = new Stack<>(); } public void push(int x) { inStack.push(x); } public int pop() { inToOut(); return outStack.pop(); } public int peek() { inToOut(); return outStack.peek(); } public boolean empty() { return inStack.isEmpty() && outStack.isEmpty(); } private void inToOut() { // 将in中的放入到out中 if (!outStack.isEmpty()) return; // 如果out不为空,直接返回 while (!inStack.isEmpty()) { outStack.push(inStack.pop()); } } }
225. 用队列实现栈
https://leetcode.cn/problems/implement-stack-using-queues
class MyStack { Queue<Integer> queue1; Queue<Integer> queue2; public MyStack() { queue1 = new LinkedList<>(); queue2 = new LinkedList<>(); } public void push(int x) { queue2.offer(x); while (!queue1.isEmpty()) { queue2.offer(queue1.poll()); } Queue<Integer> temp = queue1; queue1 = queue2; queue2 = temp; } public int pop() { return queue1.poll(); } public int top() { return queue1.peek(); } public boolean empty() { return queue1.isEmpty(); } }
20. 有效的括号
https://leetcode.cn/problems/valid-parentheses/
class Solution { public boolean isValid(String s) { Deque<Character> deque = new LinkedList<>(); for (int i = 0; i < s.length(); i++) { char ch = s.charAt(i); if (ch == '(') { deque.push(')'); } else if (ch == '[') { deque.push(']'); } else if (ch == '{') { deque.push('}'); } else if (deque.isEmpty() || deque.peek() != ch) { return false; } else { deque.pop(); } } return deque.isEmpty(); } }
1047. 删除字符串中的所有相邻重复项
https://leetcode.cn/problems/remove-all-adjacent-duplicates-in-string
class Solution { public String removeDuplicates(String s) { char[] chs = s.toCharArray(); int top = -1; for (int i = 0; i < s.length(); i++) { if (top == -1 || chs[top] != chs[i]) { chs[++top] = chs[i]; } else { top--; } } return String.valueOf(chs, 0, top+1); } }
150. 逆波兰表达式求值
https://leetcode.cn/problems/evaluate-reverse-polish-notation/
class Solution { public int evalRPN(String[] tokens) { Deque<Integer> stack = new LinkedList<>(); for (String s: tokens) { if (s.equals("+")) { stack.push(stack.pop() + stack.pop()); } else if (s.equals("-")) { stack.push(-stack.pop() + stack.pop()); } else if (s.equals("*")) { stack.push(stack.pop() * stack.pop()); } else if (s.equals("/")) { int num2 = stack.pop(); int num1 = stack.pop(); stack.push(num1 / num2); } else { stack.push(Integer.valueOf(s)); } } return stack.pop(); } }
239. 滑动窗口最大值
https://leetcode.cn/problems/sliding-window-maximum/
方法一:优先队列
class Solution { public int[] maxSlidingWindow(int[] nums, int k) { int n = nums.length; // pq存放nums[i]与i PriorityQueue<int[]> pq = new PriorityQueue<>(new Comparator<int[]>() { @Override public int compare(int[] o1, int[] o2) { // 排序方式:先按照值升序排序,如果值相同则按照下标升序排序 if (o1[0] != o2[0]) { return o2[0] - o1[0]; } else { return o2[1] - o1[1]; } } }); // 先把第一组k个数放到优先队列中 for (int i = 0; i < k; i++) { pq.offer(new int[]{nums[i], i}); } int[] ans = new int[n - k + 1]; ans[0] = pq.peek()[0]; // 第一组k个数的最大值 for (int i = k; i < n; i++) { pq.offer(new int[]{nums[i], i}); // 插入一个元素后,若最大值的下标不在滑窗中,就要把前面的都移出去 while (pq.peek()[1] <= i - k) { pq.poll(); } ans[i - k + 1] = pq.peek()[0]; } return ans; } }
方法二:单调队列
class Solution { public int[] maxSlidingWindow(int[] nums, int k) { int n = nums.length; Deque<Integer> deque = new LinkedList<>(); for (int i = 0; i < k; i++) { while (!deque.isEmpty() && nums[i] >= nums[deque.peekLast()]){ // 如果右侧的新值大于左侧的旧值,那么左侧的旧值就可以永久删除了 deque.pollLast(); } deque.offerLast(i); } // for结束后,deque存储的下标按照从小到大的顺序存储,并且它们在数组nums中对应的值是递减的 int[] ans = new int[n - k + 1]; ans[0] = nums[deque.peekFirst()]; for (int i = k; i < n; i++) { // 重复上一个for循环的内容 while (!deque.isEmpty() && nums[i] >= nums[deque.peekLast()]){ deque.pollLast(); } deque.offerLast(i); while (deque.peekFirst() <= i - k) { deque.pollFirst(); } ans[i - k + 1] = nums[deque.peekFirst()]; } return ans; } }
347. 前 K 个高频元素
https://leetcode.cn/problems/top-k-frequent-elements/
class Solution { public int[] topKFrequent(int[] nums, int k) { Map<Integer, Integer> map = new HashMap<>(); for (int num: nums) { map.put(num, map.getOrDefault(num, 0) + 1); } PriorityQueue<int[] > pq = new PriorityQueue<>(new Comparator<int[]>() { @Override public int compare(int[] o1, int[] o2) { return o1[1] - o2[1]; } }); // 优先队列,根据频率从小到大排 for (var x : map.entrySet()) { pq.offer(new int[]{x.getKey(), x.getValue()}); if (pq.size() > k) { pq.poll(); } } int[] res = new int[k]; for (int i = 0; i < k; i++) { res[i] = pq.poll()[0]; } return res; } }
二叉树
144. 二叉树的前序遍历
https://leetcode.cn/problems/binary-tree-preorder-traversal/
class Solution { public List<Integer> preorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<>(); if (root == null) return res; Deque<TreeNode> stack = new LinkedList<>(); stack.push(root); while (!stack.isEmpty()) { TreeNode temp = stack.pop(); res.add(temp.val); if (temp.right != null) stack.push(temp.right); if (temp.left != null) stack.push(temp.left); } return res; } }
94. 二叉树的中序遍历
https://leetcode.cn/problems/binary-tree-inorder-traversal/
class Solution { public List<Integer> inorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<>(); if (root == null) return res; Deque<TreeNode> stack = new LinkedList<>(); TreeNode cur = root; while (cur != null || !stack.isEmpty()) { if (cur != null) { // 先一直向左走到头 stack.push(cur); cur = cur.left; } else { cur = stack.pop(); res.add(cur.val); cur = cur.right; } } return res; } }
145. 二叉树的后序遍历
https://leetcode.cn/problems/binary-tree-postorder-traversal/
class Solution { public List<Integer> postorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<>(); if (root != null) stack.push(root); Deque<TreeNode> stack = new LinkedList<>(); while (!stack.isEmpty()) { TreeNode temp = stack.pop(); res.add(temp.val); if (temp.left != null) stack.push(temp.left); // 跟前序反过来 if (temp.right != null) stack.push(temp.right); } Collections.reverse(res); // 逆序 return res; } }
102. 二叉树的层序遍历(模板)
https://leetcode.cn/problems/binary-tree-level-order-traversal/
class Solution { public List<List<Integer>> levelOrder(TreeNode root) { Deque<TreeNode> deque = new LinkedList<>(); List<List<Integer>> res = new ArrayList<List<Integer>>(); if (root == null) return res; deque.offer(root); while (!deque.isEmpty()) { List<Integer> temp = new ArrayList<>(); // 存放一层的结点值 // 对一层进行遍历 int len = deque.size(); for (int i = 0; i < len; i++) { TreeNode node = deque.poll(); temp.add(node.val); if (node.left != null) deque.offer(node.left); if (node.right != null) deque.offer(node.right); } res.add(temp); } return res; } }
107. 二叉树的层序遍历 II
https://leetcode.cn/problems/binary-tree-level-order-traversal-ii/
class Solution { public List<List<Integer>> levelOrderBottom(TreeNode root) { Deque<TreeNode> deque = new LinkedList<>(); List<List<Integer>> res = new ArrayList<List<Integer>>(); if (root == null) return res; deque.offer(root); while (!deque.isEmpty()) { List<Integer> temp = new ArrayList<>(); // 存放一层的结点值 // 对一层进行遍历 int len = deque.size(); for (int i = 0; i < len; i++) { TreeNode node = deque.poll(); temp.add(node.val); if (node.left != null) deque.offer(node.left); if (node.right != null) deque.offer(node.right); } res.add(temp); } Collections.reverse(res); return res; } }
199. 二叉树的右视图
https://leetcode.cn/problems/binary-tree-right-side-view/
class Solution { public List<Integer> rightSideView(TreeNode root) { Deque<TreeNode> deque = new LinkedList<>(); List<Integer> res = new ArrayList<>(); if (root == null) return res; deque.offer(root); while (!deque.isEmpty()) { // 对一层进行遍历 int len = deque.size(); for (int i = 0; i < len; i++) { TreeNode node = deque.poll(); if (i == len - 1) { res.add(node.val); } if (node.left != null) deque.offer(node.left); if (node.right != null) deque.offer(node.right); } } return res; } }
637. 二叉树的层平均值
https://leetcode.cn/problems/average-of-levels-in-binary-tree/
class Solution { public List<Double> averageOfLevels(TreeNode root) { Deque<TreeNode> deque = new LinkedList<>(); List<Double> res = new ArrayList<>(); if (root == null) return res; deque.offer(root); while (!deque.isEmpty()) { int len = deque.size(); long sum = 0; for (int i = 0; i < len; i++) { TreeNode node = deque.poll(); sum += node.val; if (node.left != null) deque.offer(node.left); if (node.right != null) deque.offer(node.right); } res.add(1.0 * sum / len); } return res; } }
429. N 叉树的层序遍历
https://leetcode.cn/problems/n-ary-tree-level-order-traversal/
class Solution { public List<List<Integer>> levelOrder(Node root) { Deque<Node> deque = new LinkedList<>(); List<List<Integer>> res = new ArrayList<List<Integer>>(); if (root == null) return res; deque.offer(root); while (!deque.isEmpty()) { List<Integer> temp = new ArrayList<>(); // 存放一层的结点值 // 对一层进行遍历 int len = deque.size(); for (int i = 0; i < len; i++) { Node node = deque.poll(); temp.add(node.val); for (Node children: node.children) { if (children != null) deque.offer(children); } } res.add(temp); } return res; } }
515. 在每个树行中找最大值
https://leetcode.cn/problems/find-largest-value-in-each-tree-row/
class Solution { public List<Integer> largestValues(TreeNode root) { Deque<TreeNode> deque = new LinkedList<>(); List<Integer> res = new ArrayList<>(); if (root == null) return res; deque.offer(root); while (!deque.isEmpty()) { int maxx = -Integer.MAX_VALUE-1; // 对一层进行遍历 int len = deque.size(); for (int i = 0; i < len; i++) { TreeNode node = deque.poll(); maxx = Math.max(maxx, node.val); if (node.left != null) deque.offer(node.left); if (node.right != null) deque.offer(node.right); } res.add(maxx); } return res; } }
116. 填充每个节点的下一个右侧节点指针
https://leetcode.cn/problems/populating-next-right-pointers-in-each-node/
class Solution { public Node connect(Node root) { Deque<Node> deque = new LinkedList<>(); if (root == null) return root; deque.offer(root); while (!deque.isEmpty()) { int len = deque.size(); Node node = null; Node nodePre = null; for (int i = 0; i < len; i++) { node = deque.poll(); if (i != 0) nodePre.next = node; nodePre = node; if (node.left != null) deque.offer(node.left); if (node.right != null) deque.offer(node.right); } } return root; } }
117. 填充每个节点的下一个右侧节点指针 II
https://leetcode.cn/problems/populating-next-right-pointers-in-each-node-ii/
与上一题不同的地方在于:本题不是完全二叉树,但是不影响,和上一题代码完全一样
class Solution { public Node connect(Node root) { Deque<Node> deque = new LinkedList<>(); if (root == null) return root; deque.offer(root); while (!deque.isEmpty()) { int len = deque.size(); Node node = null; Node nodePre = null; for (int i = 0; i < len; i++) { node = deque.poll(); if (i != 0) nodePre.next = node; nodePre = node; if (node.left != null) deque.offer(node.left); if (node.right != null) deque.offer(node.right); } } return root; } }
104. 二叉树的最大深度
https://leetcode.cn/problems/maximum-depth-of-binary-tree/
深度优先搜索
class Solution { public int maxDepth(TreeNode root) { if (root == null) return 0; int leftDepth = maxDepth(root.left); int rightDepth = maxDepth(root.right); return Math.max(leftDepth, rightDepth) + 1; } }
时间复杂度O(n),n是节点数
空间复杂度O(height),height是树的高度
广度优先搜素
class Solution { public int maxDepth(TreeNode root) { if (root == null) return 0; Deque<TreeNode> deque = new LinkedList<>(); int res = 0; deque.offer(root); while (!deque.isEmpty()) { int len = deque.size(); for (int i = 0; i < len; i++) { TreeNode node = deque.poll(); if (node.left != null) deque.offer(node.left); if (node.right != null) deque.offer(node.right); } res++; } return res; } }
时间复杂度O(n),n是节点数
空间复杂度O(n),此方法空间的消耗取决于队列存储的元素数量,其在最坏情况下会达到O(n)
111. 二叉树的最小深度
https://leetcode.cn/problems/minimum-depth-of-binary-tree/
深度优先搜索
class Solution { public int minDepth(TreeNode root) { if (root == null) return 0; // if (root.left == null && root.right == null) return 1; 这行帮助理解,可以省略 int m1 = minDepth(root.left); int m2 = minDepth(root.right); if (root.left == null || root.right == null) return m1 + m2 + 1; return Math.min(m1, m2) + 1; } }
广度优先搜索
class Solution { public int minDepth(TreeNode root) { if (root == null) return 0; Deque<TreeNode> deque = new LinkedList<>(); deque.push(root); int res = 0; while (!deque.isEmpty()) { int len = deque.size(); res++; for (int i = 0; i < len; i++) { TreeNode node = deque.poll(); if (node.left != null) deque.offer(node.left); if (node.right != null) deque.offer(node.right); if (node.left == null && node.right == null) return res; } } return res; } }
226. 翻转二叉树
https://leetcode.cn/problems/invert-binary-tree/
class Solution { public TreeNode invertTree(TreeNode root) { if (root == null) return root; TreeNode temp = root.left; root.left = root.right; root.right = temp; invertTree(root.left); invertTree(root.right); return root; } }
101. 对称二叉树
https://leetcode.cn/problems/symmetric-tree/
class Solution { public boolean isSymmetric(TreeNode root) { if (root == null) return true; return compare(root.left, root.right); } public boolean compare(TreeNode left, TreeNode right) { if (left == null && right != null) return false; else if (left != null && right == null) return false; else if (left == null && right == null) return true; else if (left.val != right.val) return false; return compare(left.left, right.right) && compare(left.right, right.left); } }
222. 完全二叉树的节点个数
https://leetcode.cn/problems/count-complete-tree-nodes/
class Solution { public int countNodes(TreeNode root) { if (root == null) return 0; int leftDepth = 1; int rightDepth = 1; TreeNode leftNode = root.left; TreeNode rightNode = root.right; while (leftNode != null) { leftDepth++; leftNode = leftNode.left; } while (rightNode != null) { rightDepth++; rightNode = rightNode.right; } if (leftDepth == rightDepth) { return (1 << leftDepth) - 1; // 2^n-1, n是树高度 } return countNodes(root.left) + countNodes(root.right) + 1; } }
110. 平衡二叉树
https://leetcode.cn/problems/balanced-binary-tree/
这里强调一波概念:
- 二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数。
- 二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数。
因为求深度可以从上到下去查 所以需要前序遍历(中左右),而高度只能从下到上去查,所以只能后序遍历(左右中)
本题要求高度,那必然是用后序遍历,首先写一个getHeight(node)函数,如果在递归的过程中发现不是平衡树,就返回-1,具体看代码
class Solution { public boolean isBalanced(TreeNode root) { if (getHeight(root) == -1) return false; return true; } public int getHeight(TreeNode node) { if (node == null) return 0; int leftHeight = getHeight(node.left); if (leftHeight == -1) return -1; int rightHeight = getHeight(node.right); if (rightHeight == -1) return -1; if (Math.abs(leftHeight - rightHeight) > 1) return -1; else return Math.max(leftHeight, rightHeight) + 1; } }
257. 二叉树的所有路径
https://leetcode.cn/problems/binary-tree-paths/
import java.util.StringJoiner; class Solution { public List<String> binaryTreePaths(TreeNode root) { List<String> res = new ArrayList<>(); if (root == null) return res; List<Integer> paths = new ArrayList<>(); dfs(root, paths, res); return res; } public void dfs(TreeNode node, List<Integer> paths, List<String> res) { paths.add(node.val); // 前序遍历 if (node.left != null) { dfs(node.left, paths, res); // 递归 paths.remove(paths.size()-1); // 回溯,把最后一个元素删掉 } if (node.right != null) { dfs(node.right, paths, res); // 递归 paths.remove(paths.size()-1); // 回溯,把最后一个元素删掉 } if (node.left == null && node.right == null) { // 遇到叶子结点,就得到了一条path,把path按照要求格式添加到res中 res.add(format(paths)); } } public String format(List<Integer> paths) { // 如果paths=[1,2,3],那么返回"1->2->3" StringJoiner s = new StringJoiner("->"); for (int i = 0; i < paths.size(); i++) { s.add(String.valueOf(paths.get(i))); } return s.toString(); } }
404. 左叶子之和
https://leetcode.cn/problems/sum-of-left-leaves/
class Solution { public int sumOfLeftLeaves(TreeNode root) { if (root == null) return 0; return sumOfLeftLeaves(root.left) + sumOfLeftLeaves(root.right) + (root.left != null && root.left.left == null && root.left.right == null? root.left.val: 0); } }
513. 找树左下角的值
https://leetcode.cn/problems/find-bottom-left-tree-value/
class Solution { public int findBottomLeftValue(TreeNode root) { Deque<TreeNode> deque = new LinkedList<>(); int res = root.val; deque.offer(root); while (!deque.isEmpty()) { int len = deque.size(); for (int i = 0; i < len; i++) { TreeNode node = deque.poll(); if (i == 0) { res = node.val; } if (node.left != null) deque.offer(node.left); if (node.right != null) deque.offer(node.right); } } return res; } }
112. 路径总和
https://leetcode.cn/problems/path-sum/
class Solution { public boolean hasPathSum(TreeNode root, int targetSum) { if (root == null) return false; return dfs(root, targetSum-root.val); } public boolean dfs(TreeNode node, int count) { if (node.left == null && node.right == null) { // 叶子 if (count == 0) return true; else return false; } if (node.left != null) { count -= node.left.val; if (dfs(node.left, count)) return true; count += node.left.val; } if (node.right != null) { count -= node.right.val; if (dfs(node.right, count)) return true; count += node.right.val; } return false; } }
精简版:
class Solution { public boolean hasPathSum(TreeNode root, int targetSum) { if (root == null) return false; if (root.left == null && root.right == null && targetSum == root.val) return true; return hasPathSum(root.left, targetSum - root.val) || hasPathSum(root.right, targetSum - root.val); } }
113. 路径总和 II
https://leetcode.cn/problems/path-sum-ii/
class Solution { private List<List<Integer>> res = new ArrayList<>(); private List<Integer> path = new LinkedList<>(); public List<List<Integer>> pathSum(TreeNode root, int targetSum) { if (root == null) return res; dfs(root, targetSum); return res; } private void dfs(TreeNode node, int targetSum) { path.add(node.val); if (node.left == null && node.right == null) { // 叶子 if (targetSum - node.val == 0) { // 找到了一个路径 res.add(new ArrayList<>(path)); } return; } if (node.left != null) { dfs(node.left, targetSum - node.val); path.remove(path.size() - 1); } if (node.right != null) { dfs(node.right, targetSum - node.val); path.remove(path.size() - 1); } } }
105. 从前序与中序遍历序列构造二叉树
https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/
给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder
是同一棵树的中序遍历,请构造二叉树并返回其根节点。
class Solution { Map<Integer, Integer> map = new HashMap<>(); public TreeNode buildTree(int[] preorder, int[] inorder) { int n = preorder.length; // 节点总数 for (int i = 0; i < n; i++) { map.put(inorder[i], i); } return myBuildTree(preorder, inorder, 0, n-1, 0, n-1); } public TreeNode myBuildTree(int[] preorder, int[] inorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right) { // 四个参数分别代表前序的左右边界(下标),中序的左右边界(下标) if (preorder_left > preorder_right) return null; int preorder_root = preorder_left; // 根节点在前序遍历的下标 int root_val = preorder[preorder_root]; // 根节点值 int inorder_root = map.get(root_val); // 根节点在中序遍历的下标 TreeNode root = new TreeNode(root_val); int len_left = inorder_root - inorder_left; // 左子树的节点数 root.left = myBuildTree(preorder, inorder, preorder_left+1, preorder_left+len_left, inorder_left, inorder_root-1); root.right = myBuildTree(preorder, inorder, preorder_left+len_left+1, preorder_right, inorder_root+1, inorder_right); return root; } }
106. 从中序与后序遍历序列构造二叉树
https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/
给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。
class Solution { Map<Integer, Integer> map = new HashMap<>(); public TreeNode buildTree(int[] inorder, int[] postorder) { int n = inorder.length; // 节点总数 for (int i = 0; i < n; i++) { map.put(inorder[i], i); } return myBuildTree(inorder, postorder, 0, n-1, 0, n-1); } public TreeNode myBuildTree(int[] inorder, int[] postorder, int inorder_left, int inorder_right, int postorder_left, int postorder_right) { // 四个参数分别代表中序的左右边界(下标),后序的左右边界(下标) if (inorder_left > inorder_right || postorder_left > postorder_right) return null; int root_val = postorder[postorder_right]; // 根节点值 int inorder_root = map.get(root_val); // 根节点在中序遍历的下标 TreeNode root = new TreeNode(root_val); int len_left = inorder_root - inorder_left; // 左子树的节点数 root.left = myBuildTree(inorder, postorder, inorder_left, inorder_root-1, postorder_left, postorder_left+len_left-1); root.right = myBuildTree(inorder, postorder, inorder_root+1, inorder_right, postorder_left+len_left, postorder_right-1); return root; } }
654. 最大二叉树
https://leetcode.cn/problems/maximum-binary-tree/
给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建:
1.创建一个根节点,其值为 nums 中的最大值。
2.递归地在最大值 左边 的 子数组前缀上 构建左子树。
3.递归地在最大值 右边 的 子数组后缀上 构建右子树。
4.返回 nums 构建的 最大二叉树 。
class Solution { public TreeNode constructMaximumBinaryTree(int[] nums) { return travelsal(nums, 0, nums.length - 1); } public TreeNode travelsal(int[] nums, int left_index, int right_index) { if (left_index > right_index) { return null; } if (left_index == right_index) { // 只有一个元素 return new TreeNode(nums[left_index]); } // 寻找最大值以及它的下标 int max_value = nums[left_index]; int max_index = left_index; for (int i = left_index + 1; i <= right_index; i++) { if (nums[i] > max_value) { max_value = nums[i]; max_index = i; } } TreeNode root = new TreeNode(max_value); root.left = travelsal(nums, left_index, max_index - 1); root.right = travelsal(nums, max_index + 1, right_index); return root; } }
617. 合并二叉树
https://leetcode.cn/problems/merge-two-binary-trees/
class Solution { public TreeNode mergeTrees(TreeNode root1, TreeNode root2) { if (root1 == null && root2 == null) return null; if (root1 == null && root2 != null) return root2; if (root1 != null && root2 == null) return root1; return new TreeNode(root1.val + root2.val, mergeTrees(root1.left, root2.left), mergeTrees(root1.right, root2.right)); } }
700. 二叉搜索树中的搜索
https://leetcode.cn/problems/search-in-a-binary-search-tree/
class Solution { public TreeNode searchBST(TreeNode root, int val) { if (root == null || root.val == val) return root; if (root.val > val) return searchBST(root.left, val); if (root.val < val) return searchBST(root.right, val); return null; } }
98. 验证二叉搜索树
https://leetcode.cn/problems/validate-binary-search-tree/
class Solution { public boolean isValidBST(TreeNode root) { return travelsal(root, Long.MIN_VALUE, Long.MAX_VALUE); } public boolean travelsal(TreeNode node, long lower, long upper) { if (node == null) return true; if (node.val <= lower || node.val >= upper) return false; return travelsal(node.left, lower, node.val) && travelsal(node.right, node.val, upper); } }
530. 二叉搜索树的最小绝对差
https://leetcode.cn/problems/minimum-absolute-difference-in-bst/
530. 二叉搜索树的最小绝对差
https://leetcode.cn/problems/minimum-absolute-difference-in-bst/
class Solution { TreeNode pre = null; int res = Integer.MAX_VALUE; public int getMinimumDifference(TreeNode root) { travelsal(root); return res; } public void travelsal(TreeNode cur) { if (cur == null) return; travelsal(cur.left); if (pre != null) res = Math.min(res, cur.val - pre.val); pre = cur; travelsal(cur.right); return; } }
501. 二叉搜索树中的众数
https://leetcode.cn/problems/find-mode-in-binary-search-tree/
class Solution { TreeNode pre = null; int count = 0; int maxCount = 0; List<Integer> list = new ArrayList<>(); public int[] findMode(TreeNode root) { travelsal(root); int[] res = new int[list.size()]; for (int i = 0; i < res.length; i++) { res[i] = list.get(i); } return res; } public void travelsal(TreeNode cur) { if (cur == null) return; travelsal(cur.left); if (pre == null) count++; else if (cur.val == pre.val) count++; else count = 1; pre = cur; if (count == maxCount) { list.add(cur.val); } else if (count > maxCount) { list.clear(); list.add(cur.val); maxCount = count; } travelsal(cur.right); return; } }
235. 二叉搜索树的最近公共祖先
https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-search-tree/
class Solution { public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { return travelsal(root, p, q); } public TreeNode travelsal(TreeNode cur, TreeNode p, TreeNode q) { if (cur == null) return null; if (cur.val > p.val && cur.val > q.val) { TreeNode left = travelsal(cur.left, p, q); if (left != null) { return left; } } else if (cur.val < p.val && cur.val < q.val) { TreeNode right = travelsal(cur.right, p, q); if (right != null) { return right; } } return cur; } }
236. 二叉树的最近公共祖先
https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/
class Solution { public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { if (root == p || root == q || root == null) return root; TreeNode left = lowestCommonAncestor(root.left, p, q); TreeNode right = lowestCommonAncestor(root.right, p, q); if (left != null && right != null) return root; if (left == null) return right; return left; } }
701. 二叉搜索树中的插入操作
https://leetcode.cn/problems/insert-into-a-binary-search-tree/
class Solution { public TreeNode insertIntoBST(TreeNode root, int val) { if (root == null) { return new TreeNode(val); } if (root.val < val) { root.right = insertIntoBST(root.right, val); } else if (root.val > val) { root.left = insertIntoBST(root.left, val); } return root; } }
450. 删除二叉搜索树中的节点
https://leetcode.cn/problems/delete-node-in-a-bst/
class Solution { public TreeNode deleteNode(TreeNode root, int key) { if (root == null) return null; if (root.val < key) { root.right = deleteNode(root.right, key); } else if (root.val > key) { root.left = deleteNode(root.left, key); } else { if (root.left == null && root.right == null) { // 左右都为空 return null; } else if (root.right == null) { // 左不空,右为空 return root.left; } else if (root.left == null) { // 左为空,右不空 return root.right; } else { // 左右都不为空,就将左子树放到右子树的最左边节点的left指针中 TreeNode cur = root.right; while (cur.left != null) { cur = cur.left; } cur.left = root.left; return root.right; } } return root; } }
669. 修剪二叉搜索树
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; } }
108. 将有序数组转换为二叉搜索树
https://leetcode.cn/problems/convert-sorted-array-to-binary-search-tree/
class Solution { public TreeNode sortedArrayToBST(int[] nums) { TreeNode root = traversal(nums, 0, nums.length - 1); return root; } public TreeNode traversal(int[] nums, int left, int right) { if (left > right) return null; int mid = (left + right) / 2; TreeNode root = new TreeNode(nums[mid]); root.left = traversal(nums, left, mid - 1); root.right = traversal(nums, mid + 1, right); return root; } }
538. 把二叉搜索树转换为累加树
https://leetcode.cn/problems/convert-bst-to-greater-tree/
class Solution { int pre = 0; public TreeNode convertBST(TreeNode root) { travelsal(root); return root; } public void travelsal(TreeNode root) { if (root == null) return; if (root.right != null) travelsal(root.right); root.val += pre; pre = root.val; if (root.left != null) travelsal(root.left); } }
回溯
77. 组合
https://leetcode.cn/problems/combinations/
class Solution { private List<Integer> path = new ArrayList<>(); private List<List<Integer>> result = new ArrayList<>(); public List<List<Integer>> combine(int n, int k) { backtrack(n, k, 1); return result; } public void backtrack(int n, int k, int startIndex) { if (path.size() == k) { result.add(new ArrayList(path)); return; } for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) { path.add(i); backtrack(n, k, i+1); path.remove(path.size()-1); } } }
时间复杂度: O(n * 2^n)
空间复杂度: O(n)
216. 组合总和 III
https://leetcode.cn/problems/combination-sum-iii/
class Solution { List<Integer> path = new ArrayList<>(); List<List<Integer>> result = new ArrayList<>(); public List<List<Integer>> combinationSum3(int k, int n) { backtrack(n, k, 1, 0); return result; } public void backtrack(int targetSum, int k, int startIndex, int sum) { if (path.size() == k) { if (sum == targetSum) result.add(new ArrayList(path)); return; } for (int i = startIndex; i <= 9 - (k - path.size()) + 1; i++) { path.add(i); backtrack(targetSum, k, i+1, sum+i); path.remove(path.size() - 1); } } }
17. 电话号码的字母组合
https://leetcode.cn/problems/letter-combinations-of-a-phone-number/
class Solution { String[] phone = new String[]{"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"}; List<String> result = new ArrayList<>(); StringBuilder path = new StringBuilder(); public List<String> letterCombinations(String digits) { if (digits.length() == 0) return result; backtrack(digits, 0); return result; } public void backtrack(String digits, int index) { if (index == digits.length()) { result.add(path.toString()); return; } String s = phone[digits.charAt(index) - '0']; for (int i = 0; i < s.length(); i++) { path.append(s.charAt(i)); backtrack(digits, index+1); path.deleteCharAt(path.length()-1); } } }
39. 组合总和
https://leetcode.cn/problems/combination-sum/
class Solution { List<Integer> path = new ArrayList<>(); List<List<Integer>> result = new ArrayList<>(); public List<List<Integer>> combinationSum(int[] candidates, int target) { Arrays.sort(candidates); backtrack(candidates, target, 0, 0); return result; } public void backtrack(int[] candidates, int target, int sum, int startIndex) { if (sum > target) { return; } if (sum == target) { result.add(new ArrayList(path)); return; } for (int i = startIndex; i < candidates.length && sum + candidates[i] <= target; i++) { path.add(candidates[i]); backtrack(candidates, target, sum+candidates[i], i); path.remove(path.size()-1); } } }
40. 组合总和 II
https://leetcode.cn/problems/combination-sum-ii/
class Solution { List<Integer> path = new ArrayList<>(); List<List<Integer>> result = new ArrayList<>(); public List<List<Integer>> combinationSum2(int[] candidates, int target) { Arrays.sort(candidates); backtrack(candidates, target, 0, 0); return result; } public void backtrack(int[] candidates, int target, int sum, int startIndex) { if (sum > target) { return; } if (sum == target) { result.add(new ArrayList(path)); return; } for (int i = startIndex; i < candidates.length && sum + candidates[i] <= target; i++) { if (i > startIndex && candidates[i] == candidates[i-1]) continue; path.add(candidates[i]); backtrack(candidates, target, sum+candidates[i], i+1); path.remove(path.size()-1); } } }
131. 分割回文串
https://leetcode.cn/problems/palindrome-partitioning/
class Solution { List<List<String>> result = new ArrayList<>(); List<String> path = new ArrayList<>(); public List<List<String>> partition(String s) { backtrack(s, 0); return result; } public void backtrack(String s, int startIndex) { if (startIndex == s.length()) { result.add(new ArrayList(path)); return; } for (int i = startIndex; i < s.length(); i++) { if (isPalindrome(s, startIndex, i)) { path.add(s.substring(startIndex, i+1)); } else { continue; } backtrack(s, i+1); path.remove(path.size()-1); } } public boolean isPalindrome(String s, int start, int end) { for (int i = start, j = end; i < j; i++, j--) { if (s.charAt(i) != s.charAt(j)) { return false; } } return true; } }
93. 复原 IP 地址
https://leetcode.cn/problems/restore-ip-addresses/
class Solution { List<String> result = new ArrayList<>(); StringBuilder path = new StringBuilder(); public List<String> restoreIpAddresses(String s) { backtrack(s, 0, 0); return result; } public void backtrack(String s, int startIndex, int pointNum) { if (startIndex >= s.length()) return; if (pointNum == 3) { // 判断最后一个分段是否合法 if (isValid(s, startIndex, s.length()-1)) { path.append(s.substring(startIndex, s.length())); result.add(path.toString()); } return; } for (int i = startIndex; i < s.length(); i++) { if (isValid(s, startIndex, i)) { path.append(s.substring(startIndex, i+1)); if (pointNum < 3) path.append("."); backtrack(s, i+1, pointNum+1); path.delete(startIndex+pointNum, path.length()); // 删除最后的分段 } else { break; } } } public boolean isValid(String s, int start, int end) { if (end + 1 - start > 3) return false; // 长度超过3 if (start != end && s.charAt(start) == '0') return false; // 有前导0 if (Integer.valueOf(s.substring(start, end+1)) > 255) return false; // 值超过255 return true; } }
78. 子集
https://leetcode.cn/problems/subsets/
class Solution { List<List<Integer>> result = new ArrayList<>(); List<Integer> path = new ArrayList<>(); public List<List<Integer>> subsets(int[] nums) { backtrack(nums, 0); return result; } public void backtrack(int[] nums, int startIndex) { result.add(new ArrayList(path)); if (startIndex == nums.length) { return; } for (int i = startIndex; i < nums.length; i++) { path.add(nums[i]); backtrack(nums, i+1); path.remove(path.size()-1); } } }
90. 子集 II
https://leetcode.cn/problems/subsets-ii/
class Solution { List<List<Integer>> result = new ArrayList<>(); List<Integer> path = new ArrayList<>(); public List<List<Integer>> subsetsWithDup(int[] nums) { Arrays.sort(nums); backtrack(nums, 0); return result; } public void backtrack(int[] nums, int startIndex) { result.add(new ArrayList(path)); if (startIndex == nums.length) { return; } for (int i = startIndex; i < nums.length; i++) { if (i > startIndex && nums[i] == nums[i-1]) { continue; } path.add(nums[i]); backtrack(nums, i+1); path.remove(path.size()-1); } } }
491. 递增子序列
https://leetcode.cn/problems/non-decreasing-subsequences/
class Solution { List<List<Integer>> result = new ArrayList<>(); List<Integer> path = new ArrayList<>(); public List<List<Integer>> findSubsequences(int[] nums) { backtrack(nums, 0); return result; } public void backtrack(int[] nums, int startIndex) { if (path.size() >= 2) { result.add(new ArrayList(path)); } if (startIndex == nums.length) { return; } int[] used = new int[201]; // 记录本层使用过的结点, for (int i = startIndex; i < nums.length; i++) { if (path.size() > 0 && nums[i] < path.get(path.size()-1)) continue; if (used[nums[i]+100] == 1) // 如果nums[i]在本层用过了,就跳过去 continue; used[nums[i]+100] = 1; path.add(nums[i]); backtrack(nums, i+1); path.remove(path.size()-1); } } }
46. 全排列
https://leetcode.cn/problems/permutations/
class Solution { List<List<Integer>> result = new ArrayList<>(); List<Integer> path = new ArrayList<>(); public List<List<Integer>> permute(int[] nums) { // 需要记录path路径上已经用过的元素 int[] used = new int[nums.length]; backtrack(nums, used); return result; } public void backtrack(int[] nums, int[] used) { if (path.size() == nums.length) { result.add(new ArrayList(path)); return; } for (int i = 0; i < nums.length; i++) { // 对同一层的结点遍历 if (used[i] == 1) { continue; } path.add(nums[i]); used[i] = 1; backtrack(nums, used); // 往下一层走 path.remove(path.size()-1); used[i] = 0; } } }
47. 全排列 II
https://leetcode.cn/problems/permutations-ii/
class Solution { List<List<Integer>> result = new ArrayList<>(); List<Integer> path = new ArrayList<>(); public List<List<Integer>> permuteUnique(int[] nums) { Arrays.sort(nums); // 需要记录path路径上已经用过的元素 int[] used = new int[nums.length]; backtrack(nums, used); return result; } public void backtrack(int[] nums, int[] used) { if (path.size() == nums.length) { result.add(new ArrayList(path)); return; } for (int i = 0; i < nums.length; i++) { // 对同一层的结点遍历 if (i > 0 && nums[i] == nums[i-1] && used[i-1] == 0) continue; if (used[i] == 1) { continue; } path.add(nums[i]); used[i] = 1; backtrack(nums, used); // 往下一层走 path.remove(path.size()-1); used[i] = 0; } } }
332. 重新安排行程(没做)
https://leetcode.cn/problems/reconstruct-itinerary/
51. N 皇后
https://leetcode.cn/problems/n-queens/
class Solution { List<List<String>> res = new ArrayList<>(); public List<List<String>> solveNQueens(int n) { char[][] chessboard = new char[n][n]; // 定义一个棋盘 for (char[] c : chessboard) { // 用.来填充棋盘 Arrays.fill(c, '.'); } backtrack(n, 0, chessboard); // 第二个参数表示棋盘的行,从第0行开始 return res; } public void backtrack(int n, int row, char[][] chessboard) { if (row == n) { // 当“行”遍历完后,就表示找到了一个棋盘 res.add(Arrays2List(chessboard)); return; } for (int col = 0; col < n; col++) { // 遍历一行中的每一列 if (isValid(row, col, n, chessboard)) { // 如果Q可以放在rowcol处,那就递归到下一行 chessboard[row][col] = 'Q'; backtrack(n, row+1, chessboard); chessboard[row][col] = '.'; } } } public boolean isValid(int row, int col, int n, char[][] chessboard) { // 检查chessboard[row][col]处的皇后也没有和其他冲突 // 列冲突 for (int i = 0; i < row; i++) { if (chessboard[i][col] == 'Q') { return false; } } // 45度对角线 for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) { if (chessboard[i][j] == 'Q') { return false; } } // 135度对角线 for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) { if (chessboard[i][j] == 'Q') { return false; } } // 没有行冲突 return true; } public List<String> Arrays2List(char[][] chessboard) { // 将char[][]数组转换成List<String> List<String> list = new ArrayList<>(); for (char[] c : chessboard) { list.add(String.valueOf(c)); } return list; } }
37. 解数独
https://leetcode.cn/problems/sudoku-solver/
class Solution { public void solveSudoku(char[][] board) { backtrack(board); } public boolean backtrack(char[][] board) { for (int i = 0; i < 9; i++) { for (int j = 0; j < 9; j++) { if (board[i][j] == '.') { // 可以填数 for (char val = '1'; val <= '9'; val++) { // 遍历可以填的数 if (isValid(i, j, val, board)) { // 如果可以填这个数 board[i][j] = val; if (backtrack(board)) return true; // 递归去寻找,如果这个位置填入val后,递归得到的是true,说明这个位置就确定了,直接返回 board[i][j] = '.'; } } return false; // 遍历1-9都没有合法的,那就说明本次递归节点是无解的,返回false } } } return true; } public boolean isValid(int row, int col, char val, char[][] board) { // 判断在board[row][col]处填入val是否合法 // 检查行、列、所在的3*3网格 for (int i = 0; i < 9; i++) { // 检查列 if (board[i][col] == val) { return false; } } for (int j = 0; j < 9; j++) { // 检查行 if (board[row][j] == val) { return false; } } // 检查3*3网格 int startRow = (row / 3) * 3; int startCol = (col / 3) * 3; for (int i = startRow; i < startRow + 3; i++) { for (int j = startCol; j < startCol + 3; j++) { if (board[i][j] == val) { return false; } } } return true; } }
贪心
动态规划
509. 斐波那契数
class Solution: def fib(self, n: int) -> int: if n < 2: return n a, b, c = 0, 1, 0 for i in range(1, n): c = a + b a = b b = c return c
class Solution { public int fib(int n) { if (n < 2) return n; int a = 0, b = 1, ans = 0; for (int i = 1; i < n; i++) { // 执行n-1次for循环 ans = a + b; a = b; b = ans; } return ans; } }
70. 爬楼梯
class Solution { // 用变量记录代替数组 public int climbStairs(int n) { /* f(i)表示爬n阶楼梯的方法数 f(1) = 1, f(2) = 2 */ if(n <= 2) return n; int a = 1, b = 2, ans = 0; for(int i = 2; i < n; i++){ // for循环n-2次即可 ans = a + b; a = b; b = ans; } return ans; } }
746. 使用最小花费爬楼梯
class Solution { public int minCostClimbingStairs(int[] cost) { int len = cost.length; int[] dp = new int[len+1]; dp[0] = 0; dp[1] = 0; for (int i = 2; i <= len; i++) { dp[i] = Math.min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2]); } return dp[len]; } }
62. 不同路径
class Solution { public int uniquePaths(int m, int n) { int[][] dp = new int[m][n]; for (int i = 0; i < m; i++) { dp[i][0] = 1; } for (int j = 0; j < n; j++) { dp[0][j] = 1; } for (int i = 1; i < m; i++) { for (int j = 1; j < n; j++) { dp[i][j] = dp[i-1][j] + dp[i][j-1]; } } return dp[m-1][n-1]; } }
63. 不同路径 II
class Solution { public int uniquePathsWithObstacles(int[][] obstacleGrid) { int m = obstacleGrid.length; int n = obstacleGrid[0].length; int[][] dp = new int[m][n]; for (int i = 0; i < m && obstacleGrid[i][0] == 0; i++) { dp[i][0] = 1; } for (int j = 0; j < n && obstacleGrid[0][j] == 0; j++) { dp[0][j] = 1; } for (int i = 1; i < m; i++) { for (int j = 1; j < n; j++) { if (obstacleGrid[i][j] == 0) { dp[i][j] = dp[i-1][j] + dp[i][j-1]; } } } return dp[m-1][n-1]; } }
343. 整数拆分
dp[i]:拆分数字i,可以得到的最大乘积为dp[i]
数字i可以拆分为1 * i, 2(i-1), 3(i-2), ..., (i/2) * (i-i/2) 再往后就重复了
所以定义变量j,j从1开始遍历到 i/2, i可以拆分为j * (i-j) ,因为j是从小到大的,所以i-j是从大到小,可以不断拆分
进一步,可以选择拆分i-j或者不拆分i-j
class Solution { public int integerBreak(int n) { int[] dp = new int[n+1]; for (int i = 2; i <= n; i++) { // 题目指定n>=2 for (int j = 1; j <= i/2; j++) { dp[i] = Math.max(dp[i], Math.max(j * (i-j), j * dp[i-j])); } } return dp[n]; } }
96. 不同的二叉搜索树
dp[n]表示元素1到n构成的二叉搜索树的种数
举个例子分析:
dp[3]=元素1为头结点搜索树的数量+元素2为头结点搜索树的数量+元素3为头结点搜索树的数量
元素1为头结点搜索树的数量=右子树有2个元素的搜索树数量 * 左子树有0个元素的搜索树数量=dp[2] * dp[0]
元素2为头结点搜索树的数量=右子树有1个元素的搜索树数量 * 左子树有1个元素的搜索树数量=dp[1] * dp[1]
元素3为头结点搜索树的数量=右子树有0个元素的搜索树数量 * 左子树有2个元素的搜索树数量=dp[0] * dp[2]
所以dp[3] = dp[2] * dp[0] + dp[1] * dp[1] + dp[0] * dp[2]
所以
初始化dp[0] = 1
class Solution { public int numTrees(int n) { int[] dp = new int[n+1]; dp[0] = 1; for (int i = 1; i <= n; i++) { for (int j = 1; j <= i; j++) { dp[i] += dp[j-1] * dp[i-j]; } } return dp[n]; } }
01背包理论基础(一)
背包最大容量是4,物品重量分别为1、3、4,物品价值分别为15、20、30。问怎么装物品,才能获得最大价值?
动规五部曲:
- 确定dp数组以及下标的含义:对于背包问题,有一种写法, 是使用二维数组,即dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。
- 确定递推公式:分别从不选i和选i来分析,可以得到递推公式为
- dp初试化:(1) 首先从dp[i][j]的定义出发,如果背包容量j为0的话,即dp[i][0],无论是选取哪些物品,背包价值总和一定为0,所以dp[i][0]=0。(2) 因为状态转移方程需要i-1来推导i,所以dp[0]必须初始化,d[0][j]表示存放编号0这个物品时,各个容量的背包所能存放的最大价值,所以当 j<weight[0] 时,背包容量放不下0号物品,dp[0][j]=0,当 j >= weight[0]时,dp[0][j] = value[0]
- 确定遍历顺序:
- 举例推导dp数组
public class Main { public static void main(String[] args) { int bagSize = 4; // 背包的承重 int[] weight = {1, 3, 4}; // 物品重量 int[] value = {15, 20, 30}; // 物品价值 int n = weight.length; // 获取物品的个数 int[][] dp = new int[n][bagSize+1]; // 1、dp[i][j]表示从下标[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少 // 2、dp[0][j] 就只能选第一个物品,所以最大不会超过value[0] for (int j = weight[0]; j <= bagSize; j++) { dp[0][j] = value[0]; } // 3、确定递推公式,从不选i和选i来分析,dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]]+value[i]) for (int i = 1; i < n; i++) { // 遍历物品 for (int j = 0; j <= bagSize; j++) { // 遍历背包容量 if (j < weight[i]) { // 当前背包容量不足以放入物品i,那么只能不选i dp[i][j] = dp[i-1][j]; } else { dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-weight[i]] + value[i]); } } } System.out.println(dp[n-1][bagSize]); } }
01背包理论基础(二)
用一维dp数组来解决上一个问题
动规五部曲:
- dp[j] 表示容量为j的背包所能盛放的物品最大价值。
- 确定递推公式:
- dp初试化:全部初始化为0即可
- 确定遍历顺序:正序遍历物品0、1、2号,逆序遍历背包容量,注意这里不能正序遍历
- 举例推导dp数组:
public class Main{ public static void main(String[] args){ int bagSize = 4; int[] weight = {1, 3, 4}; int[] value = {15, 20, 30}; int[] dp = new int[bagSize+1]; for (int i = 0; i < weight.length; i++){ for (int j = bagSize; j >= weight[i]; j--){ dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]); } } System.out.println(dp[bagSize]); } }
416. 分割等和子集
dp[j]表示背包总容量是j时所能背的最大重量
通俗点说,例如数组[2,2,3,5],和为12,要想平均分成两份,每份就必须是6,也就是说给定一个背包容量为6,要从物品中选择,重量为[2,2,3,5],价值也为[2,2,3,5]
所以递推公式为
按照01背包的方式处理完后,检查dp[6]是否等于6就可以判断能否二等分了
class Solution { public boolean canPartition(int[] nums) { int sum = 0; for (int num: nums) { sum += num; } if (sum % 2 == 1) return false; int target = sum / 2; int[] dp = new int[target+1]; for (int i = 0; i < nums.length; i++) { for (int j = target; j >= nums[i]; j--) { dp[j] = Math.max(dp[j], dp[j-nums[i]] + nums[i]); } } return dp[target] == target; } }
1049. 最后一块石头的重量II
class Solution { public int lastStoneWeightII(int[] stones) { int sum = 0; for (int num: stones) { sum += num; } int target = sum / 2; int[] dp = new int[target+1]; for (int i = 0; i < stones.length; i++) { for (int j = target; j >= stones[i]; j--) { dp[j] = Math.max(dp[j], dp[j-stones[i]] + stones[i]); } } return sum - dp[target] - dp[target]; } }
本题其实和416. 分割等和子集 (opens new window)几乎是一样的,只是最后对dp[target]的处理方式不同。
494. 目标和
数组和为sum,假设加法的和为x,那么减法对应的和为sum-x,所以x-(sum-x)=target
即x=(target+sum)/2
dp[j] 表示:填满j(包括j)这么大容积的包,有dp[j]种方法
class Solution { public int findTargetSumWays(int[] nums, int target) { int sum = 0; for (int num: nums) { sum += num; } if (Math.abs(target) > sum) return 0; if ((target + sum) % 2 == 1) return 0; int bagSize = (target + sum) / 2; int[] dp = new int[bagSize +1]; dp[0] = 1; // 必须初始化为1,否则后面就全是0 for (int i = 0; i < nums.length; i++) { for (int j = bagSize; j >= nums[i]; j--) { dp[j] += dp[j - nums[i]]; } } return dp[bagSize]; } }
474. 一和零
class Solution: def findMaxForm(self, strs: List[str], m: int, n: int) -> int: # dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j] # dp[i][j] = max(dp[i][j], dp[i-zeroNum][j-oneNum] + 1) dp = [[0] * (n+1) for _ in range(m+1)] # 外层for循环遍历物品 for s in strs: oneNum = s.count('1') # 字符串中1的个数 zeroNum = s.count('0') # 字符串中0的个数 # 内层for循环遍历背包容量从后向前遍历 for i in range(m, zeroNum-1, -1): for j in range(n, oneNum-1, -1): dp[i][j] = max(dp[i][j], dp[i-zeroNum][j-oneNum] + 1) return dp[m][n]
完全背包理论基础
完全背包与01背包的区别在于,每种物品有无限件
在01背包中,需要从大到小遍历背包容量,因为每个物品只能放入一次
在完全背包中,每个物品可以无限次放入,所以需要从小到大遍历背包容量
518. 零钱兑换 II
dp[j]:凑成总金额j的货币组合数为dp[j]
物品不限个数,遍历背包容量时,从小到大
组合问题,先遍历物品,再遍历背包
class Solution { public int change(int amount, int[] coins) { int[] dp = new int[amount+1]; dp[0] = 1; for (int i = 0; i < coins.length; i++) { for (int j = coins[i]; j <= amount; j++) { dp[j] += dp[j-coins[i]]; } } return dp[amount]; } }
377. 组合总和 Ⅳ
力扣题目链接
题目说是求组合,但其实是求排列,因为顺序不同被视作不同的组合,这不就是排列吗
dp[i]表示凑成正整数为j的排列个数
dp[i]+=dp[i-nums[j]]
dp[0]=1纯粹为了递推公式
注意:
class Solution { public int combinationSum4(int[] nums, int target) { int[] dp = new int[target+1]; dp[0] = 1; for (int i = 1; i <= target; i++) { for (int j = 0; j < nums.length; j++) { if (i >= nums[j]) dp[i] += dp[i-nums[j]]; } } return dp[target]; } }
爬楼梯进阶版
如果每一步可以选择爬一个台阶、两个台阶、三个台阶,.......,直到 m个台阶。问有多少种不同的方法可以爬到楼顶呢?
这是一个完全背包问题,这个题的代码和上一个完全一样,target就是楼梯阶数,nums就是[1,2,...,n]
322. 零钱兑换
class Solution { public int coinChange(int[] coins, int amount) { int[] dp = new int[amount+1]; for (int i = 0; i <= amount; i++) { dp[i] = Integer.MAX_VALUE; } dp[0] = 0; for (int i = 0; i < coins.length; i++) { for (int j = coins[i]; j <= amount; j++) { if (dp[j-coins[i]] != Integer.MAX_VALUE) dp[j] = Math.min(dp[j], dp[j-coins[i]] + 1); } } if (dp[amount] == Integer.MAX_VALUE) return -1; else return dp[amount]; } }
279. 完全平方数
class Solution { public int numSquares(int n) { // 完全背包 遍历背包时由小到大,组合问题,先物品后背包 int[] dp = new int[n+1]; for (int i = 0; i <= n; i++) { dp[i] = Integer.MAX_VALUE; } dp[0] = 0; for (int i = 1; i * i <= n; i++) { for (int j = i*i; j <= n; j++) { dp[j] = Math.min(dp[j], dp[j-i*i] + 1); } } return dp[n]; } }
139. 单词拆分
class Solution { public boolean wordBreak(String s, List<String> wordDict) { /* s是背包,wordDict是物品 dp[i]:字符串长度为i的时候,dp[i]=true,表示可以拆分为1个或者多个在字典中出现的单词 这是排列问题,外层for遍历背包,内层for遍历物品 如果确定dp[j] 是true,且 [j, i] 这个区间的子串出现在字典里,那么dp[i]一定是true。(j < i )。 */ HashSet<String> wordDictSet = new HashSet(wordDict); // 转成哈希表来查找更快 boolean[] dp = new boolean[s.length()+1]; dp[0] = true; for (int i = 1; i <= s.length(); i++) { for (int j = 0; j < i; j++) { if (dp[j] && wordDictSet.contains(s.substring(j,i))) { dp[i] = true; break; } } } return dp[s.length()]; } }
背包问题总结
01背包问题:二维dp,先遍历物品或者先遍历背包都可以,遍历背包时没有顺序要求
一维dp,必须先遍历物品后遍历背包,遍历背包时要按照从大到小
完全背包问题:一维dp没有先后遍历顺序的要求,但是遍历背包要从小到大
组合问题:组合问题,先遍历物品,再遍历背包;
排列问题:先遍历背包,再遍历物品
动态规划五部曲:
- 确定dp数组(dp table)以及下标的含义
- 确定递推公式
- dp数组如何初始化
- 确定遍历顺序
- 举例推导dp数组
递推公式
问能否能装满背包(或者最多装多少):dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]),对应题目如下:
动态规划:416.分割等和子集
动态规划:1049.最后一块石头的重量 II
问装满背包有几种方法:dp[j] += dp[j - nums[i]] ,对应题目如下:
动态规划:494.目标和
动态规划:518. 零钱兑换 II
动态规划:377.组合总和Ⅳ
动态规划:70. 爬楼梯进阶版(完全背包)
问背包装满最大价值:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]),对应题目如下:
动态规划:474.一和零
问装满背包所有物品的最小个数:dp[j] = min(dp[j], dp[j - coins[i]] + 1),对应题目如下:
动态规划:322.零钱兑换
动态规划:279.完全平方数
遍历顺序
01背包
二维dp数组01背包先遍历物品还是先遍历背包都是可以的,且第二层for循环是从小到大遍历。
一维dp数组01背包只能先遍历物品再遍历背包容量,且第二层for循环是从大到小遍历。
完全背包
纯完全背包的一维dp数组实现,先遍历物品还是先遍历背包都是可以的,且第二层for循环是从小到大遍历。
如果求组合数就是外层for循环遍历物品,内层for遍历背包。
如果求排列数就是外层for遍历背包,内层for循环遍历物品。
198. 打家劫舍
class Solution { public int rob(int[] nums) { // dp[i]表示包括i在内之前的房屋最大偷窃金额 // dp[i] = max(dp[i-2] + nums[i], dp[i-1]) int n = nums.length; if (n == 1) return nums[0]; int[] dp = new int[n]; dp[0] = nums[0]; dp[1] = Math.max(nums[0], nums[1]); for (int i = 2; i < n; i++) { dp[i] = Math.max(dp[i-2] + nums[i], dp[i-1]); } return dp[n-1]; } }
213. 打家劫舍 II
力扣题目链接
相比于上一题,这一题是一个环
class Solution { public int rob(int[] nums) { if (nums.length == 1) return nums[0]; int results1 = robRange(nums, 0, nums.length-2); // 去掉尾元素 int results2 = robRange(nums, 1, nums.length-1); // 去掉首元素 return Math.max(results1, results2); } public int robRange(int[] nums, int start, int end) { if (start == end) return nums[start]; int[] dp = new int[nums.length]; dp[start] = nums[start]; dp[start+1] = Math.max(nums[start], nums[start+1]); for (int i = start+2; i <= end; i++) { dp[i] = Math.max(dp[i-2]+nums[i], dp[i-1]); } return dp[end]; } }
337. 打家劫舍 III
https://leetcode.cn/problems/house-robber-iii/
class Solution { public int rob(TreeNode root) { int[] res = robTree(root); return Math.max(res[0], res[1]); } public int[] robTree(TreeNode cur) { int[] res = new int[2]; if (cur == null) return res; // 返回当前节点不偷或偷的最大盗取金额,返回结果为{不偷,偷} int[] left = robTree(cur.left); int[] right = robTree(cur.right); // 不偷当前节点, res[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]); // 偷当前节点,左右就不能偷 res[1] = cur.val + left[0] + right[0]; return res; } }
121. 买卖股票的最佳时机
https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/
class Solution { public int maxProfit(int[] prices) { int min = Integer.MAX_VALUE; int max = 0; for (int i = 0; i < prices.length; i++) { if (prices[i] < min) { min = prices[i]; } else if (prices[i] - min > max) { max = prices[i] - min; } } return max; } }
122. 买卖股票的最佳时机 II
https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/
class Solution { public int maxProfit(int[] prices) { // 贪心 int res = 0; for (int i = 1; i < prices.length; i++) { // 计算当天与前一天的差值,得到每天的利润,将利润大于0的加起来就是最终结果 res += Math.max(prices[i] - prices[i-1], 0); } return res; } }
123. 买卖股票的最佳时机 III
https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iii/
class Solution { public int maxProfit(int[] prices) { /* 一天一共就有五个状态, 0 没有操作 (其实我们也可以不设置这个状态) 1 第一次持有股票 2 第一次不持有股票 3 第二次持有股票 4 第二次不持有股票 dp[i][j]中 i表示第i天,j为 [0 - 4] 五个状态,dp[i][j]表示第i天状态j手头的现金。 dp[i][0] = dp[i-1][0] dp[i][1] = Math.max(dp[i-1][1], dp[i][0] - prices[i]) 延续前一天的状态,或者第i天买入 再减去股票钱 求max dp[i][2] = Math.max(dp[i-1][2], dp[i][1] + prices[i]) 延续前一天的状态,或者第i天卖出 再加上股票钱 求max dp[i][3] = Math.max(dp[i-1][3], dp[i][2] - prices[i]) 延续前一天的状态,或者第i天卖出 再加上股票钱 求max dp[i][4] = Math.max(dp[i-1][4], dp[i][3] + prices[i]) 延续前一天的状态,或者第i天卖出 再加上股票钱 求max */ int[][] dp = new int[prices.length][5]; dp[0][1] = -prices[0]; dp[0][3] = -prices[0]; for (int i = 1; i < prices.length; i++) { dp[i][0] = dp[i-1][0]; dp[i][1] = Math.max(dp[i-1][1], dp[i][0] - prices[i]); dp[i][2] = Math.max(dp[i-1][2], dp[i][1] + prices[i]); dp[i][3] = Math.max(dp[i-1][3], dp[i][2] - prices[i]); dp[i][4] = Math.max(dp[i-1][4], dp[i][3] + prices[i]); } return dp[prices.length-1][4]; } }
188. 买卖股票的最佳时机 IV
https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv/
class Solution { public int maxProfit(int k, int[] prices) { /* 0 不操作 1 第一次买入 2 第一次卖出 3 第二次买入 4 第二次卖出 。。。 */ int[][] dp = new int[prices.length][2 * k + 1]; // j为奇数时,dp[0][j] = -prices[0] for (int j = 1; j < 2 * k + 1; j += 2) { dp[0][j] = -prices[0]; } for (int i = 1; i < prices.length; i++) { for (int j = 1; j < 2 * k + 1; j+=2) { dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-1] - prices[i]); // 对应的是买入 dp[i][j+1] = Math.max(dp[i-1][j+1], dp[i-1][j] + prices[i]); // 对应的是卖出 } } return dp[prices.length-1][2*k]; } }
309. 买卖股票的最佳时机含冷冻期(没做)
https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-cooldown/
714. 买卖股票的最佳时机含手续费(没做)
https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/
300. 最长递增子序列
https://leetcode.cn/problems/longest-increasing-subsequence/
class Solution { public int lengthOfLIS(int[] nums) { // dp[i]表示i之前包括i的以nums[i]结尾的最长递增子序列的长度 // 位置i处的最长递增子序列就是求j从0到i-1各个位置的最长递增子序列+1的最大值 if (nums.length <= 1) return nums.length; int res = 0; int[] dp = new int[nums.length]; Arrays.fill(dp, 1); for (int i = 1; i < nums.length; i++) { for (int j = 0; j < i; j++) { if (nums[i] > nums[j]) { dp[i] = Math.max(dp[i], dp[j] + 1); } } res = Math.max(res, dp[i]); } return res; } }
674. 最长连续递增序列
https://leetcode.cn/problems/longest-continuous-increasing-subsequence/
class Solution { public int findLengthOfLCIS(int[] nums) { int res = 1; int cur_len = 1; for (int i = 1; i < nums.length; i++) { if (nums[i] > nums[i-1]) { cur_len += 1; res = Math.max(res, cur_len); } else { cur_len = 1; } } return res; } }
718. 最长重复子数组
https://leetcode.cn/problems/maximum-length-of-repeated-subarray/
class Solution { public int findLength(int[] nums1, int[] nums2) { /* dp[i][j] 表示以 nums1[i-1]结尾 nums2[j-1]结尾的公共最长子数组的长度 */ int res = 0; int[][] dp = new int[nums1.length+1][nums2.length+1]; for (int i = 1; i < nums1.length+1; i++) { for (int j = 1; j < nums2.length+1; j++) { if (nums1[i-1] == nums2[j-1]) { dp[i][j] = dp[i-1][j-1] + 1; } res = Math.max(res, dp[i][j]); } } return res; } }
1143. 最长公共子序列
https://leetcode.cn/problems/longest-common-subsequence/
class Solution { public int longestCommonSubsequence(String text1, String text2) { int[][] dp = new int[text1.length()+1][text2.length()+1]; for (int i = 1; i <= text1.length(); i++) { for (int j = 1; j <= text2.length(); j++) { if (text1.charAt(i-1) == text2.charAt(j-1)) dp[i][j] = dp[i-1][j-1] + 1; else dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]); } } return dp[text1.length()][text2.length()]; } }
53. 最大子数组和
https://leetcode.cn/problems/maximum-subarray/
class Solution { public int maxSubArray(int[] arr) { int[] dp = new int[arr.length]; dp[0] = arr[0]; int res = dp[0]; for (int i = 1; i < arr.length; i++) { dp[i] = Math.max(dp[i-1] + arr[i], arr[i]); res = Math.max(res, dp[i]); } return res; } }
392. 判断子序列
https://leetcode.cn/problems/is-subsequence/
class Solution { public boolean isSubsequence(String s, String t) { // dp[i][j] 表示以下标i-1为结尾的字符串s,和以下标j-1为结尾的字符串t,相同子序列的长度为dp[i][j]。 int[][] dp = new int[s.length()+1][t.length()+1]; for (int i = 1; i < s.length() + 1; i++) { for (int j = 1; j < t.length() + 1; j++) { if (s.charAt(i-1) == t.charAt(j-1)) dp[i][j] = dp[i-1][j-1] + 1; else dp[i][j] = dp[i][j-1]; } } if (dp[s.length()][t.length()] == s.length()) return true; return false; } }
115. 不同的子序列
https://leetcode.cn/problems/distinct-subsequences/
class Solution { public int numDistinct(String s, String t) { // 可以理解为删除s中的元素,有多少种方案可以变成t int[][] dp = new int[s.length()+1][t.length()+1]; for (int i = 0; i <= s.length(); i++) dp[i][0] = 1; for (int j = 1; j <= t.length(); j++) dp[0][j] = 0; for (int i = 1; i <= s.length(); i++) { for (int j = 1; j <= t.length(); j++) { if (s.charAt(i-1) == t.charAt(j-1)) { dp[i][j] = dp[i-1][j-1] + dp[i-1][j]; } else { dp[i][j] = dp[i-1][j]; } } } return dp[s.length()][t.length()]; } }
583. 两个字符串的删除操作
https://leetcode.cn/problems/delete-operation-for-two-strings/
class Solution { public int minDistance(String word1, String word2) { // dp[i][j]:以i-1为结尾的字符串word1,和以j-1位结尾的字符串word2,想要达到相等,所需要删除元素的最少次数 int[][] dp = new int[word1.length()+1][word2.length()+1]; // 初始化 for (int i = 1; i <= word1.length(); i++) dp[i][0] = i; for (int j = 1; j <= word2.length(); j++) dp[0][j] = j; for (int i = 1; i <= word1.length(); i++) { for (int j = 1; j <= word2.length(); j++) { if (word1.charAt(i-1) == word2.charAt(j-1)) { // 相等就不用删 dp[i][j] = dp[i-1][j-1]; } else { // 有2种情况 // 删除word1的 dp[i][j] = dp[i-1][j] + 1; // 删除word2的 dp[i][j] = dp[i][j-1] + 1; dp[i][j] = Math.min(dp[i-1][j] + 1, dp[i][j-1] + 1); } } } return dp[word1.length()][word2.length()]; } }
72. 编辑距离
https://leetcode.cn/problems/edit-distance/
class Solution { public int minDistance(String word1, String word2) { /* dp[i][j] 表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j] */ int[][] dp = new int[word1.length()+1][word2.length()+1]; for (int i = 0; i <= word1.length(); i++) dp[i][0] = i; for (int j = 0; j <= word2.length(); j++) dp[0][j] = j; for (int i = 1; i <= word1.length(); i++) { for (int j = 1; j <= word2.length(); j++) { if (word1.charAt(i-1) == word2.charAt(j-1)) { dp[i][j] = dp[i-1][j-1]; } else { dp[i][j] = Math.min(Math.min(dp[i-1][j-1], dp[i-1][j]), dp[i][j-1]) + 1; // word1删除一个元素, dp[i][j] = dp[i - 1][j] + 1; // word1添加一个元素(word2删除一个元素), dp[i][j] = dp[i][j - 1] + 1; // word1替换一个元素, dp[i][j] = dp[i - 1][j - 1] + 1; } } } return dp[word1.length()][word2.length()]; } }
647. 回文子串
https://leetcode.cn/problems/palindromic-substrings/
class Solution { public int countSubstrings(String s) { int res = 0; // 中心扩展法 for (int i = 0; i < s.length(); i++) { res += expand(s, i, i); // 以i为中心 res += expand(s, i, i+1); // 以i+1为中心 } return res; } public int expand(String s, int left, int right) { int res = 0; while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) { left--; right++; res++; } return res; } }
516. 最长回文子序列
https://leetcode.cn/problems/longest-palindromic-subsequence/
class Solution { public int longestPalindromeSubseq(String s) { // dp[i][j]:字符串s在[i, j]范围内最长的回文子序列的长度 // if(s[i] == s[j]) dp[i][j] = dp[i+1][j-1] + 2 // else dp[i][j] = max(dp[i+1][j], dp[i][j-1]) // i要依赖i+1,j要依赖j-1,所以i从右往左遍历,因为j要在i的右边,所以j从i+1开始往右遍历 int[][] dp = new int[s.length()][s.length()]; // 对角线初始化为1 for (int k = 0; k < s.length(); k++) { dp[k][k] = 1; } for (int i = s.length() - 1; i >= 0; i--) { for (int j = i + 1; j < s.length(); j++) { if (s.charAt(i) == s.charAt(j)) { dp[i][j] = dp[i+1][j-1] + 2; } else { dp[i][j] = Math.max(dp[i+1][j], dp[i][j-1]); } } } return dp[0][s.length()-1]; } }
单调栈
739. 每日温度
https://leetcode.cn/problems/daily-temperatures/
class Solution { public int[] dailyTemperatures(int[] temperatures) { int n = temperatures.length; int[] res = new int[n]; Deque<Integer> deque = new LinkedList<>(); // 存放下标 deque.push(0); // 先把第一个元素下标放进去 for (int i = 1; i < n; i++) { while (!deque.isEmpty() && temperatures[i] > temperatures[deque.peek()]) { res[deque.peek()] = i - deque.peek(); deque.pop(); } deque.push(i); } return res; } }
496. 下一个更大元素 I
https://leetcode.cn/problems/next-greater-element-i/
class Solution { public int[] nextGreaterElement(int[] nums1, int[] nums2) { Map<Integer, Integer> map = new HashMap<>(); for (int i = 0; i < nums1.length; i++) { map.put(nums1[i], i); } int[] res = new int[nums1.length]; Arrays.fill(res, -1); Deque<Integer> deque = new LinkedList<>(); for (int i = 0; i < nums2.length; i++) { while (!deque.isEmpty() && nums2[i] > nums2[deque.peek()]) { int pre = nums2[deque.pop()]; if (map.containsKey(pre)) { res[map.get(pre)] = nums2[i]; } } deque.push(i); } return res; } }
503. 下一个更大元素 II
https://leetcode.cn/problems/next-greater-element-ii/
class Solution { public int[] nextGreaterElements(int[] nums) { Deque<Integer> deque = new LinkedList<>(); int n = nums.length; int[] res = new int[n]; Arrays.fill(res, -1); for (int i = 0; i < n * 2; i++) { while (!deque.isEmpty() && nums[i % n] > nums[deque.peek()]) { res[deque.pop()] = nums[i % n]; } deque.push(i % n); } return res; } }
42. 接雨水
https://leetcode.cn/problems/trapping-rain-water/description/
class Solution { public int trap(int[] height) { int res = 0; Deque<Integer> deque = new LinkedList<>(); for (int i = 0; i < height.length; i++) { while (!deque.isEmpty() && height[i] > height[deque.peek()]) { int mid = deque.pop(); if (!deque.isEmpty()) { int h = Math.min(height[deque.peek()], height[i]) - height[mid]; int w = i - deque.peek() - 1; res += h * w; } } deque.push(i); } return res; } }
84. 柱状图中最大的矩形
https://leetcode.cn/problems/largest-rectangle-in-histogram/description
class Solution { public int largestRectangleArea(int[] heights) { // 数组扩容,在height前后各加一个0 int n = heights.length; int[] newHeights = new int[n + 2]; newHeights[0] = 0; newHeights[n + 1] = 0; for (int i = 1; i < n + 1; i++) { newHeights[i] = heights[i - 1]; } int res = 0; Deque<Integer> deque = new LinkedList<>(); for (int i = 0; i < newHeights.length; i++) { while (!deque.isEmpty() && newHeights[i] < newHeights[deque.peek()]) { int mid = deque.pop(); int w = i - deque.peek() - 1; int h = newHeights[mid]; res = Math.max(res, h * w); } deque.push(i); } return res; } }
图论
本文作者:若乔
本文链接:https://www.cnblogs.com/lijinrun/p/17783485.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步