代码随想录刷题记录

代码随想录刷题记录

1、数组

1.1、二分查找

题目传送门

方法一:二分查找

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;}
}

1.2、移除元素

题目传送门

方法一:快慢指针

class Solution {public int removeElement(int[] nums, int val) {int fast = 0, slow = 0;while (fast < nums.length){if (nums[fast] != val){nums[slow] = nums[fast];slow++;}fast++;}return slow;}
}

方法二:双指针

这个方法避免了需要保留的元素的重复赋值操作。

class Solution {public int removeElement(int[] nums, int val) {int left = 0, right = nums.length - 1;while (left <= right){if (nums[left] == val){nums[left] = nums[right];right--;}else {left++;}}return left;}
}

1.3、有序数组的平方

题目传送门

方法一:直接排序

效率较低

class Solution {public int[] sortedSquares(int[] nums) {for (int i = 0; i < nums.length; i++) {nums[i] *= nums[i];}Arrays.sort(nums);return nums;}
}

方法二:双指针

设置左右双指针,选择较大的逆序放入结果数组中。

class Solution {public int[] sortedSquares(int[] nums) {int len = nums.length;int left = 0, right = len - 1;int[] res = new int[nums.length];int index = len - 1;while (left <= right){int val1 = nums[left] * nums[left];int val2 = nums[right] * nums[right];//选择较大的逆序放入结果数组中if (val1 > val2){res[index--] = val1;left++;}else {res[index--] = val2;right--;}}return res;}
}

1.4、长度最小的子数组

题目传送门

方法一:滑动窗口

class Solution {public int minSubArrayLen(int target, int[] nums) {int res = Integer.MAX_VALUE;int left = 0, right = 0;int sum = nums[0];while (right < nums.length){if (sum >= target){ //满足条件,先减再左移res = Math.min(res, right - left + 1);sum -= nums[left];left++;}else { //先右移再加right++;if (right < nums.length){ //防止下标越界sum += nums[right];}}}//如果res变了说明找到了结果,否则没找到满足条件的结果,返回0return res == Integer.MAX_VALUE ? 0 : res;}
}

1.5、螺旋矩阵 II

题目传送门

方法一:

class Solution {public int[][] generateMatrix(int n) {int[][] res = new int[n][n];int val = 1, len = n * n;int t = 0, d = res.length - 1, l = 0, r = res[0].length - 1;//上下左右边界while (true){for (int i = l; i <= r; i++) { //从左到右res[t][i] = val;val++;}if (++t > d) break; //如果越过边界,直接退出循环退出for (int i = t; i <= d; i++) { //从上到下res[i][r] = val;val++;}if (--r < l) break;for (int i = r; i >= l; i--) { //从右到左res[d][i] = val;val++;}if (--d < t) break;for (int i = d; i >= t; i--) { //从下到上res[i][l] = val;val++;}if (++l > r) break;}return res;}
}

2、链表

2.1、移除链表元素

题目传送门

方法一:迭代

class Solution {public ListNode removeElements(ListNode head, int val) {ListNode newHead = new ListNode();newHead.next = head;ListNode temp = newHead;while (temp.next != null){if (temp.next.val == val){temp.next = temp.next.next;}else {temp = temp.next;}}return newHead.next;}
}

方法二:递归

class Solution {public ListNode removeElements(ListNode head, int val) {if (head == null){return null;}head.next = removeElements(head.next, val);return head.val == val ? head.next : head;}
}

2.2、设计链表

题目传送门

方法一:手写单向链表

这个题直接使用LinkedList也能通过。

class MyLinkedList {int size;ListNode head;//头节点public MyLinkedList() {size = 0;head = new ListNode(0);}public int get(int index) {if (index < 0 || index >= size){ //index不符合条件return -1;}else {ListNode temp = head.next;for (int i = 0; i < index; i++) {temp = temp.next;}return temp.val;}}public void addAtHead(int val) {addAtIndex(0, val);}public void addAtTail(int val) {addAtIndex(size, val);}public void addAtIndex(int index, int val) {if (index < 0 || index > size ) return; //index不符合条件ListNode temp = head;for (int i = 0; i < index; i++) {temp = temp.next;}ListNode node = new ListNode(val);node.next = temp.next;temp.next = node;size++;}public void deleteAtIndex(int index) {if (index < 0 || index >= size ) return; //index不符合条件ListNode temp = head;for (int i = 0; i < index; i++) {temp = temp.next;}temp.next = temp.next.next;size--;}
}class ListNode {int val;ListNode next;public ListNode(int val) {this.val = val;}
}

2.3、反转链表

题目传送门

方法一:迭代

class Solution {public ListNode reverseList(ListNode head) {ListNode pre = null;ListNode cur = head;while (cur != null){ListNode temp = cur.next;//临时节点保存cur的下一个节点cur.next = pre;//更改指向pre = cur;//更新precur = temp;//更新cur}return pre;//pre作为头节点返回}
}

递归

class Solution {public ListNode reverseList(ListNode head) {if (head == null || head.next == null){ //找到最后面的节点return head;}ListNode newHead = reverseList(head.next);//返回的节点作为新的头节点head.next.next = head;//反转指向head.next = null;//避免形成环return newHead;//一直返回这个新的头节点}
}

2.4、两两交换链表中的节点

题目传送门

方法一:迭代+两两交换(自己写的)

class Solution {public ListNode swapPairs(ListNode head) {if (head == null){return head;}//双指针ListNode pre = head;ListNode cur = head.next;while (pre != null && cur != null){ //如果接下来的两个节点有空的就不交换了//交换值int temp = pre.val;pre.val = cur.val;cur.val = temp;//再取后面的两个节点pre = cur.next;if (pre != null){cur = pre.next;}}return head;}
}

方法二:递归

class Solution {public ListNode swapPairs(ListNode head) {if (head == null || head.next ==null){ //接下来的两个节点又一个空了停止递归return head;}ListNode temp = head.next;//取到每两个节点的第二个节点//交换head.next = swapPairs(temp.next);temp.next = head;return temp;}
}

2.5、删除链表的倒数第N个节点

题目传送门

方法一:计算链表的长度

class Solution {public ListNode removeNthFromEnd(ListNode head, int n) {int size = size(head);ListNode pre = new ListNode();pre.next = head;ListNode temp = pre;for (int i = 0; i < size - n; i++) { //找到要删除节点的前一个节点temp = temp.next;}temp.next = temp.next.next; //删除节点return pre.next;}//计算链表的长度public int size(ListNode head){int size = 0;ListNode temp = head;while (temp != null){temp = temp.next;size++;}return size;}
}

方法二:栈

class Solution {public ListNode removeNthFromEnd(ListNode head, int n) {Stack<ListNode> stack = new Stack<>();ListNode pre = new ListNode();pre.next = head;ListNode temp = pre;while (temp != null){ //将元素全部入栈stack.push(temp);temp = temp.next;}for (int i = 1; i <= n; i++) { //弹出n个stack.pop();}temp = stack.peek();temp.next = temp.next.next; //删除return pre.next;}
}

方法三:快慢指针

class Solution {public ListNode removeNthFromEnd(ListNode head, int n) {ListNode pre = new ListNode();pre.next = head;ListNode slow = pre, fast = pre;//快慢指针for (int i = 0; i < n; i++) { //快指针指向慢节点的后n个节点fast = fast.next;}while (fast.next != null){ //快指针到最后一个节点时,此时慢指针指向要删除节点的前一个节点slow = slow.next;fast = fast.next;}slow.next = slow.next.next;//删除return pre.next;//使用pre节点可以防止删除的是第一个元素}
}

2.6、链表相交

题目传送门

方法一:双指针

遍历的长度为第一个链表的长度加上第二个链表的开头到相交节点或者末尾的长度。

这个题也可以使用哈希Set来解决。

class Solution {public ListNode getIntersectionNode(ListNode headA, ListNode headB) {if (headA == null || headB == null){return null;}ListNode temp1 = headA;ListNode temp2 = headB;while (temp1 != temp2){ //退出循环时要不为相交节点,要不都为空temp1 = temp1 == null ? headB : temp1.next;temp2 = temp2 == null ? headA : temp2.next;}return temp1;}
}

2.7、环形链表 II

题目传送门

方法一:哈希表Set

class Solution {public ListNode detectCycle(ListNode head) {HashSet<ListNode> set = new HashSet<>();ListNode temp = head;while (temp != null){ //退出循环使用链表没有成环if(set.add(temp)){temp = temp.next;}else {return temp;}}return null;}
}

方法二:快慢指针

快指针走两步,慢指针走一步,直到相遇;然后快指针指到头节点,快慢指针都走一步,再次相遇为结果。

class Solution {public ListNode detectCycle(ListNode head) {ListNode fast = head, slow = head;while (true){if (fast == null || fast.next == null) return null;//不成环slow = slow.next;fast = fast.next.next;if (fast == slow) break;//相遇退出循环}fast = head;//快指针到头节点while (true){if (fast == slow) break;//再次相遇的节点为结果fast = fast.next;slow = slow.next;}return fast;}
}

3、哈希表

3.1、有效的字母异位词

题目传送门

方法一:排序

class Solution {public boolean isAnagram(String s, String t) {if (s.length() != t.length()){return false;}char[] chars1 = s.toCharArray();char[] chars2 = t.toCharArray();Arrays.sort(chars1);Arrays.sort(chars2);return Arrays.equals(chars1, chars2);//先比较内存地址是否为同一个,再逐个字符比较}
}

方法二:数组

使用长度为26的数组来维护26个字母。

class Solution {public boolean isAnagram(String s, String t) {if (s.length() != t.length()){return false;}int[] arr = new int[26];for (int i = 0; i < s.length(); i++) {arr[s.charAt(i) - 'a']++;}for (int i = 0; i < t.length(); i++) {arr[t.charAt(i) - 'a']--;if (arr[t.charAt(i) - 'a'] < 0){return false;}}return true;}
}

方法三:哈希表

class Solution {public boolean isAnagram(String s, String t) {if (s.length() != t.length()){return false;}HashMap<Character, Integer> map = new HashMap<>();for (int i = 0; i < s.length(); i++) {char c = s.charAt(i);map.put(c, map.getOrDefault(c, 0) + 1);}for (int i = 0; i < t.length(); i++) {char c = t.charAt(i);map.put(c, map.getOrDefault(c, 0) - 1);if (map.get(c) < 0){return false;}}return true;}
}

3.2、两个数组的交集

题目传送门

方法一:两个哈希表

class Solution {public int[] intersection(int[] nums1, int[] nums2) {HashSet<Integer> set = new HashSet<>();HashSet<Integer> res = new HashSet<>();for (int i = 0; i < nums1.length; i++) {set.add(nums1[i]);}for (int i = 0; i < nums2.length; i++) {if (set.contains(nums2[i])){res.add(nums2[i]);}}int[] arr = new int[res.size()];int index = 0;for (int val : set) {arr[index++] = val;}return arr;}
}

方法二:排序+双指针

class Solution {public int[] intersection(int[] nums1, int[] nums2) {Arrays.sort(nums1);Arrays.sort(nums2);int len1 = nums1.length, len2 = nums2.length;int[] res = new int[len1 + len2];int index = 0, index1 = 0, index2 = 0;while (index1 < len1 && index2 < len2){int val1 = nums1[index1];int val2 = nums2[index2];if (val1 == val2){//保证不加入重复的元素if (index == 0 || val1 != res[index - 1]){res[index++] = val1;}index1++;index2++;}else if (val1 < val2){index1++;}else {index2++;}}return Arrays.copyOfRange(res, 0, index);//复制指定范围的元素到另一个数组}
}

3.3、快乐数

题目传送门

方法一:哈希表

有两种情况:

