柚子快报激活码778899分享:算法 动态规划相关题目总结

http://yzkb.51969.com/

221.最大正方形

设dp[i][j]为以点(i, j)为右下角的正方形最大边长,多画画图模拟模拟可以发现递推式dp[i][j] = min(dp[i][j-1], dp[i-1][j-1], dp[i-1][j])+1。

class Solution {

public:

int maximalSquare(vector>& matrix) {

int n = matrix.size(), m = matrix[0].size(), res = 0;

vector> dp(n, vector(m));

for(int i = 0; i < n; i++)

for(int j = 0; j < m; j++){

if(matrix[i][j] == '0') dp[i][j] = 0;

else{

if(i-1 < 0 || j-1 < 0) dp[i][j] = 1;

else{

int mn = 0x3f3f3f3f;

mn = min(mn, dp[i][j-1]);

mn = min(mn, dp[i-1][j-1]);

mn = min(mn, dp[i-1][j]);

dp[i][j] = mn+1;

}

}

res = max(res, dp[i][j]);

}

return res*res;

}

};

53.最大子数组和

经典dp,设dp[i]为以nums[i]结尾的最大子数组和,考虑是否与nums[i-1]结尾的最大子数组结合,如果dp[i-1] < 0那肯定不结合,反之应该结合。

class Solution {

public:

int maxSubArray(vector& nums) {

int last = nums[0], res = nums[0];

for(int i = 1; i < nums.size(); i++){

last = max(0, last)+nums[i];

res = max(last, res);

}

return res;

}

};

300.最长递增子序列

两种做法,第一种就是普通的dp,设dp[i]为以nums[i]结尾的最长递增子序列长度,然后向前枚举子序列前一个数字,时间复杂度是O(n^2)。第二种做法利用二分查找,时间复杂度为O(nlogn)。

//O(n^2)写法

class Solution {

public:

int lengthOfLIS(vector& nums) {

vector dp(nums.size());

int res = 0;

for(int i = 0; i < nums.size(); i++){

dp[i] = 1;

for(int j = 0; j < i; j++)

if(nums[j] < nums[i]) dp[i] = max(dp[i], dp[j]+1);

res = max(res, dp[i]);

}

return res;

}

};

//O(nlogn)写法

class Solution {

public:

int lengthOfLIS(vector& nums) {

vector a(nums.size()+1, 0x3f3f3f3f);

for(int i = 0; i < nums.size(); i++){

int idx = lower_bound(a.begin(), a.end(), nums[i])-a.begin();

a[idx] = nums[i];

}

return lower_bound(a.begin(), a.end(), 0x3f3f3f3f)-a.begin();

}

};

32.最长有效括号

设dp[i]为以第i个括号结尾的最长有效括号串,如果第i个括号是左括号那dp[i]肯定是0,如果如果第i个括号是右括号那需要根据上一个括号分类讨论,若s[i-1]是左括号,那s[i-1]和s[i]正好凑成一对,此时dp[i] = dp[i-2]+2,若s[i-1]是右括号,那dp[i-1]就是该右括号最长有效括号长度,i-dp[i-1]就是该有效括号串的左端点,i-dp[i-1]-1的位置如果是个右括号,那它不能与i位置的右括号匹配,此时dp[i] = 0,如果i-dp[i-1]-1的位置是左括号,那它可以与i位置的右括号匹配,此时dp[i] = dp[i-1]+2+dp[i-dp[i-1]-2]。

class Solution {

public:

int longestValidParentheses(string s) {

s = " "+s;

int n = s.size();

vector dp(n);

int mx = 0;

for(int i = 1; i < n; i++){

if(s[i] == '(') continue;

if(i >= 2){

if(s[i-1] == '(') dp[i] = dp[i-2]+2;

else{

if(i-dp[i-1]-1 >= 1 && s[i-dp[i-1]-1] == '(')

dp[i] = dp[i-1]+2+dp[i-dp[i-1]-2];

}

}

mx = max(mx, dp[i]);

}

return mx;

}

};

322.零钱兑换

设dp[i][j]表示只使用前i种货币,恰好凑到j元所需要的最少货币数。状态转移的时候考虑是否使用第i种货币,不使用的话就是dp[i-1][j],使用的话就是dp[i][j-coins[i]]+1,二者取最小值就行了。

class Solution {

public:

int coinChange(vector& coins, int amount) {

int n = coins.size(), m = amount+1;

vector> dp(n, vector(m));

for(int i = 0; i < m; i++)

if(i%coins[0] == 0) dp[0][i] = i/coins[0];

else dp[0][i] = 0x3f3f3f3f;

for(int i = 0; i < n; i++) dp[i][0] = 0;

for(int i = 1; i < n; i++)

for(int j = 0; j < m; j++){

dp[i][j] = dp[i-1][j];

if(j >= coins[i])

dp[i][j] = min(dp[i][j], dp[i][j-coins[i]]+1);

}

return dp[n-1][m-1]!=0x3f3f3f3f?dp[n-1][m-1]:-1;

}

};

