0
点赞
收藏
分享

微信扫一扫

LeetCodeHOT100学习题解

孟佳 2022-05-02 阅读 55

2. 两数相加

解题思路:模拟,利用一个变量记录当前节点相加是否产生进位,利用一个虚拟节点作为头节点,循环判断该节点的节点值是否大于10,若大于10进行修改,否则记录下来,只有当两链表全部遍历结束,结束记录,最后判断最后一位是否产生进位,若产生进位,再生成一个节点值为1的节点,否则直接返回。

 public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        int carry = 0;//用于进位
        int sum = 0;//用于记录当前节点和
        ListNode res = new ListNode(-1);
        ListNode  cur  = res;
        while (l1 != null || l2 != null){
            int val1 = l1 == null ? 0 :l1.val;
            int val2 = l2 == null ? 0 :l2.val;
            sum = val1 + val2 + carry;
            carry = sum >= 10 ? 1 : 0;
            sum = sum  >= 10 ? sum - 10 : sum;
            ListNode node = new ListNode(sum);
            cur.next = node;

            l1 = l1 == null ? null :l1.next;
            l2 = l2 == null ? null :l2.next;
            cur = cur.next;
        }
        if(carry == 1){
            cur.next = new ListNode(carry);
        }
        return res.next;
    }

3. 无重复字符的最长子串

解题思路:哈希表,利用一个哈希表更新当前字符的位置,每当哈希表已存在该字符(说明当前位置到前一个起始位置已经出现了重复字符),更新一个起始位置,通过比较当前位置到起始位置的大小,得到最大无重复字符的最长子串。

public int lengthOfLongestSubstring(String s) {
    if(s.length()==0){
            return 0;
        }
      Map<Character,Integer> map = new HashMap<>();
      int res = 1;
      int start = -1;
        for (int i = 0; i < s.length(); i++) {
            if(map.containsKey(s.charAt(i))){
                //更新起始位置
                start = Math.max(start,map.get(s.charAt(i)));

            }
            map.put(s.charAt(i),i);
            //每次更新返回大小
            res = Math.max(res,i - start);
        }
        return res;
}

4. 寻找两个正序数组的中位数

解题思路:模拟,判断总数组长度为奇数还是偶数 ->计算中位数位置->分别比较nums1 和nums2大小,用num循环记录,记录的过程中,避免数组越界,当到达中位数位置时,更新返回值即可

当总数组长度为偶数时,分别记录pos和pos+1位置的值,返回这两个数之和的平均值

   int len1 = nums1.length;
        int len2 = nums2.length;
        boolean flag = (len1 + len2) % 2 == 1 ?true :false;
        int pos = (len1 + len2 -1) /2;
        int index = 0;
        int index1 =0;
        int index2 =0;
        double res = 0.0;
        int num = 0;
        while (index < len1 + len2){
            if(index1 >= len1){
                num = nums2[index2];
                index2++;
            }else if(index2 >= len2){
                num = nums1[index1];
                index1++;
            }else {
                if(nums1[index1] > nums2[index2]){
                    num = nums2[index2];
                    index2++;
                }else {
                    num = nums1[index1];
                    index1++;
                }
            }

            if(index == pos){
                if(flag){
                    res += num;
                    return (double) res ;
                }else {
                    res += num;
                }
            }else if(index == pos + 1){
                res += num;
                res /=2;
                break;
            }
            index ++;
        }
        return res;

5. 最长回文子串

解题思路:动态规划 dp i j 表示字符串从i到j是否为回文串

if(right - left <= 2) dp(i)(j)]= true;

else dp(i)(j) = dp(i+1)(j-1);

注意:循环的时候外循环时right,内循环left,这样可以保证判断外部是否为回文串的时候,内部已经判断好结果了

 public String longestPalindrome(String s) {
int start = 0;
        int maxLen = 1;
        //表示从第i到第j个为回文字串是否为回文串
        boolean dp[][] = new boolean[s.length()][s.length()];
        for (int right = 1; right < s.length(); right++) {
            for (int left = 0; left < right; left++) {
                    if(s.charAt(left) != s.charAt(right)){
                        dp[left][right] = false;
                    }else {
                        if(left == right){
                            dp[left][right] = true;
                        }else if(right - left <= 2){
                            dp[left][right] = true;
                        }else {
                            dp[left][right] = dp[left + 1][right - 1];
                        }

                        if(dp[left][right] && right-left+1>maxLen){
                            maxLen = right - left + 1;
                            start = left;
                        }
                    }



            }

        }

        return s.substring(start,start+maxLen);
    }

6. Z 字形变换

解题思路:矩阵模拟的方法,通过边界改变数组注入的方向,如果是循环可以通过取余操作

注意:Sring在java中为对象,建立数组对象时,需要初始化对象,否则会报空指针异常

 public String convert(String s, int numRows) {
          if(numRows == 1){
            return s;
        }
        String[] sb = new String[numRows];
        for (int i = 0; i < numRows; i++) {
            sb[i] = "";
        }
        int index = 0;
        boolean flag =false;
        for (int i = 0; i < s.length(); i++) {
            if(index == 0){
                flag = false;
            }else if(index == numRows -1){
                flag = true;
            }
            char charAt = s.charAt(i);
            if(!flag){
              sb[index++]+= charAt;
            }else {
                sb[index--]+= charAt;
            }
        }
        StringBuilder res = new StringBuilder();
        for(String str : sb){
            res.append(str);
        }
        return res.toString();
    }

7. 整数反转

解题思路:模拟,由于本题假设不能存储64位数,则通过

if(res > Integer.MAX_VALUE /10 || res < Integer.MIN_VALUE/10)

语句提前返回

       int res = 0;

        while(x != 0){
              if(res > Integer.MAX_VALUE /10 || res < Integer.MIN_VALUE/10){
                return 0;
            }
            int temp = x % 10;
            res = res * 10 + temp;
            x =x /10;
        }  
        return res ;

10. 正则表达式匹配

解题思路:动态规划 dp(s.length + 1)(p.length + 1) ,表明前i个s与前j个p字符是否匹配

初始化 : dp(0)(0) = true 无字符一定匹配

dp(0)(j) = dp(0)(j-2)且 p(i -1) = ‘*’: 首行 s 为空字符串,因此当 p 的偶数位为 * 时才能够匹配(即让 p 的奇数位出现 0 次,保持 p 是空字符串)。因此,循环遍历字符串 p ,步长为 2(即只看偶数位)。

状态转移:

当p(i -1) = ‘*’ 时, dp[i][j] 在当以下任一情况为 truetrue 时等于 true :

  • dp[i][j - 2]: 即将字符组合 p[j - 2] * 看作出现 0 次时,能否匹配;

  • dp(i -1)(j)且 s[i - 1] = p[j - 2]: 即让字符 p[j - 2] 多出现 1 次时,能否匹配;

  • dp(i-1)(j)且 p[j - 2] = ‘.’ : 即让字符 ‘.’ 多出现 1 次时,能否匹配;

    当 p(i -1)!= ‘*’ 时, dp[i][j] 在当以下任一情况为 truetrue 时等于 true:

  • dp(i-1)(j-1)且s[i - 1] = p[j - 1]: 即让字符 p[j - 1] 多出现一次时,能否匹配;

  • dp(i-1)(j-1)且 p[j - 1] = ‘.’: 即将字符 . 看作字符 s[i - 1] 时,能否匹配;

public boolean isMatch(String s, String p) {
        boolean[][] dp = new boolean[s.length() + 1][p.length() + 1];
        dp[0][0] = true;
        for (int i = 2; i <= p.length(); i+=2) {
            if(p.charAt(i-1)=='*' && dp[0][i-2] ){
                dp[0][i] = true;
            }
        }
        for (int i = 1; i <= s.length(); i++) {
            for (int j = 1; j <= p.length(); j++) {
                char charAt = p.charAt(j - 1);
                if(charAt == '*'){
                    if(dp[i][j-2]) {
                        dp[i][j] = true;
                    }else if(dp[i - 1][j] && s.charAt(i-1) == p.charAt(j-2)){
                        dp[i][j] = true;
                    }else if(dp[i - 1][j] && p.charAt(j-2) == '.'){
                        dp[i][j] = true;
                    }
                }else {
                    if(dp[i-1][j-1] && p.charAt(j-1) == s.charAt(i-1) ){
                        dp[i][j] = true;
                    }else if(dp[i-1][j-1]&&p.charAt(j-1) == '.' ){
                        dp[i][j] = true;
                    }
                }
            }
        }
        return dp[s.length()][p.length()];
    }

11. 盛最多水的容器

解题思路:采用双指针+贪心,

贪心规则:若当前left > right ,left+1;若right > left right-1;形成局部最优

  public int maxArea(int[] height) {
          int left = 0;
        int right = height.length -1;
        int res = 0;
        while (right > left){
            int area = Math.min(height[left],height[right])*(right - left);
            res = Math.max(res,area);
          if(height[left] > height[right]){
                right--; 
            }else {
                left++;
            }
        }
        return res;
    }

15. 三数之和

解题思路:排序+双指针

public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);
        List<List<Integer>>res = new ArrayList<>();
        for (int i = 0; i < nums.length; i++) {
            //当前数已经大于0,不可能存在和为0的三个数,直接退出
            if(nums[i] > 0){
                break;
            }
            //去重
            if(i > 0 && nums[i] == nums[i - 1]){
                continue;
            }
            int left = i + 1;
            int right =nums.length -1;
            while (right > left){
                int sum = nums[i] + nums[left]+nums[right];
                if(sum == 0){
                    res.add(Arrays.asList(nums[i], nums[left], nums[right]));
                      //去重
                    while (left < right &&nums[right] == nums[right-1]){
                        right--;
                    }
                      //去重
                    while (left < right &&nums[left] == nums[left+1]){
                        left++;
                    }
                    left ++;
                    right --;
                }else if(sum > 0){
                    right--;
                }else {
                    left++;
                }
            }
        }
        return res;
    }

16. 最接近的三数之和

解题思路:双指针

public int threeSumClosest(int[] nums, int target) {
       int res = Integer.MIN_VALUE;
        int div = Integer.MAX_VALUE;;
        Arrays.sort(nums);
         for (int i = 0; i < nums.length; i++) {
            if(i > 0 && nums[i] == nums[i-1]){
                continue;
            }
            int left = i + 1;
            int right = nums.length - 1;
            while (right > left){
                int sum = nums[left] + nums[right] + nums[i];
                if(sum == target){
                    return sum;
                }else {
                    if(sum > target){
                        while (left <right && nums[right] == nums[right-1]){
                            right--;
                        }
                        right--;
                        if(div > Math.abs(sum -target)){
                            div =  Math.abs(sum -target);
                            res = sum;
                        }
                    }else {
                        while (left < right && nums[left] == nums[left+1]){
                            left ++;
                        }
                        left++;
                        if(div > Math.abs(sum -target)){
                            div =  Math.abs(sum -target);
                            res = sum;
                        }
                    }
                }
            }
        }
        return  res;
    }

17. 电话号码的字母组合

解题思路:构建电话号码hash + 回溯

public List<String> letterCombinations(String digits) {

        Map<Integer,String> map = new HashMap(){{
            put(2,"abc");
            put(3,"def");
            put(4,"ghi");
            put(5,"jkl");
            put(6,"mno");
            put(7,"pqrs");
            put(8,"tuv");
            put(9,"wxyz");
        }};
        List<String> res = new ArrayList<>();
        if( digits.length() == 0){
            return new ArrayList<>();
        }
        backTracking(digits,map,res,new StringBuilder(),0);
        return res;

    }

    private void backTracking(String digits, Map<Integer, String> map, List<String> res, StringBuilder stringBuilder, int start){
        if(stringBuilder.length() ==digits.length()){
            res.add(stringBuilder.toString());
        }
        for (int i = start; i < digits.length() ; i++) {
            String s = map.get(Integer.parseInt(digits.charAt(i)+""));
            for (int j = 0; j < s.length(); j++) {
                char charAt = s.charAt(j);
                stringBuilder.append(charAt);
                backTracking(digits,map,res,stringBuilder,i+1);
                stringBuilder.delete(stringBuilder.length()-1,stringBuilder.length());
            }
        }

    }

19. 删除链表的倒数第 N 个结点

