LeetCode——100热题对应方法一览(301~739)

408 阅读25分钟

301. 删除无效的括号

回溯,首先计算需要分别删除多少个左括号和右括号;而后建立recurse辅助函数,对于每个char有两个选择保留或抛弃,若需删除数量还有剩余则可考虑抛弃并进入下个char的处理,同时也可选择保留,若是非括号元素必保留,若是左括号可直接保留,若是右括号则必须保证保留后右括号数量小于等于左括号;当s全部处理结束且正好删除需要的括号数则认为符合要求加入统计HashSet

删除最小数量的无效括号,使得输入的字符串有效,返回所有可能的结果。

说明: 输入可能包含了除 () 以外的字符。

示例:
输入: "()())()"
输出: ["()()()", "(())()"]

输入: "(a)())()"
输出: ["(a)()()", "(a())()"]

class Solution {
    Set<String> res = new HashSet<>();

    public List<String> removeInvalidParentheses(String s) {
        int left = 0, right = 0;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c == '(') {
                left++;
            } else if (c == ')') {
                right = left == 0 ? right + 1 : right;
                 left = left > 0 ? left - 1 : left;
            }
        }
        recurse(s, 0, 0, 0, left, right, new StringBuilder());
        return new ArrayList<String>(res);
    }
    private void recurse(String s, int index, int leftCount, int rightCount,
                         int leftRem, int rightRem, StringBuilder expression) {
        if (index == s.length()) {
            if (leftRem == 0 && rightRem == 0)
                res.add(expression.toString());
        } else {
            char c = s.charAt(index);
            int len = expression.length();
            if ((c == '(' && leftRem > 0) || (c == ')' && rightRem > 0)) {
                recurse(s, index + 1, leftCount, rightCount, leftRem - (c == '(' ? 1 : 0),
                        rightRem - (c == ')' ? 1 : 0), expression);
            }
            expression.append(c);
            if (c != '(' && c != ')') {
                recurse(s, index + 1, leftCount, rightCount, leftRem, rightRem, expression);
            } else if (c == '(') {
                recurse(s, index + 1, leftCount + 1, rightCount, leftRem , rightRem, expression);
            } else if (leftCount > rightCount) {
                recurse(s, index + 1, leftCount, rightCount + 1, leftRem, rightRem, expression);
            }
            expression.deleteCharAt(len);
        }
    }
}


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

动态规划,三个状态:持股、不持股、冷冻期,它们之间存在相互转化关系,由于dp[i][*]仅与dp[i-1][*]相关,所有可通过单独暂存上一状态来进行空间复杂度优化

给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。​

设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):  

  • 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 
  • 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
输入: [1,2,3,0,2]
输出: 3 
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]

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


312. 戳气球

动态规划,dp[i][j]表示戳破ij间的气球(包括ij)所获得的最多金币数,每个dp[i][j]可以通过更小的问题的最优解进行递推,设在ij中最后戳第k个气球(i <= k <= j),dp[i][j] = Max{dp[i][k-1] + dp[k+1][j] + nums[i-1] * nums[k] * nums[j+1], ...};需要注意边缘状态的判断,当i<j时dp[i][j]=0,nums[-1]=1等

n 个气球,编号为0n-1,每个气球上都标有一个数字,这些数字存在数组 nums 中。 

现在要求你戳破所有的气球。每当你戳破一个气球 i 时,你可以获得 nums[left] * nums[i] * nums[right] 个硬币。 这里的 leftright 代表和 i 相邻的两个气球的序号。注意当你戳破了气球 i 后,气球 left 和气球 right 就变成了相邻的气球。

求所能获得硬币的最大数量。

说明: 

  • 你可以假设 nums[-1] = nums[n] = 1,但注意它们不是真实存在的所以并不能被戳破。
  • 0 ≤ n ≤ 500, 0 ≤ nums[i] ≤ 100 

输入: [3,1,5,8]
输出: 167 
解释: nums = [3,1,5,8] --> [3,5,8] -->   [3,8]   -->  [8]  --> []
     coins =  3*1*5      +  3*5*8    +  1*3*8      + 1*8*1   = 167  

