天天看点

LeetCode Weekly Contest 1745328. 方阵中战斗力最弱的 K 行5329. 数组大小减半5330. 分裂二叉树的最大乘积5331. 跳跃游戏 V

5328. 方阵中战斗力最弱的 K 行

给你一个大小为 m * n 的方阵 mat,方阵由若干军人和平民组成,分别用 0 和 1 表示。

请你返回方阵中战斗力最弱的 k 行的索引,按从最弱到最强排序。

如果第 i 行的军人数量少于第 j 行,或者两行军人数量相同但 i 小于 j,那么我们认为第 i 行的战斗力比第 j 行弱。

军人 总是 排在一行中的靠前位置,也就是说 1 总是出现在 0 之前。

示例 1:

输入:mat =

[[1,1,0,0,0],

[1,1,1,1,0],

[1,0,0,0,0],

[1,1,0,0,0],

[1,1,1,1,1]],

k = 3

输出:[2,0,3]

解释:

每行中的军人数目:

行 0 -> 2

行 1 -> 4

行 2 -> 1

行 3 -> 2

行 4 -> 5

从最弱到最强对这些行排序后得到 [2,0,3,1,4]

示例 2:

输入:mat =

[[1,0,0,0],

[1,1,1,1],

[1,0,0,0],

[1,0,0,0]],

k = 2

输出:[0,2]

解释:

每行中的军人数目:

行 0 -> 1

行 1 -> 4

行 2 -> 1

行 3 -> 1

从最弱到最强对这些行排序后得到 [0,2,3,1]

提示:

m == mat.length

n == mat[i].length

2 <= n, m <= 100

1 <= k <= m

matrix[i][j] 不是 0 就是 1

思路

代码中可以进行的优化(比赛时间紧张因此没有在代码的时间复杂度上做优化):

  • 在方阵的一行求军人个数可以用二分查找求最后一个军人的位置
  • 用长度为k的最大堆或长度为n的最小堆,求最小的k个行:如果用长度为k的最大堆,则复杂度为O(nlogk);如果用长度为n的最小堆,则复杂度为O(n + klogn)

代码

class Solution {
    private class Row implements Comparable<Row> {
        public int row, armies;
        
        public Row(int row, int armies) {
            this.row = row;
            this.armies = armies;
        }
        
        @Override
        public int compareTo(Row other) {
            if (this.armies == other.armies) {
                return this.row - other.row;
            } else {
                return this.armies - other.armies;
            }
        }
    }
    
    public int[] kWeakestRows(int[][] mat, int k) {
        int n = mat.length, m = mat[0].length, i = 0, j = 0;
        Row[] rows = new Row[n];
        int[] ans = new int[k];
        for (i=0; i<n; ++i) {
            rows[i] = new Row(i, m);
            for (j=0; j<m; ++j) {
                if (mat[i][j] == 0) {
                    rows[i] = new Row(i, j);
                    break;
                }
            }
        }
        Arrays.sort(rows);
        for (i=0; i<k; ++i) {
            ans[i] = rows[i].row;
        }
        return ans;
    }
}
           

5329. 数组大小减半

给你一个整数数组 arr。你可以从中选出一个整数集合,并删除这些整数在数组中的每次出现。

返回 至少 能删除数组中的一半整数的整数集合的最小大小。

示例 1:

输入:arr = [3,3,3,3,5,5,5,2,2,7]

输出:2

解释:选择 {3,7} 使得结果数组为 [5,5,5,2,2]、长度为 5(原数组长度的一半)。

大小为 2 的可行集合有 {3,5},{3,2},{5,2}。

选择 {2,7} 是不可行的,它的结果数组为 [3,3,3,3,5,5,5],新数组长度大于原数组的二分之一。

示例 2:

输入:arr = [7,7,7,7,7,7]

输出:1

解释:我们只能选择集合 {7},结果数组为空。

示例 3:

输入:arr = [1,9]

输出:1

示例 4:

输入:arr = [1000,1000,3,7]

输出:1