解题思路:双指针(快慢指针) 快指针记录到达第k个节点,慢指针到达倒数第K个节点,并记录倒数第k个节点的前节点,用于删除节点

    public ListNode removeNthFromEnd(ListNode head, int n) {
           if(head == null){
            return head;
        }
      
        ListNode res = new ListNode(-1);
        ListNode cur = res;
        res.next = head;
        while (n-- > 0){
            head = head.next;
        }

        while (head != null){
            cur = cur.next;
            head = head.next;
        }
        cur.next = cur.next.next;
        return res.next;
    }

20. 有效的括号

解题思路:堆的使用,利用堆的特性,当遇到(,{,】时,)} 】入堆,当遇到)} 】判断堆顶是否匹配,不匹配则返回false,如果匹配则弹出堆顶,

 public boolean isValid(String s) {
            if(s.length() % 2 == 1){
            return false;
        }
        Stack<Character> stack = new Stack<>();
        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);
            if(ch == '('){
                stack.add(')');
            }else if(ch == '{'){
                stack.push('}');
            }else if(ch == '[') {
                stack.push(']');
            }else if(stack.isEmpty() || ch != stack.peek()){
                return false;
            }
            else {
               
                    stack.pop();
                }
        }
        return stack.isEmpty();
    }

21. 合并两个有序链表

解题思路:直接遍历链表,判断两链表相对大小,较小的节点接入当前链表的next指针,若到达其中一个链表结尾,直接将当前节点的next指针指向令一链表节点.

 public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        ListNode cur = new ListNode(-1);
        ListNode res = cur;
        while (list1 != null && list2!=null){
            if(list1.val > list2.val){
                cur.next = list2;
                list2 = list2.next;
            }else {
                cur.next = list1;
                list1 = list1.next;
            }
            cur = cur.next;
        }
        cur.next = list1 == null? list2:list1;

          return res.next;
    }

22. 括号生成

解题思路:DFS + 回溯

用left和right分别记录左括号和右括号的个数,为保证有序括号生成,必须保证左括号left大于右括号数right时,才能添加右括号,回溯时,删除字符串最后一个字符

 public List<String> generateParenthesis(int n) {
     List<String> list = new ArrayList<>();
        backTracking(n,list,0,0,new StringBuilder());
        return list;
    }

    private void backTracking(int n, List<String> list, int left, int right, StringBuilder s) {
        if(s.length() == n * 2){
            list.add(s.toString());
            return;
        }
       
        if(left < n){
            s.append("(");
            backTracking(n,list,left + 1,right,s);
           s.delete(s.length()-1,s.length());
        }
         if(right < n && right < left){
            s.append(")");
            backTracking(n,list,left,right + 1 ,s);
          s.delete(s.length()-1,s.length());
        }

    }

23. 合并K个升序链表

解题思路:依次合并两个升序链表,即可完成堆k个链表的合并

public ListNode mergeKLists(ListNode[] lists) {
        if(lists.length == 0){
            return null;
        }
        ListNode cur = null;
        for (int i = 1; i < lists.length; i++) {
           cur =  merge(cur,lists[i]);
        }
        return cur;
    }
    private ListNode merge(ListNode l1, ListNode l2) {
        ListNode res = new ListNode(-1);
        ListNode cur = res;
        while (l1!= null && l2 != null){
            if(l1.val > l2.val){
                cur.next = l2;
                l2 = l2.next;
            }else {
                cur.next = l1;
                l1 = l1.next;
            }
            cur = cur.next;
        }
        cur.next = l1 == null ? l2 : l1;
        return res.next;
    }

解题思路2:采用优先队列形成小堆顶,通过定义一个类,用于优先队列排序,依次将lists的头节点放入队列中,每把当前节点放入队列中时,需要判断当前节点是否存在下一个节点,若存在,再将下一个节点放入队列。

注意:不能一次性将所有节点放入队列,那样会造成val值相同的节点会重构成同一个对象,导致出现错误

  class CompareListNode implements Comparable<CompareListNode>{
    int val;
    ListNode node;
  

    public CompareListNode(int val, ListNode node) {
        this.val = val;
        this.node = node;
    }

    @Override
    public int compareTo(CompareListNode o) {
        return this.val - o.val;
    }
}

 PriorityQueue<CompareListNode> queue = new PriorityQueue<>();
    public ListNode mergeKLists(ListNode[] lists) {
        if (lists.length == 0) {
            return null;
        }
       
        for (int i = 0; i < lists.length; i++) {
               ListNode node = lists[i];
            if(node!=null){
                queue.add(new CompareListNode(node.val, node));
            }
        }

        ListNode cur = new ListNode(-1);
        ListNode res = cur;
        while (!queue.isEmpty()){
            CompareListNode poll = queue.poll();
            cur.next = poll.node;
            cur = cur.next;
            if (poll.node.next != null) {
                queue.offer(new CompareListNode(poll.node.next.val, poll.node.next));
            }
            
        }
        return res.next;

31. 下一个排列

题目分析:我们需要将一个左边的「较小数」与一个右边的「较大数」交换,以能够让当前排列变大,从而得到下一个排列。同时我们要让这个「较小数」尽量靠右而「较大数」尽可能小

解题思路:

  • 找到第1组nums[i] > nums [i-1]的位置index(从后向前遍历,找到后一个比前一个数大的情况)
  • 如果找到第1组nums[i] > nums [i-1]的位置,
  • 找到index ~ nums.length之间最小值,使得最小值大于nums[index - 1],交换nums[i - 1]和最小值,后对index到nums.length排序
  • 没有找到,则排序,如 3 2 1;
 public void nextPermutation(int[] nums) {
        int index = -1;
        //找到第1组nums[i] > nums [i-1]的位置
        for (int i = nums.length - 1; i > 0 ; i--) {
            if(nums[i] > nums[i - 1]){
                index = i;
                break;
            }
        }

        if(index != -1){
        //如果找到第1组nums[i] > nums [i-1]的位置
        //如1 3 2  i = 1 下一组排列是 2 1 3
        //找到index ~ nums.length之间最小值,使得最小值大于nums[index - 1],交换nums[i - 1]和最小值
            int minIndex  = index;
            for (int i = index; i < nums.length; i++) {
                if(nums[i] > nums[index -1] && nums[i] <nums[minIndex]){
                    minIndex = i;
                }
            }
            //交换minIndex和i-1之间的值
            int temp = nums[minIndex];
            nums[minIndex] = nums[index -1];
            nums[index-1] = temp;
            Arrays.sort(nums,index,nums.length);
        }else {
            //没有找到 如3 2 1
            //数组逆转
           Arrays.sort(nums);
        }
    }

32. 最长有效括号

解题思路:暴力

由于最长有效括号一定为偶数,那么找到小于等于s.length的最大整数,依次找到以该位长度的子串,进行判断,若判断成功,则直接返回,否则继续判断。O(n^3)

public  int longestValidParentheses(String s) {
        int len = s.length();
        if(len % 2 == 1){
            len = len -1;
        }
        String sub = "";
        for (int i = len; i > 0 ; i = i - 2) {
            for (int j = 0; j < s.length(); j++) {
                if(i + j <= s.length()){
                    sub = s.substring(j,i + j);
                }
               if(isVaild(sub)){
                    return sub.length();
                }
            }
           
        }
        return 0;
    }
    private  boolean isVaild(String sub) {
        Stack<Character> stack = new Stack<>();
        for (int i = 0; i < sub.length(); i++) {
            char charAt = sub.charAt(i);
            if(charAt == '('){
                stack.add(')');
            }else if(!stack.isEmpty()&&charAt == stack.peek()){
                stack.pop();
            }else {
                return false;
            }
        }
        return stack.isEmpty();

    }

解题思路2:动态规划

dp()()以i为结尾的最长子串个数,由于有效子串是以)结尾,只有当前字符为)才有可能是组成有效子串

当前字符为)时,判断下标为i - dp[i - 1] - 1是否为(,且i - dp[i - 1] > 0时:

状态转移:当前有效子串 = 2 + 前一个有效子串 + 外部有效子串

其中需要判断,外部有效字串的下标是否溢出。

eg:没有溢出 ()(());溢出((()));

 public int longestValidParentheses(String s) {
         int[] dp = new int[s.length()]; //以i为结尾的最长子串个数
        int res = 0;

        for (int i = 1; i < s.length(); i++) {
            char ch = s.charAt(i);
            if(ch == ')'){
                if(i - dp[i - 1] > 0 && s.charAt(i - dp[i - 1] - 1) == '('){
             dp[i] = 2 + dp[i - 1] + (i - dp[i-1] -2 > -1? dp[i - dp[i - 1] - 2]:0);
                }
                res = Math.max(res,dp[i]);
            }
        }
        return res;
    }

解题思路:栈,用于存储下标,

  • 若当前字符为(,下标入栈,
  • 否则弹出栈顶元素,此时判断栈是否为空,
    • 若为空,则说明此时的字符分割了左右字符子串,并把当前下标入栈
    • 否则,说明配对成功,配对有效子串。
  public int longestValidParentheses(String s) {
        int len = 0;
        int max_len = 0;
        Stack<Integer> stack = new Stack<>();
        stack.add(-1);
        for (int i = 0; i < s.length(); i++) {
            char charAt = s.charAt(i);
            if(charAt == '('){
                stack.push(i);
            }else {
                stack.pop();
                if(stack.isEmpty()){
                    stack.push(i);
                }else {
                    len = i - stack.peek();
                    max_len = Math.max(len,max_len);
                }
            }
        }
        return max_len;
}

33. 搜索旋转排序数组

解题思路:二分法 + 分类讨论

根据当前子数组的最左侧和最右侧数值大小关系,判断出当前子数组是否为升序数组,

  • 若为升序数组,则直接进行普通二分法
  • 否则,需要重新瓜分数组,通过target在不是升序数组中的位置,继续瓜分数组
    • 若mid > left,则left -mid 都是升序 //判断哪一侧为升序
      • 如果target > nums[mid] 说明 target在右侧 left = mid +1;
      • 否则,target在左侧
        • 如果left > target,tareget在右侧
        • 否则在左侧
    • 否则mid - right 都是升序
      • 如果 target>mid
        • 如果 target >right right = mid -1;
        • 否则left = mid +1
      • 否则 right= mid -1
 public int search(int[] nums, int target) {
        int left = 0;
        int right = nums.length -1;
        while (right >= left){
            if(nums[left] == target) return left;
            if(nums[right] == target) return right;
            int mid  = (right + left) >> 1;
            if (nums[mid] == target){
                return mid;
            }
            boolean flag = nums[left] < nums[right];
            if(flag){
                //左右为正常递增序列
                //正常二分法
                if(target > nums[mid]){
                    left = mid + 1;
                }else {
                    right = mid - 1;
                }
            }else {
                //分区间讨论
                boolean flag1 = nums[mid] > nums[left];
                if(flag1) {
                    //mid 在第一个递增区间
                 if(target > nums[mid]){
                     left = mid + 1;
                 }else {
                     if(nums[left] > target){
                         //target 在第二的递增区间
                         left = mid +1;
                     }else {
                         //target 在第二的递增区间
                         right = mid -1;
                     }
                 }
                }else {
                    if(target > nums[mid]){
                        if(target > nums[right]){
                            //target 在第一个递增区间
                            right = mid -1;
                        }else {
                            //target 在第二个递增区间
                            left = mid +1;
                        }
                    }else {
                        right = mid - 1;
                    }
                }

            }
        }
        return -1;
    }

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

解题思路 : 二分法

通过二分法,找到target,然后通过向左向右遍历 依次得到左右终点 返回即可

  public int[] searchRange(int[] nums, int target) {
            int[] res = new int[2];
        Arrays.fill(res,-1);
        int left = 0;
        int right = nums.length-1;

        while (right >= left){
            int mid = (right + left) >> 1;
            if(nums[mid] == target){
                for (int i = mid; i <= right; i++) {
                    if(nums[i] == target){
                        res[1] = i;
                    }
                }

                for (int j = mid; j >=0 ; j--) {
                    if(nums[j] == target){
                        res[0] = j;
                }
                }
                break;
            }else if(nums[mid] > target){
                right = mid -1;
            }else {
                left = mid+1;
            }
        }
    return res;
    }

39. 组合总和

解题思路:回溯

注意考虑到一个数字可以多次使用,每次返回的时候,start点保持不变

 List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {

        backTracking(candidates,target,0,new ArrayList<Integer>());
        return res;
    }

    private void backTracking(int[] candidates, int sum, int start, ArrayList<Integer> list) {
        if(sum == 0){
            res.add(new ArrayList<>(list));
            return;
        }
        if(sum < 0){ 
            return ;
        }
        for (int i = start; i < candidates.length; i++) {
            sum -= candidates[i];
            list.add(candidates[i]);
            backTracking(candidates,sum,i,list);
            list.remove(list.size()-1);
             sum += candidates[i];
        }

    }

42. 接雨水

解题思路:单调栈 单调递增栈

   public int trap(int[] height) {
        int res = 0;
        Deque<Integer> deque = new LinkedList<>();
        for (int i = 0; i < height.length; i++) {
            while (!deque.isEmpty() &&height[deque.peekLast()] < height[i]){
                int top = height[deque.pollLast()] ;
                if(deque.isEmpty()){ //如果为空,则说明没有左墙壁,不能装水
                    break;
                }
                int left = deque.peekLast();
                int high = Math.min(height[left],height[i]) - top;
                int wid = i - left - 1;
                res += wid * high;
            }
            deque.addLast(i);
        }
        return res;
    
    }

解题思路:的双指针


46. 全排列

解题思路 :回溯 无重复数字

注意:每个元素只能使用一次,则可以利用used数组来判断当前该数是否已经访问

 public List<List<Integer>> permute(int[] nums) {

        if(nums.length == 0){
            return new ArrayList<>();
        }
        List<List<Integer>> res = new ArrayList<>();
        boolean[] used = new boolean[nums.length];
        backTracking(nums,0,new ArrayList<Integer>(),res,used);
        return res;
    }

    private void backTracking(int[] nums, int start, ArrayList<Integer> list, List<List<Integer>> res,  boolean[] used) {
        if(list.size() == nums.length){
            res.add(new ArrayList<>(list));
            return ;
        }
		 if(list.size() >nums.length ){ //剪枝
            return;
        }
        for (int i = start; i <nums.length ; i++) {
            if(used[i] == false){
                list.add(nums[i]);
                used[i] = true;
                backTracking(nums,start,list,res,used);
                list.remove(list.size()-1);
                used[i] = false;
            }else {
                continue;
            }

        }
    }

47. 全排列 II

题解:回溯 存在重复数字,可以通过提前排序,使相同数字在相邻,则在回溯的时候方便判断

public List<List<Integer>> permuteUnique(int[] nums) {
        if(nums.length == 0){
            return new ArrayList<>();
        }
        Arrays.sort(nums);
        boolean[] used = new boolean[nums.length];
        List<List<Integer>> res = new ArrayList<>();
        backTracking(nums,new ArrayList<Integer>(),res,used);
        return res;


    }

    private void backTracking(int[] nums, ArrayList<Integer> list, List<List<Integer>> res,  boolean[] used) {
        if(list.size() == nums.length){
            res.add(new ArrayList<>(list));
            return;
        }

        for (int i = 0 ; i < nums.length; i++) {
            if(i>0 && nums[i] == nums[i - 1]&&used[i-1]==false){
                continue;
            }
            if(used[i] == false){
                used[i] = true;
                list.add(nums[i]);
                backTracking(nums,list,res,used);
                list.remove(list.size()-1);
                used[i]=false;
            }
        }

    }

48. 旋转图像

解题思路 : 先水平翻转 -> 主对角线反转 On^2 所有方法都是On^2

public void rotate(int[][] matrix) {

         int len = matrix.length >> 1;
    //水平反转 最后一行提升至第一行 最后第二行提升至第二行 依次类推
        for (int i = 0; i < len; i++) {
            for (int j = 0; j < matrix.length; j++) {
                int temp = matrix[i][j];
                matrix[i][j] = matrix[matrix.length - i-1][j];
                matrix[matrix.length - i-1][j] = temp;
            }
        }
    //主对角线反转
        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < i; j++) {
               
                int temp = matrix[i][j];
                matrix[i][j] = matrix[j][i];
                matrix[j][i] = temp;
            }
        }

    }

