设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;
}
};
经典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;
}
};
两种做法,第一种就是普通的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();
}
};
设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;
}
};
设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;
}
};
设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];
}
};
维护一个前缀最小值或者后缀最大值就行了。
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;
}
};
设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];
}
};
经典入门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];
}
};
设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];
}
};
设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];
}
};
一开始想的是把单词都存到哈希表里,然后遍历字符串,过程中把字符累加到临时字符串中,只要临时字符串在哈希表中出现就置空,看最后临时字符串是否为空。但是这个方法没法通过样例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];
}
};
对于(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];
}
};
设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;
}
};
两种做法,第一种是用哈希+并查集,首先把数组中的数字都保存在哈希表中,之后遍历一遍数组,检查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;
}
};
设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];
}
};