class Solution {
    public int maxCoins(int[] nums) {
        if (nums == null || nums.length == 0) return 0;
        int length = nums.length;
        int[][] dp = new int[length][length];
        for (int i = length - 1; i >= 0; i--) {
            for (int j = i; j < length; j++) {
                int max = 0;
                for (int k = i; k <= j; k++) {
                    max = Math.max(max, (i > k - 1 ? 0 : dp[i][k - 1]) + (k + 1 > j ? 0 : dp[k + 1][j]) +
                                  (i < 1 ? 1 : nums[i - 1]) * nums[k] * (j > length - 2 ? 1 : nums[j + 1]));
                }
                dp[i][j] = max;
            }
        }
        return dp[0][length - 1];
    }
}


322. 零钱兑换

动态规划,建立dp[amount + 1],dp[i]表示i至少需要多少个硬币组合而成,dp初始化为一个不可能到达的值amount+1(全1硬币也是amount),最后若dp[amount]没有更新过则返回-1

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1

示例:
输入: coins = [1, 2, 5], amount = 11
输出: 3 
解释: 11 = 5 + 5 + 1

class Solution {
    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 coin : coins) {
                if (i - coin >= 0)
                     dp[i] = Math.min(dp[i], dp[i - coin] + 1);
            }
        }
        return dp[amount] > amount ? -1 : dp[amount];
    }
}


337. 打家劫舍 III (二叉树)

递归,动态规划,返回int[2]分别储存这个节点偷或不偷两种选择,偷则两孩子都不偷,不偷则两孩子可偷可不偷

在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。

计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。

输入: [3,2,3,null,3,null,1]

     3
    / \
   2   3
    \   \ 
     3   1

输出: 7 
解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7.  

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


338. 比特位计数 ^_^

动态规划,偶数i的二进制相当于i/2的二进制后加一个0,奇数i的二进制相当与i-1的二进制的最后一位由0变为1

给定一个非负整数 num。对于 0 ≤ i ≤ num 范围中的每个数字 i ,计算其二进制数中的 1 的数目并将它们作为数组返回。

示例:
输入: 2
输出: [0,1,1]

输入: 5
输出: [0,1,1,2,1,2]

class Solution {
    public int[] countBits(int num) {
        int[] res = new int[num + 1];
        res[0] = 0;
        for (int i = 1; i <= num; i++) {
            if ((i & 1) == 0)
                res[i] = res[i / 2];
            else
                res[i] = res[i - 1] + 1;
        }
        return res;
    }
}


347. 前K个高频元素

  • 桶排序,Map统计元素频率,建立一个List数组,将频率相同的元素存在对应数组的list中,从后往前遍历数组在满足数量小于k的情况下不断添入频率最高的元素
  • map.keySet()遍历map的key;list1.addAll(list2)添加list中的所有元素
  • 堆排序,统计元素频率,建立一个大顶堆,但需重写大顶堆的Comparator变为比较其对应频率大小,最后从大顶堆中poll前k个返回

给定一个非空的整数数组,返回其中出现频率前 高的元素。

示例:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]

class Solution {
    public List<Integer> topKFrequent(int[] nums, int k) {
        Map<Integer, Integer> count = new HashMap<>();
        for (int num : nums) {
            if (!count.containsKey(num)) {
                count.put(num, 0);
            }
            count.put(num, count.get(num) + 1);
        }
        List<Integer>[] list = new List[nums.length + 1];
        for (Integer key : count.keySet()) {
            int i = count.get(key);
            if (list[i] == null)
                 list[i] = new ArrayList<>();
            list[i].add(key);
        }
        List<Integer> res = new ArrayList<>();
        for (int i = nums.length; i >= 0 && res.size() < k; i--) {
            if (list[i] != null)
                res.addAll(list[i]);
        }
        return res;
    }
}