49. 字母异位词分组

解题分析:当两个单词为异位词时,字母排序相同,用排序值作为map的Key,当前字母作位值,即可记录异位词

解题思路:字符串排序+哈希表

注意:当map中没有list时,需要重新建立一个list,

map.values()该函数返回map的value;

public List<List<String>> groupAnagrams(String[] strs) {
         if(strs.length == 0){
            return new ArrayList<>();
        }
        Map<String,List<String>> map = new HashMap<>();
        for (int i = 0; i < strs.length; i++) {
            char[] chars = strs[i].toCharArray();
            Arrays.sort(chars);
            String str = new String(chars);
            List<String> list = map.getOrDefault(str, new ArrayList<>());
            list.add(strs[i]);
            map.put(str,list);
        }
        return new ArrayList(map.values());
    }

解题思路2:可以记录当前单词字母的个数,然后通过记录当前字母的个数,恢复字符串,作为哈希表的key,放入map中

 public List<List<String>> groupAnagrams(String[] strs) {
         if(strs.length == 0){
            return new ArrayList<>();
        }
        Map<String,List<String>> map = new HashMap<>();
        for (int i = 0; i < strs.length; i++) {
            char[] chars = strs[i].toCharArray();
            Arrays.sort(chars);
            String str = new String(chars);
            List<String> list = map.getOrDefault(str, new ArrayList<>());
            list.add(strs[i]);
            map.put(str,list);
        }
        return new ArrayList(map.values());
    }

50. Pow(x, n)

解题思路:将指数转换为2进制数表示,如果当前为1,

eg: 2^10 = 2^2 * 2^8 10的二进制1010

 public double myPow(double x, int n) {
        long N = n;
        return N >= 0 ? quickMul(x, N) : 1.0 / quickMul(x, -N);
    }

    public double quickMul(double x, long N) {
        double ans = 1.0;
        // 贡献的初始值为 x
        double x_contribute = x;
        // 在对 N 进行二进制拆分的同时计算答案
        while (N > 0) {
            if (N % 2 == 1) {
                // 如果 N 二进制表示的最低位为 1,那么需要计入贡献
                ans *= x_contribute;
            }
            // 将贡献不断地平方
            x_contribute *= x_contribute;
            // 舍弃 N 二进制表示的最低位,这样我们每次只要判断最低位即可
            N /= 2;
        }
        return ans;
    
    }

55. 跳跃游戏

解题思路:动态规划+贪心

计算当前位置的最大跳跃步数,作为循环的阈值,只要当前跳跃步数>数组长度,则可以到达终点

    public boolean canJump(int[] nums) {

     if(nums.length == 1)return true;
        int maxStep = 0;
        for (int i = 0; i <= maxStep;i++ ) {
           maxStep = Math.max(maxStep,i+nums[i]);
           if(maxStep>= nums.length-1){
               return true;
           }
               
        }
        return false;
    }

56. 合并区间

解题思路:根据左区间大小对数组进行排序,然后依次遍历数组判断当前左区间是否大于右区间

  • 若大于,则得到一个独立区间,并更新当前的左右区间
  • 否则,更新右区间

注意,最后一个区间需要单独加入

 public int[][] merge(int[][] intervals) {
  Arrays.sort(intervals,(o1,o2)->{
            if(o1[0]!=o2[0]){
                return o1[0]-o2[0];
            }else {
                return o1[1]-o2[1];
            }
        });
        List<int[]> res = new ArrayList<>();
        int preLeft = intervals[0][0];
        int preRight = intervals[0][1];
        for (int i = 1; i < intervals.length; i++) {
            if(intervals[i][0] > preRight){
                res.add(new int[]{preLeft,preRight});
                preRight = intervals[i][1];
                preLeft = intervals[i][0];
            }else {
                preRight = Math.max(preRight,intervals[i][1]);
            }
        }
        res.add(new int[]{preLeft,preRight});
        return res.toArray(new int[res.size()][]);
    }

62. 不同路径

解题思路:动态规划 dp[][]记录到达当前位置的的走法总数‘

初始化 :当i = 0 j= 0 时,dp = 1

转移方程 : dp i j = dp i-1 j + dp i j-2;

 public int uniquePaths(int m, int n) {
            int n = obstacleGrid.length, m = obstacleGrid[0].length;
        int[][] dp = new int[n][m];
	
        for (int i = 0; i < m; i++) {
	    if (obstacleGrid[0][i] == 1) break; //一旦遇到障碍,后续都到不了
	    dp[0][i] = 1;
        }
        for (int i = 0; i < n; i++) {
	    if (obstacleGrid[i][0] == 1) break; 一旦遇到障碍,后续都到不了
	    dp[i][0] = 1;
        }
        for (int i = 1; i < n; i++) {
            for (int j = 1; j < m; j++) {
                if (obstacleGrid[i][j] == 1) continue;
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
            }
        }
        return dp[n - 1][m - 1];
    }

64. 最小路径和

解题思路 :动态规划 dp[][]表示到达当前位置的最小路径和

初始化 :当i = 0 时,dp i j = dp i-1j +grid i j ; j= 0dp i j =dp i j-1+grid i j;

状态转移 dp i j = Math.min(dp i-1 j, dp i j-1) ;

public int minPathSum(int[][] grid) {
        int[][] dp = new int[grid.length][grid[0].length];
        for (int i = 0; i < grid.length; i++) {
            for (int j = 0; j < grid[0].length; j++) {
                  if(i == 0 && j == 0){
                    dp[i][j] = grid[0][0];
                }else if(i == 0 ){
                    dp[i][j] = grid[i][j] + dp[i][j-1];
                }else if(j == 0 ){
                    dp[i][j] = grid[i][j] + dp[i-1][j];
                }else {
                    dp[i][j] = grid[i][j] + Math.min(dp[i - 1][j],dp[i][j - 1]);
                }
            }
        }
        return dp[grid.length - 1][grid[0].length -1];

    }

72. 编辑距离

解题思路 :动态规划 dp i j 表示 当前位置单词转换的最少步骤

状态转移: if(ch1 == ch2) dp[i][j] = dp[i-1][j-1];
else dp[i][j] = Math.min(dp[i-1][j],Math.min(dp[i-1][j-1],dp[i][j-1]))+ 1;

horse
012345
r112233
s222323
e333343
 public int minDistance(String word1, String word2) {
        int[][] dp = new int[word1.length() + 1][word2.length() + 1];
            for (int i = 0; i < word2.length() + 1; i++) {
                dp[0][i] = i;
            }
            for (int i = 0; i < word1.length() + 1; i++) {
                dp[i][0] = i;
            }
            for (int i = 1; i <word1.length() + 1 ; i++) {
                char ch1 = word1.charAt(i - 1);
                for (int j = 1; j < word2.length() + 1 ; j++) {
                    char ch2 = word2.charAt(j - 1);
                    if(ch1 == ch2){
                       dp[i][j] = dp[i-1][j-1]; 
                    }else {
                        dp[i][j] = Math.min(dp[i-1][j],Math.min(dp[i-1][j-1],dp[i][j-1]))+ 1;
                    }
                }
            }
        return dp[word1.length()][word2.length()];
    }

583. 两个字符串的删除操作

解题思路:动态规划 dp i j表示使前i个word1和前j个word2相同的最少删除步数

sea
0123
e1212
a2321
t3432

状态转移:

