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<vector<char>>& matrix) {int n = matrix.size(), m = matrix[0].size(), res = 0;vector<vector<int>> dp(n, vector<int>(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<int>& 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<int>& nums) {vector<int> 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<int>& nums) {vector<int> 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<int> 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<int>& coins, int amount) {int n = coins.size(), m = amount+1;vector<vector<int>> dp(n, vector<int>(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<vector<int>> dp(n, vector<int>(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<int>& prices) {vector<int> 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<vector<int>> dp(n, vector<int>(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<int> 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<int>& prices) {int n = prices.size();vector<vector<int>> dp(n, vector<int>(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<int>& nums) {int n = nums.size();vector<int> 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<string>& wordDict) {s = " "+s;int n = s.size();vector<bool> 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<vector<int>>& grid) {int n = grid.size(), m = grid[0].size();vector<vector<int>> dp(n, vector<int>(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<int>& nums1, vector<int>& nums2) {int n = nums1.size(), m = nums2.size();vector<vector<int>> dp(n, vector<int>(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<int> fa;int find(int x){if(fa[x] == x) return x;return fa[x] = find(fa[x]);}int longestConsecutive(vector<int>& nums) {fa.resize(nums.size());vector<int> sz(nums.size());unordered_map<int, int> 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<int>& nums) {unordered_map<int, int> 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<vector<int>> dp(n, vector<int>(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];}
};