class Solution {
    public List<Integer> topKFrequent(int[] nums, int k) {
        Map<Integer, Integer> count = new HashMap<>();
        for (int num : nums) {
            if (!count.containsKey(num)) {
                count.put(num, 0);
            }
            count.put(num, count.get(num) + 1);
        }
        PriorityQueue<Integer> pq = new PriorityQueue<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return count.get(o2) - count.get(o1);
            }
        });
        for (Integer key : count.keySet()) {
            pq.add(key);
        }
        List<Integer> res = new ArrayList<>();
        for (int i = 0; i < k; i++) {
            res.add(pq.poll());
        }
        return res;
    }
}


394. 字符串解码

数字和字符串双栈,遇到‘[’则入栈并将当前数字和StringBuilder清空,遇到‘]’则出栈并将当前SB置为str.pop() + currStr * num.pop(),注意判断区分数字和字母

给定一个经过编码的字符串,返回它解码后的字符串。

编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。

你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。

此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 2[4] 的输入。

示例:
s = "3[a]2[bc]", 返回 "aaabcbc".
s = "3[a2[c]]", 返回 "accaccacc".
s = "2[abc]3[cd]ef", 返回 "abcabccdcdcdef".  

class Solution {
    public String decodeString(String s) {
        int multi = 0;
        StringBuilder res = new StringBuilder();
        Stack<String> str_stack = new Stack<>();
        Stack<Integer> num_stack = new Stack<>();
        for (int i = 0; i < s.length(); i++) {
            char curr = s.charAt(i);
            if (curr == '[') {
                num_stack.push(multi);
                str_stack.push(res.toString());
                multi = 0;
                res = new StringBuilder();
            } else if (curr == ']') {
                StringBuilder tmp = new StringBuilder();
                int num_top = num_stack.pop();
                for (int k = 0; k < num_top; k++)
                    tmp.append(res);
                res = new StringBuilder(str_stack.pop() + tmp);
            } else {
                if (curr >= '0' && curr <='9')
                    multi = multi * 10 + (curr - '0');
                else
                    res.append(curr);
            }
        }
        return res.toString();
    }
}


399. 除法求值

并查集,路径压缩,除数(child)->被除数(parent),节点对应的val表示当前节点是其父节点的多少倍,路径压缩是指将树压缩为两层即子节点逐层乘以父节点的val而将同一集合内的所有子节点都直接连在最顶层的父节点上;当遇到新的相关方程式时可合并两个集合的父节点,首先进行路径压缩,设a / fa = val[a]b / fb = val[b]合并a, ba / b = value ,则father[fa] = fb val[fa] = fa / fb = a/b * b/fb * fa/a = value * (val[b] / val[a])

给出方程式 A / B = k, 其中 A 和 B 均为代表字符串的变量, k 是一个浮点型数字。根据已知方程式求解问题,并返回计算结果。如果结果不存在,则返回 -1.0

示例 : 

给定 a / b = 2.0, b / c = 3.0 

问题: a / c = ?, b / a = ?, a / e = ?, a / a = ?, x / x = ? 

返回 [6.0, 0.5, -1.0, 1.0, -1.0 ] 

输入为: vector<pair<string, string>> equations, vector<double>& values, vector<pair<string, string>> queries(方程式,方程式结果,问题方程式), 其中 equations.size() == values.size(),即方程式的长度与方程式结果长度相等(程式与结果一一对应),并且结果值均为正数。以上为方程式的描述。 返回vector<double>类型。 

基于上述例子,输入如下:

equations(方程式) = [ ["a", "b"], ["b", "c"] ],
values(方程式结果) = [2.0, 3.0],
queries(问题方程式) = [ ["a", "c"], ["b", "a"], ["a", "e"], ["a", "a"], ["x", "x"] ]. 

输入总是有效的。你可以假设除法运算中不会出现除数为0的情况,且不存在任何矛盾的结果。