1143.最长公共子序列

设dp[i][j]表示考虑串1前i位以及串2前j位的最长公共子序列,如果串1的第i位和串2的第j位恰好相等,那就dp[i][j] = dp[i-1][j-1]+1,否则dp[i][j] = max(dp[i-1][j], dp[i][j-1])。

class Solution {

public:

int longestCommonSubsequence(string text1, string text2) {

int n = text1.size(), m = text2.size();

vector> dp(n, vector(m));

bool flag = false;

for(int i = 0; i < m; i++){

if(text1[0] == text2[i]) flag = true;

if(flag) dp[0][i] = 1;

}

flag = false;

for(int i = 0; i < n; i++){

if(text1[i] == text2[0]) flag = true;

if(flag) dp[i][0] = 1;

}

for(int i = 1; i < n; i++)

for(int j = 1; j < m; j++){

if(text1[i] == text2[j]) dp[i][j] = dp[i-1][j-1]+1;

dp[i][j] = max(dp[i][j], dp[i-1][j]);

dp[i][j] = max(dp[i][j], dp[i][j-1]);

}

return dp[n-1][m-1];

}

};

121.买卖股票的最佳时机

维护一个前缀最小值或者后缀最大值就行了。

class Solution {

public:

int maxProfit(vector& prices) {

vector mn(prices.size());

int mx = 0;

for(int i = 0; i < prices.size(); i++){

mn[i] = prices[i];

if(i > 0) mn[i] = min(mn[i-1], mn[i]);

mx = max(mx, prices[i]-mn[i]);

}

return mx;

}

};

72.编辑距离

设dp[i][j]表示将串1的前i位变为串2前j位所需最少操作数,状态转移就是考虑最后一次执行的是什么操作,如果最后一次是删除,那就对应dp[i][j] = dp[i-1][j]+1,如果最后一次是添加,那就对应dp[i][j] = dp[i][j-1]+1,如果最后一次是修改,那就是dp[i][j] = dp[i-1][j-1]+1,当然如果本身第i位和第j位就相等,那dp[i][j] = dp[i-1][j-1],这四种情况取最小值。

class Solution {

public:

int minDistance(string word1, string word2) {

word1 = "!"+word1;

word2 = "!"+word2;

int n = word1.size(), m = word2.size();

vector> dp(n, vector(m));

for(int i = 0; i < m; i++)

dp[0][i] = i;

for(int i = 0; i < n; i++)

dp[i][0] = i;

for(int i = 1; i < n; i++)

for(int j = 1; j < m; j++){

dp[i][j] = min(dp[i][j-1], min(dp[i-1][j], dp[i-1][j-1]))+1;

if(word1[i] == word2[j]) dp[i][j] = min(dp[i][j], dp[i-1][j-1]);

}

return dp[n-1][m-1];

}

};

70.爬楼梯

经典入门dp题目。

class Solution {

public:

int climbStairs(int n) {

vector dp(n+5);

dp[1] = 1, dp[2] = 2;

for(int i = 3; i <= n; i++)

dp[i] = dp[i-1]+dp[i-2];

return dp[n];

}

};

122.买卖股票的最佳时机 II

设dp[i][0]表示在第i天手上没有股票时的钱,dp[i][1]表示在第i天手上有股票时的钱,状态转移的话如果当天没有股票那说明可能是今天卖出去了,也可能是昨天手上就没股票继承过来了,所以dp[i][0] = max(dp[i-1][0], dp[i-1][1]+prices[i]),然后如果当天有股票那说明可能是今天刚买,也可能是昨天手上就有股票继承过来了,所以dp[i][1] = max(dp[i-1][1], dp[i-1][0]-prices[i])。

class Solution {

public:

int maxProfit(vector& prices) {

int n = prices.size();

vector> dp(n, vector(2));

dp[0][0] = 0;

dp[0][1] = -prices[0];

for(int i = 1; i < n; i++){

dp[i][0] = max(dp[i-1][0], dp[i-1][1]+prices[i]);

dp[i][1] = max(dp[i-1][1], dp[i-1][0]-prices[i]);

}

return dp[n-1][0];

}

};

198.打家劫舍

设dp[i]表示前i个位置拿到的钱,状态转移的时候考虑是否拿第i个位置的钱,如果拿的话那dp[i] = dp[i-2]+nums[i],如果不拿的话dp[i] = dp[i-1],二者取一个最大值就行了。

class Solution {

public:

int rob(vector& nums) {

int n = nums.size();

vector dp(n);

dp[0] = nums[0];

if(n > 1) dp[1] = max(nums[0], nums[1]);

for(int i = 2; i < n; i++)

dp[i] = max(dp[i-1], dp[i-2]+nums[i]);

return dp[n-1];

}

};

139.单词拆分

一开始想的是把单词都存到哈希表里,然后遍历字符串,过程中把字符累加到临时字符串中,只要临时字符串在哈希表中出现就置空,看最后临时字符串是否为空。但是这个方法没法通过样例s = "aaaaaaa", wordDict = ["aaa", "aaaa"]。

