代码随想录刷题记录

代码随想录刷题记录

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变了说明找到了结果,否则没找到满足条件的结果,返回0
        return 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;//更新pre
            cur = 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;//直接退出,因为排序后的第一个数不能小于0
            if (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)){ //不合法返沪false
                    return 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>() {
            @Override
            public 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){
                //处理左子节点的next
                head.left.next = head.right;
                //处理右子节点的next
                if (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:跟节点索引 - 右子树长度 - 1
        node.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;
            }
            //剪枝优化:从第二个数开始,同一层相同数值的结点,结果一定发生重复,因此跳过,用continue
            if (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],否则直接break
            if (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的话,continue
            if (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的情况表示初始时的preDiff
            if ((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){ //此时需要更新end
                end = Math.max(end, intervals[i][1]);
            }else { //此时已经得到一个线段,更新start和end
                int[] 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,并记录当前位开始后面全是9
        for (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];
        //初始化第一行和第一列位1
        for (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 - 1
                dp[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、初始化全为0
        int[] 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) / 2
        if ((target + sum) % 2 == 1 || target + sum < 0){ //表示无法凑成target。left不能小于0
            return 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];

        //不会出现负数,因此初始化为0

        for (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]初始化为0
        for (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] = 0
        for (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){
            //初始化:叶子节点初始化为0
            return 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];
        //初始化:每个元素都至少可以单独成为子序列,全初始化为1
        Arrays.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];
        //初始化:全初始化为1
        Arrays.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;//遍历过程中记录最大结果

        //初始化:第一行的第一列初始化为0
        
        for (int i = 1; i <= nums1.length; i++) {
            for (int j = 1; j <= nums2.length; j++) {
                //递推公式:
                if (nums1[i - 1] == nums2[j - 1]){ //第i个数的下标时i-1
                    dp[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];
        
        //初始化:第一行和第一列初始化为0
        
        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()];
    }
}

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];
        //初始化:第一行初始化为1
        for (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];
        //初始化:第一行和第一列初始化为0
        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] + 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];
        //初始化:第一行和第一列初始化为i
        for (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];
        //初始化:第一行和第一列初始化为i
        for (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时,初始化为1
        for (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);//默认全部初始化为-1
        Deque<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;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值