示例 5:

输入:arr = [1,2,3,4,5,6,7,8,9,10]

输出:5

提示:

1 <= arr.length <= 10^5

arr.length 为偶数

1 <= arr[i] <= 10^5

思路

用HashMap统计所有数字出现的次数,然后按次数sort,从出现次数大的开始找

代码

class Solution {
    private class Number implements Comparable<Number> {
        public int val, cnt;
        
        public Number(int val, int cnt) {
            this.val = val;
            this.cnt = cnt;
        }
        
        @Override
        public int compareTo(Number other) {
            return other.cnt - this.cnt;
        }
    }
    
    public int minSetSize(int[] arr) {
        int len = arr.length, half = len % 2 == 0? (len / 2): (len/2 + 1);
        HashMap<Integer, Integer> cnts = new HashMap<>();
        for (int a: arr) {
            if (cnts.containsKey(a)) {
                cnts.put(a, cnts.get(a) + 1);
            } else {
                cnts.put(a, 1);
            }
        }
        int n = cnts.size(), counter = 0, i = 0;
        Number[] nums = new Number[n];
       for (Map.Entry<Integer, Integer> entry: cnts.entrySet()) {
            nums[counter++] = new Number(entry.getKey(), entry.getValue());
        }
        Arrays.sort(nums);
        counter = 0;
        for (i=0; i<n; ++i) {
            counter += nums[i].cnt;
            if (counter >= half) {
                return i+1;
            }
        }
        return n;
    }
}
           

5330. 分裂二叉树的最大乘积

给你一棵二叉树,它的根为 root 。请你删除 1 条边,使二叉树分裂成两棵子树,且它们子树和的乘积尽可能大。

由于答案可能会很大,请你将结果对 10^9 + 7 取模后再返回。

示例 1:

LeetCode Weekly Contest 1745328. 方阵中战斗力最弱的 K 行5329. 数组大小减半5330. 分裂二叉树的最大乘积5331. 跳跃游戏 V

输入:root = [1,2,3,4,5,6]

输出:110

解释:删除红色的边,得到 2 棵子树,和分别为 11 和 10 。它们的乘积是 110 (11*10)

示例 2:

LeetCode Weekly Contest 1745328. 方阵中战斗力最弱的 K 行5329. 数组大小减半5330. 分裂二叉树的最大乘积5331. 跳跃游戏 V

输入:root = [1,null,2,3,4,null,null,5,6]

输出:90

解释:移除红色的边,得到 2 棵子树,和分别是 15 和 6 。它们的乘积为 90 (15*6)

示例 3:

输入:root = [2,3,9,10,7,8,6,5,4,11,1]

输出:1025

示例 4:

输入:root = [1,1]

输出:1

提示:

每棵树最多有 50000 个节点,且至少有 2 个节点。

每个节点的值在 [1, 10000] 之间。

思路

树的总和是固定的,要使乘积最大,问题转化为求子树和,使得该子树和最接近总和的一半。具体做法是将所有子树和放在一个数组中,顺序查找。

注意最后的long和int之间的类型转换。

代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    private static final long mod = 1000000007L;
    
    private ArrayList<Integer> sums = new ArrayList<>();
    
    private int sumTree(TreeNode root) {
        if (root == null) {
            return 0;
        }
        int ans = root.val + sumTree(root.left) + sumTree(root.right);
        sums.add(ans);
        return ans;
    }
    
    /**
    * If same distance, pick the larger one
    */
    private int getNearest(ArrayList<Integer> arr, int target) {
        int minDis = Integer.MAX_VALUE, nearestVal = 0;
        for (int a: arr) {
            int tmp = Math.abs(a - target);
            if (tmp < minDis) {
                minDis = tmp;
                nearestVal = a;
            } else if (tmp == minDis) {
                if (a > target) {
                    nearestVal = a;
                }
            }
        }
        return nearestVal;
    }
    
    public int maxProduct(TreeNode root) {
        int total = sumTree(root), half = total/2, nearest = getNearest(sums, half);
        return (int)((((long)nearest) % mod) * (((long)(total - nearest)) % mod) % mod);
    }
}
           

