DP代码的模版:两个for 循环,然后在循环里面写递推表达式。返回数组第一个值或最后一个值。要注意的是DP问题的解代码中是不存在递归的。从遍历各个元素的角度思考递归式。
递推表达式:
minpath[k][i] = min( minpath[k+1][i], minpath[k+1][i+1]) + triangle[k][i];
int minimumTotal(vector<vector<int> > &triangle) {
int n = triangle.size();
vector<int> minlen(triangle.back());
for (int layer = n-2; layer >= 0; layer--) // For each layer
{
for (int i = 0; i <= layer; i++) // Check its every 'node'
{
// Find the lesser of its two children, and sum the current value in the triangle with it.
minlen[i] = min(minlen[i], minlen[i+1]) + triangle[layer][i];
}
}
return minlen[0];
}
这题跟这几题有点像
int* countBits(int num, int *returnSize)
{
num++;
*returnSize = num;
int* arr = (int*)malloc(sizeof(int)*num);
arr[0] = 0;
for(int i = 1; i < num; i++)
arr[i] = (i&1)? arr[i>>1]+1 : arr[i>>1];
return arr;
}
//这题等价与完全背包问题,即从1到n-1取数,可以取重复的数使总的乘积最大。
列出了4中情况 8 = Max{dp[3] + 5, dp[3] + dp[5], 3 + 5, 3 + dp[5]} (i-j = 3, j =5)
class Solution {
public int integerBreak(int n) {
int [] dp = new int[n + 1];
for(int i = 2;i <= n;i++) {
for(int j = 1;j <= i / 2;j++) {
dp[i] = Math.max(j * (i - j), dp[i]);
dp[i] = Math.max(dp[j] * (i - j), dp[i]);
dp[i] = Math.max(dp[i - j] * j, dp[i]);
dp[i] = Math.max(dp[i - j] * dp[j], dp[i]);
}
}
return dp[n];
}
}
假设j不动,只求dp[i-j] 8 = dp[1]+7,dp[2]+6,dp[3]+5,dp[4]+dp[4],dp[5]+3,dp[6]+2,dp[7]+1
public int integerBreak(int n) {
int[] dp = new int[n + 1];
dp[1] = 1;
for(int i = 2; i <= n; i ++) {
for(int j = 1; j < i; j ++) {
dp[i] = Math.max(dp[i], (Math.max(j,dp[j])) * (Math.max(i - j, dp[i - j])));
}
}
return dp[n];
}
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int> > &obstacleGrid) {
int m = obstacleGrid.size() , n = obstacleGrid[0].size();
vector<vector<int>> dp(m+1,vector<int>(n+1,0));
dp[0][1] = 1;
for(int i = 1 ; i <= m ; ++i)
for(int j = 1 ; j <= n ; ++j)
if(!obstacleGrid[i-1][j-1])
dp[i][j] = dp[i-1][j]+dp[i][j-1];
return dp[m][n];
}
};
dp[i][j] = min(dp[i - 1][j - 1], min(dp[i - 1][j], dp[i][j - 1])) + 1;这段代码的意思是dp[i][j] 临近的三个位置只要有一个为0,则为1,如果都为1,则加1.
class Solution {
public:
int maximalSquare(vector<vector<char>>& matrix) {
if (matrix.empty()) {
return 0;
}
int m = matrix.size(), n = matrix[0].size(), sz = 0;
vector<vector<int>> dp(m, vector<int>(n, 0));
for (int j = 0; j < n; j++) {
dp[0][j] = matrix[0][j] - '0';
sz = max(sz, dp[0][j]);
}
for (int i = 1; i < m; i++) {
dp[i][0] = matrix[i][0] - '0';
sz = max(sz, dp[i][0]);
}
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
if (matrix[i][j] == '1') {
dp[i][j] = min(dp[i - 1][j - 1], min(dp[i - 1][j], dp[i][j - 1])) + 1;
sz = max(sz, dp[i][j]);
}
}
}
return sz * sz;
}
};
题意是让在一个数组中的一些数之前添加“+”,其它的数之前添加“-”,从而让数组之和达到给定的数。
我们将添加“+”的数放入集合P,其它的数放入集合N,于是我们有:
sum(P) - sum(N) = target
sum(P) + sum(N) = sum
于是有sum(P) = (target + sum) / 2,那么不妨这样理解题意,从一个数组中选定一些数,使它们的和为sum(P),如此就变成了很经典的0/1背包问题,从一个n大小的背包中选出总和为sum(P)的方案个数。
状态表示:dp[i][j]代表前i个数中和为j的方案个数。
状态转移方程:dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i]],dp[0][0] = 1
返回结果:dp[n][target],n为数组大小,target为sum(P)。
如此时间复杂度为O(N^2),空间复杂度为O(M*N)。
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int S) {
int sum = 0;
for (auto n : nums) {
sum += n;
}
/*判断数组中的元素的和是否能达到目标数*/
if ((sum + S) % 2 == 1 || sum < S || S < -sum) {
return 0;
}
int target = (sum + S) / 2;
vector<int> dp(target + 1, 0);
/*相同于初始化dp[0][0] = 1*/
dp[0] = 1;
for (int i = 0; i < nums.size(); i++) {
for (int j = target; j >= nums[i]; j--) {
/*利用滚动数组循环更新dp[j],其效果等同于dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i]]
*空间复杂度优化至O(M)。
*/
dp[j] += dp[j - nums[i]];
}
}
return dp[target];
}
};
public class Solution {
public boolean wordBreak(String s, Set<String> dict) {
boolean[] f = new boolean[s.length() + 1];
f[0] = true;
for(int i=1; i <= s.length(); i++){
for(int j=0; j < i; j++){
if(f[j] && dict.contains(s.substring(j, i))){
f[i] = true;
break;
}
}
}
return f[s.length()];
}
}
为什么要检查f[j]是否为真,若为真,则从j到i开始匹配,也就是前面的j个字母均匹配成功,看下一个字母是否匹配了。