class Solution {
    Map<String, String> parents = new HashMap<>();
    Map<String, Double> quotients = new HashMap<>();
    private void add(String s) {
        if (!parents.containsKey(s)) {
            parents.put(s, s);
            quotients.put(s, 1.0);
        }
    }
    private String find(String p) {
        if (parents.get(p) != p) {
            String fa = parents.get(p);
            parents.put(p, find(fa));
            quotients.put(p, quotients.get(p) * quotients.get(fa));
        }
        return parents.get(p);
    }
    private void merge(String a, String b, Double value) {
        add(a);
        add(b);
        String fa = find(a);
        String fb = find(b);
        if (fa == fb) return;
        parents.put(fa, fb);
        quotients.put(fa, value * (quotients.get(b) / quotients.get(a)));
    }
    public double[] calcEquation(List<List<String>> equations, double[] values, List<List<String>> queries) {
        for (int i = 0; i < equations.size(); i++) {
            List<String> equation = equations.get(i);
            merge(equation.get(0), equation.get(1), values[i]);
        }
        double[] res = new double[queries.size()];
        int index = 0;
        for (List<String> query : queries) {
            String a = query.get(0);
            String b = query.get(1);
            if (!parents.containsKey(a) || !parents.containsKey(b)) {
                res[index++] = -1;
            } else {
                res[index++] = find(a) == find(b) ? quotients.get(a) / quotients.get(b) : -1;
            }
        }
        return res;
    }
}


406. 根据身高重建队列 ^_^

身高低的人对于身高高的人不存在,因此先将所有人按身高排序,从高到低按k加入输出队列

  • list.toArray(int[0][])括号内为转换后的类型

假设有打乱顺序的一群人站成一个队列。 每个人由一个整数对(h, k)表示,其中h是这个人的身高,k是排在这个人前面且身高大于或等于h的人数。 编写一个算法来重建这个队列。

注意: 总人数少于1100人。

示例:
输入:
[[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]
输出:
[[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]  

class Solution {
    public int[][] reconstructQueue(int[][] people) {
        Arrays.sort(people, new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                return o1[0] == o2[0] ? o1[1] - o2[1] : o2[0] - o1[0];
            }
        });
        List<int[]> res = new ArrayList<>();
        for (int i = 0; i < people.length; i++) {
            res.add(people[i][1], people[i]);
        }
        return res.toArray(new int[0][]);
    }
}


416. 分割等和子集

背包问题,动态规划,找到一个等于总和一半的子集,确定target,建立一个dp[target+1],dp[i]代表是否有可能组合出i,而后遍历nums,只要i可以组合成功那i+num也可以,注意剪枝

给定一个只包含正整数非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。 

注意: 

    1. 每个数组中的元素不会超过 100 
    2. 数组的大小不会超过 200
示例:
输入: [1, 5, 11, 5]
输出: true
解释: 数组可以分割成 [1, 5, 5] 和 [11].

class Solution {
    public boolean canPartition(int[] nums) {
        int taget = 0;
        for (int num : nums) {
            taget += num;
        }
        if ((taget & 1) == 1) return false;
        taget = taget / 2;
        boolean[] dp = new boolean[taget + 1];
        dp[0] = true;
        for (int num : nums) {
            for (int i = taget; i >= num; i--) {
                if (dp[i - num] == true)
                    dp[i] = true;
                if (dp[taget] == true)
                    return true;
            }
        }
        return dp[taget];
    }
}


437. 路径总和 III

递归,建立helper函数,沿途记录节点的val,每到一个新节点就往回进行累加,若遇到满足sum要求的情况就路径数++,返回为当前节点为终点的路径+左子节点为终点的+右子节点为终点的

给定一个二叉树,它的每个结点都存放着一个整数值。

找出路径和等于给定数值的路径总数。

路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。  

二叉树不超过1000个节点,且节点数值范围是 [-1000000,1000000] 的整数。

示例:
root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8
      10
     /  \
    5   -3
   / \    \
  3   2   11
 / \   \
3  -2   1
返回 3。和等于 8 的路径有:
1.  5 -> 3
2.  5 -> 2 -> 1
3.  -3 -> 11

