周赛339(模拟、贪心)

文章目录

  • 周赛339
    • [6362. 最长平衡子字符串](https://leetcode.cn/problems/find-the-longest-balanced-substring-of-a-binary-string/)
      • 模拟
    • [6363. 转换二维数组](https://leetcode.cn/problems/convert-an-array-into-a-2d-array-with-conditions/)
      • 贪心
    • [6364. 老鼠和奶酪](https://leetcode.cn/problems/mice-and-cheese/)
      • 贪心
      • 记忆化搜索(超时)
      • (相似)[1029. 两地调度](https://leetcode.cn/problems/two-city-scheduling/)
    • [6365. 最少翻转操作数](https://leetcode.cn/problems/minimum-reverse-operations/)

周赛339

6362. 最长平衡子字符串

难度简单0

给你一个仅由 01 组成的二进制字符串 s

如果子字符串中 所有的 0 都在 1 之前 且其中 0 的数量等于 1 的数量,则认为 s 的这个子字符串是平衡子字符串。请注意,空子字符串也视作平衡子字符串。

返回 s 中最长的平衡子字符串长度。

子字符串是字符串中的一个连续字符序列。

示例 1:

输入:s = "01000111"
输出:6
解释:最长的平衡子字符串是 "000111" ,长度为 6 。

示例 2:

输入:s = "00111"
输出:4
解释:最长的平衡子字符串是 "0011" ,长度为  4 。

示例 3:

输入:s = "111"
输出:0
解释:除了空子字符串之外不存在其他平衡子字符串,所以答案为 0 。

提示:

  • 1 <= s.length <= 50
  • '0' <= s[i] <= '1'

模拟

优雅写法:

class Solution {
    public int findTheLongestBalancedSubstring(String s) {
        // 记录连续0的个数pre和连续1的个数cur
        // 那么这个01串的长度就是2 * min(pre, cur)
        var c = s.toCharArray();
        int ans = 0, pre = 0, cur = 0, n = c.length;
        for(int i = 0; i < n; i++){
            ++cur; // 
            if(i == c.length-1 || c[i] != c[i+1]){
                if(c[i] == '1'){ // 在01串的末尾
                    ans = Math.max(ans, Math.min(pre, cur) * 2);
                }
                pre = cur; // 进入下一段, pre 上一个连续段的长度
                cur = 0;
            }
        }
        return ans;
    }
}
class Solution {
    public int findTheLongestBalancedSubstring(String s) {
        int n = s.length();
        int res = 0;
        int i = 0;
        while(i < n){
            if(s.charAt(i) == '1'){
                i++;
                continue;
            }
            int tmp = i+1;
            int cnt0 = 0, cnt1 = 0;
            while(i < n && s.charAt(i) == '0'){
                cnt0++;
                i++;
            }
            while(i < n && s.charAt(i) == '1' && cnt1 < cnt0){
                cnt1++;
                i++;
            }
            if(cnt0 == cnt1) res = Math.max(res, cnt0+cnt1);
            i = tmp;
        }
        return res;
    }
}

6363. 转换二维数组

难度中等0

给你一个整数数组 nums 。请你创建一个满足以下条件的二维数组:

  • 二维数组应该 包含数组 nums 中的元素。
  • 二维数组中的每一行都包含 不同 的整数。
  • 二维数组的行数应尽可能

返回结果数组。如果存在多种答案,则返回其中任何一种。

请注意,二维数组的每一行上可以存在不同数量的元素。

示例 1:

输入:nums = [1,3,4,1,2,3,1]
输出:[[1,3,4,2],[1,3],[1]]
解释:根据题目要求可以创建包含以下几行元素的二维数组:
- 1,3,4,2
- 1,3
- 1
nums 中的所有元素都有用到,并且每一行都由不同的整数组成,所以这是一个符合题目要求的答案。
可以证明无法创建少于三行且符合题目要求的二维数组。

示例 2:

输入:nums = [1,2,3,4]
输出:[[4,3,2,1]]
解释:nums 中的所有元素都不同,所以我们可以将其全部保存在二维数组中的第一行。

提示:

  • 1 <= nums.length <= 200
  • 1 <= nums[i] <= nums.length

贪心

因为没有限制每一行最多放几个数,我们可以把当前所有不同的数都放一个进去。因此行数就是众数出现的次数。

class Solution {
    public List<List<Integer>> findMatrix(int[] nums) {
        int n = nums.length;
        int[] cnt = new int[n+1];
        for(int c : nums) cnt[c]++;
        int maxnum = 0;
        for(int c : cnt){
            if(c > maxnum){
                maxnum = c;
            }
        }
        List<List<Integer>> res = new ArrayList<>();
        for(int i = 0; i < maxnum; i++){
            res.add(new ArrayList<Integer>());
        }
        for(int i = 0; i < cnt.length; i++){
            int cnum = cnt[i];
            for(int j = 0; j < cnum; j++){
                res.get(j).add(i);
            }
        }
        return res;
    }
}

6364. 老鼠和奶酪

难度中等1

有两只老鼠和 n 块不同类型的奶酪,每块奶酪都只能被其中一只老鼠吃掉。

下标为 i 处的奶酪被吃掉的得分为:

  • 如果第一只老鼠吃掉,则得分为 reward1[i]
  • 如果第二只老鼠吃掉,则得分为 reward2[i]

给你一个正整数数组 reward1 ,一个正整数数组 reward2 ,和一个非负整数 k

请你返回第一只老鼠恰好吃掉 k 块奶酪的情况下,最大 得分为多少。

示例 1:

输入:reward1 = [1,1,3,4], reward2 = [4,4,1,1], k = 2
输出:15
解释:这个例子中,第一只老鼠吃掉第 2 和 3 块奶酪(下标从 0 开始),第二只老鼠吃掉第 0 和 1 块奶酪。
总得分为 4 + 4 + 3 + 4 = 15 。
15 是最高得分。

示例 2:

输入:reward1 = [1,1], reward2 = [1,1], k = 2
输出:2
解释:这个例子中,第一只老鼠吃掉第 0 和 1 块奶酪(下标从 0 开始),第二只老鼠不吃任何奶酪。
总得分为 1 + 1 = 2 。
2 是最高得分。

提示:

  • 1 <= n == reward1.length == reward2.length <= 105
  • 1 <= reward1[i], reward2[i] <= 1000
  • 0 <= k <= n

贪心

比赛的时候光想记忆化搜索=>递推去了,应该先看数据范围,最差 n^2,应该直接放弃这个解法

–https://leetcode.cn/problems/mice-and-cheese/solution/tan-xin-ji-qi-zheng-ming-by-endlesscheng-u783/

套路, 通过比较奶酪被第一个老鼠吃还是被第二个老鼠吃,可以找到一些性质

比较两个物品ij 被吃后的得分:

如果1i2j比较大: r1[i] + r2[j] > r2[i] + r1[j]

不等式变形技巧:将下标相同的放一起

==> r1[i] - r2[i] > r1[j] - r2[j]

d[i] = r1[i] - r2[i]

d[i] > d[j]

  • 则按照d[i] = r1[i] - r2[i]从大到小排序,前k个给第一只老鼠,余下的给第二只老鼠

经典贪心问题。先假设所有奶酪都是第二只老鼠吃掉的,那么得分为 sum(reward2)

现在我们要把 k 个奶酪改成第一只老鼠吃掉的。假设我们把第 i 个奶酪改成第一只老鼠吃掉,那么与第二只老鼠吃掉相比,得分的变化量为 reward1[i] - reward2[i]

为了让得分尽量大,我们需要让得分的变化量总和尽量大。因此选择前 k 大的变化量即可。

class Solution {
    // 贪心 + 优先队列
    // 统计一下每个奶酪上的差值
    // 差值最大的前k个都给 鼠1 吃, 剩下的都给 鼠2 吃
    public int miceAndCheese(int[] reward1, int[] reward2, int k) {
        int n = reward1.length;
        // 一开始假设都是第二只老鼠吃掉的
        long ans = 0;
        for(int x : reward2) ans += x;

        // 计算每个奶酪的得分变化量
        Integer[] diff = new Integer[n];
        for(int i = 0; i < n; i++) diff[i] = reward1[i] - reward2[i];

        // 选出前k大的得分变化量
        Arrays.sort(diff, (a,b) -> b-a);
        for(int i = 0; i < k; i++) 
            ans += diff[i];
        return (int)ans;
    }
}

记忆化搜索(超时)

class Solution {
    int[] num1;
    int[] num2;
    int k;
    int[][] cache; // 第i位置上还剩k口的最高得分
    public int miceAndCheese(int[] reward1, int[] reward2, int k) {
        // 第一只老鼠吃还是不吃
        // 吃 +reword1[i], k=k-1
        // 不吃 +reword2[i], k
        this.num1 = reward1;
        this.num2 = reward2;
        this.k = k;
        cache = new int[num1.length+1][k+1];
        for(int i = 0; i < num1.length; i++) Arrays.fill(cache[i], Integer.MIN_VALUE);
        return dfs(0, k);
    }
    
    public int dfs(int i, int k){
        if(i == num1.length){
            if(k == 0) return 0;
            return Integer.MIN_VALUE/2;
        }
        if(k < 0 || k > num1.length-i) return Integer.MIN_VALUE/2;
        if(cache[i][k] != Integer.MIN_VALUE) return cache[i][k];
        int res = Integer.MIN_VALUE/2;
        res = Math.max(num2[i] + dfs(i+1, k), num1[i] + dfs(i+1, k-1));
        return cache[i][k] = res;
    }
}

(相似)1029. 两地调度

难度中等284

公司计划面试 2n 人。给你一个数组 costs ,其中 costs[i] = [aCosti, bCosti] 。第 i 人飞往 a 市的费用为 aCosti ,飞往 b 市的费用为 bCosti

返回将每个人都飞到 ab 中某座城市的最低费用,要求每个城市都有 n 人抵达**。**

示例 1:

输入:costs = [[10,20],[30,200],[400,50],[30,20]]
输出:110
解释:
第一个人去 a 市,费用为 10。
第二个人去 a 市,费用为 30。
第三个人去 b 市,费用为 50。
第四个人去 b 市,费用为 20。

最低总费用为 10 + 30 + 50 + 20 = 110,每个城市都有一半的人在面试。

示例 2:

输入:costs = [[259,770],[448,54],[926,667],[184,139],[840,118],[577,469]]
输出:1859

示例 3:

输入:costs = [[515,563],[451,713],[537,709],[343,819],[855,779],[457,60],[650,359],[631,42]]
输出:3086

提示:

  • 2 * n == costs.length
  • 2 <= costs.length <= 100
  • costs.length 为偶数
  • 1 <= aCosti, bCosti <= 1000
证明一下为什么要使cost[i][0] - cost[i][1]越小越好:

假设A团体去A市,B团体去B市,此时费用为a。

假设上述A团体去B市,B团体去A市,此时费用为b。

求a的最小值,相当于求b的最大值,又相当于要使b-a的值是所有组合中最大的。

b-a的值等于A团体所有人的cost[1]-cost[0]的和,加上B团体所有人的cost[0]-cost[1]的和。

b-a的值又等于A团体所有人的cost[1]-cost[0]的和,减去B团体所有人的cost[1]-cost[0]的和。

那么只要A团体所有人的cost[1]-cost[0]的和最大,B团体所有人的cost[1]-cost[0]的和最小,就可以使得b-a最大。

只要对所有人的cost[1]-cost[0]排个序,前n个人去A市,后n个人去B市,即可使得b-a最大,亦即a最小。
class Solution {
    public int twoCitySchedCost(int[][] costs) {
        int res = 0;
        for(int i = 0; i < costs.length; i++){
            res += costs[i][1]; // 假设都飞往b市
        }
        int[] diff = new int[costs.length];
        for(int i = 0; i < costs.length; i++){
            // 统计一下飞两地的差值
            diff[i] = costs[i][0] - costs[i][1];
        }
        // 要求最低费用,将变化量从小到大排序
        Arrays.sort(diff);
        for(int i = 0; i < costs.length/2; i++){
            // 前n个飞a市,计算变化量
            res += diff[i];
        }   
        return res;
    }
}

6365. 最少翻转操作数

难度困难2

给你一个整数 n 和一个在范围 [0, n - 1] 以内的整数 p ,它们表示一个长度为 n 且下标从 0 开始的数组 arr ,数组中除了下标为 p 处是 1 以外,其他所有数都是 0

同时给你一个整数数组 banned ,它包含数组中的一些位置。banned 中第 i 个位置表示 arr[banned[i]] = 0 ,题目保证 banned[i] != p

你可以对 arr 进行 若干次 操作。一次操作中,你选择大小为 k 的一个 子数组 ,并将它 翻转 。在任何一次翻转操作后,你都需要确保 arr 中唯一的 1 不会到达任何 banned 中的位置。换句话说,arr[banned[i]] 始终 保持 0

请你返回一个数组 ans ,对于 [0, n - 1] 之间的任意下标 ians[i] 是将 1 放到位置 i 处的 最少 翻转操作次数,如果无法放到位置 i 处,此数为 -1

  • 子数组 指的是一个数组里一段连续 非空 的元素序列。
  • 对于所有的 ians[i] 相互之间独立计算。
  • 将一个数组中的元素 翻转 指的是将数组中的值变成 相反顺序

示例 1:

输入:n = 4, p = 0, banned = [1,2], k = 4
输出:[0,-1,-1,1]
解释:k = 4,所以只有一种可行的翻转操作,就是将整个数组翻转。一开始 1 在位置 0 处,所以将它翻转到位置 0 处需要的操作数为 0 。
我们不能将 1 翻转到 banned 中的位置,所以位置 1 和 2 处的答案都是 -1 。
通过一次翻转操作,可以将 1 放到位置 3 处,所以位置 3 的答案是 1 。

示例 2:

输入:n = 5, p = 0, banned = [2,4], k = 3
输出:[0,-1,-1,-1,-1]
解释:这个例子中 1 一开始在位置 0 处,所以此下标的答案为 0 。
翻转的子数组长度为 k = 3 ,1 此时在位置 0 处,所以我们可以翻转子数组 [0, 2],但翻转后的下标 2 在 banned 中,所以不能执行此操作。
由于 1 没法离开位置 0 ,所以其他位置的答案都是 -1 。

示例 3:

输入:n = 4, p = 2, banned = [0,1,3], k = 1
输出:[-1,-1,0,-1]
解释:这个例子中,我们只能对长度为 1 的子数组执行翻转操作,所以 1 无法离开初始位置。

提示:

  • 1 <= n <= 105
  • 0 <= p <= n - 1
  • 0 <= banned.length <= n - 1
  • 0 <= banned[i] <= n - 1
  • 1 <= k <= n
  • banned[i] != p
  • banned 中的值 互不相同

以后再看

你可能感兴趣的:(算法刷题记录,算法)