设dp[i]表示前i个字符能否凑出来,状态转移就是枚举最后一个单词,时间复杂度接近O(n^2)。

class Solution {

public:

bool wordBreak(string s, vector& wordDict) {

s = " "+s;

int n = s.size();

vector dp(n);

dp[0] = true;

for(int i = 0; i < n; i++){

for(int j = 0; j < wordDict.size(); j++){

if(i >= wordDict[j].size() && s.substr(i-wordDict[j].size()+1, wordDict[j].size()) == wordDict[j])

dp[i] = max(dp[i], dp[i-wordDict[j].size()]);

}

}

return dp[n-1];

}

};

64.最小路径和

对于(i, j)要么从(i-1, j)过来,要么从(i, j-1)过来,根据这个进行状态转移。

class Solution {

public:

int minPathSum(vector>& grid) {

int n = grid.size(), m = grid[0].size();

vector> dp(n, vector(m));

for(int i = 0; i < n; i++)

for(int j = 0; j < m; j++){

int mn = 0x3f3f3f3f;

if(i > 0) mn = min(mn, dp[i-1][j]);

if(j > 0) mn = min(mn, dp[i][j-1]);

if(mn == 0x3f3f3f3f) mn = 0;

dp[i][j] = grid[i][j]+mn;

}

return dp[n-1][m-1];

}

};

718.最长重复子数组

设dp[i][j]表示串1前i位和串2前j位最长公共子串长度,如果对应的两个位置值不同那就置0,否则dp[i][j] = dp[i-1][j-1]+1。

class Solution {

public:

int findLength(vector& nums1, vector& nums2) {

int n = nums1.size(), m = nums2.size();

vector> dp(n, vector(m));

int mx = 0;

for(int i = 0; i < m; i++){

dp[0][i] = nums1[0]==nums2[i];

mx = max(mx, dp[0][i]);

}

for(int i = 0; i < n; i++){

dp[i][0] = nums1[i]==nums2[0];

mx = max(mx, dp[i][0]);

}

for(int i = 1; i < n; i++)

for(int j = 1; j < m; j++){

if(nums1[i] == nums2[j]) dp[i][j] = dp[i-1][j-1]+1;

mx = max(mx, dp[i][j]);

}

return mx;

}

};

128.最长连续序列

两种做法,第一种是用哈希+并查集,首先把数组中的数字都保存在哈希表中,之后遍历一遍数组,检查nums[i]-1是否在哈希表中,如果也存在的话那就用并查集把两个集合合并,同时可以开一个数组维护一下每个连通块大小,最后答案就是最大连通块的大小。

第二种做法是哈希+遍历,思路更简单一些,还是把数组中的数字都保存在哈希表中,然后遍历一遍原数组,当某个元素是所在连续序列中最小的一个时,直接从它的数值递增来求该序列长度。

//哈希+并查集写法

class Solution {

public:

vector fa;

int find(int x){

if(fa[x] == x) return x;

return fa[x] = find(fa[x]);

}

int longestConsecutive(vector& nums) {

fa.resize(nums.size());

vector sz(nums.size());

unordered_map mp;

for(int i = 0; i < nums.size(); i++)

fa[i] = i, sz[i] = 1, mp[nums[i]] = i;

for(int i = 0; i < nums.size(); i++){

if(mp.count(nums[i]-1)){

int r1 = find(mp[nums[i]]), r2 = find(mp[nums[i]-1]);

if(r1 == r2) continue;

if(sz[r1] > sz[r2]){

sz[r1] += sz[r2];

fa[r2] = r1;

}

else{

sz[r2] += sz[r1];

fa[r1] = r2;

}

}

}

int res = 0;

for(int i = 0; i < nums.size(); i++)

res = max(sz[i], res);

return res;

}

};

//哈希+遍历写法

class Solution {

public:

int longestConsecutive(vector& nums) {

unordered_map mp;

for(int i = 0; i < nums.size(); i++)

mp[nums[i]] = 1;

int res = 0;

for(auto x:mp){

if(!mp.count(x.first-1)){

int len = 0;

while(mp.count(x.first+len)) len++;

res = max(res, len);

}

}

return res;

}

};

62.不同路径

设dp[i][j]为从起点到(i, j)的路径数,状态转移根据上一步的位置,dp[i][j] = dp[i-1][j]+dp[i][j-1]。

class Solution {

public:

int uniquePaths(int m, int n) {

swap(m, n);

vector> dp(n, vector(m));

for(int i = 0; i < n; i++)

for(int j = 0; j < m; j++){

if(i == 0 && j == 0) dp[i][j] = 1;

if(i > 0) dp[i][j] += dp[i-1][j];

if(j > 0) dp[i][j] += dp[i][j-1];

}

return dp[n-1][m-1];

}

};

柚子快报激活码778899分享:算法 动态规划相关题目总结

http://yzkb.51969.com/

推荐阅读

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: