动态规划——划分型
目录
- 划分型动态规划概述
- 最少将n分成几个完全平方数之和
- 求字符串划分回文串的最少次数
- 求最短时间抄写完所有的书
1. 划分型动态规划概述
给定长度为N的序列或字符串,要求划分成若干段
- 段数不限,或指定K段
- 每一段满足一定的性质
2. 最少将n分成几个完全平方数之和
1. 题目描述:给定一个正整数n,问最少可以将n分成几个完全平方数之和
例子:
输入:n=13
输出:2(13=4+9)
2. 思路
-
确定状态
- 最优策略中最后一个完全平方数
j^2
,那么n-j^2
也一定能被划分成最少的完成平方数之和。
- 最优策略中最后一个完全平方数
-
转移方程
- 设 f[i] 表示 i 最少被分成几个完全平方数之和
f[i] = min(1<=jj<=i){f[i-jj] + 1}
- 设 f[i] 表示 i 最少被分成几个完全平方数之和
-
初始条件和边界情况
初始条件:f[0] = 0 -
计算顺序
初始化 f[0]
计算f[1]…f[N]
答案是 f[N]
3. 代码实现
public static int numSquares(int n) {
int[] f = new int[n];
f[0] = 0;
for (int i = 1; i <= n; i++) {
f[i] = Integer.MAX_VALUE;
for (int j = 1; j * j <= i; j++) {
f[i] = Math.min(f[i - j * j] + 1, f[i]);
}
}
return f[n];
}
3. 求字符串划分回文串的最少次数
1. 题目描述
给定一个字符串S[0…N-1],将这个字符串划分成若干段,每一段都是一个回文串,求最少划分次数
例如:
输入:“aab”
输出:1(划分1次->“aa”,“b”)
2. 思路
-
确定状态
关注最优策略中最后一段回文串,设为S[j…N-1],那么则需要知道S前 j 个字符[0…j-1]最少可以划分成几个回文串 -
转移方程
设 f[i] 为前 i 个字符 S[0…i-1] 最少可以划分成几个回文串
f[i] = min(j=0,…i-1){f[j]+1|S[j…i+1]是回文串} -
初始条件和边界情况
初始条件:f[0] = 0 -
计算顺序
f[0],f[1],…f[N]
答案是 f[N]-1
3. 代码实现
private boolean[][] calcPalin(char[] s) {
int n = s.length;
boolean[][] f = new boolean[n][n];
int i, j, c;
for (i = 0; i < n; i++) {
for (j = i; j < n; j++) {
f[i][j] = false;
}
}
//奇数
for (c = 0; c < n; c++) {
i = j = c;
while (i >= 0 && j < n && s[i] == s[j]) {
f[i][j] = true;
i--;
j++;
}
}
//偶数
for (c = 0; c < n; c++) {
i = c;
j = c + 1;
while (i >= 0 && j < n && s[i] == s[j]) {
f[i][j] = true;
i--;
j++;
}
}
return f;
}
public int minCut(String ss) {
char[] s = ss.toCharArray();
int n = s.length;
if (n == 0) {
return 0;
}
boolean[][] isPalin = calcPalin(s);
int[] f = new int[n + 1];
f[0] = 0;
for (int i = 1; i <= n; i++) {
f[i] = Integer.MAX_VALUE;
for (int j = 0; j < i; j++) {
if (isPalin[j][i - 1]) {
f[i] = Math.min(f[i], f[j] + 1);
}
}
}
return f[n] - 1;
}
4. 求最短时间抄写完所有的书
1. 题目描述
有N本书需要被抄写,第i本书有A[i]页,i=0,1,…N-1。
有K个抄写员,每个抄写员可以抄写连续的若干本书(例如:第3~5本书,或者第10本书),每个抄写员的抄写速度都一样:一分钟一页。
最少需要多少时间抄写完所有的书
例子:
输入:A=[3,2,4],K=2
输出:5(第一个抄写员抄写第1本和第2本,第二个抄写员抄写第3本书)
2. 思路
- 确定状态
最后一步:最优策略中最后一个抄写员Bob(设他为第K个)抄写的部分为一段连续的书,包含最后一本,如果Bob抄写第j本到n-1本书,则Bob需要时间A[j]+…+A[N-1]
则需要知道前面K-1个人最少需要多少时间抄完前j本书(第0~i-1本书)
状态:设f[k][i]为前k个抄写员最少需要多少时间抄完前i本书 - 转移方程
设f[k][i]为前k个抄写员最少需要多少时间抄完前i本书
f[k][i] = min(j=0,…i){max{f[k-1][j], A[j]+…+A[N-1]}} - 初始条件和边界情况
0个抄写员只能抄0本书:f[0][0] = 0,f[0][1] = f[0][2] = … = f[0][N] = 正无穷
k个抄写员(k>0)需要0时间抄0本书:f[k][0] = 0 (k>0)
如果K>N,可以赋值K=N - 计算顺序
计算 f[0][0],f[0][1],…f[0][N]
计算 f[1][0],f[1][1],…f[1][N]
…
计算 f[K][0],f[K][1],…f[K][N]
时间复杂度O(N^2K),空间复杂度O(NK)
3. 代码实现
public int copyBook(int K, int[] A) {
int n = A.length;
if (n == 0) {
return 0;
}
int[][] f = new int[K + 1][n + 1];
f[0][0] = 0;
for (int i = 1; i <= n; i++) {
f[0][i] = Integer.MAX_VALUE;
}
for (int k = 1; k <= K; k++) {
f[k][0] = 0;
for (int i = 1; i <= n; i++) {
f[k][i] = Integer.MAX_VALUE;
int sum = 0;
for (int j = i; j >= 0; j--) {
f[k][i] = Math.min(f[k][i], Math.max(f[k - 1][j], sum));
if (j > 0) {
sum += A[j - 1];
}
}
}
}
return f[K][n];
}
5. 解密方式
1. 题目描述
- 有一段由A-Z组成的字母串信息被加密成数字串,加密方式为:A->1, B->2,…, Z->26,
- 给定加密后的数字串S[0…N-1],问有多少种方式解密成字母串
例子:
输入:12
输出:2(AB或者L)
2. 思路
- 确定状态
1. 解密数字串即划分成若干段数字,每段数字对应一个字母
2. 最后一步:对应一个字母A, B,…,Z,这个字母加密时变成1, 2, …26
一共100+50=150种解密方式
3. 要求数字串前N个字符的解密方式数,需要知道数字串前N-1和N-2个字符的解密方式数 - 转移方程
1. 状态:设数字串S前i个数字解密成字母串有f[i]种方式
- 初始条件和边界情况
初始条件:f[0]=1,即空串有1种方式解密
边界情况:如果i=1,只看最后一个数字 - 计算顺序
f[0], f[1],…,f[N]
答案是f[N]
3. 代码实现
public int numDecodings(String ss) {
if (ss.length() == 0) {
return 1;
}
char[] s = ss.toCharArray();
int n = s.length;
int[] f = new int[n + 1];
f[0] = 1;
for (int i = 1; i <= n; i++) {
f[i] = 0;
if (s[i - 1] >= '1' && s[i - 1] <= '9') {
f[i] += f[i - 1];
}
if (i > 1) {
int num = 10 * (s[i - 2] - '0') + (s[i - 1] - '0');
if (num >= 10 || num <= 26) {
f[i] += f[i - 2];
}
}
}
return f[n];
}