if(ch1 == ch2){dp i j = dp i -1j -1

else dp i j = Math.min(dp i-1 j ,dp i j-1)+1

public int minDistance(String word1, String word2) {
        int[][] dp = new int[word1.length() + 1][word2.length() +1];
         for (int i = 0; i < word2.length() + 1; i++) {
            dp[0][i] = i;
        }
        for (int i = 0; i < word1.length() + 1; i++) {
            dp[i][0] = i;
        }
        for (int i = 1; i < word1.length() + 1; i++) {
            char ch1 = word1.charAt(i -1);
            for (int j = 1; j < word2.length() + 1; j++) {
                char ch2 = word2.charAt(j - 1);
                if(ch1 == ch2){
                    dp[i][j] = dp[i -1][j -1];
                }else {
                    dp[i][j] = Math.min(dp[i-1][j],dp[i][j-1]) + 1;
                }
            }
        }
        return dp[word1.length()][word2.length()];
}

75. 颜色分类

分析:由于只有三个数,直接使用排序(归并和快速可以完成题目,但是浪费了数据的规律)

解题思路:双指针,题中只存在三个数,那么可以只使用两个个指针分别指向当前 0 ,1的位置,遇到零,与当前index0指向位置交换数据,遇到1与当前index1位置交换数据,为保证数字1在数字0后面,当index1 > index0时,要将index1的位置向后移。

 public void sortColors(int[] nums) {
        int index0 = 0;
        int index1 = 0;
        for (int i = 0; i < nums.length; i++) {
            if(nums[i] == 1){
                int temp = nums[i];
                nums[i] = nums[index1];
                nums[index1] = temp;
                index1++;
            }else if(nums[i] == 0){
                int temp = nums[i];
                nums[i] = nums[index0];
                nums[index0] = temp;
                if(index0 < index1){
                     temp = nums[i];
                    nums[i] = nums[index1];
                    nums[index1] = temp;
                }
                index0++;
                index1++;
            }
        }
    
    }

76. 最小覆盖子串

解题思路:哈希表+滑动窗口

利用哈希表记录 字符个数,用于判断当前子串是否满足要求

  • 若包含所有字符,左移滑动窗口,更新最小子串,直至不满足要求
  • 否则,右移滑动串口
 public static String minWindow(String s, String t) {
        if(t.length() > s.length()){
            return "";
        }
        Map<Character,Integer> sMap = new HashMap<>();
        Map<Character,Integer> tMap = new HashMap<>();
        for (int i = 0; i < t.length(); i++) {
            tMap.put(t.charAt(i), tMap.getOrDefault(t.charAt(i),0)+1);
        }
        int left = 0;
        String res = new String();
        for (int right = 0; right < s.length(); right++) {
            char charAt = s.charAt(right);
            sMap.put(charAt, sMap.getOrDefault(charAt,0) + 1);
            while (solve(sMap,tMap)){
                String temp = s.substring(left,right + 1);
                if(res.length() == 0){
                    res = temp;
                }else {
                    if(res.length() > temp.length()){
                        res = temp;
                    }
                }
                char ch = s.charAt(left);
                sMap.put(ch, sMap.getOrDefault(ch,0) - 1);
                left ++;
            }
        }
        return res;
    }

    private static boolean solve(Map<Character, Integer> sMap, Map<Character, Integer> tMap) {
        for(Character ch : tMap.keySet()){
            if(!sMap.containsKey(ch)||sMap.get(ch) < tMap.get(ch)){
                return false;
            }
        }
        return true;
    }

78. 子集

解题思路: 回溯

注意:由于空数组也是子集,终止条件不要加入return

List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> subsets(int[] nums) {
       backTracking(nums,0,new ArrayList<Integer>());
       return res;
    }

    private void backTracking(int[] nums, int start, ArrayList<Integer> list) {
        if(list.size() <= nums.length){
            res.add(new ArrayList<>(list));
        }
        for (int i = start; i < nums.length; i++) {
            list.add(nums[i]);
            backTracking(nums,i+1,list);
            list.remove(list.size() -1);
        }
        return;
    }
 public boolean exist(char[][] board, String word) {
        boolean[][] visited = new boolean[board.length][board[0].length];
        for (int i = 0; i < board.length; i++) {
            for (int j = 0; j < board[0].length; j++) {
                if(backTracking(i,j,0,visited,word,board)){
                    return true;
                }
            }
        }
        return  false;
    }

    private boolean backTracking(int i, int j, int index, boolean[][] visited, String word, char[][] board) {
       if(index >= word.length()){
           return true;
       }

       if(i < 0 || j < 0 ||i >= visited.length|| j >= visited[0].length || word.charAt(index)!=board[i][j]
               ||visited[i][j]){
           return false;
       }


        visited[i][j] = true;
        boolean res = backTracking(i + 1,j,index + 1,visited,word, board)||
               backTracking(i ,j+1,index + 1,visited,word, board)||
               backTracking(i - 1,j,index + 1,visited,word, board)||
               backTracking(i ,j -1,index + 1,visited,word, board);

        visited[i][j] = false;
        return  res;
    }

79. 单词搜索

解题思路:回溯 依次以数字为起点,利用回溯依次遍历;

 public boolean exist(char[][] board, String word) {
        boolean[][] visited = new boolean[board.length][board[0].length];
        for (int i = 0; i < board.length; i++) {
            for (int j = 0; j < board[0].length; j++) {
                if(backTracking(i,j,0,visited,word,board)){
                    return true;
                }
            }
        }
        return  false;
    }

    private boolean backTracking(int i, int j, int index, boolean[][] visited, String word, char[][] board) {
       if(index >= word.length()){
           return true;
       }

       if(i < 0 || j < 0 ||i >= visited.length|| j >= visited[0].length || word.charAt(index)!=board[i][j]
               ||visited[i][j]){
           return false;
       }


        visited[i][j] = true;
        boolean res = backTracking(i + 1,j,index + 1,visited,word, board)||
               backTracking(i ,j+1,index + 1,visited,word, board)||
               backTracking(i - 1,j,index + 1,visited,word, board)||
               backTracking(i ,j -1,index + 1,visited,word, board);

        visited[i][j] = false;
        return  res;
    }

84. 柱状图中最大的矩形

解题思路:双指针(超时):假设当前的高度为最高的高度(有点贪心的意思),以当前位置为中心,向左,向右依次遍历,直至左右的位置比当前位置低,则退出遍历,更新面积。

 public int largestRectangleArea(int[] heights) {
              int res = 0;
        for (int i = 0; i < heights.length; i++) {
            int l = i ;
            int r = i ;
            for (int j = i-1; j >= 0 ; j--) {
                if(heights[j] < heights[i]){
                    break;
                }
                l--;
            }
            for (int j = i + 1; j < heights.length; j++) {
                if(heights[j]<heights[i]){
                    break;
                }
                r++;
            }
            res = Math.max(res,heights[i] * (r - l + 1));
            
        }
        return res;
    }

解题思路:单调栈(维护一个单调递减栈)

0 1 2 入栈,当遇到heights[i] >heights[ stack.peak()],右边界为i,高度为 stack.pop(),左边界为stack.peak(),然后,更新面积,直到heights[i] <= heights[ stack.peak()],

index01234567
height02156230
area020610836
 public int largestRectangleArea(int[] heights) {
        Stack<Integer> st = new Stack();
        int [] newHeights = new int[heights.length + 2];
        newHeights[0] = 0;
        newHeights[newHeights.length - 1] = 0;
        for (int index = 0; index < heights.length; index++){
            newHeights[index + 1] = heights[index];
        }
        int res = 0;
        st.add(0);
        for (int i = 1; i < newHeights.length; i++) {
            if(newHeights[i] > newHeights[st.peek()]){
                st.push(i);
            }else if(newHeights[i] == newHeights[st.peek()]){
                st.pop();
                st.push(i);
            }else {
                while (!st.isEmpty() && newHeights[st.peek()] > newHeights[i]){
                    int mid = st.peek();
                    st.pop();
                    int left = st.peek();
                    int right = i;
                    int w = right - left - 1;
                    int h = newHeights[mid];
                    res = Math.max(res,w * h);
                }
                st.push(i);
            }
        }
        return res;
    }

85. 最大矩形

题意分析:初看该题,可能为一道新题,但仔细一想,则是题84从一维空间发展到二维空间,只要计算每一行的柱状高度,然后带入柱状图中最大矩形的求法,即可完成本题求解

解题思路:双指针

  public int maximalRectangle(char[][] matrix) {
        if (matrix.length == 0) {
            return 0;
        }
        int res = 0;
        int[] height = new int[matrix[0].length];
        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < matrix[i].length; j++) {
                if (matrix[i][j] == '0') {
                    height[j] = 0;
                } else {
                    height[j] += 1;
                }
            }
            res = Math.max(res,largestRectangleArea(height));
        }
        return res;
    }


    private int largestRectangleArea(int[] heights) {
        int res = 0;
        for (int i = 0; i < heights.length; i++) {
            int l  = i ;
            int r = i ;
            for (int j = i -1; j >= 0 ; j--) {
                if(heights[j] < heights[i]){
                    break;
                }
                l--;
            }
            for (int j = i + 1; j < heights.length; j++) {
                if(heights[j]<heights[i]){
                    break;
                }
                r++;
            }
            res = Math.max(res,heights[i] * (r - l + 1));

        }
        return res;
    
    }

解题思路:单调栈

   public int maximalRectangle(char[][] matrix) {
        if (matrix.length == 0) {
            return 0;
        }
        int res = 0;
        int[] height = new int[matrix[0].length];
        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < matrix[i].length; j++) {
                if (matrix[i][j] == '0') {
                    height[j] = 0;
                } else {
                    height[j] += 1;
                }
            }
            res = Math.max(res,largestRectangleArea(height));
        }
        return res;
    }


    private  int largestRectangleArea(int[] heights) {
        Stack<Integer> st = new Stack<Integer>();
        int[] newHeights = new int[heights.length + 2];
        newHeights[0] = 0;
        newHeights[newHeights.length - 1] = 0;
        for (int index = 0; index < heights.length; index++) {
            newHeights[index + 1] = heights[index];
        }
        int res = 0;
        st.add(0);
        for (int i = 1; i < newHeights.length; i++) {
            if (newHeights[i] > newHeights[st.peek()]) {
                st.push(i);
            } else if (newHeights[i] == newHeights[st.peek()]) {
                st.pop();
                st.push(i);
            } else {
                while (!st.isEmpty() && newHeights[st.peek()] > newHeights[i]) {
                    int mid = st.peek();
                    st.pop();
                    int left = st.peek();
                    int right = i;
                    int w = right - left - 1;
                    int h = newHeights[mid];
                    res = Math.max(res, w * h);
                }
                st.push(i);
            }
        }
        return res;
    }

96. 不同的二叉搜索树

解题思路:动态规划 定义dp[] 1到i节点组成二叉搜索树的个数位dp[i];

解析见:

https://programmercarl.com/0096.%E4%B8%8D%E5%90%8C%E7%9A%84%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.html#%E6%80%9D%E8%B7%AF

 public int numTrees(int n) {
         int[] dp = new int[n + 1]; // 1到i节点组成二叉搜索树的个数位dp[i];
        dp[0] = 1;
        dp[1] = 1;

        for (int i = 2; i <= n ; i++) {
            for (int j = 1; j <=i; j++) {
                dp[i] += dp[i-j] *dp[j - 1];
            }
        }
        return dp[n];
    }

98. 验证二叉搜索树

解题思路:中序遍历

根据搜索树的特点,中序遍历二叉树后,遍历结果集是否满足从小到大排列

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

        List<Integer> list = new ArrayList<>();
        dfs(root,list);
        for (int i = 0; i < list.size() - 1; i++) {
            if(list.get(i+1)<= list.get(i))
                return false;
        }
        return true;
    }

    private void dfs(TreeNode root, List<Integer> list) {
        if(root == null){
            return;
        }

        dfs(root.left,list);
        list.add(root.val);
        dfs(root.right,list);
        return;
    }

解题思路:递归 + 中序遍历

注意:

  • 需要使用一个临时变量记录上一次迭代遇到的节点,根据搜索树的定义,通过中序遍历可知,当前节点的值一定大于前一节点的值,若小于,则直接返回false;
  • 遇到空节点也认为是二叉搜索树
 TreeNode  node ;
    public boolean isValidBST(TreeNode root) {
        if(root == null){
            return true;
        }

        return dfs(root);
    }

    private boolean dfs(TreeNode root) {
        if(root == null){
            return true;
        }
        boolean left = dfs(root.left);
        if(!left){
            return false;
        }
        if(max!=null && root.val <= max.val){
            return false;
        }
        max = root;
        boolean right = dfs(root.right);
        return right;

    }

101. 对称二叉树

