[Leetcode Weekly Contest]301

链接:LeetCode

[Leetcode]2335. 装满杯子需要的最短总时长

现有一台饮水机,可以制备冷水、温水和热水。每秒钟,可以装满 2 杯 不同 类型的水或者 1 杯任意类型的水。
给你一个下标从 0 开始、长度为 3 的整数数组 amount ,其中 amount[0]、amount[1] 和 amount[2] 分别表示需要装满冷水、温水和热水的杯子数量。返回装满所有杯子所需的 最少 秒数。

简单贪心。
肯定是最好每次都选两个。分两种情况,一种是有一种水特别多,那么答案就是这种水的数量。否则,一定可以匹配到只剩一杯,或匹配完。

class Solution {
    public int fillCups(int[] amount) {
        Arrays.sort(amount);
        if(amount[2] == 0) return 0;
        if(amount[2] > amount[0] + amount[1]) return amount[2];
        else return 1+fillCups(new int[]{amount[0], amount[1]-1, amount[2]-1});
    }
}

[Leetcode]2336. 无限集中的最小数字

现有一个包含所有正整数的集合 [1, 2, 3, 4, 5, ...] 。
实现 SmallestInfiniteSet 类:

  • SmallestInfiniteSet() 初始化 SmallestInfiniteSet 对象以包含 所有 正整数。
  • int popSmallest() 移除 并返回该无限集中的最小整数。
  • void addBack(int num) 如果正整数 num 不 存在于无限集中,则将一个 num 添加 到该无限集中。

逆向思维,既然原来的集合是无限的,那我就构造一个有限的。 收集被无限集踢掉的元素、踢掉被无限集收录的元素。

class SmallestInfiniteSet {
    HashSet<Integer> removeSet = new HashSet<>();
    int cur = 1;

    public SmallestInfiniteSet() {
    }

    public int popSmallest() {
        int res = cur;
        while(removeSet.contains(res)) {
            res++;
        }
        removeSet.add(res);
        cur = res+1;
        return res;
    }

    public void addBack(int num) {
        cur = Math.min(cur, num);
        if(removeSet.contains(num)) removeSet.remove(num);
    }
}

/**
 * Your SmallestInfiniteSet object will be instantiated and called as such:
 * SmallestInfiniteSet obj = new SmallestInfiniteSet();
 * int param_1 = obj.popSmallest();
 * obj.addBack(num);
 */

[Leetcode]2337. 移动片段得到字符串

给你两个字符串 start 和 target ,长度均为 n 。每个字符串 仅 由字符 'L'、'R' 和 '_' 组成,其中:

  • 字符 'L' 和 'R' 表示片段,其中片段 'L' 只有在其左侧直接存在一个 空位 时才能向 左 移动,而片段 'R' 只有在其右侧直接存在一个 空位 时才能向 右 移动。
  • 字符 '_' 表示可以被 任意 'L' 或 'R' 片段占据的空位。

如果在移动字符串 start 中的片段任意次之后可以得到字符串 target ,返回 true ;否则,返回 false 。

双指针 + 字符串模拟。

class Solution {
    public boolean canChange(String start, String target) {
        int n = start.length();
        int l = 0;
        for(int r=0;r<n;++r) {
            if(target.charAt(r) == '_') continue;
            else {
                char ch = target.charAt(r);
                while(l<n && start.charAt(l) == '_') {
                    ++l;
                }
                if(l>=n) return false;
                if(start.charAt(l)!=target.charAt(r))return false;
                if(target.charAt(r)=='L' && r>l)return false;
                if(target.charAt(r)=='R' && r<l)return false;
                ++l;
            }
        }
        while(l<n) {
            if(start.charAt(l) != '_') return false;
            l++;
        }
        return true;
    }
}

[Leetcode]2338. 统计理想数组的数目

给你两个整数 n 和 maxValue ,用于描述一个 理想数组 。
对于下标从 0 开始、长度为 n 的整数数组 arr ,如果满足以下条件,则认为该数组是一个 理想数组 :

  • 每个 arr[i] 都是从 1 到 maxValue 范围内的一个值,其中 0 <= i < n 。
  • 每个 arr[i] 都可以被 arr[i - 1] 整除,其中 0 < i < n 。

返回长度为 n 的 不同 理想数组的数目。由于答案可能很大,返回对 \(10^9 + 7\) 取余的结果。

