代码随想录刷题笔记

代码随想录

数组

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

dp[i]=max(dp[i],max(j(ij),jdp[ij]))

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[n]=i=0n1dp[i]dp[n1i]

初始化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[i][j]=max(dp[i1][j],dp[i1][jweight[i]]+value[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[j]=max(dp[j],dp[jweight[i]]+value[i])

  • 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]
所以递推公式为

dp[j]=max(dp[j],dp[jnums[i])+nums[i])

按照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 中国大陆许可协议进行许可。

posted @   若乔  阅读(86)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起