剑指Offer14- I&&II -- dp
剪绳子I
1. 题目描述
2. 思路
这题的思路还是比较简单的,就是划分子区间,一个长度为 \(k\) 的绳子,我们可以将其划分为 \(i\) + \(j\),(\(i>=0,j>=0\))。
我们用 \(f[k]\) 表示长度为 \(k\) 的绳子划分长度之后的最大乘积,则:
\(f[i] = max(f[i], f[i] * f[j])\),NO NO NO,不是这样的!如果你只是这么简单的想,那就大错特错了!
虽然说,我们将 \(k\) 划分为了 \(i\) + \(j\),但是 \(i\) 和 \(j\) 有必要划分吗?就例如 \(2\),它不划分时的长度乘积为 \(2\),划分之后的长度乘积为 \(1*1=1\),可以看出,它不划分时的乘积更大!当然,只有 \(2\) 作为被划分的子区间时才可以不继续划分,如果 \(k=2\),那么我们就必须划分了。
因此,我们需要判断子区间,是否继续划分,即:
f[k] = max(f[k], f[i] * f[j]); // 两个都划分
f[k] = max(f[k], i * f[j]); // 划分 j
f[k] = max(f[k], f[i] * j); // 划分 i
f[k] = max(f[k], i * j); // 都不划分
// 当然,上买的形式太复杂了。我们可以简化为:
f[k] = max(f[k], max(i, f[i]) * max(j, fp[j]));
综上,我们其实就讨论了一个容易犯问题,给定原始长度 \(k\),我们是必须要划分的,但是划分之后的长度\(i\)和\(j\),可以划分,也可以不划分!
可能你会问?为什么是划分为两段,而不是三段,四段。。。?
划分为二段,三段不行!因为两段肯定包含三段,四段的情况,而三段不可能包含两段的情况,除非你划分一个 \(0\),但那样乘积也为 \(0\)了,同理,四段不可能包含两段和三段的情况。
3. 代码
class Solution {
public:
int cuttingRope(int n) {
int f[60];
// f[i][j] 表示长度为 i 的绳子剪成 j 段的最大乘机
memset(f, 0, sizeof f);
for(int i = 2; i <= n; i ++ ) {
for(int j = 0; j <= i; j ++ ) {
// [j] [i-j]
int a = max(i - j, f[i - j]);
int b = max(j, f[j]);
f[i] = max(a * b, f[i]);
}
}
return f[n];
}
};
剪绳子2
1. 题目描述
相较于上一题,本体数据范围由58上升到2000,这个数据范围再用上一题的方法肯定会溢出了!因此本题需要对答案取模,但是取模就意味着大小关系可能有问题!原本我是比你大的,但取模之后我比你小了!还会做吗?
2. 思路
前面已经分析过了,\(dp\) 是用不了了!那怎么办呢?
我们可以尝试分析一下规律,挖掘出来一些性质。我们可以通过 \(dp\) 求出来长度为 \(1~20\) (当然,样例够用就行)的绳子划分之后的最大长度,看看有没有什么规律。
dp[2] = 1 * 1
dp[3] = 1 * 2
------------
dp[4] = 2 * 2
dp[5] = 2 * 3
dp[6] = 3 * 3
dp[7] = 2 * 2 * 3
dp[8] = 2 *3 * 3
dp[9] = 3 * 3 * 3
dp[10] = 3 * 3 * 2 * 2
dp[11] = 3 * 3 * 3 * 2
dp[12] = 3 * 3 * 3 * 3
dp[13] = 3 * 3 * 3 * 2 * 2
我们将 \(k=2/3\) 的情况单独拿出来,是因为他们包含 \(1\),而其余都不包含 \(1\),观察 \(dp[4]~dp[13]\),可以发现,他们划分之后的长度不是 \(2\) 就是 \(3\),这就是规律!并且,\(3\) 的优先级比 \(2\) 要大。
因此,我们便得到了以下三个规律(\(k>=4\))
- 划分因子不是 \(2\) 就是 \(3\),并且 \(2\) 的个数要么为 \(1\),要么为 \(2\),要么为 \(o\)
- \(3\) 优先于 \(2\)
- 当 \(k=4\) 时,划分为 \(2+2\),而不是 \(3+1\)
只要找到了这些规律,就可以贪心求解了!
当然,这种贪心做法对于上一题也是适用的。
3. 代码
贪心 ,O(N)
class Solution {
public:
int cuttingRope(int n) {
const int mod = 1e9 + 7;
if(n == 2) return 1;
if(n == 3) return 2;
if(n == 4) return 4;
int res = 1;
while(n > 4) {
n -= 3;
res = (long long)res * 3 % mod;
}
// n == 2, 3, 4
if(n) res = (long long)res * n % mod;
return res;
}
};
贪心+快速幂 O(logN)
class Solution {
public:
int ksm(int a, int b, int mod) {
int res = 1;
while(b) {
if(b & 1) res = (long long)res * a % mod;
b >>= 1;
a = (long long)a * a % mod;
}
return res % mod;
}
int cuttingRope(int n) {
if(n == 2) return 1;
if(n == 3) return 2;
if(n == 4) return 4;
const int mod = 1e9 + 7;
int res = 1;
int cnt = n / 3; // 可以划分为多少个3
if(n % 3 == 1) { // 特盘下,等剩余一个1时,划分为2+2而不是1+3
cnt -- ;
res *= 4;
}
else if(n % 3 == 2) {
res *= 2;
}
return (long long)res * ksm(3, cnt, mod) % mod;
}
};