DP问题总结

DP代码的模版:两个for 循环,然后在循环里面写递推表达式。返回数组第一个值或最后一个值。要注意的是DP问题的解代码中是不存在递归的。从遍历各个元素的角度思考递归式。

120. Triangle

递推表达式:

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];
}

这题跟这几题有点像
 

338. Counting Bits

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;
}

343. Integer Break

//这题等价与完全背包问题,即从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];
    }

63. Unique Paths II 

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];
    }
};

221. Maximal Square

 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;
    }
};

494. Target Sum 

题意是让在一个数组中的一些数之前添加“+”,其它的数之前添加“-”,从而让数组之和达到给定的数。

我们将添加“+”的数放入集合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];
    }
};

 139. Word Break

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个字母均匹配成功,看下一个字母是否匹配了。

猜你喜欢

转载自blog.csdn.net/raylrnd/article/details/84633846