  1. 循环到数字1,为快乐数。
  2. 进入死循环,并且循环不到1,不是快乐数。
class Solution {public boolean isHappy(int n) {HashSet<Integer> set = new HashSet<>();int sum = n;while (true){String s = sum + "";sum = 0;for (int i = 0; i < s.length(); i++) {int val = s.charAt(i) - '0';sum += val * val;}if (sum == 1){ //是快乐数return true;}if (!set.add(sum)){ //进入循环,不是快乐数return false;}}}
}

方法二:快慢指针

class Solution {public boolean isHappy(int n) {int slow = n, fast = getNext(n);while (fast != 1 && slow != fast){ //快指针走两步,慢指针走一步slow = getNext(slow);fast = getNext(getNext(fast));}return fast == 1;//退出循环时,结果要不为1,要不进入了不为1的循环}//得到下一个计算的数public int getNext(int n){int sum = 0;while (n > 0){int val = n % 10;n /= 10;sum += val * val;}return sum;}
}

3.4、两数之和

题目传送门

方法一:哈希表

class Solution {public int[] twoSum(int[] nums, int target) {Map<Integer, Integer> map = new HashMap<>();for (int i = 0; i < nums.length; i++) {int val = target - nums[i];if (map.containsKey(val)){return new int[]{map.get(val), i};//找到}else {map.put(nums[i], i);}}return new int[2];//没有正确答案}
}

3.5、四数相加 II

题目传送门

方法一:分组 + 哈希表

nums1和nums2分一组;nums3和nums4分一组。

map的key存nums1和nums2相加的值,value存出现的次数。

class Solution {public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {Map<Integer, Integer> map = new HashMap<>();int count = 0;for (int i = 0; i < nums1.length; i++) {for (int j = 0; j < nums2.length; j++) {int val = nums1[i] + nums2[j];map.put(val, map.getOrDefault(val, 0) + 1);}}for (int i = 0; i < nums3.length; i++) {for (int j = 0; j < nums4.length; j++) {int val = nums3[i] + nums4[j];if (map.containsKey(0 - val)){count += map.get(0 - val);}}}return count;}
}

3.6、赎金信

题目传送门

方法一:哈希表

class Solution {public boolean canConstruct(String ransomNote, String magazine) {if (ransomNote.length() > magazine.length()) {return false;}Map<Character, Integer> map = new HashMap<>();for (int i = 0; i < magazine.length(); i++) {char c = magazine.charAt(i);map.put(c, map.getOrDefault(c, 0) + 1);}for (int i = 0; i < ransomNote.length(); i++) {char c = ransomNote.charAt(i);map.put(c, map.getOrDefault(c, 0) - 1);if (map.get(c) < 0){return false;}}return true;}
}

方法二:长度为26的数组

class Solution {public boolean canConstruct(String ransomNote, String magazine) {if (ransomNote.length() > magazine.length()) {return false;}int[] arr = new int[26];for (int i = 0; i < magazine.length(); i++) {char c = magazine.charAt(i);arr[c - 'a']++;}for (int i = 0; i < ransomNote.length(); i++) {char c = ransomNote.charAt(i);arr[c - 'a']--;if (arr[c - 'a'] < 0){return false;}}return true;}
}

3.7、三数之和

题目传送门

方法一:排序+双指针

class Solution {public List<List<Integer>> threeSum(int[] nums) {Arrays.sort(nums);List<List<Integer>> lists = new ArrayList<>();for (int k = 0; k < nums.length - 2; k++) { //分别固定数组中的每一个数作为第一个数if (nums[k] > 0) break;//直接退出,因为排序后的第一个数不能小于0if (k > 0 && nums[k] == nums[k - 1]) continue;//防止找重复的集合int left = k + 1, right = nums.length - 1;//左指针指向k的后一个数,右指针指向数组最右边while (left < right){int sum = nums[k] + nums[left] + nums[right];if (sum == 0){//找到一个结果,放入集合lists.add(new ArrayList<>(Arrays.asList(nums[k], nums[left], nums[right])));while (left < right && nums[left] == nums[++left]);//防止重复while (left < right && nums[right] == nums[--right]); //防止重复}else if (sum < 0){//左指针右移while (left < right && nums[left] == nums[++left]);//防止重复}else {//右指针左移while (left < right && nums[right] == nums[--right]); //防止重复}}}return lists;}
}

3.8、四数之和

题目传送门

方法一:排序+双指针

和上题思路相似。

class Solution {public List<List<Integer>> fourSum(int[] nums, int target) {List<List<Integer>> lists = new ArrayList<>();int len = nums.length;if (len < 4){return lists;}Arrays.sort(nums);for (int i = 0; i < len - 3; i++) {if (i > 0 && nums[i] == nums[i - 1]) continue;//优化://在确定第一个数之后,如果和后面三个数的和大于target,那么不需要在循环了,因此退出循环if ((long)nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target) break;//在确定第一个数之后,如果和数组最后三个数的和小于targrt,直接进入下一轮循环if ((long)nums[i] + nums[len - 1] + nums[len - 2] + nums[len - 3] < target) continue;for (int j = i + 1; j < len - 2; j++) {if (j > i + 1 && nums[j] == nums[j - 1]) continue;//优化://在确定第前两个数之后,如果和后面两个数的和大于target,那么不需要在循环了,因此退出循环if ((long)nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target) break;//在确定第前两个数之后,如果和数组最后两个数的和小于targrt,直接进入下一轮循环if ((long)nums[i] + nums[j] + nums[len - 1] + nums[len - 2] < target) continue;int left = j + 1, right = len - 1;while (left < right){long sum = (long)nums[i] + nums[j] + nums[left] + nums[right];if (sum == target){lists.add(new ArrayList<>(Arrays.asList(nums[i], nums[j], nums[left], nums[right])));while (left < right && nums[left] == nums[++left]);while (left < right && nums[right] == nums[--right]);}else if (sum < target){while (left < right && nums[left] == nums[++left]);}else {while (left < right && nums[right] == nums[--right]);}}}}return lists;}
}

4、字符串

4.1、反转字符串

题目传送门

方法一:双指针

class Solution {public void reverseString(char[] s) {int left = 0, right = s.length - 1;while (left < right){char temp = s[left];s[left] = s[right];s[right] = temp;left ++;right--;}}
}

4.2、反转字符串II

题目传送门

方法一:自己写的

class Solution {public String reverseStr(String s, int k) {char[] chars = s.toCharArray();int len = chars.length;int index = 0;while (index + 2 * k - 1 < len){ //循环反转2k个字符串的前k个字符helper(chars, index, index + k - 1);index += 2 * k;}if (len - index < k){ //剩余字符少于k个,全部反转helper(chars, index, len - 1);}else { //剩余字符大于k个,饭反转前k个helper(chars, index, index + k - 1);}return String.valueOf(chars);}//反转字符串方法public void helper(char[] chars, int left, int right) {while (left < right){char temp = chars[left];chars[left] = chars[right];chars[right] = temp;left ++;right--;}}
}

方法二:模拟

class Solution {public String reverseStr(String s, int k) {char[] chars = s.toCharArray();int len = chars.length;for (int i = 0; i < len; i += 2 * k) {helper(chars, i, Math.min(i + k, len) - 1);}return String.valueOf(chars);}//反转字符串的方啊public void helper(char[] chars, int left, int right) {while (left < right){char temp = chars[left];chars[left] = chars[right];chars[right] = temp;left ++;right--;}}
}

4.3、替换空格

题目传送门

方法一:StringBuffer

class Solution {public String replaceSpace(String s) {StringBuffer sb = new StringBuffer();for (int i = 0; i < s.length(); i++) {char c = s.charAt(i);if (c == ' '){sb.append("%20");}else {sb.append(c);}}return sb.toString();}
}

4.4、翻转字符串里的单词

题目传送门

方法一:分割

trim():去掉字符串头部和尾部的空格。

class Solution {public String reverseWords(String s) {//trim():去掉字符串头部和尾部的空格String[] strs = s.trim().split(" ");StringBuffer sb = new StringBuffer();for (int i = strs.length - 1; i >= 0; i--) {if (!strs[i].equals("")){sb.append(strs[i]);sb.append(" ");}}return sb.toString().trim();}
}

4.5、左旋转字符串

题目传送门

方法一:字符串切片

class Solution {public String reverseLeftWords(String s, int n) {return s.substring(n, s.length()) + s.substring(0, n);}
}

4.6、找出字符串中第一个匹配项的下标

题目传送门

方法一:暴力匹配

class Solution {public int strStr(String haystack, String needle) {int len1 = haystack.length(), len2 = needle.length();char[] chars1 = haystack.toCharArray();char[] chars2 = needle.toCharArray();// 枚举原串的「发起点」for (int i = 0; i < len1 - len2 + 1; i++) {// 从原串的「发起点」和匹配串的「首位」开始,尝试匹配int p1 = i, p2 = 0;while (p2 < len2 && chars1[p1] == chars2[p2]){p1++;p2++;}//匹配成功if (p2 == len2) return i;}return -1;}
}

4.7、重复的子字符串

方法一:暴力匹配

class Solution {public boolean repeatedSubstringPattern(String s) {int len = s.length();for (int i = 1; i <= len / 2; i++) { //子字符串的长度if (len % i == 0){ //字符串的总长度一定是子字符串长度的倍数boolean match = true;for (int j = i; j < len; j++) { //逐个字符比较是否匹配if (s.charAt(j) != s.charAt(j - i)){match = false;break;}}if (match) return true;}}return false;}
}

方法二:字符串匹配

一个字符串右移,如果可以和原始的字符串匹配,则存在重复的子字符串。

把两个字符串拼接,去掉首位元素,如果包含s,说明存在重复的子字符串。

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

5、栈与队列

5.1、用栈实现队列

题目传送门

方法一:双栈

class MyQueue {Stack<Integer> stack1;Stack<Integer> stack2;public MyQueue() {stack1 = new Stack<>();stack2 = new Stack<>();}public void push(int x) {stack1.push(x);}public int pop() {if (stack2.size() == 0){//把stack1中的元素放入stack2中while (stack1.size() != 0){stack2.push(stack1.pop());}}return stack2.pop();}public int peek() {if (stack2.size() == 0){//把stack1中的元素放入stack2中while (stack1.size() != 0){stack2.push(stack1.pop());}}return stack2.peek();}public boolean empty() {if (stack2.size() == 0 && stack1.size() == 0){return true;}else {return false;}}
}

5.2、用队列实现栈

题目传送门

方法一:两个队列

class MyStack {Queue<Integer> queue1;Queue<Integer> queue2;public MyStack() {queue1 = new LinkedList<>();queue2 = new LinkedList<>();}public void push(int x) {queue2.offer(x);//先往queue2中添加元素//把queue1中的元素弹出放入queue2中while (queue1.size() != 0){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() && queue2.isEmpty();}
}

方法二:一个队列

class MyStack {Queue<Integer> queue;public MyStack() {queue = new LinkedList<>();}public void push(int x) {int size = queue.size();queue.offer(x);//先让元素入队列//把这个元素前面的所有元素依次出队列,并重新入队列for (int i = 0; i < size; i++) {queue.offer(queue.poll());}}public int pop() {return queue.poll();}public int top() {return queue.peek();}public boolean empty() {return queue.isEmpty();}
}

5.3、有效的括号

题目传送门

方法一:栈

class Solution {public boolean isValid(String s) {int len = s.length();if (len % 2 != 0) return false; //字符串中的字符数必须为偶数Map<Character, Character> map = new HashMap<>();map.put(')', '(');map.put(']', '[');map.put('}', '{');Stack<Character> stack = new Stack<>();for (int i = 0; i < len; i++) {char c = s.charAt(i);if (map.containsKey(c)){ //c为右括号if (stack.isEmpty() || stack.peek() != map.get(c)){ //不合法返沪falsereturn false;}else {stack.pop(); //pop方法在头部添加;add在尾部添加}}else { //c为左括号stack.push(c); //是左括号的话直接入栈}}return stack.isEmpty(); //最后栈应该是空的}
}

5.4、删除字符串中的所有相邻重复项

题目传送门

方法一:StringBuffer模拟栈

class Solution {public String removeDuplicates(String s) {StringBuffer stack = new StringBuffer();//模拟栈int top = -1;//指向栈顶的元素int len = s.length();for (int i = 0; i < len; i++) {char c = s.charAt(i);if (top != -1 && stack.charAt(top) == c){stack.deleteCharAt(top);top--;}else {stack.append(c);top++;}}return stack.toString();}
}

5.5、逆波兰表达式求值

题目传送门

方法一:栈

class Solution {public int evalRPN(String[] tokens) {LinkedList<Integer> stack = new LinkedList<>();int len = tokens.length;for (int i = 0; i < len; i++) { //如果是运算符号,弹出两个数计算后再压入栈中if (tokens[i].equals("+") || tokens[i].equals("-") || tokens[i].equals("*") || tokens[i].equals("/")){int num1 = stack.pop();int num2 = stack.pop();if (tokens[i].equals("+")){stack.push(num2 + num1);}else if (tokens[i].equals("-")){stack.push(num2 - num1);}else if (tokens[i].equals("*")){stack.push(num2 * num1);}else {stack.push(num2 / num1);}}else { //如果是数字,直接入栈stack.push(Integer.valueOf(tokens[i]));}}return stack.peek();}
}

方法二:数组模拟栈

class Solution {public int evalRPN(String[] tokens) {int len = tokens.length;int[] stack = new int[(len + 1) / 2]; //操作数最后有 (len + 1) / 2 个int index = 0;for (int i = 0; i < len; i++) {if (tokens[i].equals("+")){index -= 2;stack[index] += stack[index + 1];index++;}else if (tokens[i].equals("-")){index -= 2;stack[index] -= stack[index + 1];index++;}else if (tokens[i].equals("*")){index -= 2;stack[index] *= stack[index + 1];index++;}else if (tokens[i].equals("/")){index -= 2;stack[index] /= stack[index + 1];index++;}else {stack[index++] = Integer.parseInt(tokens[i]);}}return stack[0];}
}

5.6、滑动窗口最大值

题目传送门

方法一:单调队列

class Solution {public int[] maxSlidingWindow(int[] nums, int k) {int[] res = new int[nums.length - k + 1];int index = 1;Deque<Integer> queue = new LinkedList<>();//存放窗口内递减的元素//先处理前k个元素for (int i = 0; i < k; i++) {//保证队列单调递减while (!queue.isEmpty() && queue.peekLast() < nums[i]){queue.removeLast();}queue.addLast(nums[i]);}res[0] = queue.peekFirst();//得到第一个元素for (int i = k; i < nums.length; i++) {//从队列中删除窗口外的元素if (queue.peekFirst() == nums[i - k]){queue.removeFirst();}//保证队列单调递减while (!queue.isEmpty() && queue.peekLast() < nums[i]){queue.removeLast();}queue.addLast(nums[i]);res[index++] = queue.peekFirst();//记录窗口内的最大值}return res;}
}

5.7、前 K 个高频元素

题目传送门

方法一:最小堆

class Solution {public int[] topKFrequent(int[] nums, int k) {//使用map记录频率Map<Integer, Integer> map = new HashMap<>();for (int i = 0; i < nums.length; i++) {map.put(nums[i], map.getOrDefault(nums[i], 0) + 1);}//使用优先队列代替堆,保存频率最大的k个元素PriorityQueue<Integer> queue = new PriorityQueue<>(new Comparator<Integer>() {@Overridepublic int compare(Integer o1, Integer o2) {return map.get(o1) - map.get(o2);}});for (int val : map.keySet()) {if (queue.size() < k){queue.add(val);}else if (map.get(val) > map.get(queue.peek())){queue.remove();queue.add(val);}}//取出最小堆中的元素int[] res = new int[k];int index = 0;for (int val : queue) {res[index++] = val;}return res;}
}

6、二叉树

6.1、二叉树的层序遍历

题目传送门

方法一:bfs

class Solution {public List<List<Integer>> levelOrder(TreeNode root) {List<List<Integer>> lists = new ArrayList<>();if (root == null) {return lists;}Queue<TreeNode> queue = new LinkedList<>();queue.offer(root);while (!queue.isEmpty()){List<Integer> list = new ArrayList<>();int nodeCount = queue.size(); //计算这一层节点的个数for (int i = 0; i < nodeCount; i++) {TreeNode node = queue.poll();list.add(node.val);if (node.left != null){queue.offer(node.left);}if (node.right != null){queue.offer(node.right);}}lists.add(list);}return lists;}
}

6.2、二叉树的层序遍历 II

题目传送门

方法一:bfs

直接在链表头部添加元素即可。

class Solution {public List<List<Integer>> levelOrderBottom(TreeNode root) {LinkedList<List<Integer>> lists = new LinkedList<>();if (root == null) {return lists;}Queue<TreeNode> queue = new LinkedList<>();queue.offer(root);while (!queue.isEmpty()){List<Integer> list = new ArrayList<>();int nodeCount = queue.size(); //计算这一层节点的个数for (int i = 0; i < nodeCount; i++) {TreeNode node = queue.poll();list.add(node.val);if (node.left != null){queue.offer(node.left);}if (node.right != null){queue.offer(node.right);}}lists.addFirst(list);}return lists;}
}

6.3、二叉树的右视图

题目传送门

方法一:bfs

class Solution {public List<Integer> rightSideView(TreeNode root) {List<Integer> list = new ArrayList<>();if (root == null){return list;}Queue<TreeNode> queue = new LinkedList<>();queue.offer(root);while (!queue.isEmpty()){int size = queue.size();for (int i = 0; i < size; i++) {TreeNode node = queue.poll();if (i == 0) list.add(node.val);//出队列的第一个节点,就是当前层最右边的节点if (node.right != null) queue.offer(node.right);//先添加右节点if (node.left != null) queue.offer(node.left);//再添加左节点}}return list;}
}

6.4、二叉树的层平均值

题目传送门

方法一:bfs

class Solution {public List<Double> averageOfLevels(TreeNode root) {List<Double> list = new ArrayList<>();Queue<TreeNode> queue = new LinkedList<>();queue.offer(root);while (!queue.isEmpty()){int size = queue.size();double sum = 0;for (int i = 0; i < size; i++) {TreeNode node = queue.poll();sum += node.val;if (node.left != null) queue.offer(node.left);if (node.right != null) queue.offer(node.right);}list.add(sum / size);}return list;}
}

6.5、N 叉树的层序遍历

题目传送门

方法一:bfs

class Solution {public List<List<Integer>> levelOrder(Node root) {List<List<Integer>> lists = new ArrayList<>();if (root == null){return lists;}Queue<Node> queue = new LinkedList<>();queue.offer(root);while (!queue.isEmpty()){int size = queue.size();List<Integer> list = new ArrayList<>();for (int i = 0; i < size; i++) {Node node = queue.poll();list.add(node.val);if (node.children != null){for (Node child : node.children) {queue.offer(child);}}}lists.add(list);}return lists;}
}

6.6、在每个树行中找最大值

题目传送门

方法一:dfs

class Solution {List<Integer> list;public List<Integer> largestValues(TreeNode root) {list = new ArrayList<>();if (root == null) {return list;}dfs(root, 0);return list;}public void dfs(TreeNode node, int curHeight){if (node == null){return;}if (curHeight == list.size()){ //到了新的一层list.add(node.val);}else { //该层在集合中存在list.set(curHeight, Math.max(list.get(curHeight), node.val));}dfs(node.left, curHeight + 1);dfs(node.right, curHeight + 1);}
}

方法二:bfs

class Solution {public List<Integer> largestValues(TreeNode root) {List<Integer> list = new ArrayList<>();if (root == null){return list;}Queue<TreeNode> queue = new LinkedList<>();queue.offer(root);while (!queue.isEmpty()){int size = queue.size();int max = Integer.MIN_VALUE;for (int i = 0; i < size; i++) {TreeNode node = queue.poll();max = Math.max(max, node.val);if (node.left != null) queue.offer(node.left);if (node.right != null) queue.offer(node.right);}list.add(max);}return list;}
}

6.7、填充每个节点的下一个右侧节点指针

题目传送门

方法一:bfs

class Solution {public Node connect(Node root) {if (root == null){return null;}Queue<Node> queue = new LinkedList<>();queue.offer(root);while (!queue.isEmpty()){int size = queue.size();Node nextNode = null;for (int i = 0; i < size; i++) {Node node = queue.poll();node.next = nextNode;nextNode = node;if (node.right != null) queue.offer(node.right);if (node.left != null) queue.offer(node.left);}}return root;}
}

方法二:使用已建立的 next 指针