class Solution {
    public int pathSum(TreeNode root, int sum) {
        return helper(root, sum, new int[1000], 0);
    }
    private int helper(TreeNode root, int sum, int[] array, int p) {
        if (root == null) return 0;
        int tmp = root.val;
        int n = tmp == sum ? 1 : 0;
        for (int i = p - 1; i >= 0; i--) {
            tmp += array[i];
            if (tmp == sum)
                n++;
        }
        array[p] = root.val;
        int n_l = helper(root.left, sum, array, p + 1);
        int n_r = helper(root.right, sum, array, p + 1);
        return n + n_l + n_r;
    }
}


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

滑动窗口,双指针,用int[26]代替Map可提升速度,不同的是需要字串仅含p的异位字母,这可以通过right - left + 1 == p.length来限制,满足此条件的字串符合要求加入返回数组

给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引。

字符串只包含小写英文字母,并且字符串 sp 的长度都不超过 20100。

说明: 

  • 字母异位词指字母相同,但排列不同的字符串。
  • 不考虑答案输出的顺序。

示例:
输入:
s: "cbaebabacd" p: "abc"
输出:
[0, 6]
解释:
起始索引等于 0 的子串是 "cba", 它是 "abc" 的字母异位词。
起始索引等于 6 的子串是 "bac", 它是 "abc" 的字母异位词。

class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        if (s == null || s.length() == 0) return new ArrayList<>();
        int[] need = new int[26];
        int[] window = new int[26];
        List<Integer> res = new ArrayList<>();
        int left = 0, right = 0, total = p.length();
        for (char c : p.toCharArray()) {
            need[c - 'a']++;
        }
        while (right < s.length()) {
            char c_r = s.charAt(right);
            if (need[c_r - 'a'] > 0) {
                window[c_r - 'a']++;
                if (window[c_r - 'a'] <= need[c_r - 'a'])
                    total--;
            }
            while (total == 0) {
                if (right - left + 1 == p.length())
                    res.add(left);
                char c_l = s.charAt(left);
                if (need[c_l - 'a'] > 0) {
                    window[c_l - 'a']--;
                    if (window[c_l - 'a'] < need[c_l - 'a'])
                        total++;
                }
                left++;
            }
            right++;
        }
        return res;
    }
}


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

注意到数字范围与数组大小正好对应,因此可以利用数组来记录数字出现情况且不能影响原本的数值,因此可利用负号标记 -> 将出现的数值对应的数组格子取负,注意提取数值的绝对值

给定一个范围在 1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次。 

找到所有在 [1, n] 范围之间没有出现在数组中的数字。

您能在不使用额外空间且时间复杂度为O(n)的情况下完成这个任务吗? 你可以假定返回的数组不算在额外空间内。

示例:
输入:
[4,3,2,7,8,2,3,1]
输出:
[5,6]

class Solution {
    public List<Integer> findDisappearedNumbers(int[] nums) {
        for (int num : nums) {
            int index = Math.abs(num) - 1;
            if (nums[index] > 0) {
                nums[index] *= -1;
            }
        }
        List<Integer> res = new ArrayList<>();
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] > 0)
                res.add(i + 1);
        }

        return res;
    }
}


461. 汉明距离 ^_^

位运算,与非“ ^ ”不同为1同为0

两个整数之间的汉明距离指的是这两个数字对应二进制位不同的位置的数目。

给出两个整数 xy,计算它们之间的汉明距离。

注意:
0 ≤ x, y < 231.

示例:
输入: x = 1, y = 4
输出: 2
解释:
1   (0 0 0 1)
4   (0 1 0 0)
       ↑   ↑

class Solution {
    public int hammingDistance(int x, int y) {
        int s = x ^ y;
        int count = 0;
        while (s > 0) {
            if ((s & 1) == 1)
                count++;
            s = s >>> 1;
        }
        return count;
    }
}


494. 目标和

  • DFS深度优先搜索,建立递归函数,对每个元素有正负两种选择
  • 动态规划,背包问题,因数组和不会超过1000但可以加正号或负号,所以包括0 dp有2001个状态,dp[i]表示和为i共有多少种可能,每到达一个新num就遍历更新dp,如树结构一般,上层的每个状态通过加减新num可以到达两个新状态,若状态重叠则叠加,注意上下层不同不能直接在dp上更新,需要两个数组轮换更新