解题思路:递归+中序遍历 将树分为内外两侧,依次判断内外侧是否对称

 public boolean isSymmetric(TreeNode root) {
        if(root == null){
            return true;
        }
       return dfs(root.left,root.right);
    }

    private boolean dfs(TreeNode left, TreeNode right) {
        if(left == null && right != null){
            return false;
        }else if(left != null && right==null){
            return false;
        }else if(left == null && right == null){
            return true;
        }else if(left.val != right.val){
            return false;
        }
        boolean flag1 = dfs(left.left, right.right //内侧
        boolean flag2 = dfs(left.right, right.left); // 外侧
        
        return flag1 == false ? false : flag2;
    }

105. 从前序与中序遍历序列构造二叉树

解题思路 :递归 前序遍历的顺序是 中 左 右 中序遍历的顺序是 左 中 右 ,可以从pre数组中获得根节点,然后在in数组中找到该节点的位置,那么该节点的左边就是根节点的左子树,右边是根节点的右子树,递归遍历,直到左右区间重合 返回null

pre 3 9 20 15 7

in 9 3 15 20 7 -> 左子树 3 右子树

 public TreeNode buildTree(int[] preorder, int[] inorder) {
        if(preorder.length == 0){
            return null;
        }

        return solve(preorder,0,preorder.length - 1,inorder,0,inorder.length - 1);

    }

    private TreeNode solve(int[] preorder, int preLeft, int preRight, int[] inorder, int inLeft, int inRight) {
        if(preLeft > preRight || inLeft > inRight){
            return null;
        }
        //找到根节点坐标
        int rootValue =  preorder[preLeft];
        int index = inLeft;
        for (int i = inLeft; i <= inRight; i++) {
                if(inorder[i] == rootValue){
                    index = i;
                    break;
                }

        }
        TreeNode node = new TreeNode(rootValue);
        node.left = solve(preorder,preLeft + 1,preLeft + (index - inLeft),inorder,inLeft,index -1);
        node.right = solve(preorder,preLeft +(index - inLeft) + 1,preRight,inorder,index+1,inRight);
        return node;
    }

106. 从中序与后序遍历序列构造二叉树

解题思路:递归

 public TreeNode buildTree(int[] inorder, int[] postorder) {
        if(inorder.length == 0){
            return null;
        }
        return solve(inorder,0,inorder.length,postorder,0,postorder.length);
    }

    private TreeNode solve(int[] inorder, int inLeft, int inRight, int[] postorder, int postLeft, int postRight) {
           // 没有元素了
        if (inRight - inLeft < 1) {
            return null;
        }
        // 只有一个元素了
        if (inRight - inLeft == 1) {
            return new TreeNode(inorder[inLeft]);
        }
        int rootValue = postorder[postRight - 1];
        int index = 0;
        for (int i = inLeft; i < inRight ; i++) {
            if(inorder[i] == rootValue){
                index = i;
                break;
            }
        }
        TreeNode node = new TreeNode(rootValue);
        node.left = solve(inorder,inLeft,index,postorder,postLeft,postLeft + (index - inLeft));
        node.right = solve(inorder,index + 1,inRight,postorder,postLeft + (index - inLeft),postRight-1);

        return node;

    }

114. 二叉树展开为链表

解题思路:先前序遍历 + 递归构造

    public void flatten(TreeNode root) {
        List<TreeNode> list = new ArrayList<TreeNode>();
        preorderTraversal(root, list);
        int size = list.size();
        for (int i = 1; i < size; i++) {
            TreeNode prev = list.get(i - 1), curr = list.get(i);
            prev.left = null;
            prev.right = curr;
        }
    }

    public void preorderTraversal(TreeNode root, List<TreeNode> list) {
        if (root != null) {
            list.add(root);
            preorderTraversal(root.left, list);
            preorderTraversal(root.right, list);
        }
    }

解题思路:模拟

 public void flatten(TreeNode root) {
         while (root != null){
            if(root.left == null){
                root = root.right;
            }else {
                //找到左子树的最右边节点
                TreeNode pre = root.left;
                while (pre.right != null){
                    pre = pre.right;
                }
                //将右子树接到左子树的右边的节点
                pre.right = root.right;
                //将左子树查到root.right中
                root.right = root.left;
                root.left = null;
                //考虑下一个节点
                root = root.right;
            }
    }
}

124. 二叉树中的最大路径和

解题思路:哈希表 用于记录当前数字相邻的最大长度,递归寻找当前数字+1出现的最长相邻数字

  public int longestConsecutive(int[] nums) {
        if(nums.length <= 1){
            return nums.length;
        }
        Map<Integer,Integer> map = new HashMap<>();
        for (int i = 0; i < nums.length; i++) {
            map.put(nums[i],1);
        }
        int res = 0;
        for(int i : nums){
            solve(map ,i);
        }
        for(Integer i :map.keySet()){
            res = Math.max(res,map.get(i));
        }
        return res;

    }

    private int solve(Map<Integer, Integer> map, int num) {
        if(!map.containsKey(num)){
            return 0;
        }
        int count = map.get(num);
        if(count > 1){
            return count;
        }
        count = solve(map,num + 1)+ 1;
        map.put(num,count);
        return count;
    }

139. 单词拆分

解题思路:动态规划 dp[i] 表示前i个字符是否存在字典集

状态转移:dp[i]=dp[j] && check(s[ji−1])

注意:字符从后往前插入

public boolean wordBreak(String s, List<String> wordDict) {
        //前i个字符是否存在wordDict中
        boolean[] dp = new boolean[s.length() + 1];
        dp[0] = true;
        for (int i = 1; i <= s.length(); i++) {
            for (int j = i - 1; j >= 0; j--) {
                if(wordDict.contains(s.substring(j,i)) &&dp[j]){
                   dp[i] = true;
                    break;
                }
            }
        }
        return dp[s.length()];
    }

142. 环形链表 II

解题思路 :双指针(快慢指针)

 public ListNode detectCycle(ListNode head) {
        if(head == null){
            return head;
        }
        ListNode slow = head;
        ListNode fast = head;
     while (fast != null){
             if(fast.next == null){
              break;
            }
            slow = slow.next;
            fast = fast.next.next;
            if(slow == fast){
                fast = head;
                while (slow != fast){
                    slow = slow.next;
                    fast = fast.next;
                }
               return slow;
            }

        }

        return null ;
    }

146. LRU 缓存

解题思路:双端链表 + 哈希表模拟

构造器:建立哈希表和哈希表容量

get:map不存在直接返回-1;否则更新node.val,并更新双端链表,使当前节点放到head的下一个

put:如果存在当前key,更新值,并把当前node放至head.next;否则,判断是否大于capacity,若大于,删除tail前一个节点,并将该节点插入至head.next

class LRUCache {
     class LinkNode{
        int val;
        int key;
        LinkNode pre;
        LinkNode next;

        public LinkNode(int key, int val) {
            this.val = val;
            this.key = key;
        }
    }
    int capacity ;
      Map<Integer,LinkNode> map =new HashMap<>();
    LinkNode head = new LinkNode(0,0);
    LinkNode tail = new LinkNode(0,0);
    public  LRUCache(int capacity) {
        this.capacity = capacity;
        head.next = tail;
        tail.pre = head;
    }

    public int get(int key) {
        if(!map.containsKey(key)){
            return  -1;
        }else {
            LinkNode linkNode = map.get(key);
            //因为用到了,要把当前节点放到最前面
            moveTotop(linkNode);
            return linkNode.val;
        }
    }

    private void moveTotop(LinkNode node) {
         //连接前后节点
        node.pre.next = node.next;
        node.next.pre = node.pre;
        //将当前节点放到最前面
        LinkNode temp = head.next;
        head.next = node;
        node.pre = head;
        temp.pre = node;
        node.next = temp;
    }

    public void put(int key, int value) {
        if(map.containsKey(key)){
            //因为用到数据需要将该节点放到最前面
            LinkNode node = map.get(key);
            node.val = value;
            moveTotop(node);
        }else {
            //判断当前容量是否超过设定容量
            if(map.size() == capacity){//头尾节点不入map
                //需要删除最后一个节点
                deleteLastNode();
            }
            LinkNode node = new LinkNode(key,value);
            LinkNode temp = head.next;
            node.next = temp;
            node.pre = head;
            temp.pre = node;
            head.next = node;
            map.put(key,node);
            map.put(key,node);
        }
    }

    private void deleteLastNode() {
        LinkNode last = tail.pre;
        last.pre.next = tail;
        tail.pre = last.pre;
        map.remove(last.key);
    }

148. 排序链表

解题思路:内置sort

  public ListNode sortList(ListNode head) {
        List<ListNode> list = new ArrayList<>();
        while (head!=null){
            list.add(head);
            head = head.next;
        }
        Collections.sort(list,(o1,o2)->{
            return Integer.compare(o1.val,o2.val);
        });
        ListNode dummy = new ListNode(-1);
        ListNode res = dummy;
        for (int i = 0; i < list.size(); i++) {
            dummy.next = list.get(i);
            dummy = dummy.next;
        }
        dummy.next = null;
        return res.next;
    }

解题思路:归并排序(递归)

  • 先通过快慢指针找到当前链表的中间节点,反复递归,直到把每个节点看出一个单一节点
  • 合并节点,通过一个虚节点,将两节点根据val大小合并;
  public ListNode sortList(ListNode head) {
        if(head == null || head.next == null){
            return head;
        }
      //可以用长度为2的链表举例
        ListNode fast = head.next;
        ListNode slow = head;
        //分成左右两个链表
        while (fast != null && fast.next != null){
            fast = fast.next.next;
            slow = slow.next;
        }
        ListNode temp = slow.next;
        slow.next = null;
        ListNode left = sortList(head);
        ListNode right = sortList(temp);
        ListNode cur = new ListNode(0);
        ListNode res = cur;
        // 合并
        while (left != null && right != null){
            if(left.val < right.val){
                cur.next = left;
                left = left.next;
            }else {
                cur.next = right;
                right = right.next;
            }
            cur = cur.next;
        }
        cur.next = left == null ?right : left;
        return res.next;
    }

152. 乘积最大子数组

解题思路:动态规划

需要用两个dp数组记录的原因:引文如果前面的结果为负数,当前的数为负数,负负得正,也可能使最大值,所以需要用两数组记录到当前位置得最大值和最小值

public int maxProduct(int[] nums) {
        int[] min = new int[nums.length]; //第i个结尾乘积最小子数组
        int[] max = new int[nums.length]; //第i个结尾乘积最大子数组

        min[0] = nums[0];
        max[0] = nums[0];
        int res = max[0];
        for (int i = 1; i < nums.length; i++) {

            min[i] = Math.min(min[i - 1] * nums[i],Math.min(nums[i],max[i-1] * nums[i]));

            max[i] = Math.max(max[i - 1] * nums[i],Math.max(nums[i],min[i-1] * nums[i]));

            res = Math.max(res,max[i]);
        }

        return res;
    }

155. 最小栈

解题思路:利用两个栈维护

 Stack<Integer> stack;
    Stack<Integer> minStack;

    public MinStack() {
        stack = new Stack<>();
        minStack = new Stack<>();
    }

    public void push(int val) {
        if(minStack.isEmpty()){
            minStack.push(val);
            stack.push(val);
        }else {
            Integer peek = minStack.peek();
            if(val > peek){
                minStack.push(peek);
            }else {
                minStack.push(val);
            }
            stack.push(val);
        }
    }

    public void pop() {
        minStack.pop();
        stack.pop();
    }

    public int top() {
        return stack.peek();
    }

    public int getMin() {
        return minStack.peek();
    }

200. 岛屿数量

解题思路:DFS

依次遍历数组,遇到nums[i] j = 1,res ++;

递归:从该点出发,依次向 上下左右四个方向递归,如果该位置为1,将该位置得值改为0;

  public int numIslands(char[][] grid) {
        int res =0;
        for (int i = 0; i < grid.length; i++) {
            for (int j = 0; j < grid[0].length; j++) {
                if(grid[i][j] == '1'){
                    res ++;
                    dfs(grid,i,j);
                }
            }
        }
    }

    private void dfs(char[][] grid, int i, int j) {
        if(i < 0 || j <0 ||i > grid.length -1 || j > grid[0].length -1||grid[i][j] == '0'){
            return ;
        }
        grid[i][j] = '0';
        //四个方向
        dfs(grid,i+1,j);
        dfs(grid,i-1,j);
        dfs(grid,i,j+1);
        dfs(grid,i,j-1);
        return;
    }
    private void dfs2(char[][] grid, int i, int j) {
        if(i < 0 || j <0 ||i > grid.length -1 || j > grid[0].length -1||grid[i][j] == '0'){
            return ;
        }
        grid[i][j] = '0';
        //八个方向
        dfs(grid,i+1,j);
        dfs(grid,i-1,j);
        dfs(grid,i,j+1);
        dfs(grid,i,j-1);
        dfs(grid,i+1,j+1);
        dfs(grid,i+1,j-1);
        dfs(grid,i-1,j-1);
        dfs(grid,i-1,j+1);
        return;
    }


207. 课程表

解题思路:DFS+ 拓扑排序

初始化:根据课程学习关系,建立邻接表,建立visited (0为搜索 1搜索中 2搜索完成)用于深度搜索

 boolean res = true;
    ArrayList<ArrayList<Integer>> list = new ArrayList<>();
    int[] visited;

    public boolean canFinish(int numCourses, int[][] prerequisites) {
        for (int i = 0; i < numCourses; i++) {
            list.add(new ArrayList<>());
        }
        //(0 :为搜索 1:搜索中 2:搜索完成)
        visited = new int[numCourses];
        //建立邻接表
        for (int i = 0; i < prerequisites.length; i++) {
            list.get(prerequisites[i][0]).add(prerequisites[i][1]);
        }
        //利用DFS实现拓扑排序
        for (int i = 0; i < numCourses && res; i++) {
            if(visited[i] == 0){
                
                dfs(i);
            }
        }
        return res;
    }

    private void dfs(int i) {
        visited[i] = 1;
       for(int v : list.get(i)){
           if(visited[v] == 0){
               dfs(v);
           }
           if(!res){
               return;
           }else if(visited[i] == 1){
               res =false;
               return;
           }
       }
        visited[i] = 2;
    }

210. 课程表 II

解题思路:DFS+ 拓扑排序

初始化:根据课程学习关系,建立邻接表,建立visited (0为搜索 1搜索中 2搜索完成)用于深度搜索

boolean flag = true;
    ArrayList<ArrayList<Integer>> list;
    int[] visited;
    int index;
    int[] res;
    public int[] findOrder(int numCourses, int[][] prerequisites) {
        list = new ArrayList<>();
        for (int i = 0; i < numCourses; i++) {
            list.add(new ArrayList<>());
        }
        res = new int[numCourses];
        //(0 :为搜索 1:搜索中 2:搜索完成)
        visited = new int[numCourses];
        index = numCourses -1;
        //建立邻接表
        for (int i = 0; i < prerequisites.length; i++) {
            list.get(prerequisites[i][0]).add(prerequisites[i][1]);
        }
        //利用DFS实现拓扑排序
        for (int i = 0; i < numCourses && flag; i++) {
            if(visited[i] == 0){
                dfs(i);
            }
        }
        if(!flag){
            return new int[0];
        }
        return res;
    }
     private void dfs(int i) {
        visited[i] = 1;
      for(int v : list.get(i)){
           if(visited[v] == 0){
               dfs(v); 
               if(!flag){
               	return;
               }
           }else if(visited[i] == 1){
               flag = false;
               return;
           }
        visited[i] = 2;
        res[index--]= i;
    }
}

297. 二叉树的序列化与反序列化

解题思路:层序遍历+栈 ,只有层序遍历才能保证树的唯一性

public String serialize(TreeNode root) {
        if(root == null){
            return null;
        }
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        int sum = 1;
        Queue<TreeNode> deque = new LinkedList<>();
        deque.offer(root);
        boolean flag = false;
        while (!deque.isEmpty()  && !flag){
            int size = deque.size();
            while (size > 0){
                TreeNode node = deque.poll();
                if(node == null && sum !=0){
                    sb.append("null,");
                }else {
                    sb.append(node.val);
                    sum--;
                    if(node.left != null){
                        sum++;
                    }
                    if(node.right!= null){
                        sum++;
                    }
                    deque.offer(node.left);
                    deque.offer(node.right);
                    if(sum != 0){
                        sb.append(",");//未到达最后一个节点前都加入一个逗号
                    }else {
                        flag = true;
                        break;
                    }
                    size--;
                }
            }
        }
        sb.append("]");
        return sb.toString();
    }
public TreeNode deserialize(String data) {
        if(data == null){
            return null;
        }

        String substring = data.substring(1, data.length() - 1);
        String[] strings = substring.split(",");
        int index = 0;
        TreeNode root = new TreeNode(getNode(strings[index++]));
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        while (!queue.isEmpty() && index < strings.length){
            TreeNode node = queue.poll();
            if(!"null".equals(strings[index])){
                node.left = new TreeNode(getNode(strings[index++]));
                queue.offer(node.left);
            }else {
                node.left =null;
                index++;
            }
            //保证存在右节点
            if(index < strings.length && !"null".equals(strings[index])){
                node.right = new TreeNode(getNode(strings[index++]));
                queue.offer(node.right);
            }else {
                node.right =null;
                index++;
            }
        }
        return root;
    }

    private int getNode(String str) {

        int flag  = 1;
        int res = 0;
        int i = 0;
        //如果是负数
        if(str.charAt(0) == '-'){
            flag = -1;
            i++;
        }
        for ( ;i <str.length() ; i++) {
            res = res * 10  + str.charAt(i) - '0';
        }
        return res * flag;
    }

208. 实现 Trie (前缀树)

解题思路:哈希(set)逻辑简单

Set<String> set ;
    public Trie() {
        set = new HashSet<>();
    }

    public void insert(String word) {
        set.add(word);
    }

    public boolean search(String word) {
       return set.contains(word);
    }

    public boolean startsWith(String prefix) {
        for(String s : set){
            if(s.startsWith(prefix)){
                return true;
            }
        }
        return false;
    }
   

解题思路:前缀树(字典树)(leetcode720 和692)

视频讲解:https://www.bilibili.com/video/BV1Az4y1S7c7?spm_id_from=333.337.search-card.all.click

private Map<Character,Trie> map;
    private boolean isEnd;

    public Trie() {
        map = new HashMap<>();
        isEnd = false; //用于标记当前node结尾是否组成了完整单词
    }

    public void insert(String word) {
        Trie node = this;
        for (int i = 0; i < word.length(); i++) {
            char charAt = word.charAt(i);
            if(!node.map.containsKey(charAt)){
                node.map.put(charAt,new Trie());
            }
            node = node.map.get(charAt);
        }
        node.isEnd = true;
    }

    public boolean search(String word) {
        Trie trie = searchPrefix(word);
        return trie != null && trie.isEnd;

    }

    public boolean startsWith(String prefix) {
        return searchPrefix(prefix)!=null;
    }
    private Trie searchPrefix(String prefix) {
        Trie node = this;
        for (int i = 0; i < prefix.length(); i++) {
            char charAt = prefix.charAt(i);
            if(!node.map.containsKey(charAt)){
                return null;
            }
            node = node.map.get(charAt);
        }
        return node;
    }

215. 数组中的第K个最大元素

解题思路:归并排序or快速排序

public int findKthLargest(int[] nums, int k) {
        int[] temp = new int[nums.length];
        sort(nums,0,nums.length -1,temp);
        return nums[nums.length -k];
    }

    private void sort(int[] nums, int left, int right,int[] temp) {
        if(left == right){
            return ;
        }
        int mid = left + (right - left)/2;
        sort(nums,left,mid,temp);
        sort(nums,mid+1,right,temp);
        merge(nums,left,mid,right,temp);
    }

    private void merge(int[] nums, int left, int mid, int right,int[] temp) {
        for (int i = left; i <= right; i++) {
            temp[i] = nums[i];
        }
        int i = left;
        int j = mid + 1;

        for (int k = left; k <= right ; k++) {
            if(i == mid + 1){
                nums[k] = temp[j++];
            }else if(j == right + 1){
                nums[k] = temp[i++];
            }else if(temp[i] <= temp[j]){
                nums[k] = temp[i++];
            }else {
                nums[k] = temp[j++];
            }
        }
    }

221. 最大正方形

解题思路:动态规划

定义 :dp ij 以 i j 为右下角组成的正方形个数

状态转移:若字符为0,dp[i]j = 0;

​ 否则 取Math.min(dp[i][j-1],Math.min(dp[i-1][j-1],dp[i-1][j])) + 1;

 public int maximalSquare(char[][] matrix) {
        int res = 0;
        int[][] dp = new int[matrix.length][matrix[0].length];
        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < matrix[i].length; j++) {
                if(matrix[i][j] == '0'){
                    dp[i][j] = 0;
                }else {
                    if(i == 0 || j == 0){
                        dp[i][j] =1;
                    }else {
                        dp[i][j] = Math.min(dp[i][j-1],Math.min(dp[i-1][j-1],dp[i-1][j])) + 1;

                    }
                    res = Math.max(res,dp[i][j]);
                }
            }
        }
        return res *res;
    }