组合数。分别考虑以x结尾长度为n的理想数组有多少个,数组结尾可以是1 ~ maxValue,因此把这些情况累加,就是最终结果。
以结尾为4、长度为5进行分析:
4的前面可以是4、2、1, 2的前面可以是2、1, 1的前面只能是1。例如:
[1, 2, 2, 4, 4]
[1, 1, 1, 4, 4]
[2, 2, 2, 4, 4]
[4, 4, 4, 4, 4]
以[1, 2, 2, 4, 4]为例,可以记为 \([\_, *2, \_, *2, \_]\),只需记录在哪些位置的元素发生了改变(倍增),
从当前倍增的位置开始 ~ 下一次倍增的位置之前(或数组结尾),将会一直维持这个值。
可假设每个数组的开头前面有一个值1,若数组中的第一个元素为1,则没有发生倍增;若第一个元素不是1,则发生了倍增。
例如:[2, 2, 2, 4, 4] 可表示为 \([*2, \_, \_, *2, \_]\)
在同一个位置可以发生多次倍增,例如:[1, 1, 1, 4, 4] 可表示为 \([\_, \_, \_, *2*2, \_]\);[4, 4, 4, 4, 4] 可表示为 \([*2*2, \_, \_, \_, \_]\)
由于固定了结尾为4,而4的质因子为2、2,即 4 = (1) * 2 * 2
所有结尾为4、长度为5的理想数组,问题可转化为 结尾数字(4)的质因子可以放在哪些位置,当前有5个不同的位置,2个质因子2,
从5个位置中选择一个(将2个2放在一个位置)或两个(将2个2放在不同位置),因为2个2是相同的,谁先谁后,结果都是一样的。所以这是个组合问题。
问题进一步转化为隔板法:把k个相同的小球放进n个不同的盒子中,允许有些盒子为空,也允许一个盒子中放入多个小球,有多少种不同的放法?
该问题可用隔板法来求解,把n个盒子当做n-1个隔板,然后加上k个小球,相当于总共有 n-1 + k 个位置,从中选出n-1个位置放隔板,
即方案数为:C(n-1+k)(n-1)
由于maxValue <= 104,质因子最小为2,213 = 8192 < 10^4 < 16384 = 2^14,质因子越大,质因子的个数将会越小,
所以质因子为2时,质因子的个数k才能达到最大值13,即 k <= 13。所以上面的 C(n-1+k)(n-1) 可写为 C(n-1+k)(k) ,k 显然远小于n-1.
若结尾数字由多个不同的质因子组成,例如:k1个2、k2个3、k3个5,则可将问题分解为:
1、从n-1 + k1个位置中选出k1个位置放质因子2,得到 C(n-1+k1)(k1)
2、从n-1 + k2个位置中选出k2个位置放质因子3,得到 C(n-1+k2)(k2)
3、从n-1 + k3个位置中选出k3个位置放质因子5,得到 C(n-1+k3)(k3)
这3种情况之间互不影响:放质因子5的时候,不用关心这个位置之前放没放过2、3,以及放了多少个2、多少个3。
所以可采用乘法原理来计算最终结果:C(n-1+k1)(k1) * C(n-1+k2)(k2) * C(n-1+k3)(k3)

综上,原问题最终转化为:质因数分解出所有的质因子及其个数(其实只关注个数k) + 计算组合数问题
计算组合数问题 可用动态规划进行计算,假设dp[i][j] 表示从i个位置中选择j个,即 C(i)(j)。该问题可分为两种情况:
1、选择了位置i,则只需再从i-1个位置中选择j-1个,即 C(i-1)(j-1)
2、未选择位置i,则需要从i-1个位置中选择j个,即 C(i-1)(j)
所以,dp[i][j] = dp[i-1][j-1] + dp[i-1][j]

class Solution {
    static final int MOD = (int) 1e9 + 7, MX = (int) 1e4 + 1, MX_K = 13; // 至多 13 个质因数
    static List[] ks = new List[MX]; // ks[x] 为 x 分解质因数后,每个质因数的个数列表
    static int[][] c = new int[MX + MX_K][MX_K + 1]; // 组合数

    static {
        for (var i = 1; i < MX; i++) {
            ks[i] = new ArrayList<Integer>();
            var x = i;
            for (var p = 2; p * p <= x; ++p) {
                if (x % p == 0) {
                    var k = 1;
                    for (x /= p; x % p == 0; x /= p) ++k;
                    ks[i].add(k);
                }
            }
            if (x > 1) ks[i].add(1);
        }

        c[0][0] = 1;
        for (var i = 1; i < MX + MX_K; ++i) {
            c[i][0] = 1;
            for (var j = 1; j <= Math.min(i, MX_K); ++j)
                c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % MOD;
        }
    }

    public int idealArrays(int n, int maxValue) {
        var ans = 0L;
        for (var x = 1; x <= maxValue; ++x) {
            var mul = 1L;
            for (var k : ks[x]) mul = mul * c[n + (int) k - 1][(int) k] % MOD;
            ans += mul;
        }
        return (int) (ans % MOD);
    }
}

参考:LeetCode

posted @ 2022-07-18 09:22  Jamest  阅读(43)  评论(0编辑  收藏  举报