给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S。现在你有两个符号 +-。对于数组中的任意一个整数,你都可以从 +-中选择一个符号添加在前面。  

返回可以使最终数组和为目标数 S 的所有添加符号的方法数。 

示例:
输入: nums: [1, 1, 1, 1, 1], S: 3
输出: 5
解释: 
-1+1+1+1+1 = 3
+1-1+1+1+1 = 3
+1+1-1+1+1 = 3
+1+1+1-1+1 = 3
+1+1+1+1-1 = 3
一共有5种方法让最终目标和为3。

注意:

  1. 数组非空,且长度不会超过20。
  2. 初始的数组的和不会超过1000。
  3. 保证返回的最终结果能被32位整数存下。

// DFS
class Solution {
    public int findTargetSumWays(int[] nums, int S) {
        return dfs(nums, S, 0, 0);
    }
    public int dfs(int[] nums, int S, int i, int sum) {
        if (nums.length == i) {
            return S == sum ? 1 : 0;
        }
        return dfs(nums, S, i + 1, sum + nums[i]) + dfs(nums, S, i + 1, sum - nums[i]);
    }
}

// 动态规划,背包问题
class Solution {
    public int findTargetSumWays(int[] nums, int S) {
        int[] dp = new int[2001];
        dp[nums[0] + 1000] = 1;
        dp[-nums[0] + 1000] += 1;
        for (int i = 1; i < nums.length; i++) {
            int[] next = new int[2001];
            for (int sum = -1000; sum <= 1000; sum++) {
                if (dp[sum + 1000] > 0) {
                    next[sum + nums[i] + 1000] += dp[sum + 1000];
                    next[sum - nums[i] + 1000] += dp[sum + 1000];
                }
            }
            dp = next;
        }
        return S > 1000 ? 0 : dp[S + 1000];
    }
}


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

  • 递归,根节点 += 右孩子 -> 左孩子 += 根节点,设置一个变量sum专门储存累计和
  • 迭代,栈储存,与递归一样,有右孩子则找右孩子(找最小)而后回根节点更新,再处理左孩子

给定一个二叉搜索树(Binary Search Tree),把它转换成为累加树(Greater Tree),使得每个节点的值是原来的节点值加上所有大于它的节点值之和。

示例:
输入: 二叉搜索树:
              5
            /   \
           2     13
输出: 转换为累加树:
             18
            /   \
          20     13  

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

class Solution {
    public TreeNode convertBST(TreeNode root) {
        int sum = 0;
        TreeNode node = root;
        Stack<TreeNode> stack = new Stack<>();
        while (node != null || !stack.empty()) {
            while (node != null) {
                stack.push(node);
                node = node.right;
            }
            node = stack.pop();
            sum += node.val;
            node.val = sum;
            node = node.left;
        }
        return root;
    }
}


543. 二叉树的直径

递归,包括当前节点的路径上的节点个数为左子树+右子树+1,返回时应只能选某一边作为备选路径,因此返回值为max(左, 右)+1,注意直径=最长路径上的节点数-1,两个节点直径为1

给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过根结点。

示例 :
给定二叉树

          1
         / \
        2   3
       / \     
      4   5    

返回 3, 它的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。

注意:两结点之间的路径长度是以它们之间边的数目表示。

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


560. 和为K的子数组

  • 使用累计和,利用数组储存累计和,子数组的和即为结束点的累计和减去初始点的累计和,对子数组的起止遍历搜索找寻和为K的
  • 使用哈希表,map储存(sum, sum出现次数),遍历数组nums并累积求和 & 更新map中的出现次数,对于每个sum,都在map中搜索 sum−k 的出现次数,因为当前sum位置与sum-k时对应的位置中间的子数组和必为k

给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。

示例:
输入:nums = [1,1,1], k = 2
输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。

// 使用哈希表
class Solution {
    public int subarraySum(int[] nums, int k) {
        Map<Integer, Integer> map = new HashMap<>();
        map.put(0, 1);
        int sum = 0, count = 0;
        for (int i = 0; i < nums.length; i++) {
            sum += nums[i];
            if (map.containsKey(sum - k))
                 count += map.get(sum - k);
            map.put(sum, map.getOrDefault(sum, 0) + 1);
        }
        return count;
    }
}


581. 最短无序连续子数组

  • 排序,先clone新数组排序,而后和原数组对比,分别找到其前后开始改变的位置,中间即使最短无序连续子数组
  • 首尾分别遍历找到位于寻找子序列中的最大值和最小值(以从左往右变小和从右往左变大的数开始寻找),而后从左到右找到最小值的正确位置,从右往左找到最大值的正确位置,返回计算长度,注意子序列长度为0的判断

给定一个整数数组,你需要寻找一个连续的子数组,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。

你找到的子数组应是最短的,请输出它的长度。

输入: [2, 6, 4, 8, 10, 9, 15]
输出: 5
解释: 你只需要对 [6, 4, 8, 10, 9] 进行升序排序,那么整个表都会变为升序排序。

说明 :

  1. 输入的数组长度范围在 [1, 10,000]。
  2. 输入的数组可能包含重复元素 ,所以升序的意思是<=。

// 排序法
class Solution {
    public int findUnsortedSubarray(int[] nums) {
        int[] nums1 = nums.clone();
        Arrays.sort(nums1);
        int start = nums.length, end = 0;
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] != nums1[i]) {
                start = Math.min(start, i);
                end = Math.max(end, i);
            }
        }
        return start > end ? 0 : end - start + 1;
    }
}
class Solution {
    public int findUnsortedSubarray(int[] nums) {
        int min = Integer.MAX_VALUE, max = Integer.MIN_VALUE;
        int len = nums.length;
        int start, end;
        for (int i = 1; i < len; i++) {
            if (nums[i] < nums[i - 1])
                min = Math.min(nums[i], min);
            if (nums[len - i - 1] > nums[len - i])
                max = Math.max(nums[len - i - 1], max);
        }
        for (start = 0; start < len; start++) {
            if (nums[start] > min) break;
        }
        for (end = len - 1; end >= 0; end--) {
            if (nums[end] < max) break;
        }
        return start < end ? end - start + 1 : 0;
    }
}


617. 合并二叉树

  • 递归,合并当前两节点,若其中之一为空则返回另一个,若都不为空则合并数值,并继续递归合并左右子节点
  • 迭代,建立stack前序遍历,stack中储存节点对的数组,将t2合并到t1上,在节点加入的时候就保证加入的t1节点都不为null,即一旦遇到t1节点为null时就直接将t2接上

给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。 

你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。 

示例:
输入: 
	Tree 1                     Tree 2                  
          1                         2                             
         / \                       / \                            
        3   2                     1   3                        
       /                           \   \                      
      5                             4   7                  
输出: 
合并后的树:
	     3
	    / \
	   4   5
	  / \   \ 
	 5   4   7

// 递归
class Solution {
    public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
        if (t1 == null) return t2;
        if (t2 == null) return t1;
        t1.val += t2.val;
        t1.left = mergeTrees(t1.left, t2.left);
        t1.right = mergeTrees(t1.right, t2.right);
        return t1; 
   }
}

// 迭代
class Solution {
    public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
        if (t1 == null) return t2;
        Stack<TreeNode[]> stack = new Stack<>();
        stack.push(new TreeNode[] {t1, t2});
        while (!stack.empty()) {
            TreeNode[] top = stack.pop();
            if (top[0] == null || top[1] == null) continue;
            top[0].val += top[1].val;
            if (top[0].left == null)
                 top[0].left = top[1].left;
            else
                stack.push(new TreeNode[] {top[0].left, top[1].left});
            if (top[0].right == null)
                top[0].right = top[1].right;
            else
                stack.push(new TreeNode[] {top[0].right, top[1].right});
        }
        return t1;
    }
}