236. 二叉树的最近公共祖先

解题思路:递归

解析见:https://programmercarl.com/0236.%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E8%BF%91%E5%85%AC%E5%85%B1%E7%A5%96%E5%85%88.html

 public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
             if(root == null || root == p || root == q){
            return root;
        }
        TreeNode left = lowestCommonAncestor(root.left,p,q);
        TreeNode right = lowestCommonAncestor(root.right,p,q);
        if(left ==null && right == null){
            return null;
        }else if(left == null && right != null){
            return right;
        }else if(left != null && right == null){
            return left;
        }else {
            return root;
        }
    }

238. 除自身以外数组的乘积

解题思路:构造左右乘积列表

建立两个数组,分别计算当前数字左右两边的乘积

 public int[] productExceptSelf(int[] nums) {
             int[] res = new int[nums.length];
        
        int[] l = new int[nums.length];// 第i的元素的左边的乘积
        int[] r = new int[nums.length];// 第i个元素右边的乘积
        l[0] = 1;
        l[1] = nums[0];
        r[nums.length - 1] = 1;
        r[nums.length - 2] = nums[nums.length -  1];
        for (int i = 2; i < nums.length; i++) {
            l[i] = nums[i-1] * l[i - 1];
        }
        for (int i = nums.length - 3; i >= 0; i--) {
            r[i] = nums[i+1] * r[i + 1];
        }
        for (int i = 0; i < nums.length; i++) {
            res[i] = l[i] *r[i];
        }


        return res;
    }

239. 滑动窗口最大值

解题思路:栈

 public int[] maxSlidingWindow(int[] nums, int k) {
        int[] res = new int[nums.length- k + 1];
        int index = 0;
        Deque<Integer> deque = new ArrayDeque<>();
        for (int i = 0; i < nums.length; i++) {
            // i ~ i + k -1
            while (!deque.isEmpty() && deque.peekFirst() < i - k + 1 ){
                deque.poll();
            }
            //维护一个单调递减栈
            while (!deque.isEmpty() && nums[deque.peekLast()] <nums[i]){
                deque.pollLast();
            }
              deque.addLast(i);
            if(i >= k-1){
                res[index++] = nums[deque.peekFirst()];
            }
        }
        return res;
    }

240. 搜索二维矩阵 II

解题思路:对每一行进行二分查找

  public boolean searchMatrix(int[][] matrix, int target) {
        for (int[] row : matrix) {
            int index = search(row, target);
            if (index >= 0) {
                return true;
            }
        }
        return false;
    }

    public int search(int[] nums, int target) {
        int low = 0, high = nums.length - 1;
        while (low <= high) {
            int mid = (high - low) / 2 + low;
            int num = nums[mid];
            if (num == target) {
                return mid;
            } else if (num > target) {
                high = mid - 1;
            } else {
                low = mid + 1;
            }
        }
        return -1;
    }

解题思路:从(0,n-1)开始搜索,如果当前的数大于target,则说明该列全部大于target,则col–,否则row++;

 public boolean searchMatrix(int[][] matrix, int target) {
          int col = matrix[0].length - 1;
        int row = 0;
        while (row < matrix.length && col >=0){
            if(matrix[row][col] == target){
                return true;
            }else {
                if(matrix[row][col] > target){
                    col--;
                }else {
                    row++;
                }
            }
        }
        return false;
    }

279. 完全平方数

解题思路:动态规划 完全平方和为n的最少完全平方数为dp[i]

状态转移: dp[i] = Math.min(dp[i],dp[i-j*j] + 1);

 public int numSquares(int n) {
          int[] dp = new int[n + 1];
        Arrays.fill(dp,Integer.MAX_VALUE);
        dp[0] =0;
        dp[1] =1;
        for (int i = 1; i <= n ; i++) {
            for (int j = 1; j*j <= i; j++) {
             
              dp[i] = Math.min(dp[i],dp[i-j*j] + 1);
            }
        }
        return dp[n];
    }

283. 移动零

解题思路: 模拟

 public void moveZeroes(int[] nums) {
        int left = 0;
        for (int right = 0; right < nums.length; right++) {
            if(nums[right] != 0){
                nums[left++] = nums[right];
            }
            while (right == nums.length-1 &&left<nums.length){
                nums[left++] = 0;
            }
        }
    }

301. 删除无效的括号

解题思路:回溯

确定需要删除的左右括号数

根据确定的删除左右括号数,进行递归回溯,判断有效括号

