leetcode练习

分类

题单:code

难度:简单 中等 困难

类型:数组 链表 字符串 二叉树 排序

解法:递归和迭代 滑动窗口 Map Stack 双指针

前缀和 动态规划

解答:解答

code

[1. 两数之和](#1. 两数之和)

[2. 两数相加](#2. 两数相加)

[3. 无重复字符的最长子串](#3. 无重复字符的最长子串)

[5. 最长回文子串](#5. 最长回文子串)

[19. 删除链表的倒数第 N 个结点](#19. 删除链表的倒数第 N 个结点)

[20. 有效的括号](#20. 有效的括号)

[21. 合并两个有序链表](#21. 合并两个有序链表)

[23. 合并K个升序链表](#23. 合并K个升序链表)

[25. k 个一组翻转链表](#25. k 个一组翻转链表)

[26. 删除有序数组中的重复项](#26. 删除有序数组中的重复项)

[27. 移除元素](#27. 移除元素)

[34. 在排序数组中查找元素的第一个和最后一个位置](#34. 在排序数组中查找元素的第一个和最后一个位置)

[76. 最小覆盖子串](#76. 最小覆盖子串)

[86. 分隔链表](#86. 分隔链表)

[92. 反转链表II](92. 反转链表 II)

[94. 二叉树的中序遍历](#94. 二叉树的中序遍历)

[100. 相同的树](#100. 相同的树)

[101. 对称二叉树](#101. 对称二叉树)

[102. 二叉树的层序遍历](#102. 二叉树的层序遍历)

[104. 二叉树的最大深度](#104. 二叉树的最大深度)

[105. 从前序与中序遍历序列构造二叉树](#105. 从前序与中序遍历序列构造二叉树)

[106. 从中序与后序遍历序列构造二叉树](#106. 从中序与后序遍历序列构造二叉树)

[107. 二叉树的层序遍历 II](#107. 二叉树的层序遍历 II)

[111. 二叉树的最小深度](#111. 二叉树的最小深度)

[112. 路径总和](#112. 路径总和)

[113. 路径总和 II](#113. 路径总和 II)

[114. 二叉树展开为链表](#114. 二叉树展开为链表)

[141. 环形链表](#141. 环形链表)

[142. 环形链表 II](#142. 环形链表 II)

[144. 二叉树的前序遍历](#144. 二叉树的前序遍历)

[145. 二叉树的后序遍历](#145. 二叉树的后序遍历)

[160. 相交链表](#160. 相交链表)

[167. 两数之和 II - 输入有序数组](#167. 两数之和 II - 输入有序数组)

[206. 反转链表](#206. 反转链表)

[226. 翻转二叉树](#226. 翻转二叉树)

[234. 回文链表](#234. 回文链表)

[257. 二叉树的所有路径](#257. 二叉树的所有路径)

[283. 移动零](#283. 移动零)

[303. 区域和检索 - 数组不可变](#303. 区域和检索 - 数组不可变)

[344. 反转字符串](#344. 反转字符串)

[438. 找到字符串中所有字母异位词](#438. 找到字符串中所有字母异位词)

[509. 斐波那契数](#509. 斐波那契数)

[567. 字符串的排列](#567. 字符串的排列)

[654. 最大二叉树](#654. 最大二叉树)

[876. 链表的中间结点](#876. 链表的中间结点)

simple

[1. 两数之和](#1. 两数之和)

[20. 有效的括号](#20. 有效的括号)

[21. 合并两个有序链表](#21. 合并两个有序链表)

[26. 删除有序数组中的重复项](#26. 删除有序数组中的重复项)

[27. 移除元素](#27. 移除元素)

[94. 二叉树的中序遍历](#94. 二叉树的中序遍历)

[100. 相同的树](#100. 相同的树)

[101. 对称二叉树](#101. 对称二叉树)

[104. 二叉树的最大深度](#104. 二叉树的最大深度)

[111. 二叉树的最小深度](#111. 二叉树的最小深度)

[112. 路径总和](#112. 路径总和)

[141. 环形链表](#141. 环形链表)

[144. 二叉树的前序遍历](#144. 二叉树的前序遍历)

[145. 二叉树的后序遍历](#145. 二叉树的后序遍历)

[160. 相交链表](#160. 相交链表)

[206. 反转链表](#206. 反转链表)

[226. 翻转二叉树](#226. 翻转二叉树)

[234. 回文链表](#234. 回文链表)

[257. 二叉树的所有路径](#257. 二叉树的所有路径)

[283. 移动零](#283. 移动零)

[303. 区域和检索 - 数组不可变](#303. 区域和检索 - 数组不可变)

[344. 反转字符串](#344. 反转字符串)

[509. 斐波那契数](#509. 斐波那契数)

[876. 链表的中间结点](#876. 链表的中间结点)

middle

[2. 两数相加](#2. 两数相加)

[3. 无重复字符的最长子串](#3. 无重复字符的最长子串)

[5. 最长回文子串](#5. 最长回文子串)

[19. 删除链表的倒数第 N 个结点](#19. 删除链表的倒数第 N 个结点)

[23. 合并K个升序链表](#23. 合并K个升序链表)

[34. 在排序数组中查找元素的第一个和最后一个位置](#34. 在排序数组中查找元素的第一个和最后一个位置)

[86. 分隔链表](#86. 分隔链表)

[92. 反转链表II](92. 反转链表 II)

[102. 二叉树的层序遍历](#102. 二叉树的层序遍历)

[105. 从前序与中序遍历序列构造二叉树](#105. 从前序与中序遍历序列构造二叉树)

[106. 从中序与后序遍历序列构造二叉树](#106. 从中序与后序遍历序列构造二叉树)

[107. 二叉树的层序遍历 II](#107. 二叉树的层序遍历 II)

[113. 路径总和 II](#113. 路径总和 II)

[114. 二叉树展开为链表](#114. 二叉树展开为链表)

[142. 环形链表 II](#142. 环形链表 II)

[167. 两数之和 II - 输入有序数组](#167. 两数之和 II - 输入有序数组)

[438. 找到字符串中所有字母异位词](#438. 找到字符串中所有字母异位词)

[567. 字符串的排列](#567. 字符串的排列)

[654. 最大二叉树](#654. 最大二叉树)

hard

[25. k 个一组翻转链表](#25. k 个一组翻转链表)

[76. 最小覆盖子串](#76. 最小覆盖子串)

数组

[1. 两数之和](#1. 两数之和)

[26. 删除有序数组中的重复项](#26. 删除有序数组中的重复项)

[27. 移除元素](#27. 移除元素)

[34. 在排序数组中查找元素的第一个和最后一个位置](#34. 在排序数组中查找元素的第一个和最后一个位置)

[167. 两数之和 II - 输入有序数组](#167. 两数之和 II - 输入有序数组)

[283. 移动零](#283. 移动零)

[303. 区域和检索 - 数组不可变](#303. 区域和检索 - 数组不可变)

链表

[2. 两数相加](#2. 两数相加)

[19. 删除链表的倒数第 N 个结点](#19. 删除链表的倒数第 N 个结点)

[21. 合并两个有序链表](#21. 合并两个有序链表)

[23. 合并K个升序链表](#23. 合并K个升序链表)

[25. k 个一组翻转链表](#25、k 个一组翻转链表)

[92. 反转链表II](#92. 反转链表)

[141. 环形链表](#141. 环形链表)

[142. 环形链表 II](#142. 环形链表 II)

[160. 相交链表](#160. 相交链表)

[206. 反转链表](206. 反转链表)

[234. 回文链表](#234. 回文链表)

[876. 链表的中间结点](#876. 链表的中间结点)

字符串

[3. 无重复字符的最长子串](#3. 无重复字符的最长子串)

[5. 最长回文子串](#5. 最长回文子串)

[20. 有效的括号](#20. 有效的括号)

[76. 最小覆盖子串](#76. 最小覆盖子串)

[344. 反转字符串](#344. 反转字符串)

[438. 找到字符串中所有字母异位词](#438. 找到字符串中所有字母异位词)

[567. 字符串的排列](#567. 字符串的排列)

二叉树

[94. 二叉树的中序遍历](#94. 二叉树的中序遍历)

[100. 相同的树](#100. 相同的树)

[101. 对称二叉树](#101. 对称二叉树)

[102. 二叉树的层序遍历](#102. 二叉树的层序遍历)

[104. 二叉树的最大深度](#104. 二叉树的最大深度)

[105. 从前序与中序遍历序列构造二叉树](#105. 从前序与中序遍历序列构造二叉树)

[106. 从中序与后序遍历序列构造二叉树](#106. 从中序与后序遍历序列构造二叉树)

[107. 二叉树的层序遍历 II](#107. 二叉树的层序遍历 II)

[111. 二叉树的最小深度](#111. 二叉树的最小深度)

[112. 路径总和](#112. 路径总和)

[113. 路径总和 II](#113. 路径总和 II)

[114. 二叉树展开为链表](#114. 二叉树展开为链表)

[144. 二叉树的前序遍历](#144. 二叉树的前序遍历)

[145. 二叉树的后序遍历](#145. 二叉树的后序遍历)

[226. 翻转二叉树](#226. 翻转二叉树)

[257. 二叉树的所有路径](#257. 二叉树的所有路径)

[654. 最大二叉树](#654. 最大二叉树)

递归和迭代

[2. 两数相加](#2. 两数相加)

[25. k 个一组翻转链表](#25. k 个一组翻转链表)

[92. 反转链表II](92. 反转链表 II)

[94. 二叉树的中序遍历](#94. 二叉树的中序遍历)

[100. 相同的树](#100. 相同的树)

[101. 对称二叉树](#101. 对称二叉树)

[102. 二叉树的层序遍历](#102. 二叉树的层序遍历)

[104. 二叉树的最大深度](#104. 二叉树的最大深度)

[105. 从前序与中序遍历序列构造二叉树](#105. 从前序与中序遍历序列构造二叉树)

[106. 从中序与后序遍历序列构造二叉树](#106. 从中序与后序遍历序列构造二叉树)

[107. 二叉树的层序遍历 II](#107. 二叉树的层序遍历 II)

[111. 二叉树的最小深度](#111. 二叉树的最小深度)

[112. 路径总和](#112. 路径总和)

[113. 路径总和 II](#113. 路径总和 II)

[114. 二叉树展开为链表](#114. 二叉树展开为链表)

[144. 二叉树的前序遍历](#144. 二叉树的前序遍历)

[145. 二叉树的后序遍历](#145. 二叉树的后序遍历)

[206. 反转链表](#206. 反转链表)

[226. 翻转二叉树](#226. 翻转二叉树)

[234. 回文链表](#234. 回文链表)

[257. 二叉树的所有路径](#257. 二叉树的所有路径)

[654. 最大二叉树](#654. 最大二叉树)

滑动窗口

[3. 无重复字符的最长子串](#3. 无重复字符的最长子串)

[76. 最小覆盖子串](#76. 最小覆盖子串)

[438. 找到字符串中所有字母异位词](#438. 找到字符串中所有字母异位词)

[567. 字符串的排列](#567. 字符串的排列)

Map

[1. 两数之和](#1. 两数之和)

Stack

[20. 有效的括号](#20. 有效的括号)

[234. 回文链表](#234. 回文链表)

[23. 合并K个升序链表](#23. 合并K个升序链表)

双指针

[19. 删除链表的倒数第 N 个结点](#19. 删除链表的倒数第 N 个结点)

[21. 合并两个有序链表](#21. 合并两个有序链表)

[86. 分隔链表](#86. 分隔链表)

[160. 相交链表](#160. 相交链表)

左右指针

[5. 最长回文子串](#5. 最长回文子串)

[167. 两数之和 II - 输入有序数组](#167. 两数之和 II - 输入有序数组)

[344. 反转字符串](#344. 反转字符串)

快慢指针

[26. 删除有序数组中的重复项](#26. 删除有序数组中的重复项)

[27. 移除元素](#27. 移除元素)

[141. 环形链表](#141. 环形链表)

[142. 环形链表 II](#142. 环形链表 II)

[283. 移动零](#283. 移动零)

[876. 链表的中间结点](#876. 链表的中间结点)

二分查找

[34. 在排序数组中查找元素的第一个和最后一个位置](#34. 在排序数组中查找元素的第一个和最后一个位置)

前缀和

[303. 区域和检索 - 数组不可变](#303. 区域和检索 - 数组不可变)

动态规划

排序

[100000. 冒泡排序](#100000. 冒泡排序)

[100001. 插入排序](#100001. 插入排序)

[100002. 选择排序](#100002. 选择排序)

[100003. 希尔排序](#100003. 希尔排序)

[100004. 快速排序](#100004. 快速排序)

[100005. 归并排序](#100005. 归并排序)

解答

1. 两数之和
class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> map = new HashMap<>();
      //b = target - a,存储 b 值,遍历后面数字是否等于 b
        for (int i = 0; i < nums.length; i++) {
          //获取当前值
            Integer index = map.get(nums[i]);
            if (index != null) {
                return new int[]{index, i};
            }
          //存储差值
            int diff = target - nums[i];
            map.put(diff, i);
        }
        return new int[0];
    }
}
2. 两数相加
class Solution {
    //逆序存储,个位在前
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        //构建虚拟节点
        ListNode dummy = new ListNode(-1);
        ListNode p = dummy;
        //记录进位
        int carry = 0;
        while (l1 != null || l2 != null || carry > 0) {
            int val = carry;
            if (l1 != null) {
                val += l1.val;
                l1 = l1.next;
            }
            if (l2 != null) {
                val += l2.val;
                l2 = l2.next;
            }
            //记录进位
            carry = val / 10;
            val = val % 10;
            p.next = new ListNode(val);
            p = p.next;
        }
        return dummy.next;
    }

}
3. 无重复字符的最长子串
class Solution {
    //滑动窗口
    public int lengthOfLongestSubstring(String s) {
        //定义字典:记录窗口内的元素,使用 map 或 set
        //这里使用map<字符,字符数量>,这里记录数量而不记录下标是为了通用性
        Map<Character, Integer> map = new HashMap<>();
        //定义左右窗口
        int right = 0, left = 0;
        int maxLen = 0;
        //右窗口退出条件
        while (right < s.length()) {
            //形成公式:right++形成[)  区间
            char rc = s.charAt(right);
            right++;
            //统计重复字数
            map.put(rc, map.getOrDefault(rc, 0)+1);
            //左窗口退出条件是不能有重复字符
            while (map.get(rc) > 1) {
                //获取到字符后 left 直接++,形成套路
                char lc = s.charAt(left);
                left++;
                //收缩窗口,减去字符数量
                map.put(lc, map.get(lc)-1);
            }
            //在这里更新答案,在 left 中更新答案会有单字符字符串为 0 的问题
            maxLen = Math.max(maxLen, right - left);
        }
        return maxLen;
    }
}
5. 最长回文子串
class Solution {
    public String longestPalindrome(String s) {
        String res = "";
        int index = 0;
        while (index < s.length()) {
            //以 index 为中心,找奇数回文串
            String s1 = palindrome(s, index, index);
            //以 index 和 index+1为中心,找偶数回文串
            String s2 = palindrome(s, index, index+1);
            res = res.length() >= s1.length() ? res : s1;
            res = res.length() >= s2.length() ? res : s2;
            index++;
        }
        return res;
    }

    /**
     * 左右指针,由中心向外扩散找回文
     * 当 left==right时是奇数回文串
     * 当 left!=right时是偶数回文串
     * @param s
     * @param left
     * @param right
     * @return
     */
    public String palindrome(String s, int left, int right) {
        //条件是一个半开区间,注意 left 和 right 的临界判断
        while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
            left--;
            right++;
        }
        //sub 函数也是截取的也是半开区间,right 不用调整
        return s.substring(left+1, right);
    }
}
19. 删除链表的倒数第 N 个结点
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    //双指针,两个指针间间隔 n+1 个节点,则第二个指针遍历到尾的时候,第一个指针在要删除节点的前一个
    public ListNode removeNthFromEnd(ListNode head, int n) {
        //因为删除的可能是头结点,应该使用虚拟节点填充。并且使用虚拟节点刚好可以解决 n+1问题
        ListNode dummy = new ListNode(-1);
        dummy.next = head;

        ListNode p1 = dummy;
        ListNode p2 = head;
        //p2 先走 n 步
        for (int i = 0; i < n; i++) {
            p2 = p2.next;
        }
        //p1 和 p2同时走
        while (p2 != null) {
            p1 = p1.next;
            p2 = p2.next;
        }
        //此时 p1 在要删除节点的前一个
        ListNode temp = p1.next;
        p1.next = p1.next.next;
        temp.next = null;
        return dummy.next;
    }
}
20. 有效的括号
class Solution {
    //要求左括号必须以正确的顺序闭合,实际就是([)]这种是不行的,所以比较简单,用栈就行
    public boolean isValid(String s) {
        Stack<Character> left = new Stack<>();
        for (char c : s.toCharArray()) {
            if (c == '(' || c == '[' || c == '{') {
                left.push(c);
            } else {
                if (!left.isEmpty() && leftOf(c) == left.peek()) {
                    left.pop();
                } else {
                    //这里一定要返回否则,开头是右括号的就匹配不上了
                    return false;
                }
            }
        }
        return left.isEmpty();
    }

    char leftOf(char c) {
        if (c == ')') return '(';
        if (c == ']') return '[';
        return '{';
    }
}
21. 合并两个有序链表
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    //双指针
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        //新建链表,把两个节点分别加入
        ListNode dummy = new ListNode(-1);
        ListNode p = dummy;

        while (list1 != null && list2 != null) {
            // 比较 p1 和 p2 两个指针,将值较小的的节点接到 p 指针
            if (list1.val > list2.val) {
                p.next = list2;
                list2 = list2.next;
            } else {
                p.next = list1;
                list1 = list1.next;
            }
            p = p.next;
        }
        if (list1 != null) {
            p.next = list1;
        }
        if (list2 != null) {
            p.next = list2;
        }
        return dummy.next;
    }
}
23. 合并K个升序链表
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        if (lists.length == 0) {
            return null;
        }
        //需要新建链表,使用虚拟节点
        ListNode dummy = new ListNode(-1);
        ListNode p = dummy;

        //最小堆
        PriorityQueue<ListNode> queue = new PriorityQueue<ListNode>((a,b) -> (a.val - b.val));
        //这里使用最小堆真是帅,每个链表只用加入头结点就行,本身是升序链表,很好得利用了链表特性
        for (ListNode head : lists) {
            if (head != null) {
                queue.add(head);
            }
        }

        while (!queue.isEmpty()) {
            //获取最小节点,接到结果链中
            ListNode node = queue.poll();
            p.next = node;
            if (node.next != null) {
                queue.add(node.next);
            }
            p = p.next;
        }
        return dummy.next;
    }
}

25. K 个一组翻转链表
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        return recisionGroup(head,k);
    }

    /**
     * 递归
     * 类似翻转指定区间的链表:
     * 将单个节点换成组的概念
     * @param head
     * @param k
     * @return
     */
    private ListNode recisionGroup(ListNode head,int k) {
        if (head == null) {
            return head;
        }
        ListNode start = head, end = head;
        //出口:返回最后一组链表反转后的头节点
        for (int i = 0; i < k; i++) {
            if (end == null) return head;
            end = end.next;
        }
        ListNode newHead = iterition(start,end);
        //不能注入 end.next,iterition返回的是【a,b)
        start.next  = recisionGroup(end,k);
        return newHead;
    }

    /**
     * 迭代反转 【a,b) 节点间链表,返回的是 b 的前置节点
     * @param head
     * @param end
     * @return
     */
    private ListNode iterition(ListNode head,ListNode end) {
        if (head == null) {
            return head;
        }
        ListNode newHead = null;
        while (head != end) {
            ListNode temp = head.next;
            head.next = newHead;
            newHead = head;
            head = temp;
        }
        return newHead;
    }

    /**
     * 迭代法
     * @param head
     * @param k
     * @return
     */
    public ListNode iterator(ListNode head, int k) {
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        //分为前中后三块,保留前尾,中头尾,后头
        //记录反转的潜质
        ListNode pre = dummy;
        for (;head != null;) {
            //标记需要翻转的头尾
            ListNode tempHead = head,tempTail = head;
            for (int i=1; i < k && head != null;i++,head = head.next) {
                tempTail = head.next;
                if (tempTail == null) {
                    return dummy.next;
                }
            }
            //记录反转的后置
            ListNode back = tempTail.next;
            //反转前切割出需要翻转的部分
            tempTail.next = null;
            //反转
            ListNode newHead = iterator(tempHead);
            //反转后的头接后置
            tempHead.next = back;
            //反转的头接前置
            pre.next = newHead;
            //更新前置
            pre = tempHead;
            //更新 head
            head = back;
        }
        return dummy.next;
    }

    /**
     * 反转链表
     * @param head
     * @param tail
     * @return
     */
    private ListNode iterator(ListNode head) {
        ListNode newHead = null;
        while (head != null) {
            ListNode temp = head.next;
            head.next = newHead;
            newHead = head;
            head = temp;
        }
        return newHead;
    }

}
26. 删除有序数组中的重复项
class Solution {
    /**
     * 数组是升序的,直接快慢指针就行
     * 但是需要注意的是,原数组不变,所以快慢指针的运行方式是:
     * fast增加一步如果相等,fast 继续加,直到不相等后,slow 加,把 fast 的值赋给 slow
     * @param nums
     * @return
     */
    public int removeDuplicates(int[] nums) {
        int slow = 0, fast = 0;
        for (;fast < nums.length;) {
            //注意覆盖方式
            if (nums[slow] != nums[fast]) {
                nums[++slow] = nums[fast];
            }
            fast++;
        }
        //slow 表示 index,所以实际值应该+1
        return slow + 1;
    }
}
27. 移除元素
class Solution {
    /**
     * 这个和原地重复删除元素类似,也是快慢指针,快指针找不等于 val,慢指针指向相等的准备覆盖
     * @param nums
     * @param val
     * @return
     */
    public int removeElement(int[] nums, int val) {
        int slow = 0, fast = 0;
        while (fast < nums.length) {
            if (nums[fast] != val) {
                //这里需要后++,这类题应该根据示例推导
                nums[slow++] = nums[fast];
            }
            fast++;
        }
        return slow;
    }
}
34. 在排序数组中查找元素的第一个和最后一个位置
class Solution {
    /**
     * 看到有序数组,就要想一下双指针、二分解法是能可行
     * 实际遍历一次就行,但是有复杂度要求,所以用二分查找。
     * @param nums
     * @param target
     * @return
     */
    public int[] searchRange(int[] nums, int target) {
        return new int[]{leftBound(nums,target), rightBound(nums,target)};
    }

    /**
     * 二分查找,但是对于等于的判断,不进行值返回
     * @param nums
     * @param target
     * @return
     */
    public int leftBound(int[] nums, int target) {
       int left = 0, right = nums.length - 1;
       while (left <= right) {
           int mid = left + (right - left) / 2;
           if (nums[mid] < target) {
               left = mid + 1;
           } else if (nums[mid] > target) {
               right = mid - 1;
           } else if (nums[mid] == target) {
               right = mid - 1;
           }
       }
       if (left >= nums.length || nums[left] != target) {
           return -1;
       }
       return left;
    }

    public int rightBound(int[] nums, int target) {
        int left = 0, right = nums.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] < target) {
                left = mid + 1;
            } else if (nums[mid] > target) {
                right = mid - 1;
            } else if (nums[mid] == target) {
                left = mid + 1;
            }
        }
        if (right < 0 || nums[right] != target) {
            return -1;
        }
        return right;
    }
}
76. 最小覆盖子串
class Solution {
    //还是滑动窗口问题,与 3 的问题类似,不过需要返回子串
    public String minWindow(String s, String t) {
        //统计 t 中的字符数量
        Map<Character, Integer> subMap = new HashMap<>();
        for (int i = 0; i < t.length(); i++) {
            char ch = t.charAt(i);
            subMap.put(ch, subMap.getOrDefault(ch, 0)+1);
        }
        //定义左右窗口
        int right = 0, left = 0;
        //记录目标子串开始下标,最大长度,s 串中满足 t 中字符数量的字符个数
        int start = 0, minLen = Integer.MAX_VALUE, vaild = 0;
        //记录父串的字典
        Map<Character, Integer> map = new HashMap<>();
        //不变的右窗口退出条件
        while (right < s.length()) {
            //不变的右窗口移动
            char rc = s.charAt(right);
            right++;
            //更新字典
            map.put(rc, map.getOrDefault(rc, 0) + 1);
            //如果字符个数相等,说明已经有一个字符数量满足了
            if (map.get(rc).equals(subMap.get(rc))) {
                vaild++;
            }
            //思考:左窗口什么时候更新?审题:包含 t 所有字母的最小子串,因此满足一个 t 的时候
            while (vaild == subMap.size()) {
                //思考:start 什么时候变,maxLen 什么时候变
                //获取最小的串
                if (right - left < minLen) {
                    start = left;
                    minLen = right - left;
                }
                char lc = s.charAt(left);
                left++;
                //如果该字符是 t 中的,说明移动到关键字符了,则需要将 vaild-1
                //这里必须要进行等比,否则仅subMap.containsKty(lc)无法判定 vaild 有效
                //map 中数量可能是大于 subMap,不做这个判定 map 减一个也同样满足vaild,则逻辑就会出错
                if (map.get(lc).equals(subMap.get(lc))) {
                    vaild--;
                }
                map.put(lc, map.get(lc)-1);
            }
        }
        return minLen == Integer.MAX_VALUE ? "" : s.substring(start, start+minLen);
    }
}
86. 分隔链表
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    /**
     * 可以和合并升序链表一样,构造新链表,把符合的节点从 head 中剔除,最后再把 head 拼接到新链表尾部
     * 这样实际就产生了两条链表,就使用两个虚拟节点,注意拆分节点,别把事情想复杂了
     * @param head
     * @param x
     * @return
     */
    public ListNode partition(ListNode head, int x) {
        //存小于 x 的节点
        ListNode dummy1 = new ListNode(-1);
        ListNode p1 = dummy1;

        //存大于等于 x 的节点
        ListNode dummy2 = new ListNode(-1);
        ListNode p2 = dummy2;

        while (head != null) {
            //拆分链表
            if (head.val < x) {
                p1.next = head;
                p1 = p1.next;
            } else {
                p2.next = head;
                p2 = p2.next;
            }
            //断开每个节点
            ListNode temp = head.next;
            head.next = null;
            head = temp;
        }
        p1.next = dummy2.next;
        return dummy1.next;
    }
}
92. 反转链表 II
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseBetween(ListNode head, int left, int right) {
        return iterator(head,left,right);
    }

    /**
     * 递归
     * 1,2,3,4,5 假如m=2,n=4,则对于递归我们不管其mn 的值,只要知道最后结果,
     * 则:ListNode last = recursion(head.next, m-1, n-1)
     *    head.next = last;
     * 这样递归主体就有了,剩下讨论终止条件,拆解问题规模,最小规模变成了m=1,因此终止条件变成了if (m==1)
     * m==1 此时链表实际变成了反转前n 个节点
     * @param head
     * @param left
     * @param right
     * @return
     */
    public ListNode recursion(ListNode head, int m, int n) {
        if (m == 1) {
            return recursionTopN(head, n);
        }
        ListNode last = recursion(head.next, m-1, n-1);
        head.next = last;
        return head;
    }

    /**
     * 递归前 n 个节点
     * 之前已经递归过反转链表,现在是指定前 n 个,需要记录下后驱节点,在最后将反转的部分的尾节点指向该结点
     * @param head
     * @param left
     * @param right
     * @return
     */
    //记录后去
    ListNode successor = null;
    public ListNode recursionTopN(ListNode head, int n) {
        if (n == 1) {
            // 记录第 n + 1 个节点
            successor = head.next;
            return head;
        }
        ListNode last = recursionTopN(head.next, n-1);
        head.next.next = head;
        // 让反转之后的 head 节点和后面的节点连起来
        head.next = successor;
        return last;
    }

    /**
     * 迭代
     * @param head
     * @param m
     * @param n
     * @return
     */
    public ListNode iterator(ListNode head, int m, int n) {
        if (head == null || m == n) {
            return head;
        }
        // 拆分成前中后三个部分,1,2,3,4,5,m2n4,则遍历[2,4]部分将每个元素头插进前部分之后
        // 需要记录三个节点,原始头,前部分的尾,斩开链接时的当前节点,当前节点的下一个节点
        // m 可能为 1,因此 head 可能会变化,使用虚拟节点
        // 以上思路肯定可行,写法很多,但面试时间不够,使用官方解法
        ListNode dummy = new ListNode(-1);
        //原始头
        dummy.next = head;
        //前部分的尾,这里不能给 null,必须要在 head 前一位,因为 head 是要反转的当前节点
        ListNode frontTail = dummy;
        //走完这里,就进入m-n 区间
        for (int i = 1; i < m; i++) {
            frontTail = head;
            head = head.next;
        }
        //倒转一段链表,head-12345
        //当前节点为 1,把 2 插入到head 后 1 前,继续走把 3 插到 head 后,就实现了链表反转
        for (int i = m; i < n; i++) {
            ListNode next = head.next;
            //把 1 指向 3
            head.next = next.next;
            //把 2 指向 head 后
            next.next = frontTail.next;
            frontTail.next = next;
        }
        return dummy.next;
    }

}
94. 二叉树的中序遍历
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {

    public List<Integer> inorderTraversal(TreeNode root) {
        //中序:左根右
        return deymicRescursion(root);
    }

    List<Integer> res = new ArrayList<>();
    /**
     * 遍历思维:一次遍历,借助外部变量
     * @param root
     */
    public void rescursion(TreeNode root) {
        if (root == null) {
            return;
        }
        rescursion(root.left);
        res.add(root.val);
        rescursion(root.right);
    }

    /**
     * 分解思维:一棵二叉树的前序遍历结果 = 根节点 + 左子树的前序遍历结果 + 右子树的前序遍历结果。
     * @param root
     */
    public List<Integer> deymicRescursion(TreeNode root) {
        if (root == null) {
            return new ArrayList<>();
        }
        List<Integer> result = new ArrayList<>();
        result.addAll(deymicRescursion(root.left));
        result.add(root.val);
        result.addAll(deymicRescursion(root.right));
        return result;
    }
}
100. 相同的树
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public boolean isSameTree(TreeNode p, TreeNode q) {
        return dymicRecursion2(p, q);
    }

    /**
     * 递归:问题拆解左子树相等 && 右子树相等,结束条件:任意一方为 null
     * @param p
     * @param q
     * @return
     */
    public boolean dymicRecursion(TreeNode p, TreeNode q) {
        if (p == null || q == null) {
            return p == null && q == null;
        }
        return p.val == q.val && dymicRecursion(p.left, q.left) && dymicRecursion(p.right, q.right);
    }

    /**
     * 递归第一个版本,比较繁琐:问题拆解左子树相等 && 右子树相等,结束条件:任意一方为 null
     * @param p
     * @param q
     * @return
     */
    public boolean dymicRecursion2(TreeNode p, TreeNode q) {
        if (p == null || q == null) {
            return p == null && q == null;
        }
        boolean isSame = p.val == q.val;
        if (!isSame) {
            return isSame;
        }
        boolean left = dymicRecursion(p.left, q.left);
        isSame = isSame && left;
        if (!isSame) {
            return isSame;
        }
        boolean right = dymicRecursion(p.right, q.right);
        return isSame && right;
   }
}
101. 对称二叉树
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {

    public boolean isSymmetric(TreeNode root) {
        if (root == null) {
            return true;
        }
        return isSymmetricByIteration(root);
    }

    /**
     * 动态规划,拆解思想
     * 看着说是对称的树,但是仅进行一棵树的递归遍历无法对比上
     * 因此应该把他看成两棵树,用来比较两棵树是否相同
     * 前序:根左右;中序:左根右;后续:左右根
     * 因为是对称,所以左右树传入顺序需要注意
     * @param left 左子树  right 右子树
     * @return
     */
    public boolean isSymmetricBetweenTree(TreeNode left, TreeNode right) {
        if (left == null || right == null) {
            return left == null && right == null;
        }
        //左子树的左节点与有字数的右节点比较    左子树的右节点与右子树的左节点比较
        return left.val == right.val
                && isSymmetricBetweenTree(left.left, right.right)
                && isSymmetricBetweenTree(left.right, right.left);
    }

    /**
     * 迭代思路很简单,层序遍历,需要使用两个队列,一个队列搞不定,和递归一样,分成两棵子树,比较两棵子树的值,注意顺序
     * 这里层序遍历不用 for 循环去从左到右打印顺序,只需要while 一直取值就好了
     * @param root
     * @return
     */
    public boolean isSymmetricByIteration(TreeNode root) {
        if (root == null) {
            return true;
        }
        Queue<TreeNode> queueLeft = new LinkedList<>();
        Queue<TreeNode> queueRight = new LinkedList<>();
        //添加两棵子树
        queueLeft.offer(root.left);
        queueRight.offer(root.right);
        //只用一层循环不断取值
        while (!queueLeft.isEmpty() && !queueRight.isEmpty()) {
            TreeNode left = queueLeft.poll();
            TreeNode right = queueRight.poll();
            //注意三个 if 条件,这样的安排更加合理优雅
            if (left == null && right == null) {
                continue;
            }
            if ((left == null && right != null) || (left != null && right == null)) {
                return false;
            }
            if (left.val != right.val) {
                return false;
            }
            //注意添加顺序
            queueLeft.offer(left.left);
            queueRight.offer(right.right);
            queueLeft.offer(left.right);
            queueRight.offer(right.left);
        }
        return true;
    }
}
102. 二叉树的层序遍历
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    /**
     * 层序遍历就一个,队列
     * @param root
     * @return
     */
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> res = new ArrayList<>();
        if (root == null) {
            return res;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        //while控制从上到下
        while (!queue.isEmpty()) {
            int qSize = queue.size();
            List<Integer> levels = new ArrayList<>();
            //for 控制从左到右
            for (int i = 0; i < qSize; i++) {
                TreeNode target = queue.poll();
                levels.add(target.val);
                //添加左节点,注意这里是 target不是 root
                if (target.left != null) {
                    queue.offer(target.left);
                }
                //添加右节点
                if (target.right != null) {
                    queue.offer(target.right);
                }
            }
            res.add(levels);
        }
        return res;
    }
}
104. 二叉树的最大深度
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public int maxDepth(TreeNode root) {
        return dymicRecursion(root);
    }

    int maxLen = 0;
    int depth = 0;
    /**
     * 回溯方式:遍历整个二叉树,借助外部变量
     * @param root
     */
    public void recursion(TreeNode root) {
        if (root == null) {
            return;
        }
        //解释depth加减含义:
        //比如这样一棵树:1234,123这两棵子树,不用考虑复杂情况,迭代会帮你处理复杂情况,只考虑最简单的就行
        //这两棵树下,前置遍历先进入1234,depth=4,然后离开左子树递归返回了,depth逐渐减到 0,节点返回到 1 又开始遍历右子树深度
        //所以 depth++是前置进入该结点的时候,depth--是后置离开该结点的时候
        depth++;
        recursion(root.left);
        recursion(root.right);
        maxLen = Math.max(maxLen, depth);
        depth--;
    }

    /**
     * 分解法,动态规划:当前节点最大深度,就是左子树或右子树最深的,递归条件是当到达叶子节点时,其节点深度为 0
     * @param root
     * @return
     */
    public int dymicRecursion(TreeNode root) {
        if (root == null) {
            return 0;
        }
        //左子树深度
        int left = dymicRecursion(root.left) + 1;
        //右子树深度
        int right = dymicRecursion(root.right) + 1;
        //当前节点左右子树中最深的
        return Math.max(left, right);
    }
}
105. 从前序与中序遍历序列构造二叉树
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        for (int i = 0; i < inorder.length; i++) {
            //存储节点值在中序中的下标
            valToIndex.put(inorder[i], i);
        }
        return buildTree(preorder,0, preorder.length - 1, inorder, 0, inorder.length - 1);
    }

    //优化:存储节点中下标,否则每次都需要遍历数组找 val 对应下标
    Map<Integer, Integer> valToIndex = new HashMap<>();

    /**
     * 递归,前序确定根节点,中序确定左右子树
     * 这个和最大树类似,前者是取最大节点;这个是根据前序取根节点
     * @return
     */
    public TreeNode buildTree(int[] preorder, int preStart, int preEnd,
                              int[] inorder, int inStart, int inEnd) {
        //根据前序找根节点
        if (preStart > preEnd) {
            return null;
        }
        //确定根节点
        int rootVal = preorder[preStart];
        TreeNode root = new TreeNode(rootVal);
        //根据根节点获取在中序的位置,就可以根据 inIndex 确定左右子树部分
        int inIndex = valToIndex.get(rootVal);
        //根据中序左右子树数量,根绝数量计算前序左右子树部分
        int leftSize = inIndex - inStart;

        root.left = buildTree(preorder, preStart + 1, preStart + leftSize, inorder, inStart, inIndex - 1);
        root.right = buildTree(preorder, preStart + leftSize + 1, preEnd, inorder, inIndex + 1, inEnd);

        return root;
    }
}
106. 从中序与后序遍历序列构造二叉树
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        for (int i = 0; i < inorder.length; i++) {
            valToIndex.put(inorder[i], i);
        }
        return buildTree(inorder, 0, inorder.length - 1, postorder, 0, postorder.length - 1);
    }

    //优化:存储中序节点对应下标,否则需要每次遍历中序找到对应下标
    Map<Integer, Integer> valToIndex = new HashMap<>();

    public TreeNode buildTree(int[] inoder, int inStart, int inEnd,
                              int[] postorder, int postStart, int postEnd) {
       //退出条件,这里使用后序确定的根节点,所以用它退出
        if (postStart > postEnd) {
            return null;
        }
        //构建节点:根据后序找到根节点
        int rootVal = postorder[postEnd];
        TreeNode root = new TreeNode(rootVal);

        //根据中序下标,切分出左右子树
        int inIndex = valToIndex.get(rootVal);
        int leftSize = inIndex - inStart;

        //这里需要注意 前序头节点在前面,前序是(preStart + 1, preStart + leftSize), (preStart + leftSize + 1, preEnd)
        //后续是(postStart, postStart + leftSize - 1),(postStart + leftSize, postEnd - 1),两个都需要排除当前根节点,但是根节点所在位置不同
        root.left = buildTree(inoder, inStart, inIndex - 1, postorder, postStart, postStart + leftSize - 1);
        root.right = buildTree(inoder, inIndex + 1, inEnd, postorder, postStart + leftSize, postEnd - 1);

        return root;
    }
}
107. 二叉树的层序遍历 II
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 * int val;
 * TreeNode left;
 * TreeNode right;
 * TreeNode() {}
 * TreeNode(int val) { this.val = val; }
 * TreeNode(int val, TreeNode left, TreeNode right) {
 * this.val = val;
 * this.left = left;
 * this.right = right;
 * }
 * }
 */
class Solution {
    /**
     * labuladong的还是用队列,只是把结果原来添加到队尾变成了添加到队头,很巧妙,我都没想到
     * 我的思路是用栈,细想是很麻烦,而且需要遍历两遍,脑子废了
     * @param root
     * @return
     */
    public List<List<Integer>> levelOrderBottom(TreeNode root) {
        LinkedList<List<Integer>> res = new LinkedList<>();
        if (root == null) {
            return res;
        }

        Queue<TreeNode> q = new LinkedList<>();
        q.offer(root);
        // while 循环控制从上向下一层层遍历
        while (!q.isEmpty()) {
            int sz = q.size();
            // 记录这一层的节点值
            List<Integer> level = new LinkedList<>();
            // for 循环控制每一层从左向右遍历
            for (int i = 0; i < sz; i++) {
                TreeNode cur = q.poll();
                level.add(cur.val);
                if (cur.left != null)
                    q.offer(cur.left);
                if (cur.right != null)
                    q.offer(cur.right);
            }
            // 把每一层添加到头部,就是自底向上的层序遍历。
            res.addFirst(level);
        }
        return res;
    }
}
111. 二叉树的最小深度
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public int minDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        return minDepthByIteration(root);
    }

    /**
     * labuladong 思路,使用层序遍历方法
     * @param root
     * @return
     */
    public int minDepthByIteration(TreeNode root) {
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        //root本身算一层,初始化为 1
        int depth = 1;
        while (!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size;i++) {
                TreeNode node = queue.poll();
                //左右子树都为null,说明到了最短那一层的叶子结点
                if (node.left == null && node.right == null) {
                    return depth;
                }
                if (node.left != null) {
                    queue.add(node.left);
                }
                if (node.right != null) {
                    queue.add(node.right);
                }
            }
            depth++;
        }
        return depth;
    }

    /**
     * 递归迭代树,借助外部变量,尝试用这种方法,失败。递归没法识别叶子结点,如果根左为null,则最小为 1,但实际这条线不算有效路径
     * depth.stream().min(Integer::compare).get();
     * @param root
     */
    List<Integer> depth = new ArrayList<>();
    public void traverse(TreeNode root, int level) {
        if (root.left == null) {
            depth.add(level);
            return;
        }
        traverse(root.left, level + 1);
        traverse(root.right, level + 1);
    }
}
112. 路径总和
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public boolean hasPathSum(TreeNode root, int targetSum) {
        return hasPathSumByIteration(root,targetSum);
    }

    /**
     * 动态规划法:递归,目的是求根节点到叶子节点的路径,注意是叶子节点,仅仅到中途是不算的
     * @param root
     * @param targetSum
     * @return
     */
    public boolean hasPathSumByRecursion(TreeNode root, int targetSum) {
        //避免叶子节点为空的情况,因为下面是用叶子节点判断的,这一步判断可以规避根节点为 null,并且可以避免在递归时root.left != null 的判定
        if (root == null) {
            return false;
        }
        //左右子节点都为 null,说明到达当前层的叶子节点
        if (root.left == null && root.right == null) {
            //到达当前层的时候,主要要减去 root.val,因为这里是在叶子节点判空,不是在叶子节点之后的进行判空的
            return targetSum - root.val == 0;
        }
        return hasPathSum(root.left, targetSum - root.val) || hasPathSum(root.right, targetSum - root.val);
    }

    /**
     * 迭代法,层序遍历,使用两个队列,一个装节点,一个装值,值和节点顺序对应起来比较
     * @param root
     * @param targetSum
     * @return
     */
    public boolean hasPathSumByIteration(TreeNode root, int targetSum) {
        if (root == null) {
            return false;
        }
        Queue<TreeNode> queueNode = new LinkedList<>();
        Queue<Integer> queueValue = new LinkedList<>();
        queueNode.offer(root);
        queueValue.offer(root.val);
        while (!queueNode.isEmpty()) {
            TreeNode node = queueNode.poll();
            Integer value = queueValue.poll();
            //左右节点都为空,到达叶子节点,比较之前累加的值和目标值是否相等
            //这里注意queueValue存的就是累加的值,在上一层就已经加过了,不用在加一次
            if (node.left == null && node.right == null && value == targetSum) {
                return true;
            }
            if (node.left != null) {
                queueNode.offer(node.left);
                queueValue.offer(node.left.val+value);
            }
            if (node.right != null) {
                queueNode.offer(node.right);
                queueValue.offer(node.right.val+value);
            }
        }
        return false;
    }
}
113. 路径总和 II
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
        return dfsByDynamic(root,targetSum,new ArrayList<>(),new ArrayList<>());
    }

    /**
     * 递归方式,动态规划,不借助外部变量
     * @param root
     * @param targetSum
     * @param ans
     * @param path
     * @return
     */
    public List<List<Integer>> dfsByDynamic(TreeNode root, int targetSum, List<List<Integer>> ans, List<Integer> path) {
        if (root == null) {
            return new ArrayList<>();
        }

        path.add(root.val);
        if (root.left == null && root.right == null && targetSum == root.val) {
            ans.add(path);
        }
        //这一步要把原来 path 的内容传到下一层级节点
        dfsByDynamic(root.left, targetSum - root.val, ans, new ArrayList<>(path));
        dfsByDynamic(root.right, targetSum - root.val, ans, new ArrayList<>(path));
        return ans;
    }

    List<List<Integer>> ans = new ArrayList<>();
    List<Integer> path = new ArrayList<>();
    /**
     * 深度优先搜索,递归,借助外部变量
     * 其实这个和 "路径总和I" 是一样的,但是自己没想出来
     */
    public void dfs(TreeNode root,int targetSum) {
        if (root == null) {
            return;
        }
        //这一步必须放在 if 上层,因为 if 需要存储最终结果
        path.add(root.val);
        //到达叶子节点,加入该结果(这里一定要注意,是targetSum == root,因为是到达的叶子节点,而不是叶子节点的下一层)
        if (root.left == null && root.right == null && targetSum == root.val) {
            ans.add(new ArrayList<>(path));
        }
        dfs(root.left, targetSum - root.val);
        dfs(root.right, targetSum - root.val);
        //为什么这里要移除? 实际上因为是深度遍历,所以肯定是前序遍历到底,到达叶子节点后返回到上一层,
        //如果该叶子节点满足加入该路径;如果不满足,就退回到上一层,到上一层的另一个叶子节点,所以退出时把前一个叶子节点删除
        //我之前就是没想到这个,只想到这么多路径怎么存,实际上 dfs 是一条一条遍历的
        path.remove(path.size() - 1);
    }
}
114. 二叉树展开为链表
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public void flatten(TreeNode root) {
        if (root == null) return;
        //递归
        flatten(root.left);
        flatten(root.right);

        //后序遍历,拉平左右子树
        TreeNode left = root.left;
        TreeNode right = root.right;

        //左子树置空
        root.left = null;
        //左子树赋给右子树
        root.right = left;

        //将原先的右子树拼接到当前右子树末端
        TreeNode p = root;
        while (p.right != null) {
            p = p.right;
        }
        p.right =  right;
    }
}
144. 二叉树的前序遍历
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    
    public List<Integer> preorderTraversal(TreeNode root) {
      //前序:根左右
        return deymicRecursion(root);
    }

    //遍历思维:借助外部变量
    List<Integer> res = new ArrayList<>();
    /**
     * 递归
     * @param root
     * @return
     */
    public void recursion(TreeNode root) {
        if (root == null) {
            return;
        }
        res.add(root.val);
        recursion(root.left);
        recursion(root.right);
    }

    /**
     * 分解问题:动态规划方式
     * @param root
     * @return
     */
    public List<Integer> deymicRecursion(TreeNode root) {
        if (root == null) {
            return new ArrayList<>();
        }
        List<Integer> result = new ArrayList<>();
        result.add(root.val);
        result.addAll(deymicRecursion(root.left));
        result.addAll(deymicRecursion(root.right));
        return result;
    }
}
141. 环形链表
/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    /**
     * 快慢指针:快指针走完一圈时,慢指针刚好走一半;快指针走完第二圈时,慢指针走完第一圈
     * 因此如果存在环,则快指针走完第二圈时刚好和慢指针相遇
     * 也可以用 set 遍历链表如果重复就是环了
     * @param head
     * @return
     */
    public boolean hasCycle(ListNode head) {
        ListNode fast = head, slow = head;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
            if (slow == fast) {
                return true;
            }
        }
        return false;
    }
}
142. 环形链表 II
/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */

/**
 * 公式推导:
 * 假设这是一个链表,带环的是一个循环结构;假设起点到第一个节点的距离为L,第一个节点到相遇的位置为X,整个环的大小为C
 * 那么slow一次走一步,fast一次走两步
 * 有slow和fast从起点到相遇的位置为
 * slow:L+X
 * fast:L+X+NC(因为不知道环的大小,所以不知道slow到相遇的位置时,fast转了几圈)
 * 有这样的一个公式
 * 2(L+X)=L+X+NC
 * 化简L=NC-X
 */
public class Solution {
    /**
     * 这个公式也就是:当快慢相遇时,此时slow指向 head,和 fast 一起走L步,则相遇点就是换起点
     * 也可以用 set 遍历链表如果重复就是环了
     * @param head
     * @return
     */
    public ListNode detectCycle(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        boolean res = false;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
            if (slow == fast) {
                res = true;
                break;
            }
        }
        if (!res) {
            return null;
        }
        //此时slow指向 head,和 fast 一起走L步
        slow = head;
        while (slow != fast) {
            slow = slow.next;
            fast = fast.next;
        }
        return slow;
    }
}
145. 二叉树的后序遍历
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        //后序:左右根
        return dymicRecursion(root);
    }

    List<Integer> res = new ArrayList<>();
    /**
     * 回溯思维:借助外部变量,将遍历到的每个节点加入结果集
     * @param root
     */
    public void recursion(TreeNode root) {
        if (root == null) {
            return;
        }
        recursion(root.left);
        recursion(root.right);
        res.add(root.val);
    }

    /**
     * 分解思维思维:后序结果集 = 左子树结果集 + 右子树结果集 + 根节点结果集
     * @param root
     */
    public List<Integer> dymicRecursion(TreeNode root) {
        if (root == null) {
            return new ArrayList<>();
        }
        List<Integer> result = new ArrayList<>();
        result.addAll(dymicRecursion(root.left));
        result.addAll(dymicRecursion(root.right));
        result.add(root.val);
        return result;
    }
}
160. 相交链表
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    /**
     * 三种方法:
     * 1、先计算两条链表长度,让两条链表到达尾部距离相同,也就是从相同长度走
     * 2、利用 set,把一条链表节点加入 set,再遍历另一条链表对比
     * 3、写法最简单,当一条链表走到尾时,接入另一条链表的头;另一条链表也这样遍历,则两条链表长度相同,并且能够到达相交点
     * @param headA
     * @param headB
     * @return
     */
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        return getIntersectionNodeByFixed(headA,headB);
    }

    public ListNode getIntersectionNodeByFixed(ListNode headA, ListNode headB) {
        ListNode p1 = headA, p2 = headB;
        while (p1 != p2) {
            p1 = p1 == null ? headB : p1.next;
            p2 = p2 == null ? headA : p2.next;
        }
        return p1;
    }

    /**
     * 借助 hashSet,遍历其中一个链表
     * @param headA
     * @param headB
     * @return
     */
    public ListNode getIntersectionNodeBySet(ListNode headA, ListNode headB) {
        Set<ListNode> set = new HashSet<>();
        while (headA != null) {
            set.add(headA);
            headA = headA.next;
        }
        while (headB != null) {
            if (set.contains(headB)) {
                return headB;
            }
            headB = headB.next;
        }
        return null;
    }
}
167. 两数之和 II - 输入有序数组
class Solution {
    /**
     * 升降序就考虑双指针了,就这还中等题
     * @param numbers
     * @param target
     * @return
     */
    public int[] twoSum(int[] numbers, int target) {
        int left = 0, right = numbers.length - 1;
        while (left < right) {
            int val = numbers[right] + numbers[left];
            if (val == target) {
                return new int[]{left + 1, right + 1};
            } else if (val > target) {
                right = right - 1;
            } else if (val < target) {
                left = left + 1;
            }
        }
        return new int[]{-1, -1};
    }
}
206. 反转链表
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        return iterator(head);
    }

    /**
     * 递归
     * @param head
     * @return
     */
    private ListNode recursion(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        //返回的是最后一个节点
        ListNode last = recursion(head.next);
        // 1-->2-->3-->4   ==>  1.next.next => 2.next = 1
        head.next.next = head;
        head.next = null;
        return last;
    }

    /**
     * 迭代
     * @param head
     * @return
     */
    private ListNode iterator(ListNode head) {
        ListNode newHead = null;
        //涉及到斩断连接需要
        while (head != null) {
            //保存原来链表
            ListNode temp = head.next;
            //斩断当前节点链接,指向新链表
            head.next = newHead;
            //更新新链表头结点
            newHead = head;
            //迭代旧链表
            head = temp;
        }
        return newHead;
    }
}
226. 翻转二叉树
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public TreeNode invertTree(TreeNode root) {
        return invertTreeByDynamic(root);
    }

    /**
     * 遍历思路:迭代
     * @param root
     */
    public void invertTreeByRecursion(TreeNode root) {
        if (root == null) {
            return;
        }
        TreeNode temp = root.left;
        root.left = root.right;
        root.right = temp;

        //这里不用担心原左右子树变了,子节点怎么办。从图里来看,其实子树的左右节点交换后恰好是满足条件的
        invertTreeByRecursion(root.left);
        invertTreeByRecursion(root.right);
    }

    /**
     * 分解问题思路,动态规划
     * @param root
     * @return
     */
    public TreeNode invertTreeByDynamic(TreeNode root) {
        if (root == null) {
            return null;
        }
        TreeNode left = invertTreeByDynamic(root.left);
        TreeNode right = invertTreeByDynamic(root.right);
        root.left = right;
        root.right = left;
        return root;
    }
}
234. 回文链表
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public boolean isPalindrome(ListNode head) {
        left = head;
        return traverse(head);
    }

    /**
     * 使用栈,首先计算数量,判断奇偶,再遍历遍历进行比较
     * @param head
     * @return
     */
    public boolean isPalindromeByStack(ListNode head) {
        Stack<ListNode> stack = new Stack<ListNode>();
        ListNode p = head;
        while (p != null) {
            stack.add(p);
            p = p.next;
        }
        //无论奇偶都只用比较其中一半
        int compare = stack.size()/2;
        for (int i = 0; i < compare; i++) {
            ListNode last = stack.pop();
            if (head.val != last.val) {
                return false;
            }
            head = head.next;
        }
        return true;
    }

    // 左侧指针
    ListNode left;

    /**
     * 递归,因为二叉树是链表的一种形式,所以链表也可以前后序遍历,所以如果在后序打印节点值,实际上就是链表的倒序,有了倒序和正序比较就可以了
     * 这种方式实际也是借助栈,但是是借助递归的栈
     * @param right
     * @return
     */
    boolean traverse(ListNode right) {
        if (right == null) return true;
        boolean res = traverse(right.next);
        // 后序遍历代码
        res = res && (right.val == left.val);
        left = left.next;
        return res;
    }
}
257. 二叉树的所有路径
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<String> binaryTreePaths(TreeNode root) {
        List<List<Integer>> ans = getTreePath(root, new ArrayList<>(), new ArrayList<>());
        return ans.stream().map(v -> v.stream().map(String::valueOf).collect(Collectors.joining("->"))).collect(Collectors.toList());
    }

    public List<List<Integer>> getTreePath(TreeNode root, List<Integer> path, List<List<Integer>> ans) {
        if (root == null) {
            return new ArrayList<>();
        }
        path.add(root.val);

        if (root.left == null && root.right == null) {
            ans.add(new ArrayList<>(path));
        }

        getTreePath(root.left, new ArrayList<>(path), ans);
        getTreePath(root.right, new ArrayList<>(path), ans);
        path.remove(path.size() - 1);

        return ans;
    }
  
  //代码更简洁
  public List<String> getTreePath(TreeNode root, String path, List<String> ans) {
        if (root == null) {
            return new ArrayList<>();
        }
        path = path + root.val;

        if (root.left == null && root.right == null) {
            ans.add(path);
        }

        getTreePath(root.left, path + "->", ans);
        getTreePath(root.right, path + "->", ans);
    
        return ans;
    }
}
283. 移动零
class Solution {
    /**
     * 因为要保持相对顺序,不能使用左右指针交换,使用快慢指针
     * 快指针找非0的,覆盖到慢指针,最后快指针结束后,把慢指针后的值赋0
     * @param nums
     */
    public void moveZeroes(int[] nums) {
        int slow = 0, fast = 0;
        while (fast < nums.length) {
            //覆盖 0 值,把非 0 的覆盖到 0
            if (nums[fast] != 0) {
                nums[slow++] = nums[fast];
            }
            fast++;
        }
        //后面的全部赋0
        while (slow < nums.length) {
            nums[slow++] = 0;
        }
    }
}
303. 区域和检索 - 数组不可变
class NumArray {
    int[] preSum;

    /**
     * 该场景实际是一道生产中的应用题,以空间换时间,即数组不可变下,快速计算和
     * 因为数组,就采用前缀和。新生成一个数组,该数组存储每个节点存储原数组的累加值
     * @param nums
     */
    public NumArray(int[] nums) {
        this.preSum = new int[nums.length + 1];
        //从 1 开始遍历
        for (int i = 1; i < preSum.length; i++) {
            //prevalue 0
            //nums 123   prevalue[1] = 0+1
            preSum[i] = preSum[i - 1] + nums[i - 1];
        }
    }
    
    public int sumRange(int left, int right) {
        return preSum[right + 1] - preSum[left];
    }
}
344. 反转字符串
class Solution {
    /**
     * 一想就是双指针了
     * @param s
     */
    public void reverseString(char[] s) {
        int left = 0, right = s.length - 1;
        while (left < right) {
            char temp = s[right];
            s[right] = s[left];
            s[left] = temp;

            left++;
            right--;
        }
    }
}
438. 找到字符串中所有字母异位词
class Solution {
    //滑动窗口
    public List<Integer> findAnagrams(String s, String p) {
        Map<Character, Integer> subMap = new HashMap<>();
        for (int i = 0; i < p.length(); i++) {
            char ch = p.charAt(i);
            subMap.put(ch, subMap.getOrDefault(ch, 0) + 1);
        }
        //定义左右窗口
        int right = 0, left = 0;
        //定义窗口
        Map<Character, Integer> map = new HashMap<>();
        //记录 s 中满足的 p 字符数量
        int vaild = 0;
        //记录起始索引
        List<Integer> res = new ArrayList<>();

        while (right < s.length()) {
            char rc = s.charAt(right);
            right++;
            map.put(rc, map.getOrDefault(rc, 0) + 1);
            //字符数量相同
            if (map.get(rc).equals(subMap.get(rc))) {
                vaild++;
            }
            //左窗口什么时候更新?要求 p 的异位子串,两个要求:len 相同 && 字符数量一致
            //在右窗口更新时已满足字符数量一致,此时 len 如果不相同更新左窗口,找到 len 相同时刻,则说明找到了一个子串
            while (right - left >= p.length()) {
                if (right - left == p.length() && vaild == subMap.size()) {
                    res.add(left);
                }
                char lc = s.charAt(left);
                left++;
                if (map.get(lc).equals(subMap.get(lc))) {
                    vaild--;
                }
                //这儿的数据更新一定要放在 vaild 更新之后
                map.put(lc, map.getOrDefault(lc, 0) - 1);
            }
        }
        return res;
    }
}

//思路不是很清晰,左窗口更新条件判定条件错误,导致代码冗余,有参考意义
class Solution {
    //滑动窗口
    public List<Integer> findAnagrams(String s, String p) {
        Map<Character, Integer> subMap = new HashMap<>();
        for (int i = 0; i < p.length(); i++) {
            char ch = p.charAt(i);
            subMap.put(ch, subMap.getOrDefault(ch, 0) + 1);
        }
        //定义左右窗口
        int right = 0, left = 0;
        //定义窗口
        Map<Character, Integer> map = new HashMap<>();
        //记录 s 中满足的 p 字符数量
        int vaild = 0;
        //记录起始索引
        List<Integer> res = new ArrayList<>();

        while (right < s.length()) {
            char rc = s.charAt(right);
            right++;
            map.put(rc, map.getOrDefault(rc, 0) + 1);
            //字符数量相同
            if (map.get(rc).equals(subMap.get(rc))) {
                vaild++;
            }
            //左窗口什么时候更新?右窗口不断搜索,当窗口中数量满足 p 中字符的时候,说明此时窗口中可能有这样的一个子串了
            //此时左窗口在subMap.size==vaild的时候,更新缩小范围,这个条件标注说明此时窗口中可能有满足的子串,否则退出继续在右窗口中找
            while (vaild == subMap.size()) {
                //什么时候才能判定找到了符合条件的子串:直到 map 和 subMap 的 size 相同 && vaild 不变的时候(简化为 subMap.size==vaild)
                //这里无法做判断,可能出现虽然 size 相等,但是map--a4,subMap--a1,虽然 size 和 vaild 相等,但是不是异位词,除非加上 vaule 数量的比较
                if (map.size() == subMap.size() && compareTwoMap(subMap, map)) {
                    res.add(left);
                }
                char lc = s.charAt(left);
                left++;
                //数据更新,移动左窗口需要更新 vaild,才能退出
                //必须在相等的时候更新 vaild,因为 map 中的字符数量可能大于 subMap:map--c4,sub--c--2,这样即使 map 减少一个vaild 其实也还满足
                if (map.get(lc).equals(subMap.get(lc))) {
                    vaild--;
                }
                map.put(lc, map.getOrDefault(lc, 0) - 1);
                if (map.get(lc) < 1) {
                    map.remove(lc);
                }
            }
        }
        return res;
    }

    private boolean compareTwoMap(Map<Character, Integer> map1, Map<Character, Integer> map2) {
        for (Map.Entry<Character, Integer> entry : map1.entrySet()) {
            Character key = entry.getKey();
            if (!entry.getValue().equals(map2.get(key))) {
                return false;
            }
        }
        return true;
    }
}

509. 斐波那契数
class Solution {
    /**
     * 从斐波那契数列看动态规划
     *
     * @param n
     * @return
     */
    public int fib(int n) {
        return fibWithDp(n);
    }

    /**
     * 单纯的递归解法,自顶向下
     * @param n
     * @return
     */
    public int fibOnlyRecursion(int n) {
        if (n == 0) {
            return 0;
        }
        if (n == 1) {
            return 1;
        }
        return fib(n - 1) + fib(n - 2);
    }

    /**
     * 带 dp 的解法,自底向上
     * @param n
     * @return
     */
    public int fibWithDp(int n) {
        if (n == 0 || n == 1) {
            return n;
        }
        int dp_0 = 0, dp_1 = 1;
        for (int i = 2; i <= n; i++) {
            int dp = dp_0 + dp_1;
            dp_0 = dp_1;
            dp_1 = dp;
        }
        return dp_1;
    }
}
567. 字符串的排列
class Solution {
    //滑动窗口,438异位子串的降阶版
    public boolean checkInclusion(String s1, String s2) {
        Map<Character, Integer> subMap = new HashMap<>();
        for (int i = 0; i < s1.length(); i++) {
            char ch = s1.charAt(i);
            subMap.put(ch, subMap.getOrDefault(ch, 0)+1);
        }
        int right = 0, left = 0;
        Map<Character, Integer> map = new HashMap<>();
        //记录满足 s2 的字符数
        int vaild = 0;
        while (right < s2.length()) {
            char rc = s2.charAt(right);
            right++;
            map.put(rc, map.getOrDefault(rc, 0)+1);
            if (map.get(rc).equals(subMap.get(rc))) {
                vaild++;
            }
            //什么时候更新左窗口?包含排列即:s2 中子串和 s1 len 相等 && 字符必须相同
            //右窗口中已经满足 vaild 相等,因此左窗口在 len 大于等于 s1 时进行更新,一直更新到窗口中不满足 s1 条件的位置为止
            //这里必须要用>=,等于时也还可能满足s1 情况,必须更新到不满足s1情况为止
            while (right - left >= s1.length()) {
                if (right - left == s1.length() && vaild == subMap.size()) {
                    return true;
                }
                char lc = s2.charAt(left);
                left++;
                if (map.get(lc).equals(subMap.get(lc))) {
                    vaild--;
                }
                map.put(lc, map.getOrDefault(lc, 0)-1);
            }
        }
        return false;
    }
}
654. 最大二叉树
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public TreeNode constructMaximumBinaryTree(int[] nums) {
        return buildTree(nums, 0, nums.length - 1);
    }
    
    public TreeNode buildTree(int[] nums, int start, int end) {
//        nums 为空的时候,上层节点的左右子树达到叶子节点,直接返回 null,则上层 root.left = null
//        if (nums == null || nums.length == 0) {
//            return null;
//        }
        //不能用 nums 判断,这里用的不是截断,使用 start 和 end 控制范围,所以用它进行判断
        //这里必须是大于,==的话说明还有一个值
        if (start > end) {
            return null;
        }
        //找最大值和其下标
        int maxVal = Integer.MIN_VALUE;
        int index = Integer.MAX_VALUE;
        //查找 start 到 end 间的最大值
        for (int i = start; i <= end; i++) {
            if (nums[i] > maxVal) {
                maxVal = nums[i];
                index = i;
            }
        }
        TreeNode root = new TreeNode(maxVal);
        //构建左子树
        root.left = buildTree(nums, start, index - 1);
        root.right = buildTree(nums, index + 1, end);
        return root;
    }
}
876. 链表的中间结点
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    /**
     * 快慢指针,快指针每次走两步,慢指针每次走一步,特点:当快指针到达链表尾时,慢指针刚好在链表中间
     *也可以用最朴实的办法,计算数量求中心点
     * @param head
     * @return
     */
    public ListNode middleNode(ListNode head) {
        ListNode slow = head;
        ListNode fast = head.next;

        while (fast != null) {
            slow = slow.next;
            fast = fast.next;
            if (fast != null) {
                fast = fast.next;
            }
        }
        return slow;
    }
  //更优雅
   public ListNode middleNodeMoreGentle(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;

        //这里做了两个判断,否则可能有空的问题
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }
}
100000. 冒泡排序
 /**
     * 思想:每遍历一轮把最大的交换到最后
     * @param arr
     * @param comp
     */
    public static void bubbleSort (Integer[] arr, Comparator<Integer> comp) {
        //注意边界值,以及 j<len-i-1
        for (int i = 0; i < arr.length - 1; i++) {
            for (int j = 0; j < arr.length - i - 1; j++) {
                if (comp.compare(arr[j], arr[j+1]) > 0) {
                    Integer temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            }
        }
    }
100001. 插入排序
/**
     * 思想:认为前面是有序的,从第 2 个数开始,把它往前面有序的序列里插
     * @param arr
     * @param comp
     */
    public static void insertSort(Integer[] arr, Comparator<Integer> comp) {
        for (int i = 1; i < arr.length; i++) {
            //保存待插入数据
            Integer inserting = arr[i];
            int j = i - 1;
            //找到第一个比 待插入数据小 的数据
            while (j >= 0 && comp.compare(inserting, arr[j]) >= 0) {
                //将比 待插入数据大 的数据向后移
                arr[j+1] = arr[j];
                j--;
            }
            //插入第一个小的数据之后,注意这里是 j+1
            arr[j+1] = inserting;
        }
    }
100002. 选择排序
/**
     * 每一趟找出最小的下标,交换下和第一个交换下标
     * @param arr
     */
    public static void selectSort(Integer[] arr) {
        for (int i = 0; i < arr.length; i++) {
            int minIndex = i;
            // 把当前遍历的数和后面所有的数进行比较,并记录下最小的数的下标
            for (int j = i + 1; j < arr.length; j++) {
                if (arr[j] < arr[minIndex]) {
                    // 记录最小的数的下标
                    minIndex = j;
                }
            }
            // 如果最小的数和当前遍历的下标不一致,则交换
            if (i != minIndex) {
                int temp = arr[i];
                arr[i] = arr[minIndex];
                arr[minIndex] = temp;
            }
        }
    }
100003. 希尔排序
/**
     * 1. 希尔排序把序列按下标的一定增量(步长)分组,对每组分别使用插入排序。
     * 2. 随着增量(步长)减少,一直到一,算法结束,整个序列变为有序。
     * @param arr
     */
    public static void shellSort(Integer[] arr) {

        for (int gap = arr.length / 2; gap > 0; gap /= 2) {

            for (int i = gap; i < arr.length; i++) {

                for (int j = i - gap; j >= 0; j -= gap) {
                    // 如果当前元素大于加上步长后的那个元素,说明交换
                    if (arr[j] > arr[j + gap]) {
                        int temp = arr[j];
                        arr[j] = arr[j + gap];
                        arr[j + gap] = temp;
                    }
                }
            }
        }
    }
100004. 快速排序
   public static void quickSort(Integer[] arr) {
     quickSort(arr, 0, arr.length-1);
   }
   
   /**
     * 思想:选定一个基准值,分别从头尾遍历比大小,放在基准值两边,并重复这个过程
     * 1.先把待排序的数组拆成左右两个区间,左边都比中间的基准数小,右边都比基准数大。
     * 2.递归,接着左右两边各自再做同样的操作,完成后再拆分再继续,一直到各区间只有一个数为止。
     * @param arr
     * @param start
     * @param end
     */
    public static void quickSort(Integer[] arr, int start, int end) {
        //一定要注意这个判断
        if (start >= end) {
            return;
        }
        //基准值
        int standard = arr[start];
        int low = start, high = end;
        //找一次
        while (low < high) {
            //从后向前找到比基准值小的数据,注意是<=
            while (low < high && standard <= arr[high]) {
                high--;
            }
            //将比基准值小的放在基准值之前
            arr[low] = arr[high];
            //从前向后找到比基准值大的数据,注意是<=
            while (low < high && standard >= arr[low]) {
                low++;
            }
            //将比基准值大的数据放在基准值之后
            arr[high] = arr[low];
        }
        //当high==low一次遍历结束,将基准值放在中间,则基准值左边是小于它的,右边是大于它的
        arr[low] = standard;
        //排基准值右边的数
        quickSort(arr, start, low - 1);
        //排基准值左边的数
        quickSort(arr, high + 1, end);
    }
100005. 归并排序
    public static void mergeSort(int[] arr) {
      mergeSort(arr,0,arr.length-1);
    }

   /**
     * 思想:把两个有序的数组分别遍历按序放到同一个数组
     * @param arr 待排序的数组
     * @param left 起始坐标
     * @param right 终点坐标
     */
    public static void mergeSort(int[] arr ,int left ,int right) {
        //如果起始坐标不小于终点坐标,那么代表已经拆分为一个元素一个序列的程度了,故直接返回
        if ( left >= right ) {
            return;
        }
        //声明一个变量记录中间位置的坐标,即(起始坐标 + 终点坐标)/ 2 ;
        int middle = (left + right) >> 1;
        //拆分序列,拆分方式:递归本方法两次,一次传入左边区间的序列,
        //即起始坐标到中间位置的坐标范围,一次传入右边区间的序列,即中间位置坐标 + 1 到终点坐标的范围;
        mergeSort(arr, left , middle);
        mergeSort(arr, middle+1,right);

        sort(arr,left,middle,right);
    }

    /**
     * @param arr 待排序数组
     * @param left 起始坐标
     * @param middle 中间位置坐标
     * @param right 终点坐标
     */
    public static void sort(int[] arr, int left, int middle, int right){
        //建立一个辅助数组,长度为待排序元素的长度
        int[] temp = new int[right-left+1];
        //存储起始坐标,即左边序列的起始坐标;
        int i = left;
        //存储中间位置坐标 + 1 ,即右边序列的起始坐标;
        int j = middle + 1;
        //表示辅助数组的起始坐标;
        int t = 0;

        //将左边区间的元素依次和右边区间元素进行对比;
        //对比出来将较小的元素优先放入辅助数组直到某一个序列坐标超出;
        while ( i <= middle && j <= right ){
            temp[t++] = arr[i] < arr[j] ? arr[i++] : arr[j++];
        }

        //再对两个序列进行判断,如果未超过区间的最大坐标表示未遍历完,
        //则将未参与遍历的元素依次放入辅助数组;( 值得注意的是,
        //每一个进入判断的两个序列都是相对有序的,因为递归的性质是由下至上的
        //,也就是说两个区间要么是单个元素,要么就是一个已经排序过的区间 )
        while ( i <= middle ) {
            temp[t++] = arr[i++];
        }
        while ( j <= right ) {
            temp[t++] = arr[j++];
        }
        //最后将辅助数组拷贝到待排序数组的相应位置即可
        System.arraycopy(temp, 0, arr, left, temp.length);
    }
posted @ 2023-07-19 14:38  Aidan_Chen  阅读(11)  评论(0编辑  收藏  举报