621. 任务调度器

  • 排序,按照出现次数多的任务先安排的原则逐个安排
  • 设计,直接根据任务频次计算无法执行任务的待命时常,总时间=任务数+待命时间,最大待命时长为max_task * n,而后各任务不断填在这个矩型中,最终待命时间为矩型剩余格子数,注意矩阵被填超出的情况即没有待命时间的情况

给定一个用字符数组表示的 CPU 需要执行的任务列表。其中包含使用大写的 A - Z 字母表示的26 种不同种类的任务。任务可以以任意顺序执行,并且每个任务都可以在 1 个单位时间内执行完。CPU 在任何一个单位时间内都可以执行一个任务,或者在待命状态。

然而,两个相同种类的任务之间必须有长度为 n 的冷却时间,因此至少有连续 n 个单位时间内 CPU 在执行不同的任务,或者在待命状态。 

你需要计算完成所有任务所需要的最短时间

示例:
输入: tasks = ["A","A","A","B","B","B"], n = 2
输出: 8
执行顺序: A -> B -> (待命) -> A -> B -> (待命) -> A -> B. 

注:

  1. 任务的总个数为 [1, 10000]。
  2. n 的取值范围为 [0, 100]。

class Solution {
    public int leastInterval(char[] tasks, int n) {
        int[] map = new int[26];
        for (char t : tasks) {
            map[t - 'A']++;
        }
        Arrays.sort(map);
        int max_task = map[25] - 1, idle_time = max_task * n;
        for (int i = 24; i >= 0 && map[i] > 0; i--) {
            idle_time -= Math.min(max_task, map[i]);
        }
        return idle_time > 0 ? tasks.length + idle_time : tasks.length;
    }
}


647. 回文子串

  • 动态规划,dp[i][j]表示从 i 到 j 的子串是否回文,只需填充右上三角的dp,初始值为回文中心值(包括单字母中心和双字母中心),递推为i j相同且内部回文
  • 中心扩展,建立辅助函数以单字母和双字母为中心进行扩展,注意以某个字母为中心的回文串可能有多个

给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。

具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被计为是不同的子串。

示例:
输入: "aaa"
输出: 6
说明: 6个回文子串: "a", "a", "a", "aa", "aa", "aaa".

// 动态规划
class Solution {
    public int countSubstrings(String s) {
        int count = 0;
        boolean[][] dp = new boolean[s.length()][s.length()];
        for (int j = 0; j < s.length(); j++) {
            for (int i = j; i >= 0; i--) {
                if (s.charAt(i) == s.charAt(j) && (j - i < 2 || dp[i + 1][j - 1])) {
                    dp[i][j] = true;
                    count++;
                }
            }
        }
        return count;
    }
}

// 中心扩展
class Solution {
    public int countSubstrings(String s) {
        int res = 0;
        char[] arr = s.toCharArray();
        for (int i = 0; i < s.length(); i++) {
            res += limitSubstring(arr, i, i);
            res += limitSubstring(arr, i, i + 1);
        }
        return res;
    }
    private int limitSubstring(char[] arr, int start, int end) {
        int result = 0;
        while (start >= 0 && end < arr.length && arr[start] == arr[end]) {
            start--;
            end++;
            result++;
        }
        return result;
    }
}


739. 每日温度 ^_^

栈辅助,遇到这种累计统计某数组大小比较的都可以用栈,例如接雨水等

根据每日 气温 列表,请重新生成一个列表,对应位置的输入是你需要再等待多久温度才会升高超过该日的天数。如果之后都不会升高,请在该位置用 0 来代替。

例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。 

提示:气温 列表长度的范围是 [1, 30000]。每个气温的值的均为华氏度,都是在 [30, 100] 范围内的整数。

class Solution {
    public int[] dailyTemperatures(int[] T) {
        int[] res = new int[T.length];
        Arrays.fill(res, 0);
        Stack<Integer> stack = new Stack<>();
        for (int i = 0; i < T.length; i++) {
            while (!stack.empty() && T[stack.peek()] < T[i]) {
                int top = stack.pop();
                res[top] = i - top;
            }
            stack.push(i);
        }
        return res;
    }
}