【题解】—— LeetCode一周小结34

简介: LeetCode每日一道一周小结34

🌟欢迎来到 我的博客 —— 探索技术的无限可能!

🌟博客的简介(文章目录)


【题解】—— 每日一道题目栏


上接:【题解】—— LeetCode一周小结33

19.学生出勤记录 II

题目链接:552. 学生出勤记录 II

可以用字符串表示一个学生的出勤记录,其中的每个字符用来标记当天的出勤情况(缺勤、迟到、到场)。记录中只含下面三种字符:

‘A’:Absent,缺勤

‘L’:Late,迟到

‘P’:Present,到场

如果学生能够 同时 满足下面两个条件,则可以获得出勤奖励:

按 总出勤 计,学生缺勤(‘A’)严格 少于两天。

学生 不会 存在 连续 3 天或 连续 3 天以上的迟到(‘L’)记录。

给你一个整数 n ,表示出勤记录的长度(次数)。请你返回记录长度为 n 时,可能获得出勤奖励的记录情况 数量 。答案可能很大,所以返回对 109 + 7 取余 的结果。

示例 1:

输入:n = 2

输出:8

解释:

有 8 种长度为 2 的记录将被视为可奖励:

“PP” , “AP”, “PA”, “LP”, “PL”, “AL”, “LA”, “LL”

只有"AA"不会被视为可奖励,因为缺勤次数为 2 次(需要少于 2 次)。

示例 2:

输入:n = 1

输出:3

示例 3:

输入:n = 10101

输出:183236316

提示:

1 <= n <= 105

题解:

方法:递归搜索 + 保存递归返回值 = 记忆化搜索

       

class Solution {
    private static final int MOD = 1_000_000_007;
    private static final int MX = 100_001;
    private static final int[][][] memo = new int[MX][2][3];
    public int checkRecord(int n) {
        return dfs(n, 0, 0);
    }
    private static int dfs(int i, int j, int k) {
        if (i == 0) {
            return 1;
        }
        if (memo[i][j][k] > 0) { // 之前计算过
            return memo[i][j][k];
        }
        long res = dfs(i - 1, j, 0); // 填 P
        if (j == 0) {
            res += dfs(i - 1, 1, 0); // 填 A
        }
        if (k < 2) {
            res += dfs(i - 1, j, k + 1); // 填 L
        }
        return memo[i][j][k] = (int) (res % MOD);
    }
}

20.到达第 K 级台阶的方案数

题目链接:3154. 到达第 K 级台阶的方案数

给你有一个 非负 整数 k 。有一个无限长度的台阶,最低 一层编号为 0 。

Alice 有一个整数 jump ,一开始值为 0 。Alice 从台阶 1 开始,可以使用 任意 次操作,目标是到达第 k 级台阶。假设 Alice 位于台阶 i ,一次 操作 中,Alice 可以:

向下走一级到 i - 1 ,但该操作 不能 连续使用,如果在台阶第 0 级也不能使用。

向上走到台阶 i + 2jump 处,然后 jump 变为 jump + 1 。

请你返回 Alice 到达台阶 k 处的总方案数。

注意,Alice 可能到达台阶 k 处后,通过一些操作重新回到台阶 k 处,这视为不同的方案。

示例 1:

输入:k = 0

输出:2

解释:

2 种到达台阶 0 的方案为:

Alice 从台阶 1 开始。

执行第一种操作,从台阶 1 向下走到台阶 0 。

Alice 从台阶 1 开始。

执行第一种操作,从台阶 1 向下走到台阶 0 。

执行第二种操作,向上走 20 级台阶到台阶 1 。

执行第一种操作,从台阶 1 向下走到台阶 0 。

示例 2:

输入:k = 1

输出:4

解释:

4 种到达台阶 1 的方案为:

Alice 从台阶 1 开始,已经到达台阶 1 。

Alice 从台阶 1 开始。

执行第一种操作,从台阶 1 向下走到台阶 0 。

执行第二种操作,向上走 20 级台阶到台阶 1 。

Alice 从台阶 1 开始。

执行第二种操作,向上走 20 级台阶到台阶 2 。

执行第一种操作,向下走 1 级台阶到台阶 1 。

