lcDP1 - 动态规划1
1 动态规划
从背包问题开始:
最重要的是,能够用dp数组,1到3维度一般,去表示最终结果,对于具体的题目,dp[i][j]表示什么意思,将成为解答的关键
很多动态规划都可以使用带记忆化的搜索去做
2 例题
0410splitArrayMinMax 分割出最小的子数组最大值
1 题目
https://leetcode-cn.com/problems/split-array-largest-sum/
2 解题思路
- 1 dp: 首先搞明白动态规划的单元,注意,不仅仅是说增加一个元素,对分割造成了什么影响,而且还要考虑,不通的分割数目,本题目是分割,那么一定是分割数目以及分割对象带来的变化为dp的状态迁移, dp[i][j] means: res of: nums[:i] to be splited in j’s segments, dp[i][j] = max {dp[k][j-1], sum[k+1, i] | j <= k <= i - 1},所以
- 2 二分查找法:这样,判断一个数字能否作为分割m个子数组的方案?应该很好判断,顺序遍历即可
- 2.1 那么记录该数字为x,最小就是数组里的最大值,最大即为数组和,于是仅仅需要用二分法,从这个范围中找出该数字即可
- 2.2 具体如何二分?若x过于小了,会导致分割数目太大,然后我们就知道往大处搜索,反之同理
- 3 二分法的标准写法:
- 3.1 注意用>>代表除2,尤其是考虑负数时候有区别
- 3.2 注意当往大地方搜,st = mid+1,往小地方则不用,不然可能导致ed漏搜索
1
2
3
4
5
6
7
8
9
10
11
12
13bool lastCheck = false;
while(st < ed) {
x = (st + ed) >> 1; // means: floor of (st + ed)
lastCheck = xIsLarge(x, nums, m);
if(lastCheck) {
ed = x; // when ed - st = 1, (st + ed) >> 1 == st
} else {
st = x + 1;
}
}
return st;
1 | class Solution { |
0403frogCrossRiver 青蛙过河
1 题目
https://leetcode-cn.com/problems/frog-jump/
2 解题思路
- 1 动态规划:首先明白动态规划最终是指向结果的,于是,定义dp[i][k],为到达i的最后一跳长度为k,是否能够跳到目的地
- 1.1 首先有疑问:那k岂不是max(stone) - min(stone),那k就会非常大,不合理啊?错,因为每一跳长度最多增加1,然而你最多有n-1跳,于是 k <= n-1
- 1.2 状态转移: dp[i][k] = dp[j][k] || dp[j][k-1] || dp[j][k+1],j就是i的上一跳,那么我们可以直到,j到i,k=stone[i] - stone[j]
- 1.3 需要注意到上面的状态转移: 因为是从j跳,必须保证: k <= j+1,因为你从第j个石头开始跳,最远长度就是j+1
1 | class Solution { |
0488zumaGame 祖玛游戏
1 题目
https://leetcode-cn.com/problems/zuma-game/
2 解题思路
- 1 对于hand中,每次挑选一个去填补到board中,然后消除board中的球,接着用剩下的hand再选择一个,到board里中去消除,dfs去遍历即可
- 2 使用记忆化搜索 memo[board + “ “ + hand]记录了对应的board和hand的结果,为何” “是必须的?
- 2.1 考虑以下例子:
- 2.2 可以知道必须在中间的RR中先插入B,那么假设我们的第一次搜索从第一个字符G,第二个字符是B,开始,那么我们的memo中会有结果(若是不带空格):memo[RRYGGYYRBRYYGGYRRGGBB],这样当第一个字符变成B,我们会在memo发现一个失败的方法直接返回结果,导致改题变成没有结果,同时这个例子也解释了为何减枝的条件3需要在两个相同字符之间插入字符,如果带了空格,就能够绝对避免这个问题,因为:
- memo[RRYGGYYRBRYYGGYRR GGB] 和 memo[RRYGGYYRBRYYGGYRR GGBB]就能够区分开
“RRYGGYYRRYYGGYRR”
“GGBBB”
1 | class Solution { |
0552checkRecord 学生出勤记录
1 题目
https://leetcode-cn.com/problems/student-attendance-record-ii/
2 解题思路
- 1 初步思路 dp[i][j]作为直到i天,以j为出勤记录的所有记录数,但是会发现无法处理连续的L的情况
- 2 更改,采用官方题解思路: dp[i][j]为以连续j个l为结尾的记录数,首先只考虑PL,但是此方法不行,因为,A可以隔断两个L,所以,如果先算出所有PL的方法,然后将A插入,那么结果一定会比用A去隔断两个L少。
- 3 官方接单: dp[i][j][k]是含有j个A的k个L为结尾的记录数
1 | class Solution { |
0600countUncontinous1 不含连续1的非负整数
1 题目
https://leetcode-cn.com/problems/non-negative-integers-without-consecutive-ones/
2 解题思路
- 1 解题思路:
- 1.1 很显然的动态规划,首先考虑问题: 给你一个二进制数字a = 2^n,判断从0到a有多少个数字不含有连续的两个1?动态规划即可:
- dp[len][0] = dp[len - 1][1] + dp[len - 1][0];
- dp[len][1] = dp[len - 1][0];
- 其中dp[len][0]表示长度为len然后以0开始的二进制数字的集合中,含有多少个不连续为1的数字
- 1.2 有了上述的思考,那么对于1024,32这种数字的解答就很显而易见了,比如32 = 100000,那么答案就是:首先假设最高位为0,然后res += dp[5][0] + dp[5][1],这是所有: 0xxxx和1xxxx组成的答案,但是32是6位数字,还需要加上32本身即可
- 1.3 更近一步,对于a = 101010 和 b = 110000 这样的呢?
- 以每一个1对结果的贡献来思考,从高位到低位这样去思考:
- 首先拿a来说,我们看最高位,和32一样的解法,接下来我们找到下一个1,那么就是变成,找以前缀为10,后缀为 0xxx 的有多少种,那么动态规划直接找出来就行,那么为什么不是1xxx,因为1xxx加上前缀10可能就大于a了,就超出了范围,那么我们接着找到下一个1,也就是前缀为1010,找0x有多少种,然后最后找不到1,看看a本身是否合理加上即可
- 对于b,首先第一个1对最终结果的贡献都是和32一样的,那么第二个1呢?很显然,遇到了连续的第二个1,意味着后面的1对答案都不会有贡献,因为以11为前缀的都是不合法的,所以仅仅需要考虑,将第二个连续的1变成0,以10为前缀,xxxx有多少中方案,很简单,就是 dp[4][0] + dp[4][1]
- 1.1 很显然的动态规划,首先考虑问题: 给你一个二进制数字a = 2^n,判断从0到a有多少个数字不含有连续的两个1?动态规划即可:
1 | class Solution { |