List<String> res = new ArrayList<>();
    public List<String> removeInvalidParentheses(String s) {
        //找到需要删除 左右括号的个数
        int lDelete = 0;
        int RDelete = 0;
        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);
            if(ch == '('){
                lDelete ++;
            }else if( ch == ')'){
                if(lDelete > 0){
                    lDelete --;
                }else {
                    RDelete ++;
                }
            }
        }
        //回溯 删除左右括号
        backTracking(lDelete,RDelete,s,0);
        return res;
    }

    private void backTracking(int lDelete, int rDelete, String s, int start) {
        if(lDelete == 0 && rDelete == 0 && isValid(s)){
            res.add(s);
        }

        for (int i = start; i < s.length(); i++) {
            if(i > start  && s.charAt(i) == s.charAt(i - 1)){
                continue;
            }
            if(lDelete > 0 && s.charAt(i)=='('){
                backTracking(lDelete - 1,rDelete,s.substring(0,i) + s.substring(i+1),i);
            }
            if(rDelete > 0 && s.charAt(i)==')'){
                backTracking(lDelete ,rDelete - 1,s.substring(0,i) + s.substring(i+1),i);
            }
        }
    }
//有效括号判断
    private boolean isValid(String s) {
        Stack<Character> stack = new Stack<>();
        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);
            if(ch != '(' && ch != ')'){
                continue;
            }else {
                if(ch == '('){
                    stack.push(')');
                }else{
                    if(!stack.isEmpty() && stack.peek() == ch){
                        stack.pop();
                    }else {
                        return false;
                    }
                }

            }
        }
        return stack.isEmpty();
    }

309. 最佳买卖股票时机含冷冻期

解题思路 动态规划

dp i 0 表示 第i天为买入状态的最大现金 ;dp i 1表示第i天为卖出度过冷冻期的最大现金

dp i 2 表示第i天卖出的最大现金;dp i 2表示第i天为冷冻期的最大现金

状态转移:  
  dp[i][0] =Math.max(Math.max(dp[i-1][3],dp[i-1][1])-prices[i],dp[i-1][0]);
  dp[i][1] =Math.max(dp[i-1][1],dp[i-1][3]);
  dp[i][2] =dp[i-1][0] +prices[i];
  dp[i][3] =dp[i-1][2];

public int maxProfit(int[] prices) {
        int[][] dp = new int[prices.length][4];
        dp[0][0] = -prices[0];//买入状态
        dp[0][1] = 0;//卖出过冷冻期
        dp[0][2] = 0;//今天卖出
        dp[0][3] = 0;//今天是冷冻期
        for (int i = 1; i < prices.length; i++) {
            dp[i][0] =Math.max(Math.max(dp[i-1][3],dp[i-1][1])-prices[i],dp[i-1][0]);
            dp[i][1] =Math.max(dp[i-1][1],dp[i-1][3]);
            dp[i][2] =dp[i-1][0] +prices[i];
            dp[i][3] =dp[i-1][2];
        }
        return Math.max(dp[prices.length-1][1],Math.max(dp[prices.length - 1][2],dp[prices.length-1][3]));
    }

322. 零钱兑换

解题思路:动态规划:01背包问题 先遍历物品,在遍历背包

解析见:https://programmercarl.com/0322.%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2.html#%E6%80%9D%E8%B7%AF

 public int coinChange(int[] coins, int amount) {
        int[] dp = new int[amount + 1];
        Arrays.fill(dp,amount+1);
        dp[0] = 0;
        for (int i = 0; i < coins.length; i++) {
            for (int j = coins[i]; j <= amount; j++) { // 遍历背包
                if (dp[j - coins[i]] != amount + 1) { // 如果dp[j - coins[i]]是初始值则跳过
                    dp[j] = Math.min(dp[j - coins[i]] + 1, dp[j]);
                }
        }
        }
        return dp[amount] == amount+1?-1:dp[amount];
    }



解题思路:动态规划:01背包问题 先遍历背包 在遍历物品

 public int coinChange(int[] coins, int amount) {
   int[] dp = new int[amount + 1];
        Arrays.fill(dp,amount+1);
        dp[0] = 0;
        for (int i = 1; i <= amount; i++) {
            for (int j = 0; j < coins.length; j++) {
                if(i >= coins[j]&& dp[i - coins[j]] != amount + 1 ){
                    dp[i] = Math.min(dp[i],dp[i - coins[j]] + 1);
                }
            }
        }
        return dp[amount] == amount + 1 ? -1:dp[amount];
    }

337. 打家劫舍 III

解题思路:递归+动态规划

dp[0] 不偷根节点的最大值 = max(left[0],left[1]) + max(dp[0],dp[1])

dp[1] 偷根节点的最大值 = root + left[0] + right[0]

public int rob(TreeNode root) {
        if(root == null){
            return 0;
        }
        int[] res =  dfs(root);
        return Math.max(res[0],res[1]);
    }

    private int[] dfs(TreeNode root) {
        int[] res = new int[2];
        if(root == null){
            return res;
        }
        int[] left = dfs(root.left);
        int[] right = dfs(root.right);
        res[0] = Math.max(left[0],left[1]) + Math.max(right[0] ,right[1]);
        res[1] = root.val + left[0] + right[0];
        return res;
    }

解题思路:记忆化递归

可以使用一个map把计算过的结果保存一下,这样如果计算过孙子了,那么计算孩子的时候可以复用孙子节点的结果。

  public int rob(TreeNode root) {
        if(root == null){
            return 0;
        }
        Map<TreeNode,Integer> map = new HashMap<>();
        return dfs(root,map);
    }
private int dfs(TreeNode root, Map<TreeNode, Integer> map) {
        if(root == null){
            return 0;
        }
        if(map.containsKey(root)){
            return map.get(root);
        }
        int money = root.val;
        if(root.left !=null){
            money += dfs(root.left.left,map)+dfs(root.left.right,map);
        }
        if(root.right !=null){
            money += dfs(root.right.left,map)+dfs(root.right.right,map);
        }
        int res = Math.max(money,dfs(root.left,map)+dfs(root.right,map));
        map.put(root, res);
        return res;
    }


解题思路:暴力递归

public int rob(TreeNode root) {
        if (root == null)
            return 0;
        int money = root.val;
        if (root.left != null) {
            money += rob(root.left.left) + rob(root.left.right);
        }
        if (root.right != null) {
            money += rob(root.right.left) + rob(root.right.right);
        }
        return Math.max(money, rob(root.left) + rob(root.right));
    }

338. 比特位计数

动态规划 :当前数1的个数等于该数除以2后数1的个数加上该数对2取余之和

状态转移: dp[i] = dp[i >> 1] + (i & 1);

  public int[] countBits(int n) {
        int[] dp = new int[n + 1];
        dp[0] = 0;
        for (int i = 1; i <= n; i++) {
            // (i & 1) 为除以2 的余数
            dp[i] = dp[i >> 1] + (i & 1);
        }
      
        return dp;
    }

347. 前 K 个高频元素

解题思路:优先队列+哈希表

用哈希表记录数出现的次数,利用优先队列,根据<k,v>中的value进行排序,然后依次遍历数组放入queue中,取出前K个元素即可

 public int[] topKFrequent(int[] nums, int k) {
          int[] res = new int[k];
        Map<Integer,Integer> map = new HashMap<>();

        for (int i : nums){
            map.put(i,map.getOrDefault(i,0) + 1);
        }

        Set<Map.Entry<Integer, Integer>> entries = map.entrySet();
        //降序排列 P
 PriorityQueue<Map.Entry<Integer, Integer>> queue = new PriorityQueue<>((o1, o2) -> o1.getValue() - o2.getValue());
         for (Map.Entry<Integer, Integer> entry : entries) {
            queue.offer(entry);
            if (queue.size() > k) {
                queue.poll();
            }
        }

        for (int i = k - 1; i >= 0; i--) {
            res[i] = queue.poll().getKey();
        }
        return res;
    }

394. 字符串解码

解题思路:栈 + 字符串

定义两个栈,分别记录数字和字符,根据左右括号分隔和数字,判断进栈

  • 遇到 [,就将数字进栈,并从cnt=0,将之前字符压进栈
  • 遇到],弹出栈顶数字和栈顶字符,根据次数,接到结尾。
 public String decodeString(String s) {
        Deque<Integer> timesQue = new LinkedList<>();
        Deque<String> stringsQue = new LinkedList<>();
        StringBuilder sb = new StringBuilder();
        int times = 0;
        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);
            if(ch >= '0' && ch <= '9'){
                times = times * 10 + ch - '0';
            }else if(ch =='['){
                timesQue.push(times);
                times = 0;
                stringsQue.push(sb.toString());
                sb.delete(0,sb.length());
            }else if(ch == ']'){
                int cnt = timesQue.pop();
                String str = stringsQue.pop();
                while (cnt-->0){
                    str += sb.toString();
                }
                sb = new StringBuilder(str);
            }else {
                sb.append(ch);
            }
        }
        return sb.toString();
    }

399. 除法求值

解题思路:并查集的底层为数组 一个权重数组和一个父节点数组

class Union{
        int[] parent ; //每一个节点所对应的父节点的id
        double[] weight; //节点指向父节点的权值
        public Union(int capacity) {
            parent = new int[capacity];
            weight = new double[capacity];
            for (int i = 0; i < capacity; i++) {
                parent[i] = i;
                weight[i] = 1.0;
            }
        }

        public void union(Integer x, Integer y, double value) {
          int rootX = find(x);
          int rootY = find(y);
          if(rootX == rootY){
              return ;
          }
            parent[rootX] = rootY;
            weight[rootX]  = weight[y] * value / weight[x];
        }
        /**
         * @author: 
         * @description: 路径压缩
         * @param: [x]
         * @date: 2022/4/28
         * @return: int
         */
        private int find(Integer x) {
            if(x != parent[x]){
                int pre = parent[x];
                parent[x] = find(parent[x]);
                weight[x] *= weight[pre];
            }
            return parent[x];
        }


        public double getValue(Integer x, Integer y) {
            int rootX = find(x);
            int rootY = find(y);
            if(rootX == rootY){
                return weight[x] / weight[y];
            }else {
                return -1.0d;
            }


        }
    }


    public double[] calcEquation(List<List<String>> equations, double[] values, List<List<String>> queries) {
        int equationsLen = equations.size();
        Map<String,Integer> map = new HashMap<>(2 * equationsLen);
        int id = 0;
        Union union = new Union(2 * equationsLen);
        //预处理 构建并查集
        for (int i = 0; i < equationsLen; i++) {
            List<String> equation = equations.get(i);
            String var1 = equation.get(0);
            String var2 = equation.get(1);
            if(!map.containsKey(var1)){
                map.put(var1,id++);
            }
            if(!map.containsKey(var2)){
                map.put(var2,id++);
            }
            union.union(map.get(var1),map.get(var2),values[i]);
        }

        //做查询
        int queriesLen = queries.size();
        double[] res = new double[queriesLen];
        for (int i = 0; i < queriesLen; i++) {
            List<String> list = queries.get(i);
            String var1 = list.get(0);
            String var2 = list.get(1);

            if(!map.containsKey(var1) || !map.containsKey(var2)){
                res[i] = -1.0d;
            }else {
                Integer x = map.get(var1);
                Integer y = map.get(var2);
                res[i] = union.getValue(x,y);
            }
        }
        return res;
    }

并查集相关题目

「力扣」第 547 题:省份数量(中等);
「力扣」第 684 题:冗余连接(中等);
「力扣」第 1319 题:连通网络的操作次数(中等);
「力扣」第 1631 题:最小体力消耗路径(中等);
「力扣」第 959 题:由斜杠划分区域(中等);
「力扣」第 1202 题:交换字符串中的元素(中等);
「力扣」第 947 题:移除最多的同行或同列石头(中等);
「力扣」第 721 题:账户合并(中等);
「力扣」第 803 题:打砖块(困难);
「力扣」第 1579 题:保证图可完全遍历(困难);
「力扣」第 778 题:水位上升的泳池中游泳(困难)

406. 根据身高重建队列

解题思路:数组排序 先按身高排序,身高相同按在前面的人数多少排序

  public int[][] reconstructQueue(int[][] people) {
        
        // 身高从大到小排(身高相同k小的站前面)
        Arrays.sort(people,(p1,p2)->{
            if(p1[0] == p2[0]){
                return p1[1] - p2[1];
            }
            return p2[0] - p1[0];
        });



        List<int[]> que = new ArrayList<>();
        //前面几个就在第几个插入
        for (int[] p : people) {
            que.add(p[1],p);

        }

        return que.toArray(new int[people.length][]);
    }

416. 分割等和子集

解题思路:动态规划 转换成01背包问题

物品就是数组,容量是数组和/2。

public boolean canPartition(int[] nums) {
           int sum = Arrays.stream(nums).sum();
        if(sum % 2 == 1){
            return false;
        }
        int ave = sum / 2;

        int[] dp = new int[ave + 1];//和为i的最大子集和
        dp[0] = 0;
        for (int i = 0; i < nums.length; i++) {
            for (int j = ave; j >=nums[i] ; j--) {
                dp[j] = Math.max(dp[j],dp[j - nums[i]]+nums[i]);
            }
        }
        if(dp[ave] == ave) {
            return true;
        }else {
            return false;
        }
    }