5331. 跳跃游戏 V

给你一个整数数组 arr 和一个整数 d 。每一步你可以从下标 i 跳到:

i + x ,其中 i + x < arr.length 且 0 < x <= d 。

i - x ,其中 i - x >= 0 且 0 < x <= d 。

除此以外,你从下标 i 跳到下标 j 需要满足:arr[i] > arr[j] 且 arr[i] > arr[k] ,其中下标 k 是所有 i 到 j 之间的数字(更正式的,min(i, j) < k < max(i, j))。

你可以选择数组的任意下标开始跳跃。请你返回你 最多 可以访问多少个下标。

请注意,任何时刻你都不能跳到数组的外面。

示例 1:

LeetCode Weekly Contest 1745328. 方阵中战斗力最弱的 K 行5329. 数组大小减半5330. 分裂二叉树的最大乘积5331. 跳跃游戏 V

输入:arr = [6,4,14,6,8,13,9,7,10,6,12], d = 2

输出:4

解释:你可以从下标 10 出发,然后如上图依次经过 10 --> 8 --> 6 --> 7 。

注意,如果你从下标 6 开始,你只能跳到下标 7 处。你不能跳到下标 5 处因为 13 > 9 。你也不能跳到下标 4 处,因为下标 5 在下标 4 和 6 之间且 13 > 9 。

类似的,你不能从下标 3 处跳到下标 2 或者下标 1 处。

示例 2:

输入:arr = [3,3,3,3,3], d = 3

输出:1

解释:你可以从任意下标处开始且你永远无法跳到任何其他坐标。

示例 3:

输入:arr = [7,6,5,4,3,2,1], d = 1

输出:7

解释:从下标 0 处开始,你可以按照数值从大到小,访问所有的下标。

示例 4:

输入:arr = [7,1,7,1,7,1], d = 2

输出:2

示例 5:

输入:arr = [66], d = 1

输出:1

提示:

1 <= arr.length <= 1000

1 <= arr[i] <= 10^5

1 <= d <= arr.length

思路

记忆化搜索。搜索之前计算每一个点出发可以跳到的左边界

left

和右边界

right

代码

class Solution {
    private void jump(int[] dp, int[] left, int[] right, boolean[] vis, int n, int d, int cur) {
        if (vis[cur]) {
            return;
        }
        int addi = 0;
        vis[cur] = true;
        for (int i=left[cur]; i<=right[cur]; ++i) {
            if (i == cur) {
                continue;
            }
            if (vis[i]) {
                addi = Math.max(addi, dp[i]);
            } else {
                jump(dp, left, right, vis, n, d, i);
                addi = Math.max(addi, dp[i]);
            }
        }
        dp[cur] += addi;
    }
    
    private void calBounds(int[] arr, int[] left, int[] right, int d) {
        int n = arr.length, i = 0, j = 0;
        for (i=0; i<n; ++i) {
            int begin = Math.max(0, i - d), end = Math.min(n - 1, i + d);
            left[i] = begin;
            right[i] = end;
            for (j=i+1; j<=end; ++j) {
                if (arr[i] <= arr[j]) {
                    right[i] = j - 1;
                    break;
                }
            }
            for (j=i-1; j>=begin; --j) {
                if (arr[i] <= arr[j]) {
                    left[i] = j + 1;
                    break;
                }
            }
        }
    }
    
    public int maxJumps(int[] arr, int d) {
        int n = arr.length;
        int[] dp = new int[n], left = new int[n], right = new int[n];
        calBounds(arr, left, right, d);
        boolean[] vis = new boolean[n];
        Arrays.fill(dp, 1);
        for (int i=0; i<n; ++i) {
            jump(dp, left, right, vis, n, d, i);
        }
        int maxVal = 1;
        for (int val: dp) {
            if (val > maxVal) {
                maxVal = val;
            }
        }
        return maxVal;
    }
}