动态规划--Day02--爬楼梯--2466. 统计构造好字符串的方案数,2533. 好二进制字符串的数量,2266. 统计打字方案数
动态规划–Day02–爬楼梯–2466. 统计构造好字符串的方案数,2533. 好二进制字符串的数量,2266. 统计打字方案数
今天要训练的题目类型是:【动态规划】,题单来自@灵艾山茶府。
掌握动态规划(DP)是没有捷径的,咱们唯一能做的,就是投入时间猛猛刷题。
动态规划要至少刷100道才算入门!
记忆化搜索是新手村神器。方便理解,写完之后可以转译成递推。
但是有些题目只能写递推,才能优化时间复杂度。熟练之后直接写递推也可以。
2466. 统计构造好字符串的方案数
方法:记忆化搜索
思路:
- 如果连续做几道爬楼梯,应该可以看到这个也是爬楼梯的模板题
- 楼顶在哪里?[low,high]之间的每个数都是楼顶。
- 也就是有楼顶[n=low, n=low+1, n=…, n=high-1, n=high]这么多条楼梯要爬。全部都要爬一次,累加结果。
- 每次一可以跳几步?可以跳zero或者one步。不是跳0或者1步,而是zero的值的步数。换句话说,步幅可以取zero的值或者one的值。
- 注意每次加和都要取模,不然会溢出!
class Solution {private final int mod = 1000000007;public int countGoodStrings(int low, int high, int zero, int one) {int count = 0;int[] memo = new int[high + 1];Arrays.fill(memo, -1);// 每个楼顶都要跳for (int i = low; i <= high; i++) {count = (count + dfs(i, memo, zero, one)) % mod;}return count;}private int dfs(int i, int[] memo, int zero, int one) {// 如果i==0,跳到0级台阶有1种方法:不跳if (i == 0) {return 1;}// 如果算过了这种情况,直接返回记忆(缓存)if (memo[i] != -1) {return memo[i];} else {int res1 = 0;int res2 = 0;// 当前台阶要大于步幅if (i >= zero) {res1 = dfs(i - zero, memo, zero, one) % mod;}if (i >= one) {res2 = dfs(i - one, memo, zero, one) % mod;}// 要么跳zero步到i,要么跳one步到i,这两种互相独立,所以要加和。memo[i] = (res1 + res2) % mod;}return memo[i];}
}
方法:递推(动态规划)
思路:
初始化dp[0]:如果i==0,跳到0级台阶有1种方法:不跳
从1开始一直遍历到high。算出每个i作为楼顶的方法数。
当i>=low,是需要的楼顶,开始累加到答案(记得取模)
class Solution {public int countGoodStrings(int low, int high, int zero, int one) {final int mod = 1000000007;int count = 0;int[] dp = new int[high + 1];dp[0] = 1;for (int i = 1; i <= high; i++) {int res1 = 0;int res2 = 0;// 当前台阶要大于步幅if (i >= zero) {res1 = dp[i - zero];}if (i >= one) {res2 = dp[i - one];}dp[i] = (res1 + res2) % mod;// 从low开始才是需要的楼顶if (i >= low) {count = (count + dp[i]) % mod;}}return count;}
}
2533. 好二进制字符串的数量
方法:记忆化搜索
方法:递推(动态规划)
思路:
看这个这个是不是好多朋友都有疑问这是啥?哈哈哈。
把上题的答案赋值过来,就对了!
仔细读题之后,发现是一模一样的题。只能说,要锻炼阅读理解的能力了。
class Solution {public int goodBinaryStrings(int minLength, int maxLength, int oneGroup, int zeroGroup) {return countGoodStrings(minLength, maxLength, zeroGroup, oneGroup);}
}
2266. 统计打字方案数
方法:递推(动态规划)
思路:
- 这题就不用记忆化搜索了。因为按键有两类,按键7和9要单独讨论,就要写两个dfs算法。我们直接写递推。
- 题意:要分组,每n个相同的按键当做相同的一组,先算组内这些按键可以有多少种字母的情况。然后把每组的情况数相乘(因为是独立的,每组都是不同的按键,不会影响)
- 到这里,大致能想到,因为要相乘,最终结果res要初始化为1;因为按键7和9不同,所以要分类讨论;当n个相同的字母连在一起,有多少种情况?怎么求?
爬楼梯就蕴含在这里面了:当n个相同的字母连在一起,有多少种情况?
- g数组,存按键7和9的情况。它们要有前4个情况推出来(比如按键7,有pqrs)
- f数组,存其余按键的情况。它们由前3个情况推出来
- f[n]表示,当n个相同的字母连在一起,有多少种情况。g[n]同理。
求完了f和g之后,遍历字符串,相同的字符就计数成为一组,每一组用f或者g求出种类数,再把每组的种类数相乘,得到结果。
class Solution {public int countTexts(String pressedKeys) {char[] s = pressedKeys.toCharArray();int n = s.length;final int MOD = 1000000007;// 为什么是n+5呢?因为当 n<4 的时候,进不了循环。但是仍然要初始化,数组得够大,不然索引会越界。// g数组,存按键7和9的情况。它们要有前4个情况推出来long[] g = new long[n + 4];// f数组,存其余按键的情况。它们由前3个情况推出来long[] f = new long[n + 4];// 前4个情况要手动算f[0] = g[0] = 1;f[1] = g[1] = 1;f[2] = g[2] = 2;f[3] = g[3] = 4;// f[n]表示,当n个相同的字母连在一起,有多少种情况。g[n]同理。for (int i = 4; i <= n; i++) {f[i] = (f[i - 1] + f[i - 2] + f[i - 3]) % MOD;g[i] = (g[i - 1] + g[i - 2] + g[i - 3] + g[i - 4]) % MOD;}long res = 1;// count统计,当前有多少个字母连在一起int count = 0;// 遍历字符串for (int i = 0; i < n; i++) {char c = s[i];count++;// 如果是结尾,或者下一个字母就要不同了。开始统计if (i == n - 1 || c != s[i + 1]) {// 如果是按键7或者9,使用g数组,否则使用f数组if (c == '7' || c == '9') {res = res * g[count] % MOD;} else {res = res * f[count] % MOD;}// 相同字母的数量重置count = 0;}}return (int) res;}
}