  1. 处理一个节点的左子节点的next指针:head.left.next = head.right;
  2. 处理一个节点的右子节点的next指针:head.right.next = head.next.left;
class Solution {public Node connect(Node root) {if (root == null){return null;}Node leftMost = root;//每一层最左边的节点while (leftMost.left != null){Node head = leftMost;//拿到一个节点作为头节点while (head != null){//处理左子节点的nexthead.left.next = head.right;//处理右子节点的nextif (head.next != null){head.right.next = head.next.left;}head = head.next;//遍历下一个节点}leftMost = leftMost.left;//遍历下一层}return root;}
}

6.8、填充每个节点的下一个右侧节点指针 II

题目传送门

方法一:

使用BFS方法和上一题代码一样。

class Solution{public Node connect(Node root) {if (root == null){return null;}Node cur = root;//当前节点while (cur != null){Node head = new Node(-1);//为每一层新建一个头节点Node pre = head;//pre先指向头节点while (cur != null){//处理左子节点if (cur.left != null){pre.next = cur.left;pre = pre.next;}//处理右子节点if (cur.right != null){pre.next = cur.right;pre = pre.next;}cur = cur.next;//cur指向当前层的下一个节点}cur = head.next;//cur指向下一层的第一个节点}return root;}
}

6.9、二叉树的最大深度

题目传送门

方法一:dfs

class Solution {public int maxDepth(TreeNode root) {if (root == null){return 0;}return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;}
}

6.10、二叉树的最小深度

方法一:dfs

class Solution {public int minDepth(TreeNode root) {if (root == null){return 0;}if (root.left == null && root.right == null){ //非叶子节点return 1;}int min = Integer.MAX_VALUE;if (root.left != null){int i = minDepth(root.left);min = Math.min(i, min);}if (root.right != null){int j = minDepth(root.right);min = Math.min(j, min);}return min + 1;}
}

方法二:bfs

class Solution {public int minDepth(TreeNode root) {if (root == null){return 0;}int depth = 1;Queue<TreeNode> queue = new LinkedList<>();queue.offer(root);while (!queue.isEmpty()){int size = queue.size();for (int i = 0; i < size; i++) {TreeNode node = queue.poll();if (node.left == null && node.right == null){ //是叶子节点,找到最小深度return depth;}if (node.left != null) queue.offer(node.left);if (node.right != null) queue.offer(node.right);}depth++;}return depth;}
}

6.11、翻转二叉树

题目传送门

方法一:递归

class Solution {public TreeNode invertTree(TreeNode root) {if (root == null){return null;}root.left = invertTree(root.left);root.right = invertTree(root.right);TreeNode temp = root.left;root.left = root.right;root.right = temp;return root;}
}

6.12、N 叉树的前序遍历

题目传送门

方法二:递归

class Solution {List<Integer> list;public List<Integer> preorder(Node root) {list = new ArrayList<>();preList(root);return list;}public void preList(Node node){if (node == null){return;}list.add(node.val);for (int i = 0; i < node.children.size(); i++) {preList(node.children.get(i));}}
}

6.13、N 叉树的后序遍历

题目传送门

方法一:递归

class Solution {List<Integer> list;public List<Integer> postorder(Node root) {list = new ArrayList<>();postList(root);return list;}public void postList(Node node){if (node == null){return;}for (int i = 0; i < node.children.size(); i++) {postList(node.children.get(i));}list.add(node.val);}
}

6.14、对称二叉树

题目传送门

方法一:递归

class Solution {public boolean isSymmetric(TreeNode root) {return dfs(root.left, root.right);}public boolean dfs(TreeNode left, TreeNode right){if (left == null && right == null){return true;}if (left == null || right == null || left.val != right.val){return false;}return dfs(left.left, right.right) && dfs(left.right, right.left);}
}

方法二:迭代

class Solution {public boolean isSymmetric(TreeNode root) {Queue<TreeNode> queue = new LinkedList<>();queue.offer(root.left);queue.offer(root.right);while (!queue.isEmpty()){TreeNode node1 = queue.poll();TreeNode node2 = queue.poll();if (node1 == null && node2 == null){continue;}if (node1 == null || node2 == null || node1.val != node2.val){return false;}queue.offer(node1.left);queue.offer(node2.right);queue.offer(node1.right);queue.offer(node2.left);}return true;}
}

6.15、完全二叉树的节点个数

题目传送门

方法一:递归

class Solution {public int countNodes(TreeNode root) {if (root == null){return 0;}return countNodes(root.left) + countNodes(root.right) + 1;}
}

方法二:迭代

class Solution {public int countNodes(TreeNode root) {if (root == null){return 0;}int count = 0;Queue<TreeNode> queue = new LinkedList<>();queue.offer(root);while (!queue.isEmpty()){int size = queue.size();count += size;for (int i = 0; i < size; i++) {TreeNode node = queue.poll();if (node.left != null) queue.offer(node.left);if (node.right != null) queue.offer(node.right);}}return count;}
}

方法三:针对完全二叉树优化

题中条件为完全二叉树;满二叉树的节点个数为2^n-1。

class Solution {public int countNodes(TreeNode root) {if (root == null){return 0;}int leftDepth = 0, rightDepth = 0;TreeNode left = root.left, right = root.right;while (left != null){left = left.left;leftDepth++;}while (right != null){right = right.right;rightDepth++;}if (leftDepth == rightDepth) {return (2 << leftDepth) - 1; //相当于2的leftDepth次方,满二叉树的节点个数为2^n-1;这里位运算要加括号}return countNodes(root.left) + countNodes(root.right) + 1;}
}

6.16、平衡二叉树

题目传送门

方法一:自顶向下的递归

class Solution {public boolean isBalanced(TreeNode root) {if (root == null){return true;}int left = height(root.left);int right = height(root.right);return Math.abs(left - right) <= 1 && isBalanced(root.left) && isBalanced(root.right);}//计算树的高度public int height(TreeNode node){if (node == null){return 0;}return Math.max(height(node.left) + 1, height(node.right) + 1);}
}

方法二:自底向上的递归

class Solution {public boolean isBalanced(TreeNode root) {return height(root) >= 0;}public int height(TreeNode node){if (node == null){return 0;}int leftHeight = height(node.left);int rightHeight = height(node.right);if (leftHeight == -1 || rightHeight == -1 || Math.abs(leftHeight - rightHeight) > 1){return -1;}else {return Math.max(leftHeight, rightHeight) + 1;}}
}

6.17、二叉树的所有路径

题目传送门

方法一:dfs

class Solution {List<String> res;public List<String> binaryTreePaths(TreeNode root) {res = new ArrayList<>();dfs(root, "");return res;}public void dfs(TreeNode node, String string){if (node == null) {return;}StringBuffer sb = new StringBuffer(string);sb.append(node.val);if (node.left == null && node.right == null) { //是叶子节点res.add(sb.toString());return;}else { //不是叶子节点sb.append("->");dfs(node.left, sb.toString());dfs(node.right, sb.toString());}}
}

6.18、左叶子之和

题目传送门

方法一:dfs

class Solution {public int sumOfLeftLeaves(TreeNode root) {int count = 0;if (root == null){return count;}//如果左子节点是叶子的话,处理左子节点if (root.left != null && root.left.left == null && root.left.right == null){count += root.left.val;}int left = sumOfLeftLeaves(root.left);int right = sumOfLeftLeaves(root.right);return count + left + right;}
}

6.19、找树左下角的值

方法一:bfs

class Solution {int curHeight = 0;int curVal = 0;public int findBottomLeftValue(TreeNode root) {dfs(root, 0);return curVal;}public void dfs(TreeNode node, int height){if (node == null){return;}height++;dfs(node.left, height);dfs(node.right, height);//因为我们先遍历左子树,然后再遍历右子树,所以对同一高度的所有节点,最左节点肯定是最先被遍历到的。if (height > curHeight){curHeight = height;curVal = node.val;}}
}

方法二:dfs

class Solution {public int findBottomLeftValue(TreeNode root) {int res = 0;Queue<TreeNode> queue = new LinkedList<>();queue.offer(root);while (!queue.isEmpty()){//先当右节点,再放左节点,最后一节点是结果节点TreeNode node = queue.poll();if (node.right != null) queue.offer(node.right);if (node.left != null) queue.offer(node.left);res = node.val;}return res;}
}

6.20、路径总和

题目传送门

方法一:dfs

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);}
}

6.21、从中序与后序遍历序列构造二叉树

题目传送门

方法一:分治算法

class Solution {int[] postorder;//后序遍历的数据Map<Integer, Integer> map = new HashMap<>();//存放后序遍历的值和对应的下标public TreeNode buildTree(int[] inorder, int[] postorder) {this.postorder = postorder;for (int i = 0; i < inorder.length; i++) {map.put(inorder[i], i);}return recur(postorder.length - 1, 0, inorder.length - 1);}/*** @param root:后序遍历中跟节点的下标* @param left:以root为跟节点的树,中序遍历中左边界* @param right:以root为跟节点的树,中序遍历中右边界*/public TreeNode recur(int root, int left, int right){if (left > right){return null;}TreeNode node = new TreeNode(postorder[root]);int index = map.get(postorder[root]); //找到根节点在中序遍历数组中的下标node.right = recur(root - 1, index + 1, right);//root - (right - index) - 1:跟节点索引 - 右子树长度 - 1node.left = recur(root - right + index - 1, left, index - 1);return node;}
}

6.22、最大二叉树

题目传送门

方法一:递归

class Solution {int[] nums;public TreeNode constructMaximumBinaryTree(int[] nums) {this.nums = nums;return recur(0, nums.length - 1);}public TreeNode recur(int left, int right){if (left > right){return null;}int maxIndex = left;for (int i = left + 1; i <= right; i++) {if (nums[maxIndex] < nums[i]){maxIndex = i;}}TreeNode node = new TreeNode(nums[maxIndex]);node.left = recur(left, maxIndex - 1);node.right = recur(maxIndex + 1, right);return node;}
}

6.23、合并二叉树

题目传送门

方法一:dfs

class Solution {public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {if (root1 == null){return root2;}if (root2 == null){return root1;}TreeNode node = new TreeNode(root1.val + root2.val);node.left = mergeTrees(root1.left, root2.left);node.right = mergeTrees(root1.right, root2.right);return node;}
}

6.24、二叉搜索树中的搜索

题目传送门

方法一:递归

class Solution {public TreeNode searchBST(TreeNode root, int val) {if (root == null){return null;}if (root.val == val){return root;}return root.val < val ? searchBST(root.right, val) : searchBST(root.left, val);}
}

方法二:迭代

class Solution {public TreeNode searchBST(TreeNode root, int val) {while (root != null){if (root.val == val){return root;}root = root.val > val ? searchBST(root.left, val) : searchBST(root.right, val);}return null;}
}

6.25、验证二叉搜索树

题目传送门

方法一:递归

class Solution {public boolean isValidBST(TreeNode root) {//这里使用Long,因为必须要比Integer范围大return recur(root, Long.MIN_VALUE, Long.MAX_VALUE);}public boolean recur(TreeNode node, long lower, long upper){if (node == null){return true;}if (node.val <= lower || node.val >= upper){return false;}return recur(node.left, lower, node.val) && recur(node.right, node.val, upper);}
}

方法二:中序遍历

class Solution {LinkedList<Integer> stack = new LinkedList<>();boolean flag = true;public boolean isValidBST(TreeNode root) {mixList(root);return flag;}public void mixList(TreeNode node){if (node == null){return;}mixList(node.left);if (!stack.isEmpty() && stack.peek() >= node.val){flag = false;return;}else {stack.push(node.val);}mixList(node.right);}
}

6.26、二叉搜索树的最小绝对差

题目传送门

方法一:中序遍历

class Solution {int res = Integer.MAX_VALUE;int pre = -1;public int getMinimumDifference(TreeNode root) {mixLIst(root);return res;}public void mixLIst(TreeNode node){if (node == null){return;}mixLIst(node.left);if (pre != -1){res = Math.min(res, node.val - pre);}pre = node.val;mixLIst(node.right);}
}

6.27、二叉搜索树中的众数

题目传送门

方法一:中序遍历

class Solution {int count = 1;//当前数字重复的次数int maxCount = 1;//众数出现的次数int pre = Integer.MIN_VALUE;//前一个节点的值List<Integer> list = new ArrayList<>();public int[] findMode(TreeNode root) {mixList(root);int[] res = new int[list.size()];for (int i = 0; i < list.size(); i++) {res[i] = list.get(i);}return res;}public void mixList(TreeNode node){if (node == null){return;}mixList(node.left);if (pre == node.val){count++;}else {count = 1;}if (count == maxCount){list.add(node.val);}else if (count > maxCount){list.clear();list.add(node.val);maxCount = count;}pre = node.val;mixList(node.right);}
}

6.28、二叉树的最近公共祖先

题目传送门

方法一:dfs

class Solution {public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {//遇见目标节点,或者到null了,直接返回if (root == null || p.val == root.val || q.val == root.val){ return root;}TreeNode left = lowestCommonAncestor(root.left, p, q);TreeNode right = lowestCommonAncestor(root.right, p, q);//如果left和right都为空 或者 如果left和right一个为空,返回另一个if (left == null) return right;if (right == null) return left;return root; //如果left和right都不为空,该节点为公共祖先节点}
}

6.29、二叉搜索树的最近公共祖先

题目传送门

方法一:dfs

class Solution {public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {if (p.val > q.val){ //优化:若保证 p.val < q.val,可减少循环中判断的次数TreeNode temp = p;p = q;q = temp;}if (root.val < p.val){ //p,q 都在 root 的左子树中return lowestCommonAncestor(root.right, p, q);}else if (root.val > q.val){ //p,q 都在 root 的右子树中return lowestCommonAncestor(root.left, p, q);}else { //其他情况都满足结果return root;}}
}

6.30、二叉搜索树中的插入操作

题目传送门

方法一:模拟

class Solution {public TreeNode insertIntoBST(TreeNode root, int val) {TreeNode node = new TreeNode(val);if (root == null){return node;}helper(root, val, node);return root;}public void helper(TreeNode root, int val, TreeNode node){if (val < root.val){if (root.left == null){ //插入位置root.left = node;return;}else { //继续往左找helper(root.left, val, node);}}else if (val > root.val){if (root.right == null){ //插入位置root.right = node;return;}else { //继续往右找helper(root.right, val, node);}}}
}

6.31、删除二叉搜索树中的节点

题目传送门

方法一:递归

class Solution {public TreeNode deleteNode(TreeNode root, int key) {if (root == null){return null;}if (key < root.val){ //向左递归root.left = deleteNode(root.left, key);return root;}else if (key > root.val){ //向右递归root.right = deleteNode(root.right, key);return root;}else { //找到要删除的节点if (root.left == null && root.right == null){//这个节点是叶子节点return null;}else if (root.left == null){//这个节点只有右字节点return root.right;}else if (root.right == null){//这个节点只有左字节点return root.left;}else {//这个节点有两个子节点//先找到右子节点的最左边的节点,然后把左子节点挂在那,最后返回右子树TreeNode temp = root.right;while (temp.left != null){temp = temp.left;}temp.left = root.left;return root.right;}}}
}

6.32、修剪二叉搜索树

题目传送门

方法一:递归

class Solution {public TreeNode trimBST(TreeNode root, int low, int high) {if (root == null){return null;}if (root.val >= low && root.val <= high){ //满足条件的节点root.left = trimBST(root.left, low, high);root.right = trimBST(root.right, low, high);return root;}else if (root.val < low){ //不满足条件,不处理此节点,检查它的右字节点是否满足条件并返回return trimBST(root.right, low, high);}else { //不满足条件,不处理此节点,检查它的左字节点是否满足条件并返回return trimBST(root.left, low, high);}}
}

6.33、将有序数组转换为二叉搜索树

题目传送门

方法一:中序遍历+双指针

class Solution {public TreeNode sortedArrayToBST(int[] nums) {return helper(nums, 0, nums.length - 1);}public TreeNode helper(int[] nums, int left, int right){if (left > right){return null;}int mid = (left + right) / 2;TreeNode node = new TreeNode(nums[mid]);node.left = helper(nums, left, mid - 1);node.right = helper(nums, mid + 1, right);return node;}
}

6.34、把二叉搜索树转换为累加树

题目传送门

方法一:反中序遍历

class Solution {int value = 0;//记录节点值的和public TreeNode convertBST(TreeNode root) {if (root == null){return null;}root.right = convertBST(root.right);//先处理右节点//处理当前节点value += root.val;root.val = value;root.left = convertBST(root.left);//再处理左节点return root;}
}

7、回溯算法

7.1、组合

题目传送门

方法一:回溯+剪枝

class Solution {List<List<Integer>> res;public List<List<Integer>> combine(int n, int k) {res = new ArrayList<>();LinkedList<Integer> list = new LinkedList<>();dfs(n, k, 1, list);return res;}public void dfs(int n, int k, int begin, LinkedList<Integer> list){//递归终止条件if (list.size() == k){res.add(new ArrayList<>(list));return;}//i <= n - (k - list.size()) + 1:剪枝优化//搜索起点的上界 + 接下来要选择的元素个数 - 1 = n;k - list.size():接下来要选择的元素个数//遍历可能的搜索起点:[begin, n]for (int i = begin; i <= n - (k - list.size()) + 1; i++) {list.addLast(i);//处理当前数dfs(n, k, i + 1, list);//递归list.removeLast();//回溯}}
}

7.2、组合总和 III

题目传送门

方法一:回溯+剪枝

class Solution {List<List<Integer>> res;public List<List<Integer>> combinationSum3(int k, int n) {res = new ArrayList<>();LinkedList<Integer> list = new LinkedList<>();dfs(k, n, 1, list);return res;}public void dfs(int k, int val, int begin, LinkedList list){if (list.size() == k && val == 0){res.add(new ArrayList<>(list));return;}//9 - (k - list.size()) + 1:剪枝for (int i = begin; i <= 9 - (k - list.size()) + 1; i++) {//剪枝if (val - i < 0){return;}list.addLast(i);dfs(k, val - i, i + 1, list);list.removeLast();}}
}

7.3、电话号码的字母组合

题目传送门

方法一:回溯

class Solution {List<String> res = new ArrayList<>();String[] strings = {"abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};public List<String> letterCombinations(String digits) {if (digits.length() == 0){return res;}StringBuffer sb = new StringBuffer();dfs(digits, sb, 0);return res;}//num:digits的第几位数public void dfs(String digits, StringBuffer sb, int num){if (sb.length() == digits.length()){res.add(sb.toString());return;}int val = digits.charAt(num) - '0';for (int i = 0; i < strings[val - 2].length(); i++) {sb.append(strings[val - 2].charAt(i));dfs(digits, sb, num + 1);sb.deleteCharAt(sb.length() - 1);}}
}

7.4、组合总和

题目传送门

方法一:回溯+剪枝

class Solution {List<List<Integer>> res = new ArrayList<>();LinkedList<Integer> list = new LinkedList<>();public List<List<Integer>> combinationSum(int[] candidates, int target) {Arrays.sort(candidates);//剪枝时先排序dfs(candidates, target, 0, 0);return res;}public void dfs(int[] candidates, int target, int begin, int sum){if (sum == target){res.add(new ArrayList<>(list));return;}for (int i = begin; i < candidates.length; i++) {if (sum + candidates[i] > target){ //剪枝优化break;}list.addLast(candidates[i]);dfs(candidates, target, i, sum + candidates[i]);list.removeLast();}}
}

7.2、组合总和 II

方法一:回溯+剪枝

可以使用set或者数组去重同一层的元素。

题目传送门

class Solution {List<List<Integer>> res = new ArrayList<>();LinkedList<Integer> list = new LinkedList<>();public List<List<Integer>> combinationSum2(int[] candidates, int target) {Arrays.sort(candidates);//剪枝前先排序dfs(candidates, target, 0, 0);return res;}public void dfs(int[] candidates, int target, int sum, int begin){if (target == sum){res.add(new ArrayList<>(list));return;}for (int i = begin; i < candidates.length; i++) {//剪枝优化if (sum + candidates[i] > target){break;}//剪枝优化:从第二个数开始,同一层相同数值的结点,结果一定发生重复,因此跳过,用continueif (i > begin && candidates[i] == candidates[i - 1]){continue;}list.addLast(candidates[i]);dfs(candidates, target, sum + candidates[i], i + 1);list.removeLast();}}
}

7.3、分割回文串

题目传送门

方法一:回溯+剪枝

class Solution {List<List<String>> res = new ArrayList<>();LinkedList<String> list = new LinkedList<>();public List<List<String>> partition(String s) {dfs(s, 0);return res;}public void dfs(String s, int begin){if (begin >= s.length()){ //终止条件:开始字符超过边界res.add(new ArrayList<>(list));return;}for (int i = begin + 1; i <= s.length(); i++) {if (!helper(s, begin, i - 1)){ //剪枝:判断是不是回文串continue;}String str = s.substring(begin, i);list.addLast(str);dfs(s, i);list.removeLast();}}//判断是否为回文串public boolean helper(String s, int left, int right){while (left < right){if (s.charAt(left) != s.charAt(right)){return false;}left++;right--;}return true;}
}

7.4、复原IP地址

题目传送门

方法一:剪枝+回溯

class Solution {List<String> res = new ArrayList<String>();StringBuilder stringBuilder = new StringBuilder();public List<String> restoreIpAddresses(String s) {dfs(s, 0, 0);return res;}// number表示stringbuilder中ip段的数量public void dfs(String s, int start, int number) {// 如果start等于s的长度并且ip段的数量是4,则加入结果集,并返回if (start == s.length() && number == 4) {res.add(stringBuilder.toString());return;}// 如果start等于s的长度但是ip段的数量不为4,或者ip段的数量为4但是start小于s的长度,则直接返回if (start == s.length() || number == 4) {return;}for (int i = start; i < s.length(); i++) {// 剪枝:ip段的长度最大是3,并且ip段处于[0,255],否则直接breakif (i - start > 3){break;}String str = s.substring(start, i + 1);int val = Integer.parseInt(str);if (val < 0 || val > 255){break;}// 剪枝:如果ip段的长度大于1,并且第一位为0的话,continueif (str.length() > 1 && str.charAt(0) - '0' == 0) {continue;}int len = stringBuilder.length();stringBuilder.append(str);// 当stringBuilder里的网段数量小于3时,才会加点;如果等于3,说明已经有3段了,最后一段不需要再加点if (number < 3) {stringBuilder.append(".");}dfs(s, i + 1, number + 1);stringBuilder.delete(len, stringBuilder.length());//回溯}}
}

7.5、子集

题目传送门

方法一:回溯

class Solution {List<List<Integer>> res = new ArrayList<>();LinkedList<Integer> list = new LinkedList<>();public List<List<Integer>> subsets(int[] nums) {dfs(nums, 0);return res;}public void dfs(int[] nums, int begin){res.add(new ArrayList<>(list));if (begin == nums.length){return;}for (int i = begin; i < nums.length; i++) {list.addLast(nums[i]);dfs(nums, i + 1);list.removeLast();}}
}

7.6、子集 II

题目传送门

方法一:回溯

可以使用set或者数组去重同一层的元素。

class Solution {List<List<Integer>> res = new ArrayList<>();LinkedList<Integer> list = new LinkedList<>();public List<List<Integer>> subsetsWithDup(int[] nums) {Arrays.sort(nums);//必须先排序dfs(nums, 0);return res;}public void dfs(int[] nums, int begin){res.add(new ArrayList<>(list));if (begin == nums.length){return;}for (int i = begin; i < nums.length; i++) {if (i != begin && nums[i] == nums[i - 1]){ //去重continue;}list.addLast(nums[i]);dfs(nums, i + 1);list.removeLast();}}
}

7.6、递增子序列

题目传送门

方法一:回溯

class Solution {List<List<Integer>> res = new ArrayList<>();LinkedList<Integer> list = new LinkedList<>();public List<List<Integer>> findSubsequences(int[] nums) {dfs(nums, 0);return res;}public void dfs(int[] nums, int begin){if (list.size() >= 2){ //至少需要两个元素res.add(new ArrayList<>(list));//return; //注意这里不要加return,需要继续递归}//使用set处理同一层的元素不重复Set<Integer> set = new HashSet<>();for (int i = begin; i < nums.length; i++) {//list满足元素不降序,并且同一层没出现过if ((!list.isEmpty() && list.peekLast() > nums[i]) || set.contains(nums[i])){continue;}list.addLast(nums[i]);set.add(nums[i]);dfs(nums, i + 1);list.removeLast();}}
}

7.7、全排列

题目传送门

方法一:回溯

class Solution {List<List<Integer>> res = new ArrayList<>();LinkedList<Integer> list = new LinkedList<>();public List<List<Integer>> permute(int[] nums) {int[] visited = new int[nums.length];//标记元素是否访问过dfs(nums, visited);return res;}public void dfs(int[] nums, int[] visited){if (nums.length == list.size()){res.add(new ArrayList<>(list));return;}for (int i = 0; i < nums.length; i++) {if (visited[i] == 1){continue;}list.addLast(nums[i]);visited[i] = 1;dfs(nums, visited);list.removeLast();visited[i] = 0;}}
}

7.8、全排列 II

题目传送门

方法一:回溯

使用集合使同一层的元素去重。

可以使用set或者数组去重同一层的元素。

class Solution {List<List<Integer>> res = new ArrayList<>();LinkedList<Integer> list = new LinkedList<>();public List<List<Integer>> permuteUnique(int[] nums) {int[] visited = new int[nums.length];//标记元素是否访问过dfs(nums, visited);return res;}public void dfs(int[] nums, int[] visited){if (nums.length == list.size()){res.add(new ArrayList<>(list));return;}LinkedList<Integer> temp = new LinkedList<>();//用来记录当前层的元素是否重复for (int i = 0; i < nums.length; i++) {if (visited[i] == 1){continue;}if (temp.contains(nums[i])){//如果当前层的元素重复continue;}temp.add(nums[i]);this.list.addLast(nums[i]);visited[i] = 1;dfs(nums, visited);this.list.removeLast();visited[i] = 0;}}
}

方法二:回溯

使用数组使同一层的元素去重。

可以使用set或者数组去重同一层的元素。

class Solution {List<List<Integer>> res = new ArrayList<>();LinkedList<Integer> list = new LinkedList<>();public List<List<Integer>> permuteUnique(int[] nums) {int[] visited = new int[nums.length];//标记元素是否访问过Arrays.sort(nums);//必须先排序dfs(nums, visited);return res;}public void dfs(int[] nums, int[] visited){if (nums.length == list.size()){res.add(new ArrayList<>(list));return;}for (int i = 0; i < nums.length; i++) {if (visited[i] == 1){continue;}//保证同一层不出现重复元素//visited[i - 1] == 0:保证是同一层if (i > 0 && nums[i] == nums[i - 1] && visited[i - 1] == 0){continue;}this.list.addLast(nums[i]);visited[i] = 1;dfs(nums, visited);this.list.removeLast();visited[i] = 0;}}
}

7.9、重新安排行程

题目传送门

方法一:回溯

class Solution {List<String> res;LinkedList<String> list = new LinkedList<>();public List<String> findItinerary(List<List<String>> tickets) {//使用lambda必须是函数式接口,其中equals是Object的方法,不算抽象方法。Collections.sort(tickets, (a, b) -> a.get(1).compareTo(b.get(1)));//按照集合中的到达站进行排序int[] visited = new int[tickets.size()];//标记该路程是否走过list.add("JFK");//从JKF开始dfs(tickets, visited);return res;}public boolean dfs(List<List<String>> tickets, int[] visited){if (list.size() == tickets.size() + 1){res = list;return true;}for (int i = 0; i < tickets.size(); i++) {//如果访问过,或者起点不是list中最后一站,直接跳过if (visited[i] == 1 || !tickets.get(i).get(0).equals(list.peekLast())){continue;}list.addLast(tickets.get(i).get(1));visited[i] = 1;if (dfs(tickets, visited)){return true;}list.removeLast();visited[i] = 0;}return false;}
}

7.10、N皇后

题目传送门

方法一:dfs + 回溯

class Solution {List<List<String>> res = new ArrayList<>();int[] chess;public List<List<String>> solveNQueens(int n) {chess = new int[n];//代表第几行的第几个位置放棋子dfs(n, 0);return res;}public void dfs(int n, int step){if (step == n){helper(n);//生层棋盘并放入结果集合中return;}for (int i = 0; i < n; i++) {chess[step] = i;//在第step行的第i个位置放棋子if (jugde(step)){ //判断这个位置能不能放,能的话继续放下一层dfs(n, step + 1);}//不能放的话这里不用回溯,直接下一个值覆盖}}public boolean jugde(int step){for (int i = 0; i < step; i++) {if (chess[step] == chess[i] || Math.abs(chess[step] - chess[i]) == step - i){return false;}}return true;}public void helper (int n){List<String> list = new ArrayList<>();for (int i = 0; i < n; i++) {char[] chars = new char[n];Arrays.fill(chars, '.');chars[chess[i]] = 'Q';list.add(new String(chars));}res.add(list);}
}

7.11、解数独

题目传送门

方法一:dfs + 回溯

class Solution {public void solveSudoku(char[][] board) {dfs(board);}public boolean dfs(char[][] board){for (int i = 0; i < 9; i++) {for (int j = 0; j < 9; j++) {if (board[i][j] != '.'){//不需要填数的位置跳过continue;}for (char k = '1'; k <= '9'; k++) {if (judge(i, j, k, board)){board[i][j] = k;if (dfs(board)){//如果返回true说明都放完了,不再回溯了return true;}board[i][j] = '.';//如果返回false说明这样放不行,需要回溯}}//9个数都试完了,都不行,直接返回return false;}}//所有位置都放过了,没有返回false,则全放完了return true;}//判断在i行j列这个位置能不能放k数字public boolean judge(int i, int j, int k, char[][] board){//判断同行是否有重复for (int l = 0; l < 9; l++) {if (board[i][l] == k){return false;}}//判断同列是否有重复for (int l = 0; l < 9; l++) {if (board[l][j] == k){return false;}}//判断9宫格内是否有重复int row = i / 3 * 3;int col = j / 3 * 3;for (int l = row; l < row + 3; l++) {for (int m = col; m < col + 3; m++) {if (board[l][m] == k){return false;}}}return true;}
}

8、贪心算法

8.1、分发饼干

题目传送门

方法一:排序 + 双指针 + 贪心

注意:每个孩子只分一个饼干

class Solution {public int findContentChildren(int[] g, int[] s) {Arrays.sort(g);Arrays.sort(s);int index1 = 0, index2 = 0;while (index1 < g.length && index2 < s.length){if (g[index1] <= s[index2]){index1++;index2++;}else {index2++;}}return index1;}
}

8.2、摆动序列

题目传送门

方法一:贪心

class Solution {public int wiggleMaxLength(int[] nums) {if (nums.length == 1){return 1;}int curDif = 0;//当前差值int preDif = 0;//上一个差值int count = 1;for (int i = 1; i < nums.length; i++) {curDif = nums[i] - nums[i - 1];//如果当前差值和上一个差值为一正一负;等于0的情况表示初始时的preDiffif ((preDif <=0 && curDif > 0) || (preDif >=0 && curDif < 0)){count++;preDif = curDif;}}return count;}
}

方法二:动态规划

class Solution {public int wiggleMaxLength(int[] nums) {if (nums.length == 1){return 1;}int up = 1, down = 1;for (int i = 1; i < nums.length; i++) {if (nums[i] > nums[i - 1]){//上升up = down + 1;}else if (nums[i] < nums[i - 1]){//下降down = up + 1;}}return Math.max(up, down);}
}

8.3、最大子序和

题目传送门

方法一:贪心算法

class Solution {public int maxSubArray(int[] nums) {if (nums.length == 1){return nums[0];}int res = Integer.MIN_VALUE;int sum = 0;for (int i = 0; i < nums.length; i++) {sum += nums[i];res = Math.max(res, sum);//记录最大值if (sum <= 0){sum = 0;//当前序列总和为负数时,对后面序列的贡献为负}}return res;}
}

8.4、买卖股票的最佳时机 II

题目传送门

方法一:贪心算法

class Solution {public int maxProfit(int[] prices) {int res = 0;for (int i = 1; i < prices.length; i++) {int profit = prices[i] - prices[i - 1];//当天比前一天利润了就加if (profit > 0){res += profit;}}return res;}
}

8.5、跳跃游戏

题目传送门

方法一:贪心算法

可当成范围的覆盖问题。

class Solution {public boolean canJump(int[] nums) {if (nums.length == 1){return true;}int index = 0;//覆盖的范围for (int i = 0; i <= index; i++) {index = Math.max(i + nums[i], index);//更新覆盖的范围if (index >= nums.length - 1){return true;}}return false;}
}

8.6、跳跃游戏 II

题目传送门

方法一:

也可当成范围的覆盖问题。

这题没怎么看懂。

class Solution {public int jump(int[] nums) {if (nums.length == 1) {return 0;}int count=0; //记录跳跃的次数int curDistance = 0; //当前的覆盖最大区域int maxDistance = 0; //最大的覆盖区域for (int i = 0; i < nums.length; i++) {//在可覆盖区域内更新最大的覆盖区域maxDistance = Math.max(maxDistance,i+nums[i]);//说明当前一步,再跳一步就到达了末尾if (maxDistance >= nums.length-1){count++;break;}//走到当前覆盖的最大区域时,更新下一步可达的最大区域if (i == curDistance){curDistance = maxDistance;count++;}}return count;}
}

方法二:

class Solution {public int jump(int[] nums) {int end = 0;int maxPosition = 0;int steps = 0;for (int i = 0; i < nums.length - 1; i++) {maxPosition = Math.max(maxPosition, i + nums[i]);if (i == end) {end = maxPosition;steps++;}}return steps;}
}

8.7、K 次取反后最大化的数组和

题目传送门

方法一:贪心

class Solution {public int largestSumAfterKNegations(int[] nums, int k) {int sum = 0;Arrays.sort(nums);//把前几个负数变为正数for (int i = 0; i < nums.length; i++) {if (nums[i] < 0){nums[i] = -nums[i];k--;}if (k == 0){break;}}Arrays.sort(nums);//如果k还为基数的话,把第一个数改变符号for (int i = 0; i < nums.length; i++) {if (k != 0){if (k % 2 == 1){nums[i] = -nums[i];k = 0;}}sum += nums[i];}return sum;}
}

8.8、加油站

题目传送门

方法一:贪心

class Solution {public int canCompleteCircuit(int[] gas, int[] cost) {int curSum = 0;//当前剩余的油int totalSum = 0;//统计所有补给的油减去所有消耗的油int index = 0;//结果下标for (int i = 0; i < gas.length; i++) {curSum += gas[i] - cost[i];totalSum += gas[i] - cost[i];//如果当前剩余油为负了,说明从0先这个点所有的点都不能作为起始点,试试第i+1个点if (curSum < 0) {index = (i + 1);curSum = 0;}}//如果所有的油的净剩余量为负,说明不可能成功if (totalSum < 0) return -1;//如果totalSum>0,又由于(0,index)区间累计为负数,说明(index,最后)区间累计为正,说明后一段区间累计的油可以补充前端的负数,返回结果。return index;}
}

8.9、分发糖果

题目传送门

方法一:贪心

class Solution {public int candy(int[] ratings) {int res = 0;int[] candy = new int[ratings.length];candy[0] = 1;//先从左到右遍历,只考虑右边比左边大的情况for (int i = 1; i < ratings.length; i++) {if (ratings[i] > ratings[i - 1]){candy[i] = candy[i - 1] + 1;}else {candy[i] = 1;}}res += candy[candy.length - 1];//再从右到左遍历,只考虑左边比右边大的情况for (int i = ratings.length - 2; i >= 0; i--) {if (ratings[i] > ratings[i + 1]){ //这次遍历的结果和第一次计算的结果取最大值candy[i] = Math.max(candy[i + 1] + 1, candy[i]);}res += candy[i];//边遍历边统计}return res;}
}

8.10、柠檬水找零

题目传送门

方法一:贪心

class Solution {public boolean lemonadeChange(int[] bills) {int five = 0, ten = 0;for (int i = 0; i < bills.length; i++) {if (bills[i] == 5){ //给5元的情况five++;}else if (bills[i] == 10){ //给10元的情况if (five > 0){ //找一张5元ten++;five--;}else {return false;}}else if (bills[i] == 20){ //给20元的情况if (five >= 1 && ten >= 1){ //找一张5元和一张10元five--;ten--;}else if (five >= 3){ //找三张5元five -= 3;}else {return false;}}}return true;}
}

8.11、根据身高重建队列

题目传送门

方法一:贪心

class Solution {public int[][] reconstructQueue(int[][] people) {//让元素先按身高降序排序,再让个数按升序排序Arrays.sort(people, (arr1, arr2) -> {if (arr2[0] != arr1[0]){return arr2[0] - arr1[0];}else {return arr1[1] - arr2[1];}});LinkedList<int[]> list = new LinkedList<>();//遍历一次插入队列,第二个维度为插入的位置,因为前面的元素一定比他身高高for (int[] person : people) {list.add(person[1], person);}return list.toArray(new int[list.size()][]);}
}

8.12、用最少数量的箭引爆气球

题目传送门

方法一:排序+贪心

可看成区间调度问题。

class Solution {public int findMinArrowShots(int[][] points) {//使用Integer.compare方法比较,返回值为1、-1、0,可以避免溢出Arrays.sort(points, (o1, o2) -> {return Integer.compare(o1[1],o2[1]);});int res = 1;int end = points[0][1];for (int i = 1; i < points.length; i++) {//排序后,只有当下一个的起点比前一个的终点大的时候才需多一支箭if (points[i][0] > end){res++;end = points[i][1];}}return res;}
}

8.13、无重叠区间

题目传送门

方法一:贪心

可看成区间覆盖问题,和上一题相似。

贪心:去掉覆盖范围较大的线段,因为要最后需要移除区间的最小数量。

class Solution {public int eraseOverlapIntervals(int[][] intervals) {//按照起点从小到大排序Arrays.sort(intervals, (a, b) -> {return a[0] - b[0];});int res = 0;int end = intervals[0][1];for (int i = 1; i < intervals.length; i++) {if (intervals[i][0] < end){ //贪心:去掉覆盖范围较大的线段,因为要最后需要移除区间的最小数量res++;end = Math.min(end, intervals[i][1]);//终点取小的,因为要去掉覆盖范围较大的线段}else { //不需要去掉线段,更新终点end = intervals[i][1];}}return res;}
}

8.14、划分字母区间

题目传送门

方法一:贪心

class Solution {public List<Integer> partitionLabels(String s) {int[] arr = new int[26]; //存放元素的最后出现的位置char[] chars = s.toCharArray();for (int i = 0; i < chars.length; i++) {arr[s.charAt(i) - 'a'] = i;}List<Integer> res = new LinkedList<>();int start = 0, end = 0;for (int i = 0; i < chars.length; i++) {// int index = s.lastIndexOf(s.charAt(i)); //这样写效率不高end = Math.max(end, arr[chars[i] - 'a']); //更新字符串截取的末尾位置if (i == end){ //截取字符串res.add(end - start + 1);start = end + 1; //更新开始位置,截取下一个字符串}}return res;}
}

8.15、合并区间

题目传送门

方法一:贪心+排序

可看作区间覆盖问题。

class Solution {public int[][] merge(int[][] intervals) {//按照左边界进行排序Arrays.sort(intervals, (a, b) -> {return a[0] - b[0];});ArrayList<int[]> list = new ArrayList<>();int start = intervals[0][0], end = intervals[0][1];for (int i = 1; i < intervals.length; i++) {if (intervals[i][0] <= end){ //此时需要更新endend = Math.max(end, intervals[i][1]);}else { //此时已经得到一个线段,更新start和endint[] arr = {start, end};list.add(arr);start = intervals[i][0];end = intervals[i][1];}}//处理最后一个元素int[] arr2 = {start, end};list.add(arr2);return list.toArray(new int[list.size()][]);}
}

8.16、单调递增的数字

题目传送门

贪心

class Solution {public int monotoneIncreasingDigits(int n) {char[] chars = String.valueOf(n).toCharArray();//数字转成字符串,再转成字符数组int start = chars.length;//记录在哪个位置开始后面的元素全是9//从后往前遍历,如果前一位的数比当前位大,把前一位减1,并记录当前位开始后面全是9for (int i = chars.length - 1; i > 0; i--) {if (chars[i] < chars[i - 1]){chars[i - 1] -= 1;start = i;}}for (int i = start; i < chars.length; i++) {chars[i] = '9';}return Integer.parseInt(String.valueOf(chars));//字符数组转成字符串,再转成数字}
}

9、动态规划

9.1、斐波那契数列

题目传送门

方法一:动态规划

用三个变量来代替前两项和当前项。

class Solution {public int fib(int n) {if (n < 2){return n;}//用三个变量来代替前两项和当前项int a = 0, b = 1, sum = 0;for (int i = 2; i <= n; i++) {sum = a + b;a = b;b = sum;}return sum;}
}

9.2、爬楼梯

题目传送门

方法一:动态规划

当最后一步剩下一个台阶时共f(n-1)种情况;当最后一步剩下两个台阶时共f(n-2)种情况。所以一共f(n-1)+f(n-2)种情况。类似于斐波那契数列。

class Solution {public int climbStairs(int n) {if (n < 3){return n;}int a = 1, b = 2, sum = 0;for (int i = 3; i <= n; i++) {sum = a + b;a = b;b = sum;}return sum;}
}

方法二:动态规划

类似于9.13题。

class Solution {public int climbStairs(int n) {int[] dp = new int[n + 1];dp[0] = 1;int[] weight = {1,2};for (int i = 0; i <= n; i++) {for (int j = 0; j < weight.length; j++) {if (i >= weight[j]){dp[i] = dp[i] + dp[i - weight[j]];}}}return dp[n];}
}

9.3、使用最小花费爬楼梯

题目传送门

方法一:动态规划

class Solution {public int minCostClimbingStairs(int[] cost) {//dp代表跳到当前位置花费的最小费用int[] dp = new int[cost.length + 1];dp[0] = dp[1] = 0;//初始化for (int i = 2; i < cost.length + 1; i++) {//递推公式dp[i] = Math.min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);}return dp[cost.length];}
}

9.4、不同路径

题目传送门

方法一:动态规划

class Solution {public int uniquePaths(int m, int n) {//dp代表到达当前位置的路径数目int[][] dp = new int[m][n];//初始化第一行和第一列位1for (int i = 0; i < m; i++) {dp[i][0] = 1;}for (int i = 0; i < n; i++) {dp[0][i] = 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];}
}

9.5、不同路径 II

方法一:动态规划

和上一题相似。

class Solution {public int uniquePathsWithObstacles(int[][] obstacleGrid) {int m = obstacleGrid.length, n = obstacleGrid[0].length;int[][] dp = new int[m][n];for (int i = 0; i < m; i++) {if (obstacleGrid[i][0] == 1){ //遇到障碍break;}dp[i][0] = 1;}for (int i = 0; i < n; i++) {if (obstacleGrid[0][i] == 1){ //遇到障碍break;}dp[0][i] = 1;}for (int i = 1; i < m; i++) {for (int j = 1; j < n; j++) {if (obstacleGrid[i][j] == 1){ //遇到障碍continue;}dp[i][j] = dp[i - 1][j] + dp[i][j - 1];}}return dp[m - 1][n - 1];}
}

9.6、整数拆分

题目传送门

方法一:贪心

class Solution {public int integerBreak(int n) {int[] dp = new int[n + 1];dp[2] = 1;for (int i = 3; i <= n; i++) {//这里的 j 其实最大值为 i-j,再大只不过是重复而已,for (int j = 1; j <= i - j; j++) {//j * (i - j)是单纯的把整数i拆分为两个数,也就是i,i-j,再相乘。//j * dp[i - j]是将i拆分成两个以及两个以上的个数,再相乘。dp[i] = Math.max(dp[i] , Math.max(j * dp[i - j], j * (i - j)));}}return dp[n];}
}

9.7、不同的二叉搜索树

题目传送门

方法一:贪心

class Solution {public int numTrees(int n) {//dp代表n个节点时不同二叉搜索树的数量int[] dp = new int[n + 1];dp[0] = dp[1] = 1;//初始化for (int i = 2; i <= n; i++) {for (int j = 0; j < i; j++) {//递归公式。//当共i个节点时,第i个节点作为跟节点,左子树的节点个数为j,右子树的节点个数为i - j - 1dp[i] += dp[j] * dp[i - j - 1];}}return dp[n];}
}

0-1背包问题

方法一:动态规划(二维数组)

/*** 01背包:* 有n件物品和一个最多能背重量为w的背包。* 第i件物品的重量是weight[i],得到的价值是value[i] 。* 每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。*/
public class demo10 {public static void main(String[] args) {int[] weight = {1,3,4};//物品的重量int[] value = {15,20,30};//物品的价值int bagSize = 4;//背包大小testWeightBagProblem(weight, value, bagSize);}public static void testWeightBagProblem(int[] weight, int[] value, int bagSize){//1.dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。int[][] dp = new int[weight.length][bagSize + 1];//3.初始化for (int i = weight[0]; i <= bagSize; i++) {dp[0][i] = value[0];}//2.递归公式: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);for (int i = 1; i < dp.length; i++) {for (int j = 1; j < dp[0].length; j++) {if (j < weight[i]){//当前背包的总容量放不下第i个物品dp[i][j] = dp[i - 1][j];}else {//当前背包的总容量可以放下第i个物品,可选择放也可以不放dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);}}}for (int[] ints : dp) {System.out.println(Arrays.toString(ints));}}
}

方法二:动态规划(一维数组)

/*** 01背包:* 有n件物品和一个最多能背重量为w的背包。* 第i件物品的重量是weight[i],得到的价值是value[i] 。* 每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。*/
public class demo10 {public static void main(String[] args) {int[] weight = {1,3,4};//物品的重量int[] value = {15,20,30};//物品的价值int bagSize = 4;//背包大小testWeightBagProblem(weight, value, bagSize);}public static void testWeightBagProblem(int[] weight, int[] value, int bagSize){//1.在一维dp数组中,dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j]。int[] dp = new int[bagSize + 1];//3.初始化全为零即可//2.递归公式: dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);for (int i = 0; i < weight.length; i++) {for (int j = dp.length - 1; j >= weight[i]; j--) { //倒序遍历是为了保证物品i只被放入一次!dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);}}System.out.println(Arrays.toString(dp));}
}

9.8、分割等和子集

题目传送门

方法一:动态规划(0-1背包问题)

问题可以转化为:集合中有没有总和等于sum/2的子集。

class Solution {public boolean canPartition(int[] nums) {int sum = 0;for (int i = 0; i < nums.length; i++) {sum += nums[i];}//总和为奇数,不能平分,直接返回。if (sum % 2 == 1){return false;}int target = sum / 2;//1.dp[j]表示背包总容量是j时,放进物品后背包的最大重量为dp[j]int[] dp = new int[target + 1];//3.题目给的价值都是正整数那么非0下标都初始化为0就可以了,如果题目给的价值有负数,那么非0下标就要初始化为负无穷。for (int i = 0; i < nums.length; i++) {for (int j = target; j >= nums[i]; j--) {//2.递推公式dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);}}return target == dp[target];}
}

9.9、最后一块石头的重量 II

题目传送门

方法一:动态规划(0-1背包)

尽量让石头分成重量相同的两堆,相撞之后剩下的石头最小,即可化解成01背包问题,和上题相似。

class Solution {public int lastStoneWeightII(int[] stones) {int sum = 0;for (int stone : stones) {sum += stone;}int target = sum / 2;//表示分的比较少的那一堆//1、dp[j]表示容量为j的背包,最多可以背最大重量为dp[j]。//3、初始化全为0int[] dp = new int[target + 1];for (int i = 0; i < stones.length; i++) {for (int j = target; j >= stones[i]; j--) {//2、递推公式dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]);}}//结果为大的那一堆减去小的拿一堆return (sum - dp[target]) - dp[target];}
}

9.10、目标和

题目传送门

方法一:动态规划(0-1背包)

此时问题就转化为,装满容量为left的背包,有几种方法。

有点难理解。

class Solution {public int findTargetSumWays(int[] nums, int target) {int sum = 0;for (int num : nums) {sum += num;}//left:所有正数的和;right:所有负数的和//left + right = sum; left - right = target ==> left = (target + sum) / 2if ((target + sum) % 2 == 1 || target + sum < 0){ //表示无法凑成target。left不能小于0return 0;}int left = (target + sum) / 2;//1、dp[j] 表示:填满j(包括j)这么大容积的包,有dp[j]种方法int[] dp = new int[left + 1];//3、dp[0]为1,其他初始化为0。方便计算dp[0] = 1;for (int i = 0; i < nums.length; i++) {for (int j = left; j >= nums[i]; j--) {//2、递推公式//注意:dp代表的是方法数dp[j] = dp[j] + dp[j - nums[i]];//可看成要此数的方法数 + 不要此数的方法数}}return dp[left];}
}

9.11、一和零

题目传送门

方法一:动态规划(0-1背包问题)

class Solution {public int findMaxForm(String[] strs, int m, int n) {//dp数组:最多有i个0和j个1的strs的最大子集的大小为dp[i][j]。int[][] dp = new int[m + 1][n + 1];//不会出现负数,因此初始化为0for (String str : strs) {int x = 0, y = 0;//统计一个字符串中0和1的个数for (int i = 0; i < str.length(); i++) {if (str.charAt(i) == '0'){x++;}else {y++;}}for (int i = m; i >= x; i--) {for (int j = n; j >= y; j--) {//递推公式dp[i][j] = Math.max(dp[i][j], dp[i - x][j - y] + 1);}}}return dp[m][n];}
}

完全背包问题

方法一:动态规划(一维数组)

/*** 完全背包:* 完全背包和01背包问题唯一不同的地方就是,每种物品有无限件。* 0-1背包为了保证一个物品只放入一次,所以倒序遍历;而完全背包一个物品可以放入多次,所以正序遍历。*/
public class demo10 {public static void main(String[] args) {int[] weight = {1,3,4};//物品的重量int[] value = {15,20,30};//物品的价值int bagSize = 4;//背包大小int[] dp = new int[bagSize + 1];//先遍历物品或者背包都可以for (int i = 0; i < weight.length; i++) {for (int j = weight[i]; j <= bagSize; j++) {//正序遍历dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);}}System.out.println(Arrays.toString(dp));}
}

9.12、零钱兑换 II

题目传送门

方法一:动态规划(完全背包)

先遍历物品,再遍历背包,保证全是[1,2],防止出现[2,1]的情况。

class Solution {public int change(int amount, int[] coins) {int[] dp = new int[amount + 1];dp[0] = 1;//初始化为1,为了方便计算,否则后面累加全为0了//先遍历物品,再遍历背包,保证全是[1,2],防止出现[2,1]的情况for (int i = 0; i < coins.length; i++) {for (int j = coins[i]; j <= amount; j++) {//当多一个物品时,求加它的方法数和不加它的方法数的和dp[j] = dp[j] + dp[j - coins[i]];}}return dp[amount];}
}

9.13、组合总和 Ⅳ

题目传送门

方法一:动态规划(完全背包)

由于顺序不同的序列被视作不同的组合,因此先遍历背包,再遍历物品。

class Solution {public int combinationSum4(int[] nums, int target) {int[] dp = new int[target + 1];dp[0] = 1;//由于顺序不同的序列被视作不同的组合,因此先遍历背包,再遍历物品for (int i = 0; i <= target; i++) {for (int j = 0; j < nums.length; j++) {if (i >= nums[j]){dp[i] = dp[i] + dp[i - nums[j]];}}}return dp[target];}
}

9.14、零钱兑换

题目传送门

方法一:动态规划(完全背包)

class Solution {public int coinChange(int[] coins, int amount) {int[] dp = new int[amount + 1];//因为递推公式要求最小值,所以初始化dp数组为最大值,dp[0]初始化为0for (int i = 1; i <= amount; i++) {dp[i] = Integer.MAX_VALUE;}//遍历顺序都可以for (int i = 0; i < coins.length; i++) {for (int j = coins[i]; j <= amount; j++) {//只有dp[j-coins[i]]不是初始最大值时,该位才有选择的必要,否则Integer.MAX_VALUE + 1溢出了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];}}
}

9.15、完全平方数

题目传送门

方法一:动态规划(完全背包)

和上题相似

class Solution {public int numSquares(int n) {//dp[j]:和为j的完全平方数的最少数量为dp[j]int[] dp = new int[n + 1];int len = (int)Math.pow(n, 0.5);//物品的最大重量//初始化:非0下标的dp初始化为最大值,dp[0] = 0for (int i = 1; i <= n; i++) {dp[i] = Integer.MAX_VALUE;}for (int i = 1; i <= len; i++) {for (int j = i * i; j <= n; j++) {if (dp[j - i * i] < Integer.MAX_VALUE)//递推公式dp[j] = Math.min(dp[j], dp[j - i * i] + 1);}}return dp[n];}
}

9.16、单词拆分

题目传送门

方法一:动态规划(完全背包)

class Solution {public boolean wordBreak(String s, List<String> wordDict) {//dp[i] : 字符串长度为i的话,dp[i]为true,表示可以拆分为一个或多个在字典中出现的单词boolean[] dp = new boolean[s.length() + 1];//初始化dp[0] = true;//如果求组合数就是外层for循环遍历物品,内层for遍历背包;如果求排列数就是外层for遍历背包,内层for循环遍历物品。//本题使用后者for (int i = 1; i <= s.length(); i++) {for (int j = 0; j < wordDict.size(); j++) {int len = wordDict.get(j).length();if (i >= len){String str = s.substring(i - len, i);//递推公式:用或者不用这个单词dp[i] = dp[i] || (dp[i - len] && wordDict.contains(str));}}}return dp[s.length()];}
}

9.17、打家劫舍

题目传送门

方法一:动态规划

class Solution {public int rob(int[] nums) {if (nums.length == 1){return nums[0];}//dp[i]:考虑下标i(包括i)以内的房屋,最多可以偷窃的金额为dp[i]int[] dp = new int[nums.length + 1];//初始化:dp[0]无意义,dp[0]为第一间房的钱dp[1] = nums[0];for (int i = 2; i < dp.length; i++) {//递推公式:偷这一间房还是上一间dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i - 1]);}return dp[nums.length];}
}

方法二:

使用两个变量优化内存。

class Solution {public int rob(int[] nums) {int pre = 0, cur = 0, temp;for (int num : nums) {temp = cur;cur = Math.max(pre + num, cur);pre = temp;}return cur;}
}

9.18、打家劫舍 II

题目传送门

方法一:动态规划

分两种情况:

1、不偷第一个房间。

2、不偷最后一个房间。

class Solution {public int rob(int[] nums) {if (nums.length == 1){return nums[0];}//返回两种情况的最大值return Math.max(helper(nums, 0, nums.length - 2), helper(nums, 1, nums.length - 1));}public int helper(int[] nums, int start, int end){int pre = 0, cur = 0, tmp;for (int i = start; i <= end; i++) {tmp = cur;cur = Math.max(pre + nums[i], cur);pre = tmp;}return cur;}
}

9.19、打家劫舍 III

题目传送门

方法一:动态规划

class Solution {public int rob(TreeNode root) {//dp含义:dp[0]表示不偷当前节点,dp[1]表示偷当前节点int[] dp = postList(root);return Math.max(dp[0], dp[1]);}//后序遍历public int[] postList(TreeNode node){if (node == null){//初始化:叶子节点初始化为0return new int[]{0,0};}int[] leftdp = postList(node.left);int[] rightdp = postList(node.right);//递推公式int val1 = Math.max(leftdp[0], leftdp[1]) + Math.max(rightdp[0], rightdp[1]);//不偷当前节点int val2 = node.val + leftdp[0] + rightdp[0];//偷当前节点return new int[]{val1, val2};}
}

9.20、买卖股票的最佳时机

题目传送门

方法一:贪心

class Solution {public int maxProfit(int[] prices) {int maxProfit = 0;//最大利润int minPrices = prices[0];//最小的价格for (int i = 1; i < prices.length; i++) {if (prices[i] < minPrices){//更新最小的价格minPrices = prices[i];}else if (maxProfit < prices[i] - minPrices){//更新最大利润maxProfit = prices[i] - minPrices;}}return maxProfit;}
}

方法二:动态规划

class Solution {public int maxProfit(int[] prices) {int len = prices.length;//dp含义:dp[i][0]表示当前持有股票的最大利润;dp[i][1]表示当前不持有股票的最大利润int[][] dp = new int[len][2];//初始化dp[0][0] = -prices[0];dp[0][1] = 0;for (int i = 1; i < prices.length; i++) {//dp公式dp[i][0] = Math.max(dp[i - 1][0], -prices[i]);//可能是之前就持有,也可能当天买的dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + prices[i]);//可能之前就卖了,也可能当天卖的}return dp[len - 1][1];}
}

方法三:动态规划

因为当前状态仅和上一状态相关,因此可以使用一位数组优化空间。

class Solution {public int maxProfit(int[] prices) {int[] dp = new int[2];dp[0] = -prices[0];dp[1] = 0;int temp = 0;for (int i = 1; i < prices.length; i++) {temp = dp[0];dp[0] = Math.max(dp[0], -prices[i]);dp[1] = Math.max(dp[1], temp + prices[i]);}return dp[1];}
}

9.21、买卖股票的最佳时机 II

题目传送门

方法一:贪心

再上一节中。

方法二:动态规划

和上一题相似,区别在于dp[0]更新时,如果当前买的话,需要加上之前的利润。

class Solution {public int maxProfit(int[] prices) {int[] dp = new int[2];dp[0] = -prices[0];dp[1] = 0;int temp = 0;for (int i = 1; i < prices.length; i++) {temp = dp[0];   dp[0] = Math.max(dp[0], dp[1] - prices[i]);//如果当前买的话,需要加上之前的利润dp[1] = Math.max(dp[1], temp + prices[i]);}return dp[1];}
}

9.22、最佳买卖股票时机含冷冻期

题目传送门

方法一:动态规划

class Solution {public int maxProfit(int[] prices) {//dp含义:// dp[0]表示持股状态(包括当前买入或者之前持股)的最大利润;// dp[1]表示保持不持股的状态;// dp[2]表示当天卖出股票的状态;// dp[3]表示冷冻期状态。int[] dp = new int[4];//初始化:dp[0] = -prices[0];dp[1] = dp[2] = dp[3] = 0;for (int i = 0; i < prices.length; i++) {int temp1 = dp[0];int temp2 = dp[2];//递推公式:dp[0] = Math.max(dp[0], Math.max(dp[1] - prices[i], dp[3] - prices[i]));//当天买或者之前买的dp[1] = Math.max(dp[1], dp[3]);dp[2] = temp1 + prices[i];dp[3] = temp2;}return Math.max(dp[1], Math.max(dp[2], dp[3]));}
}

9.23、买卖股票的最佳时机含手续费

方法一:动态规划

和9.20相似,只是多了手续费。

class Solution {public int maxProfit(int[] prices, int fee) {int[] dp = new int[2];dp[0] = -prices[0] - fee;for (int i = 0; i < prices.length; i++) {int temp = dp[0];dp[0] = Math.max(dp[0], dp[1] - prices[i] - fee);//算上了手续费dp[1] = Math.max(dp[1], temp + prices[i]);}return dp[1];}
}

9.24、最长递增子序列

题目传送门

方法一:动态规划

不好理解!

class Solution {public int lengthOfLIS(int[] nums) {//dp含义:dp[i]的值代表nums数组以nums[i]结尾的最长子序列长度int[] dp = new int[nums.length];//初始化:每个元素都至少可以单独成为子序列,全初始化为1Arrays.fill(dp, 1);int res = 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;// 返回dp列表最大值,即可得到全局最长上升子序列长度。}
}

9.25、最长连续递增序列

题目传送门

方法一:贪心

class Solution {public int findLengthOfLCIS(int[] nums) {if (nums.length == 1){return 1;}int res = 0;int sum = 1;for (int i = 1; i < nums.length; i++) {if (nums[i] > nums[i - 1]){sum++;}else {sum = 1;}res = Math.max(res, sum);}return res;}
}

方法二:动态规划

class Solution {public int findLengthOfLCIS(int[] nums) {if(nums.length == 1){return 1;}//dp[i]:代表当前下标最大连续值int[] dp = new int[nums.length];//初始化:全初始化为1Arrays.fill(dp, 1);int res = 0;for (int i = 1; i < nums.length; i++) {//递推公式:if (nums[i] > nums[i - 1]){dp[i] = dp[i - 1] + 1;}res = Math.max(res, dp[i]);}return res;}
}

9.26、最长重复子数组

题目传送门

方法一:动态规划

class Solution {public int findLength(int[] nums1, int[] nums2) {//dp[i][j]:以nums1的第i个数和nums2的第j个数为结尾,最长重复子数组长度int[][] dp = new int[nums1.length + 1][nums2.length + 1];int res = 0;//遍历过程中记录最大结果//初始化:第一行的第一列初始化为0for (int i = 1; i <= nums1.length; i++) {for (int j = 1; j <= nums2.length; j++) {//递推公式:if (nums1[i - 1] == nums2[j - 1]){ //第i个数的下标时i-1dp[i][j] = dp[i - 1][j - 1] + 1;}res = Math.max(res, dp[i][j]);}}return res;}
}

9.27、最长公共子序列

方法一:动态规划

class Solution {public int longestCommonSubsequence(String text1, String text2) {//dp[i][j]:长度为i的text1和长度为j的text2的最长公共子序列int[][] dp = new int[text1.length() + 1][text2.length() + 1];//初始化:第一行和第一列初始化为0for (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()];}
}

9.28、不相交的线

方法一:动态规划

思路和上一题相似。

class Solution {public int maxUncrossedLines(int[] nums1, int[] nums2) {int[][] dp = new int[nums1.length + 1][nums2.length + 1];for (int i = 1; i <= nums1.length; i++) {for (int j = 1; j <= nums2.length; j++) {if (nums1[i - 1] == nums2[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[nums1.length][nums2.length];}
}

9.29、最大子数组和

方法一:贪心

在上一节中。

方法二:动态规划

class Solution {public int maxSubArray(int[] nums) {int res = nums[0];//记录结果//dp含义:包括下标i(以nums[i]为结尾)的最大连续子序列和为dp[i]int[] dp = new int[nums.length];//初始化:dp[0] = nums[0];for (int i = 1; i < nums.length; i++) {//递推公式:如果dp[i - 1]对当前值是负贡献的话就舍去dp[i] = Math.max(nums[i], dp[i - 1] + nums[i]);res = Math.max(dp[i], res);}return res;}
}

9.30、判断子序列

方法一:双指针

class Solution {public boolean isSubsequence(String s, String t) {int index1 = 0, index2 = 0;while (index1 < s.length() && index2 < t.length()){if (s.charAt(index1) == t.charAt(index2)){ //如果匹配成功,两个指针都右移;否则,只有index2右移index1++;}index2++;}return index1 == s.length(); //如果index1移到了s的末尾,说明比配成功}
}

方法二:动态规划

class Solution {public boolean isSubsequence(String s, String t) {//dp含义:长度为i的字符串s是不是长度为j的字符串t的子序列,0表示不是,1表示是int[][] dp = new int[s.length() + 1][t.length() + 1];//初始化:第一行初始化为1for (int i = 0; i < t.length() + 1; i++) {dp[0][i] = 1;}for (int i = 1; i <= s.length(); i++) {for (int j = 1; j <= t.length(); j++) {//递推公式:if (i > j){ //不符合条件的情况dp[i][j] = 0;}else if (s.charAt(i - 1) == t.charAt(j - 1)){dp[i][j] = dp[i - 1][j - 1];}else { //t多一个字符的情况和少一个字符情况一样dp[i][j] = dp[i][j - 1];}}}return dp[s.length()][t.length()] == 1;}
}

9.31、两个字符串的删除操作

题目传送门

方法一:动态规划

通过求公共子序列反推删除操作的次数。

class Solution {public int minDistance(String word1, String word2) {int len1 = word1.length(), len2 = word2.length();//dp含义:dp[i][j]表示word1以第i个字符为结尾和word2以第j个字符为结尾,公共子序列的长度int[][] dp = new int[len1 + 1][len2 + 1];//初始化:第一行和第一列初始化为0for (int i = 1; i <= len1; i++) {for (int j = 1; j <= len2; j++) {//递推公式:if (word1.charAt(i - 1) == word2.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]);}}}//返回两个字符串的和,再减去公共字符串长度的2倍,为删除操作的次数return len1 + len2 - dp[len1][len2] * 2;}
}

方法二:动态规划

class Solution {public int minDistance(String word1, String word2) {int len1 = word1.length(), len2 = word2.length();//dp[i][j]:以第i个字符为结尾的字符串word1,和以第j个字符位结尾的字符串word2,想要达到相等,所需要删除元素的最少次数。int[][] dp = new int[len1 + 1][len2 + 1];//初始化:第一行和第一列初始化为ifor (int i = 0; i <= len1; i++) {dp[i][0] = i;}for (int i = 0; i <= len2; i++) {dp[0][i] = i;}for (int i = 1; i <= len1; i++) {for (int j = 1; j <= len2; j++) {//递推公式:if (word1.charAt(i - 1) == word2.charAt(j - 1)){ //不需要删除dp[i][j] = dp[i - 1][j - 1];}else { //删除word1的一个字符,或者删除word2的一个字符,或者各删除一个字符的最小值dp[i][j] = Math.min(dp[i - 1][j - 1] + 2, Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1));}}}return dp[len1][len2];}
}

9.32、编辑距离

题目传送门

方法一:动态规划

class Solution {public int minDistance(String word1, String word2) {int len1 = word1.length(), len2 = word2.length();//dp[i][j]:表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j]。int[][] dp = new int[len1 + 1][len2 + 1];//初始化:第一行和第一列初始化为ifor (int i = 0; i <= len1; i++) {dp[i][0] = i;}for (int i = 0; i <= len2; i++) {dp[0][i] = i;}for (int i = 1; i <= len1; i++) {for (int j = 1; j <= len2; j++) {//递推公式:if (word1.charAt(i - 1) == word2.charAt(j - 1)){ //不需要操作dp[i][j] = dp[i - 1][j - 1];}else { //改:dp[i - 1][j - 1] + 1;减:dp[i - 1][j] + 1 或 dp[i][j - 1] + 1//word2添加一个元素,相当于word1删除一个元素,因此不考虑增的情况了dp[i][j] = Math.min(dp[i - 1][j - 1] + 1, Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1));}}}return dp[len1][len2];}
}

9.33、回文子串

题目传送门

方法一:动态规划

class Solution {public int countSubstrings(String s) {int res = 0;//dp含义:表示区间范围[i,j]的子串是否是回文子串boolean[][] dp = new boolean[s.length()][s.length()];//初始化:全为false//遍历顺序:根据递推公式dp[i + 1][j - 1]决定,从下往上、从左到右遍历for (int i = s.length() - 1; i >= 0; i--) {for (int j = i; j < s.length(); j++) {if (s.charAt(i) == s.charAt(j)){//递推公式:if (j - i <= 1){ //一定是回文dp[i][j] = true;res++;}else { //下标各往中间移一个单位后判断dp[i][j] = dp[i + 1][j - 1];if (dp[i][j]) res++;}}}}return res;}
}

9.34、最长回文子序列

题目传送门

方法一:动态规划

class Solution {public int longestPalindromeSubseq(String s) {//dp[i][j]:字符串s在[i,j]范围内最长的回文子序列的长度int[][] dp = new int[s.length()][s.length()];//初始化:当i=j时,初始化为1for (int i = 0; i < s.length(); i++) {dp[i][i] = 0;}//遍历顺序:根据递推公式决定,从下往上、从左到右遍历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];//返回值}
}

10、单调栈

10.1、每日温度

方法一:单调栈

class Solution {public int[] dailyTemperatures(int[] temperatures) {int[] res = new int[temperatures.length];LinkedList<Integer> stack = new LinkedList<>();//模拟栈,存放下标for (int i = 0; i < temperatures.length; i++) {//使栈中元素递减放入while (stack.size() != 0 && temperatures[i] > temperatures[stack.peekLast()]){int val = stack.removeLast();res[val] = i - val;}stack.addLast(i);}return res;}
}

10.2、下一个更大元素 I

题目传送门

方法一:单调栈 + 哈希表

class Solution {public int[] nextGreaterElement(int[] nums1, int[] nums2) {int[] res = new int[nums1.length];Map<Integer, Integer> map = new HashMap<>();Deque<Integer> stack = new LinkedList<>();//单调栈for (int i = 0; i < nums2.length; i++) {while (!stack.isEmpty() && nums2[i] > stack.peekLast()){int val = stack.removeLast();map.put(val, nums2[i]);}stack.addLast(nums2[i]);}for (int i = 0; i < nums1.length; i++) {res[i] = map.getOrDefault(nums1[i], -1);}return res;}
}

10.3、下一个更大元素 II

题目传送门

方法一:单调栈

再上题的基础上变成了循环数组,可看成把两个数组拼接在一起,把数组遍历两遍即可。

class Solution {public int[] nextGreaterElements(int[] nums) {int len = nums.length;int[] res = new int[len];Arrays.fill(res, -1);//默认全部初始化为-1Deque<Integer> queue = new LinkedList<>();//单调栈//遍历数组两遍,下标取余操作for (int i = 0; i < len * 2; i++) {while (!queue.isEmpty() && nums[i % len] > nums[queue.peekLast()]){int index = queue.removeLast();res[index % len] = nums[i % len];}queue.addLast(i % len);}return res;}
}

10.4、接雨水

题目传送门

方法一:单调栈

class Solution {public int trap(int[] height) {Deque<Integer> queue = new LinkedList<>();//单调栈queue.addLast(0);int res = 0;for (int i = 1; i < height.length; i++) {int top = queue.peekLast();while (!queue.isEmpty() && height[i] > height[top]){ //使栈内元素递增int mid = queue.removeLast();//弹出一个元素,作为凹槽的中间位置if (queue.isEmpty()){ break;}int left = queue.peekLast();//弹出第二个元素(不为空的话),作为凹槽的左边界int h = Math.min(height[left], height[i]) - height[mid];//计算凹槽的高int k = i - left - 1;//计算凹槽的宽res += k * h;//计算凹槽的面积top = queue.peekLast();//更新栈顶元素}queue.addLast(i);}return res;}
}

方法二:双指针

class Solution {public int trap(int[] height) {int[] maxLeft = new int[height.length];int[] maxRight = new int[height.length];// 记录每个柱子左边柱子最大高度maxLeft[0] = height[0];for (int i = 1; i < height.length; i++) {maxLeft[i] = Math.max(maxLeft[i - 1], height[i]);}// 记录每个柱子右边柱子最大高度maxRight[height.length - 1] = height[height.length - 1];for (int i = height.length - 2; i >= 0; i--) {maxRight[i] = Math.max(maxRight[i + 1], height[i]);;}//计算总和int res = 0;for (int i = 0; i < height.length; i++) {int count = Math.min(maxLeft[i], maxRight[i]) - height[i];res += count;}return res;}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/8904.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

web前端tips:js继承——借用构造函数继承

上篇文章给大家分享了 js继承中的原型链继承 web前端tips&#xff1a;js继承——原型链继承 在文章末尾&#xff0c;我提到了 原型链的继承&#xff0c;子类需要传递参数给父类的构造函数&#xff0c;就无法通过直接调用父类的构造函数来实现&#xff0c;需要通过中间的过程来…

AutoSAR系列讲解(实践篇)7.6-实验:配置SWCRTE(下)

阅读建议: 实验篇是重点,有条件的同学最好跟着做一遍,然后回头对照着AutoSAR系列讲解(实践篇)7.5-OS原理进阶_ManGo CHEN的博客-CSDN博客理解其配置的目的和意义。本篇是接着AutoSAR系列讲解(实践篇)7.4-实验:配置SWC&RTE_ManGo CHEN的博客-CSDN博客的实验篇接着做…

Protobuf数据交互实战

"no one gonna make me down" 在之前呢&#xff0c;我们介绍了什么protobuf以及它的语法、数据类型。 一句老话说得好&#xff0c;"多说不练&#xff0c;假把式&#xff01;"。因此&#xff0c;本篇会选择以protobuf的语法&#xff0c;完成一个简易的通讯…

Godot 4 源码分析 - 获取脚本

获取属性列表 今天搂草打兔&#xff0c;取得了脚本内容 因为已能取得属性值&#xff0c;那就再进一步&#xff0c;取得属性名列表 if (SameText(drGet.propertyName, "propertyNames", DRGRAPH_FLAG_CASESENSITIVE)) {List<PropertyInfo> *p_list new List…

html之input复选框变为圆形、自定义复选框、消除默认样式、去除默认样式、事件代理、事件委托

文章目录 htmlcssJavaScript注意outline html <div class"checkbox_bx" onclick"checkboxF()"><input class"checkbox" type"checkbox" value"1" name"boole" onclick"checkboxF()" /><…

工信部、国家标准委联合印发《国家车联网产业指南(2023 版)》

国家工信部和标委发布了最新的《国家车联网产业标准体系建设指南&#xff08;智能网联汽车&#xff09;&#xff08;2023 版&#xff09;》&#xff0c;了解这篇文章&#xff0c;不论您是智能网联汽车的追随者&#xff0c;还是对智能网联汽车产业前景感兴趣的人&#xff0c;都非…

性能测试请求重试实现思路

文章目录 一、背景二、尝试的解决方案三、解决方案1&#xff1a;jmeter retrier插件&#xff01;有点用但是不是特别有用-_-||四&#xff0c;最终解决方案&#xff1a;lucust! 一、背景 最近系统需要压测一些活动&#xff0c;场景是新建抽奖活动之后&#xff0c;每隔2s查询1次…

Spring6——入门

文章目录 入门环境要求构建模块程序开发引入依赖创建java类创建配置文件创建测试类运行测试程序 程序分析启用Log4j2日志框架Log4j2日志概述引入Log4j2依赖加入日志配置文件测试使用日志 入门 环境要求 JDK&#xff1a;Java17&#xff08;Spring6要求JDK最低版本是Java17&…

Linux - find指令详细解释

来自chatgpt find 是一个强大的 Linux 命令行工具&#xff0c;用于在指定路径下查找文件和目录。它支持基于多种条件进行搜索&#xff0c;例如文件名、大小、类型、时间以及其他属性。 基本用法&#xff1a; find <path>&#xff1a;指定要搜索的起始路径&#xff08;默认…

【Shell】Shell编程之免交互

免交互&#xff1a;不需要人为控制就可以完成的自动化操作 自动化运维 Shell脚本和免交互是一个概念&#xff0c;但是两种写法 here document 使用i/o重定向的方式将命令的列表提供给交互式的程序或者命令 是一种标准输入&#xff0c;只能接受正确的指令或命令&#x…

1-Linux的目录结构

Linux的目录结构是规定好的&#xff0c;不可以随意进行更改&#xff01; Linux的文件系统是采用级层式的树状目录结构&#xff0c;最上层是根目录–/&#xff0c;然后再在根目录下创建其它的目录。 各个目录中主要负责的功能和作用如下&#xff1a;&#xff08;主体的结构一定…

引入第三方字体库 第三方字体库Google Fonts

googlefonts官方网站 googlefonts中国网站 本人是在微信小程序中引入 在static中建一个文件夹font-family 例如字体链接&#xff1a;https://fonts.font.im/css?familyKirangHaerang 将该链接的返回的资源的复制到css文件中 font-family.css /* [0] */ font-face {font-fam…

通过cups接口,以代码形式设置默认打印机

1. 完整代码 #include "cups.h" #include <errno.h> #include <iostream>#define HTTP_MAX_URI 1024/** validate_name() - Make sure the printer name only contains valid chars.*/static int /* O - 0 if name is no good, 1 …

使用JMeter进行接口测试教程

安装 使用JMeter的前提需要安装JDK&#xff0c;需要JDK1.7以上版本目前在用的是JMeter5.2版本&#xff0c;大家可自行下载解压使用 运行 进入解压路径如E: \apache-jmeter-5.2\bin&#xff0c;双击jmeter.bat启动运行 启动后默认为英文版本&#xff0c;可通过Options – Ch…

专项练习-04编程语言-03JAVA-04

1. 设Tree为已定义的类名&#xff0c;下列语句能正确创建 Tree 对象的是 。A Tree tnew Tree; B Tree tnew Tree(); C Tree tTree(); D Tree t[ ]new Tree[10]; 正确答案&#xff1a;B 官方解析&#xff1a;暂无官方题目解析&#xff0c;去讨论区看看吧&#xff01; 知识点&…

使用node内置test runner,和 Jest say 拜拜

参考 https://nodejs.org/dist/latest-v20.x/docs/api/test.html#test-runner 在之前&#xff0c;我们写单元测试&#xff0c;必须安装第三方依赖包&#xff0c;而从node 20.0.0 版本之后&#xff0c;可以告别繁琐的第三方依赖包啦&#xff0c;可直接使用node的内置test runner…

centos中修改防火墙端口开放配置

1、直接进入文件修改 vim /etc/sysconfig/iptables 2、添加需要开放的端口 &#xff08;1&#xff09;添加需要开放的单个端口 4001 -A INPUT -m state --state NEW -m tcp -p tcp --dport 4001 -j ACCEPT &#xff08;2&#xff09;添加需要开放的某个网段端口 4001:4020 …

需求管理中最易忽视的6大重点

需求管理是产品经理的重点工作&#xff0c;如果无法有效进行需求管理&#xff0c;往往会引起需求变更、项目延期以及成本增加等问题。那么如何对需求进行高效管理&#xff0c;我们在需求管理中&#xff0c;往往最容易忽视的重点都有哪些&#xff1f; 1、重视项目整体管理计划 首…

VMWare虚拟机常用操作命令

今日一语&#xff1a;做到所有的细节都不放过&#xff0c;则可以避免99%已知的风险&#xff0c;但大多数都因懒惰而甘愿承受风险&#xff0c;至此悔不当初 查看虚拟机在本机网络的IP ip addr 本地向虚拟机传送文件 scp 文件 rootpath 虚拟机路径 enter后输入密码即可传输&am…

运维——编写脚本,使用mysqldump实现分库分表备份。

编写脚本&#xff0c;使用mysqldump实现分库分表备份。 #!/bin/bash# MySQL连接参数 DB_HOST"localhost" DB_PORT"3306" DB_USER"your_username" DB_PASSWORD"your_password"# 备份保存路径 BACKUP_DIR"/path/to/backup"# …