Alice 从台阶 1 开始。

执行第一种操作,从台阶 1 向下走到台阶 0 。

执行第二种操作,向上走 20 级台阶到台阶 1 。

执行第一种操作,向下走 1 级台阶到台阶 0 。

执行第二种操作,向上走 21 级台阶到台阶 2 。

执行第一种操作,向下走 1 级台阶到台阶 1 。

提示:

0 <= k <= 109

题解:

方法:记忆化搜索

       

class Solution {
    public int waysToReachStair(int k) {
        return dfs(1, 0, 0, k, new HashMap<>());
    }
    // preDown = 0/1 表示 false/true
    private int dfs(int i, int j, int preDown, int k, Map<Long, Integer> memo) {
        if (i > k + 1) { // 无法到达终点 k
            return 0;
        }
        // 把状态 (i, j, preDown) 压缩成一个 long
        long mask = (long) i << 32 | j << 1 | preDown;
        if (memo.containsKey(mask)) { // 之前算过了
            return memo.get(mask);
        }
        int res = i == k ? 1 : 0;
        res += dfs(i + (1 << j), j + 1, 0, k, memo); // 操作二
        if (preDown == 0) {
            res += dfs(i - 1, j, 1, k, memo); // 操作一
        }
        memo.put(mask, res); // 记忆化
        return res;
    }
}

21.价值和小于等于 K 的最大数字

题目链接:3007. 价值和小于等于 K 的最大数字

给你一个整数 k 和一个整数 x 。整数 num 的价值是它的二进制表示中在 x,2x,3x 等位置处

设置位

的数目(从最低有效位开始)。下面的表格包含了如何计算价值的例子。

x num Binary Representation Price

1 13 000001101 3

2 13 000001101 1

2 233 011101001 3

3 13 000001101 1

3 362 101101010 2

num 的 累加价值 是从 1 到 num 的数字的 总 价值。如果 num 的累加价值小于或等于 k 则被认为是 廉价 的。

请你返回 最大 的廉价数字。

示例 1:

输入:k = 9, x = 1

输出:6

解释:由下表所示,6 是最大的廉价数字。

x num Binary Representation Price Accumulated Price

1 1 001 1 1

1 2 010 1 2

1 3 011 2 4

1 4 100 1 5

1 5 101 2 7

1 6 110 2 9

1 7 111 3 12

示例 2:

输入:k = 7, x = 2

输出:9

解释:由下表所示,9 是最大的廉价数字。

x num Binary Representation Price Accumulated Price

2 1 0001 0 0

2 2 0010 1 1

2 3 0011 1 2

2 4 0100 0 2

2 5 0101 0 2

2 6 0110 1 3

2 7 0111 1 4

2 8 1000 1 5

2 9 1001 1 6

2 10 1010 2 8

提示:

1 <= k <= 1015

1 <= x <= 8

题解:

方法:二分答案 + 数位 DP

       

class Solution {
    public long findMaximumNumber(long k, int x) {
        this.x = x;
        // 开区间二分,原理见 https://www.bilibili.com/video/BV1AP41137w7/
        long left = 0;
        long right = (k + 1) << x;
        while (left + 1 < right) {
            long mid = (left + right) >>> 1;
            if (countDigitOne(mid) <= k) {
                left = mid;
            } else {
                right = mid;
            }
        }
        return left;
    }
    private int x;
    private long num;
    private long memo[][];
    private long countDigitOne(long num) {
        this.num = num;
        int m = 64 - Long.numberOfLeadingZeros(num);
        memo = new long[m][m + 1];
        for (long[] row : memo) {
            Arrays.fill(row, -1);
        }
        return dfs(m - 1, 0, true);
    }
    private long dfs(int i, int cnt1, boolean isLimit) {
        if (i < 0) {
            return cnt1;
        }
        if (!isLimit && memo[i][cnt1] != -1) {
            return memo[i][cnt1];
        }
        int up = isLimit ? (int) (num >> i & 1) : 1;
        long res = 0;
        for (int d = 0; d <= up; d++) { // 枚举要填入的数字 d
            res += dfs(i - 1, cnt1 + (d == 1 && (i + 1) % x == 0 ? 1 : 0), isLimit && d == up);
        }
        if (!isLimit) {
            memo[i][cnt1] = res;
        }
        return res;
    }
}

22.数组最后一个元素的最小值

题目链接:3133. 数组最后一个元素的最小值

给你两个整数 n 和 x 。你需要构造一个长度为 n 的 正整数 数组 nums ,对于所有 0 <= i < n - 1 ,满足 nums[i + 1] 大于 nums[i] ,并且数组 nums 中所有元素的按位 AND 运算结果为 x 。

返回 nums[n - 1] 可能的 最小 值。

示例 1:

输入:n = 3, x = 4

输出:6

解释:

数组 nums 可以是 [4,5,6] ,最后一个元素为 6 。

示例 2:

输入:n = 2, x = 7

输出:15

解释:

数组 nums 可以是 [7,15] ,最后一个元素为 15 。

提示:

1 <= n, x <= 108

题解:

方法:位运算

       

class Solution {
    public long minEnd(int n, int x) {
        n--; // 先把 n 减一,这样下面讨论的 n 就是原来的 n-1
        long ans = x;
        int i = 0, j = 0;
        while ((n >> j) > 0) {
            // x 的第 i 个比特值是 0,即「空位」
            if ((ans >> i & 1) == 0) {
                // 空位填入 n 的第 j 个比特值
                ans |= (long) (n >> j & 1) << i;
                j++;
            }
            i++;
        }
        return ans;
    }
}

23.大数组元素的乘积

题目链接:3145. 大数组元素的乘积

一个非负整数 x 的 强数组 指的是满足元素为 2 的幂且元素总和为 x 的最短有序数组。下表说明了如何确定 强数组 的示例。可以证明,x 对应的强数组是独一无二的。

数字 二进制表示 强数组

1 00001 [1]

8 01000 [8]

10 01010 [2, 8]

13 01101 [1, 4, 8]

23 10111 [1, 2, 4, 16]

我们将每一个升序的正整数 i (即1,2,3等等)的 强数组 连接得到数组 big_nums ,big_nums 开始部分为 [1, 2, 1, 2, 4, 1, 4, 2, 4, 1, 2, 4, 8, …] 。

给你一个二维整数数组 queries ,其中 queries[i] = [fromi, toi, modi] ,你需要计算 (big_nums[fromi] * big_nums[fromi + 1] * … * big_nums[toi]) % modi 。

请你返回一个整数数组 answer ,其中 answer[i] 是第 i 个查询的答案。

示例 1:

输入:queries = [[1,3,7]]

输出:[4]

解释:

只有一个查询。

big_nums[1…3] = [2,1,2] 。它们的乘积为 4。结果为 4 % 7 = 4。

示例 2:

输入:queries = [[2,5,3],[7,7,4]]

输出:[2,2]

解释:

有两个查询。

第一个查询:big_nums[2…5] = [1,2,4,1] 。它们的乘积为 8 。结果为 8 % 3 = 2。

第二个查询:big_nums[7] = 2 。结果为 2 % 4 = 2。

提示:

1 <= queries.length <= 500

queries[i].length == 3

0 <= queries[i][0] <= queries[i][1] <= 1015

1 <= queries[i][2] <= 105

题解:

方法:试填法

       

class Solution {
    public int[] findProductsOfElements(long[][] queries) {
        int[] ans = new int[queries.length];
        for (int i = 0; i < queries.length; i++) {
            long[] q = queries[i];
            long er = sumE(q[1] + 1);
            long el = sumE(q[0]);
            ans[i] = pow(2, er - el, q[2]);
        }
        return ans;
    }
    private long sumE(long k) {
        long res = 0;
        long n = 0;
        long cnt1 = 0; // 之前填的 1 的个数
        long sumI = 0; // 之前填的 1 的幂次之和
        for (long i = 63 - Long.numberOfLeadingZeros(k + 1); i >= 0; i--) {
            long c = (cnt1 << i) + (i << i >> 1); // 新增的幂次个数
            if (c <= k) {
                k -= c;
                res += (sumI << i) + ((i * (i - 1) / 2) << i >> 1);
                sumI += i;
                cnt1++;
                n |= 1L << i; // 填 1
            }
        }
        // 剩余的 k 个幂次,由 n 的低 k 个 1 补充
        while (k-- > 0) {
            res += Long.numberOfTrailingZeros(n);
            n &= n - 1; // 去掉最低位的 1(置为 0)
        }
        return res;
    }
    private int pow(long x, long n, long mod) {
        long res = 1 % mod; // 注意 mod 可能等于 1
        for (; n > 0; n /= 2) {
            if (n % 2 == 1) {
                res = res * x % mod;
            }
            x = x * x % mod;
        }
        return (int) res;
    }
}

24.两个字符串的排列差

题目链接:3146. 两个字符串的排列差

给你两个字符串 s 和 t,每个字符串中的字符都不重复,且 t 是 s 的一个排列。

排列差 定义为 s 和 t 中每个字符在两个字符串中位置的绝对差值之和。

返回 s 和 t 之间的 排列差 。

示例 1:

输入:s = “abc”, t = “bac”

输出:2

解释:

对于 s = “abc” 和 t = “bac”,排列差是:

“a” 在 s 中的位置与在 t 中的位置之差的绝对值。

“b” 在 s 中的位置与在 t 中的位置之差的绝对值。

“c” 在 s 中的位置与在 t 中的位置之差的绝对值。

即,s 和 t 的排列差等于 |0 - 1| + |2 - 2| + |1 - 0| = 2。

示例 2:

输入:s = “abcde”, t = “edbac”

输出:12

解释: s 和 t 的排列差等于 |0 - 3| + |1 - 2| + |2 - 4| + |3 - 1| + |4 - 0| = 12。

提示:

1 <= s.length <= 26

每个字符在 s 中最多出现一次。

t 是 s 的一个排列。

s 仅由小写英文字母组成。

题解:

方法:哈希

       

class Solution {
    public int findPermutationDifference(String s, String t) {
        int[] pos = new int[26];
        for (int i = 0; i < s.length(); i++) {
            pos[s.charAt(i) - 'a'] = i;
        }
        int ans = 0;
        for (int i = 0; i < t.length(); i++) {
            ans += Math.abs(i - pos[t.charAt(i) - 'a']);
        }
        return ans;
    }
}

25.划分为k个相等的子集

题目链接:698. 划分为k个相等的子集

给定一个整数数组 nums 和一个正整数 k,找出是否有可能把这个数组分成 k 个非空子集,其总和都相等。

示例 1:

输入: nums = [4, 3, 2, 3, 5, 2, 1], k = 4

输出: True

说明: 有可能将其分成 4 个子集(5),(1,4),(2,3),(2,3)等于总和。

示例 2:

输入: nums = [1,2,3,4], k = 3

输出: false

提示:

1 <= k <= len(nums) <= 16

0 < nums[i] < 10000

每个元素的频率在 [1,4] 范围内

题解:

方法:DFS + 剪枝

       

class Solution {
    private int[] nums;
    private int[] cur;
    private int s;
    public boolean canPartitionKSubsets(int[] nums, int k) {
        for (int v : nums) {
            s += v;
        }
        if (s % k != 0) {
            return false;
        }
        s /= k;
        cur = new int[k];
        Arrays.sort(nums);
        this.nums = nums;
        return dfs(nums.length - 1);
    }
    private boolean dfs(int i) {
        if (i < 0) {
            return true;
        }
        for (int j = 0; j < cur.length; ++j) {
            if (j > 0 && cur[j] == cur[j - 1]) {
                continue;
            }
            cur[j] += nums[i];
            if (cur[j] <= s && dfs(i - 1)) {
                return true;
            }
            cur[j] -= nums[i];
        }
        return false;
    }
}

下接:【题解】—— LeetCode一周小结34


相关文章
|
1月前
|
索引
|
1月前
|
测试技术 索引
【题解】—— LeetCode一周小结40
LeetCode每日一道一周小结40
|
2月前
|
机器人
|
2月前
|
人工智能 BI
|
3月前
|
人工智能 BI 测试技术
|
5月前
|
人工智能 BI
|
5月前
|
算法