LeetCode Weekly Contest 40解题思路

时间:2022-06-22
本文章向大家介绍LeetCode Weekly Contest 40解题思路,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u014688145/article/details/75222189

LeetCode Weekly Contest 40解题思路

详细代码可以fork下Github上leetcode项目,不定期更新。

赛题

本次周赛主要分为以下4道题:

Leetcode 637. Average of Levels in Binary Tree

Problem:

Given a non-empty binary tree, return the average value of the nodes on each level in the form of an array.

Example 1:

Input: 3 / 9 20 / 15 7 Output: [3, 14.5, 11] Explanation: The average value of nodes on level 0 is 3, on level 1 is 14.5, and on level 2 is 11. Hence return [3, 14.5, 11].

Note:

  • The range of node’s value is in the range of 32-bit signed integer.

思路:一次递归,把同一层的结点加入一个链表中,可以使用Map做映射。

代码如下:

    Map<Integer, List<Integer>> map = new HashMap<>();
    public List<Double> averageOfLevels(TreeNode root) {
        List<Double> ans = new ArrayList<>();
        if (root == null) return ans;
        dfs(root, 0);

        for (Integer key : map.keySet()){
            List<Integer> tmp = map.get(key);
            long sum = 0;
            for (int n : tmp) sum += n;
            ans.add(sum / (1.0 * tmp.size()));
        }
        return ans;
    }

    public void dfs(TreeNode root, int layer){
        if (root == null) return;
        map.computeIfAbsent(layer, k -> new ArrayList<>()).add(root.val);
        dfs(root.left, layer + 1);
        dfs(root.right, layer + 1);
    }

bfs的做法,关键:int size = queue.size();

代码如下:

    public List<Double> averageOfLevels(TreeNode root) {

        List<Double> ans = new ArrayList<>();
        if (root == null) return ans;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);

        while (!queue.isEmpty()){
            int size = queue.size();
            long sum = 0;
            for (int i = 0; i < size; ++i){
                TreeNode node = queue.poll();
                sum += node.val;
                if (node.left != null) queue.offer(node.left);
                if (node.right != null) queue.offer(node.right);
            }
            ans.add(sum / (1.0 * size));
        }

        return ans;
    }

Leetcode 640. Solve the Equation

Problem:

Solve a given equation and return the value of x in the form of string “x=#value”. The equation contains only ‘+’, ‘-’ operation, the variable x and its coefficient. If there is no solution for the equation, return “No solution”. If there are infinite solutions for the equation, return “Infinite solutions”. If there is exactly one solution for the equation, we ensure that the value of x is an integer. Example 1: Input: “x+5-3+x=6+x-2” Output: “x=2” Example 2: Input: “x=x” Output: “Infinite solutions” Example 3: Input: “2x=x” Output: “x=0” Example 4: Input: “2x+3x-6x=x+2” Output: “x=-1” Example 5: Input: “x=x+2” Output: “No solution”

思路:区分x和数字即可,并把它们解析出来,代码如下:

    public String solveEquation(String equation) {
        String[] values = equation.split("=");
        values[0] = (values[0].charAt(0) != '-' ? "+" : "") + values[0];
        values[1] = (values[1].charAt(0) != '-' ? "+" : "") + values[1];
        values[0] = values[0].replace("+", " +").replace("-", " -");
        values[1] = values[1].replace("+", " +").replace("-", " -");

        long sumX = 0;
        long sum = 0;
        String[] nums = values[0].split("\ ");
        for (int i = 0; i < nums.length; ++i) {
            String num = nums[i];
            if (num.isEmpty())
                continue;
            if (num.contains("x")) {
                int flag = num.substring(0, 1).equals("+") ? 1 : -1;
                sumX += (flag * Integer.parseInt(
                        (num.substring(1, num.length() - 1)).isEmpty() ? "1" : (num.substring(1, num.length() - 1))));
            } else {
                int flag = num.substring(0, 1).equals("+") ? 1 : -1;
                sum += (flag * Integer.parseInt(num.substring(1, num.length())));
            }
        }

        nums = values[1].split("\ ");
        for (int i = 0; i < nums.length; ++i) {
            String num = nums[i];
            if (num.isEmpty())
                continue;
            if (num.contains("x")) {
                int flag = num.substring(0, 1).equals("+") ? -1 : 1;
                sumX += (flag * Integer.parseInt(
                        (num.substring(1, num.length() - 1)).isEmpty() ? "1" : (num.substring(1, num.length() - 1))));
            } else {
                int flag = num.substring(0, 1).equals("+") ? -1 : 1;
                sum += (flag * Integer.parseInt(num.substring(1, num.length())));
            }
        }

        if (sumX == 0 && sum == 0)
            return "Infinite solutions";
        if (sumX == 0 && sum != 0)
            return "No solution";
        return "x=" + (sum / (-sumX));
    }

利用正则表达式,可以简化代码,如下:

    public String solveEquation(String equation) {
        int[] res1 = parse(equation.split("=")[0]);
        int[] res2 = parse(equation.split("=")[1]);
        int x = res1[0] - res2[0];
        int n = res2[1] - res1[1];
        if (x == 0 && n == 0) return "Infinite solutions";
        if (x == 0 && n != 0) return "No solution";

        return "x=" + n / x;
    }

    public int[] parse(String express){
        String[] tokens = express.split("(?=[-+])");
        int[] res = new int[2]; 
        for (String token : tokens){
            if (token.equals("-x")) res[0] -= 1;
            else if (token.equals("+x") || token.equals("x")) res[0] += 1;
            else if (token.endsWith("x")) res[0] += Integer.parseInt(token.substring(0, token.indexOf("x")));
            else res[1] += Integer.parseInt(token);
        }
        return res;
    }

当然,我们也可以自己写解析,核心思想是对express做二次变换,以+or-为标志位,进行截断。

代码如下:

public String solveEquation(String equation) {
        String[] express = equation.split("=");
        int[] res1= parse(transform(express[0]).toCharArray());
        int[] res2 = parse(transform(express[1]).toCharArray());

        int x = res1[0] - res2[0];
        int n = res2[1] - res1[1];
        if (x == 0 && n == 0) return "Infinite solutions";
        if (x == 0 && n != 0) return "No solution";

        return "x=" + n / x;
    }

    public String transform(String express){
        return express + "+";
    }

    public int[] parse(char[] express){
        int[] res = new int[2];

        int flag = express[0] == '-' ? -1 : 1;
        int i = express[0] == '-' ? 1 : 0;
        StringBuilder sb = new StringBuilder();

        for (; i < express.length; ++i){
            if (express[i] == '-' || express[i] == '+'){
                String token = sb.toString();
                if (token.equals("x")) res[0] += (flag * 1);
                else if (token.endsWith("x")) res[0] += (flag * Integer.parseInt(token.substring(0, token.indexOf("x"))));
                else res[1] += (flag * Integer.parseInt(token));
                sb = new StringBuilder();
                flag = express[i] == '-' ? -1 : 1;
            }
            else{
                sb.append(express[i]);
            }
        }
        return res;
    }

Leetcode 638. Shopping Offers

Problem:

In LeetCode Store, there are some kinds of items to sell. Each item has a price. However, there are some special offers, and a special offer consists of one or more different kinds of items with a sale price. You are given the each item’s price, a set of special offers, and the number we need to buy for each item. The job is to output the lowest price you have to pay for exactly certain items as given, where you could make optimal use of the special offers. Each special offer is represented in the form of an array, the last number represents the price you need to pay for this special offer, other numbers represents how many specific items you could get if you buy this offer. You could use any of special offers as many times as you want.

Example 1:

Input: [2,5], [[3,0,5],[1,2,10]], [3,2] Output: 14 Explanation: There are two kinds of items, A and B. Their prices are 2and2 and 5 respectively. In special offer 1, you can pay 5for3Aand0BInspecialoffer2,youcanpay5 for 3A and 0B In special offer 2, you can pay 10 for 1A and 2B. You need to buy 3A and 2B, so you may pay 10 for 1A and 2B (special offer #2), and10 for 1A and 2B (special offer #2), and 4 for 2A.

Example 2:

Input: [2,3,4], [[1,1,0,4],[2,2,1,9]], [1,2,1] Output: 11 Explanation: The price of A is 2,and2, and 3 for B, 4forC.Youmaypay4 for C. You may pay 4 for 1A and 1B, and 9for2A,2Band1C.Youneedtobuy1A,2Band1C,soyoumaypay9 for 2A ,2B and 1C. You need to buy 1A ,2B and 1C, so you may pay 4 for 1A and 1B (special offer #1), and 3for1B,3 for 1B, 4 for 1C. You cannot add more items, though only $9 for 2A ,2B and 1C.

Note:

  • There are at most 6 kinds of items, 100 special offers.
  • For each item, you need to buy at most 6 of them.
  • You are not allowed to buy more items than you want, even if that would lower the overall price.

思路: 刚开始想用DP去做这件事,因为special offer就那么一些,所以只要随机选择符合条件的special offer并把它们保存起来,没有什么性质可以决定它们的选取顺序,所以就遍历咯,状态是有限个,直到当前状态没有special offer符合,再用商品单价去填,比较直观的做法。我用Queue进行状态搜索,且序列化保存到map中,代码如下:

    public int shoppingOffers(List<Integer> price, List<List<Integer>> special, List<Integer> needs) {
        Map<String, Integer> mem = new HashMap<>();
        mem.put(listToStr(needs), 0);

        Queue<List<Integer>> queue = new LinkedList<>();
        queue.offer(needs);
        while (!queue.isEmpty()) {
            List<Integer> aux = queue.poll();
            for (List<Integer> spec : special) {
                boolean isValid = true;
                for (int i = 0; i < aux.size(); ++i) {
                    if (spec.get(i) > aux.get(i))
                        isValid = false;
                }
                if (isValid) {
                    List<Integer> remain = new ArrayList<>();
                    for (int i = 0; i < aux.size(); ++i) {
                        remain.add(aux.get(i) - spec.get(i));
                    }
                    mem.put(listToStr(remain), Math.min(mem.get(listToStr(aux)) + spec.get(spec.size() - 1),
                            mem.containsKey(listToStr(remain)) ? mem.get(listToStr(remain)) : 1 << 30));
                    queue.offer(remain);
                }
            }
        }

        int minValue = 1 << 30;
        for (String key : mem.keySet()) {
            int[] remain = strToList(key);
            int sum = mem.get(key);
            for (int i = 0; i < remain.length; ++i) {
                sum += (remain[i] * price.get(i));
            }
            minValue = Math.min(minValue, sum);
        }
        return minValue;
    }

    private String listToStr(List<Integer> needs) {
        StringBuilder sb = new StringBuilder();
        for (Integer need : needs) {
            sb.append(need + "+");
        }
        return sb.toString().substring(0, sb.length() - 1);
    }

    private int[] strToList(String str) {
        String[] nums = str.split("\+");
        int[] needs = new int[nums.length];
        for (int i = 0; i < nums.length; ++i) {
            needs[i] = Integer.parseInt(nums[i]);
        }
        return needs;
    }

当然,如果你能够找到该问题的子问题,那么递归的做法也是可以的,看来对note的理解是相当重要,此处递归不会stack over flow,虽然搜索的效率只有O(2n)O(2^n),但对于此题足够了。

代码如下:

public int shoppingOffers(List<Integer> price, List<List<Integer>> special, List<Integer> needs) {
        dfs(price, special, needs, 0);
        return min;
    }

    int min = Integer.MAX_VALUE;
    private void dfs(List<Integer> price, List<List<Integer>> special, List<Integer> needs, int val){

        //subProbelm
        List<Integer> subPro = new ArrayList<>(needs);
        for (int j = 0; j < special.size(); ++j){
            boolean isValid = true;
            for (int k = 0; k < price.size(); ++k){
                if (special.get(j).get(k) > needs.get(k)) isValid = false;
                subPro.set(k, needs.get(k) - special.get(j).get(k));
            }
            if (isValid){
                dfs(price, special, subPro, val + special.get(j).get(price.size()));
            }
        }


        int sum = 0;
        for (int j = 0; j < price.size(); ++j){
            sum += (price.get(j) * needs.get(j));
        }
        min = Math.min(min , sum + val);
    }

Leetcode 639. Decode Ways II

Problem:

A message containing letters from A-Z is being encoded to numbers using the following mapping way: ‘A’ -> 1 ‘B’ -> 2 … ‘Z’ -> 26 Beyond that, now the encoded string can also contain the character ‘*’, which can be treated as one of the numbers from 1 to 9. Given the encoded message containing digits and the character ‘*’, return the total number of ways to decode it. Also, since the answer may be very large, you should return the output mod 109 + 7.

Example 1:

Input: “*” Output: 9 Explanation: The encoded message can be decoded to the string: “A”, “B”, “C”, “D”, “E”, “F”, “G”, “H”, “I”.

Example 2:

Input: “1*” Output: 9 + 9 = 18

Note:

  • The length of the input string will fit in range [1, 105].
  • The input string will only contain the character ‘*’ and digits ‘0’ - ‘9’.

思路: 重点描述从递归到迭代的一个过程,这个问题是一个基本的归简递归转DP题,思路是比较简单的,但细节比较复杂。

首先,对于*218231**02*1这样的一串数字,该怎么寻找子问题?这里只会出现两种模式:

模式1:(*),子问题A:(218231**02*1)
模式2:(*2),子问题B:(18231**02*1)

且很容易理解,如果能够求出子问题的解,那么由模式1得到(1~9) * 子问题A的解,即 9 * subPro(218231**02*1),同理由模式2得,ans = 2 * subPro(18231**02*1) ,这样该题的答案就全部出来了。至于为什么相乘,是排列组合问题,分别有9种和2种情况罢了,非常直观。

上述思路可以用递归去求解,但递归的本质在于把所有的子问题化归到一个较小的集合中求解,比如上述化归一定到个数为1的字符串,如{*} = 9, {1} = 1,既然如此,我们可以优先计算最右字符串,从长度为1的子问题开始,再由这些子问题生成进一步地解,这就是动规。根据题目能确定一些基本生成规则,具体可以参考:https://leetcode.com/problems/decode-ways-ii/#/description

代码如下:

    static final int MOD = 1000000000 + 7;
    public int numDecodings(String s) {
        char[] c = s.toCharArray();
        int n = c.length;
        long[] dp = new long[n + 16];
        dp[n] = 1;
        if (c[n - 1] == '*') dp[n - 1] = 9;
        else if (c[n - 1] == '0') dp[n - 1] = 0;
        else dp[n - 1] = 1;

        for (int i = n - 2; i >= 0; --i) {
            if (c[i] == '0') {
                dp[i] = 0;
            } else if (c[i] == '*') {
                dp[i] = (dp[i] + 9 * dp[i + 1]) % MOD;
                if (c[i + 1] == '*') {
                    dp[i] = (dp[i] + 15 * dp[i + 2]) % MOD;
                } else if (c[i + 1] - '0' <= 6) {
                    dp[i] = (dp[i] + 2 * dp[i + 2]) % MOD;
                } else {
                    dp[i] = (dp[i] + dp[i + 2]) % MOD;
                }
            } else {
                dp[i] = (dp[i] + dp[i + 1]) % MOD;
                if (c[i] == '1') {
                    if (c[i + 1] == '*') {
                        dp[i] = (dp[i] + 9 * dp[i + 2]) % MOD;
                    } else {
                        dp[i] = (dp[i] + dp[i + 2]) % MOD;
                    }
                } else if (c[i] == '2') {
                    if (c[i + 1] == '*') {
                        dp[i] = (dp[i] + 6 * dp[i + 2]) % MOD;
                    } else if (c[i + 1] - '0' <= 6) {
                        dp[i] = (dp[i] + dp[i + 2]) % MOD;
                    }
                }
            }
        }
        return (int) dp[0];
    }

子问题可以用一个游标的状态来记录。