写在前面
最近这一周多吧,都在做动态规划的题目。把一些经典的动态规划题目基础夯实(比如各大教材中关于动态规划章节所讲的例子),然后对每一道动态规划的题目,都要仔细思考 这个递归关系是怎么得来的,这样的话动态规划的功力才会得到进步。
Minimum Path Sum
题目描述
给定一个mxn的非负整数矩阵,问你求得一条从左上到右下的路径,使得这条路径上的数字和最小,每次只能往下走一格或者往右走一格。返回这个最小值。
思路
这道题的动态规划思路还是很明显的。我们定义一个dp[i][j]表示到达点(i, j)的最短路径。显然,该状态可由两个子状态推导得到,(i-1, j)往右走一格或者(i, j-1)往下走一格,所以递归关系:
dp[i][j] = min{dp[i-1][j], dp[i][j-1]} + grid[i][j];
边界条件
dp[0][0] = grid[0][0];
dp[0][j] = dp[0][j-1] + grid[0][j], 0 < j < n;
dp[i][0] = dp[i-1][0] + grid[i][0], 0 < i < m;
代码
int minPathSum(vector<vector<int>> &grid) {
// write your code here
if (grid.size() == 0 || grid[0].size() == 0) return 0;
int m = grid.size(), n = grid[0].size();
vector<vector<int>> dp(m, vector<int>(n, 0));
dp[0][0] = grid[0][0];
for (int i = 1; i < m; i++) dp[i][0] = dp[i-1][0] + grid[i][0];
for (int j = 1; j < n; j++) dp[0][j] = dp[0][j-1] + grid[0][j];
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j];
}
}
return dp[m-1][n-1];
}
Paint Fence
题目描述
假设有n个点,每一个点都有k种颜色可以涂。你现在要给所有的点涂色,但是要求不能有超过两个的邻近的点拥有相同的颜色。请问这样的涂色方式一共有多少种。
思路
这道题的英语真是捉急了,后面的那句”such that no more than two adjacent fence posts have the same color”愣是没怎么读懂。直到看了别人的答案才明白原来是这个要求。就是说,连续相同的颜色不能超过两个。即”000”就不符合题意,但是”0011”就符合题意。
我们发现对于第i个点来说,如果要求其颜色和前面一个点不一样,那么就有k-1种途法,如果和前面的点一样,那么就首先得要求前面的点不能和前前一个点颜色相同,否则就不符合题意。因此,对第i个点,我们用dp[i][0]表示第i个点和前面一点颜色不同的涂法,而用dp[i][1]表示第i个点和前面一点颜色相同的途法。这样,得到如下递归关系:
dp[i][0] = (dp[i-1][0]+dp[i-1][1]) * (k-1);
dp[i][1] = dp[i-1][0];
//机智的小伙伴肯定发现了可以用两个变量来代替dp,即diff = dp[i-1][0], same = diff[i-1][1],那么上述递推关系就变为:
int tmp = diff;
diff = (same + diff)*(k-1);
same = tmp;
边界条件:第一个点same = 0, diff = k.
代码
int numWays(int n, int k) {
// write your code here
if (n == 1) return k;
int same = 0, diff = k;
for (int i = 2; i <= n; i++) {
int t = diff;
diff = (same+diff)*(k-1);
same = t;
}
return same+diff;
}
总结
有的时候先用复杂的递归公式将问题刻画出来,然后再考虑如何优化空间复杂度。
Cutting a Rod
题目描述
有一根长为n的绳子,然后同时给定了长度为i的绳子可以卖j元。那么问题如何割这根绳子,