437. 路径总和 III

解题思路:递归

由于不要求叶子节点技术和根节点开始,所以要从每个节点开始,开始递归路径和。

 public int pathSum(TreeNode root, int targetSum) {
        if(root == null){
            return 0;
        }
        int res = 0;
        res = dfs(root,targetSum);
        res += pathSum(root.left,targetSum);
        res += pathSum(root.right,targetSum);
        return res;
    }

    private int dfs(TreeNode root, int targetSum) {
        
        if(root ==null){
            return 0;
        }
        int res =0;
        if(root.val == targetSum){
            res++;
        }
        
        res+=dfs(root.left,targetSum -root.val);
        res+=dfs(root.right,targetSum-root.val);
        return res;
    }

112. 路径总和

解题思路:递归

   boolean res = false;
    public boolean hasPathSum(TreeNode root, int targetSum) {
        if(root == null){
            return false;
        }

        dfs(root,targetSum);
        return res;
    }


    private void dfs(TreeNode root,int sum){
        if(root == null){
            return;
        }
        sum -= root.val;
        if(root.left ==null && root.right == null && sum ==0){
            res= true;
        }
        dfs(root.left,sum);

        dfs(root.right,sum);
    }

113. 路径总和 II

解题思路:回溯

 List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
        if(root == null){
            return new ArrayList<>();
        }

        dfs(root,targetSum,new ArrayList<>());
        return res;


    }
    private void dfs(TreeNode root,int sum,ArrayList<Integer> list){
        if(root == null){
            return ;
        }
        list.add(root.val);
        sum -= root.val;
        

        if(root.left == null && root.right == null && sum == 0){
            res.add(new ArrayList<>(list));
        }

        dfs(root.left,sum,list);

        dfs(root.right,sum,list);
        
        list.remove(list.size() -1);
    }

438. 找到字符串中所有字母异位词

解题思路:滑动串口+哈希比较

由于都是小写字母,用两个数组记录滑动串口内的字符与比较的字符是否相等

 public List<Integer> findAnagrams(String s, String p) {
        if(p.length() > s.length()){
            return new ArrayList<>();
        }

        List<Integer> res = new ArrayList<>();

        int[] A = new int[26];
        for (int i = 0; i < p.length(); i++) {
            A[p.charAt(i) - 'a'] ++;
        }
        int[] B = new int[26];
        
        for (int right = 0,left = 0; right< s.length(); right++) {
            B[s.charAt(right) - 'a']++;

            if(right - left + 1 >p.length() ){
                B[s.charAt(left++) - 'a']--;
            }
            if(right - left + 1 == p.length()){
                if(helper(A,B)){
                    res.add(left);
                }
            }
        }
        return res;
    }

    private boolean helper(int[] a, int[] b) {

        for (int i = 0; i < a.length; i++) {
            if(a[i] != b[i]){
                return false;
            }
        }
        return true;
    }

解题思路:滑动窗口+排序 (大量实践浪费在排序上,性能差)

 public List<Integer> findAnagrams(String s, String p) {
        if(p.length() > s.length()){
            return new ArrayList<>();
        }
        List<Integer>res = new ArrayList<>();
        char[] B = p.toCharArray();
        Arrays.sort(B);
       for (int r = p.length(),l = 0; r <= s.length(); r++,l++) {
            String substring = s.substring(l, r);
            char[] A = substring.toCharArray();
            Arrays.sort(A);
            if(helper(A,B)){
                res.add(l);
            }
        }
        return res;
        
    }

    private boolean helper(char[] a, char[] b) {

        for (int i = 0; i < b.length; i++) {
            if(a[i]!=b[i]){
                return false;
            }
        }
        return true;
    }

448. 找到所有数组中消失的数字

解题思路:哈希表,利用set存入数据,前后遍历两次即可

  public List<Integer> findDisappearedNumbers(int[] nums) {
             List<Integer> res = new ArrayList<>();
        Set<Integer> set = new HashSet<>();
        

        for (int i = 0; i < nums.length; i++) {

            set.add(nums[i]);

        }
        for (int i = 1; i < nums.length + 1; i++) {
            if(!set.contains(i)){
                res.add(i);
            }
        }
        return res;
    }

461. 汉明距离

解题思路:位运算 汉明距离==两数异或后1的个数

 public int hammingDistance(int x, int y) {
        int num = x ^ y;
        return hammingWeight(num);
    }

    private int hammingWeight(int num) {
        int ret = 0;
        for (int i = 0; i < 32; i++) {
            if ((num & (1 << i)) != 0) {
                ret++;
            }
        }
        return ret;
    }

494. 目标和

解题思路:动态规划 转换成01 背包问题

数组的和记为 sum,目标和位 target ,记数组部分和为x,则剩下部分和为sum - x;

那么 x-(sum -x) = target ,则有x = (tartget + sum)/2,问题转化成在数组内,存在和为x的个数

public int findTargetSumWays(int[] nums, int target) {
         int sum = Arrays.stream(nums).sum();
        if(sum < target) return 0;
        //加法的总和为x 减法的总和为sum -x
        //target = x - (sum -x)   -> x = (target + sum) / 2;
        if((target+sum) % 2 !=0 ){
            return 0;
        }
        int x = (target + sum) / 2;

        if(x < 0) x = -x;
        int[] dp = new int[x+1];//填满j容量的包,有几种方法
        dp[0] = 1;
         //转化为01背包为问题描述为:nums中找到和为x的组合有几个
        //x = 5 (05 14,23,32 41)
        for (int i = 0; i < nums.length; i++) {
            for (int j = x; j >= nums[i] ; j--) {
                dp[j] += dp[j - nums[i]];
            }
        }

        return dp[x];
    }

解题思路:回溯,不仅可以计算出个数,也可以求出组合,同理。

数组的和记为 sum,目标和位 target ,记数组部分和为x,则剩下部分和为sum - x;

那么 x-(sum -x) = target ,则有x = (tartget + sum)/2,问题转化成在数组内,存在和为x的个数

注意:由于有重复数字,可以对数组先进行排序或者用set存(复杂度会高)

 List<List<Integer>> res= new ArrayList<>();
    public int findTargetSumWays(int[] nums, int target) {
        int sum = Arrays.stream(nums).sum();
        int ave = (sum + target) / 2;
        if((sum + target) %2 ==1){
            return 0;
        }
        Arrays.sort(nums);
        backTracking(nums,ave,0,0,new ArrayList<Integer>());


        return res.size();
    }

    private void backTracking(int[] nums, int target,int sum, int start, ArrayList<Integer> list) {
        if(target == sum){
            res.add(new ArrayList<>(list));
        }
        
         for (int i = start; i < nums.length && sum + nums[i] <= target; i++) {
            sum += nums[i];
            list.add(nums[i]);
            backTracking(nums,target,sum,i+1,list);
            list.remove(list.size()-1);
            sum -= nums[i];
        }

    }

538. 把二叉搜索树转换为累加树

解题思路:逆后序遍历,记录sum并赋给当前节点的val即可

在求二叉树的属性时,均有后序遍历

   int sum = 0;
    public TreeNode convertBST(TreeNode root) {
        if(root == null){
            return null;
        }
        convertBST(root.right);
        sum += root.val;
        root.val = sum;
        convertBST(root.left);
        return root;
    }

543. 二叉树的直径

解题思路:后序遍历 当前节点的最大直接 = 左右节点的最大深度的较大值

  int res = 0;
    public int diameterOfBinaryTree(TreeNode root) {
        if(root == null || (root.left == null && root.right == null)){
            return 0;
        }
        dfs(root);
        return res;
    }

    private int dfs(TreeNode root) {
        if(root  == null){
            return 0;
        }
        int left = dfs(root.left);
        int right= dfs(root.right);
        res = Math.max(res,left + right);
        return Math.max(left,right)+1;
    }

560. 和为 K 的子数组

解题思路:前缀和 +哈希

我们定义 {pre}[i]pre[i] 为 [0…i][0…i] 里所有数的和,则 pre}[i]pre[i] 可以由{pre}[i-1]pre[i−1] 递推而来,即:pre[i]=pre[i−1]+nums[i]

 public int subarraySum(int[] nums, int k) {
        int res = 0;
        Map<Integer,Integer> map = new HashMap<>();
        map.put(0,1);
        int Sum = 0;
        for(int num : nums){
            Sum += num;
            if(map.containsKey(Sum - k)){
                res += map.get(Sum - k);
            }
            if(map.containsKey(Sum)){
                map.put(Sum,map.get(Sum) + 1);
            }else {
                map.put(Sum,1);
            }
        }
       return res;
    }

581. 最短无序连续子数组

解题思路:排序:创建一个相同的数组进行排序,然后用该数组与原数组比较边界值,返回边界差即可

复杂度:O(n) O(n)

  public int findUnsortedSubarray(int[] nums) {
        int[] temp = Arrays.copyOf(nums, nums.length);
        Arrays.sort(temp);
        int left = 0,right = nums.length - 1;
        for (; left < nums.length ; left++) {
            if(nums[left] != temp[left]){
                break;
            }
              if(left == nums.length -1){
                return 0;
            }
        }
        for (; right >= 0 ; right--){
            if(nums[right]!=temp[right]){
                break;
            }
        }
          return right-left + 1;
    }

解题思路:利用数组特征 [ nums1 nums 2 nums3]

复杂度:O(n) O(1)

  public int findUnsortedSubarray(int[] nums) {
         int left = -1;
        int right = -1;
        int min = Integer.MAX_VALUE;
        int max = Integer.MIN_VALUE;
        for (int i = 0; i < nums.length; i++) {
            if(max > nums[i]){
                right = i;
            }else {
                max = nums[i];
            }
            
            if(min < nums[nums.length - i - 1]){
                left = nums.length - i - 1;
            }else {
                min =  nums[nums.length - i - 1];
            }
            
            
        }
        return right == -1?0:right - left + 1;
}

617. 合并二叉树

解题思路:前序遍历

public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
        if(root1 == null && root2 == null){
            return null;
        }else if(root1 != null && root2 == null){
            return root1;
        }else if(root1 == null && root2 != null){
            return root2;
        }

        root1.val = root1.val + root2.val;
        root1.left = mergeTrees(root1.left,root2.left);

        root1.right = mergeTrees(root1.right,root2.right);


        return root1;
    }

621. 任务调度器

解题思路:贪心

找到最大的同一种任务数量max,那么至少需要(max - 1) * (n + 1);

若存在多个等于max的任务,那么这些任务放到最后即可 AABB

  public int leastInterval(char[] tasks, int n) {
         if(n == 0){
            return tasks.length;
        }

        int res = 0;
        int[] count = new int[26];
        int max = 0;
        for (int i = 0; i < tasks.length; i++) {
            count[tasks[i] - 'A']++;
            max = Math.max(count[tasks[i]- 'A'],max);
        }
        res = (max - 1) * (n + 1);

        for (int i = 0; i < 26; i++) {
            if(max == count[i]){
                res ++;
            }
        }

        return Math.max(res,tasks.length);
    }

647. 回文子串

解题思路:动态规划

  public int countSubstrings(String s) {
       boolean[][] dp = new boolean[s.length()][s.length()];
        int res = 0;
        for (int right = 0; right < s.length() ; right++) {
            for (int left = 0; left <= right ; left++) {
                    if(right - left < 1){
                        res ++;
                        dp[left][right] = true;
                    }else {
                        if(s.charAt(left) == s.charAt(right)){
                            if(right - left ==1){
                                dp[left][right] = true;
                                res++;
                            }else if(dp[left + 1][right - 1]){
                                dp[left][right] = true;
                                res ++;
                            }else {
                                dp[left][right] = false;
                            }
                        }
                    }
            }
        }
        return res;
}

739. 每日温度

解题思路:单调栈

  public int[] dailyTemperatures(int[] temperatures) {
        int[] res = new int[temperatures.length];

        Deque<Integer> deque = new ArrayDeque<>();
        for (int i = 0; i < temperatures.length; i++) {
            while (!deque.isEmpty() && temperatures[deque.peekLast()] < temperatures[i]){
                Integer integer = deque.pollLast();
                res[integer] = i - integer;
            }
            deque.offerLast(i);
        }
        return res;

    }

举报

相关推荐

0 条评论