LeetCode中動態規劃題解合集(根據難易程度))
121.Best Time to Buy and Sell Stock
思路:遍歷陣列,找到當前位置之前最小的數,然後選擇當前買入差值和之前最大值之間的最大值。
int maxProfit(vector<int>& prices) {
int res = 0,buy = INT_MAX;
for (int price : prices) {
buy = min(buy,price);
res = max(res,price - buy);
}
return res;
}
746.Min Cost Climbing Stairs
思路:從第二個臺階開始,計算到達該臺階最小的cost,因為第i個臺階,和i-1和i-2有關,則計算cost[i]
- min(cost[i-1],cost[i-2]).
int minCostClimbingStairs(vector<int>& cost) {
int n = cost.size();
vector<int> dp(n,0);
dp[0] = cost[0];
dp[1] = cost[1];
for (int i = 2; i < n; i++) {
dp[i] = cost[i] + min(dp[i-1],dp[i-2]);
}
return min(dp[n-1],dp[n-2]);
}
思路2:因為我們只關注i-1和i-2的值,所以沒必要儲存所有臺階的值,只需要儲存i-1和i-2.
int minCostClimbingStairs(vector<int>& cost) {
int n = cost.size();
int a = cost[0];
int b = cost[1];
int t;
for (int i = 2; i < n; i++) {
t = cost[i] + min(a,b);
a = b;
b = t;
}
return min(t,a);
}
- Climbing Stairs
思路:第i層臺階,只和第i-1和i-2層臺階有關係,那麼從底3層臺階開始,第i層臺階組合 = i-1層臺階組合數 + i-2層臺階組合數。
int climbStairs(int n) {
int a = 1,b = 2;
int res = 0;
if (n == 1)
return 1;
if (n == 2)
return 2;
for (int i = 3; i <= n; i++) {
res = a + b;
a = b;
b = res;
}
return res;
}
- Maximum Subarray
思路:最大連續子陣列,curSum初始值為0,每遍歷一個數字num,比較curSum + num和num中的較大值存入curSum,然後再把res和curSum中的較大值存入res,以此類推直到遍歷完整個陣列,可得到最大子陣列的值存在res中。原理,如果前面的和是負數,那麼就沒有必要相加,每次元素累加和小於0時,從下一個元素重新開始累加。
int maxSubArray(vector<int>& nums) {
int res = INT_MIN;
int curSum = 0;
for (int num : nums) {
curSum = max(num,curSum + num);//換句話就是,如果cursum為負,那麼最大值就是num
res = max(curSum,res);
}
return res;
}
- House Robber
思路:遞推公式dp[i] = max(num[i] + dp[i - 2], dp[i - 1]), 由此看出我們需要初始化dp[0]和dp[1],其中dp[0]即為num[0],dp[1]此時應該為max(num[0], num[1])。但是我們只需要前兩個值,所以不需要儲存所有dp的值,所以用a,b區域性變數。
int rob(vector<int>& nums) {
if (nums.size() <= 1) return nums.empty() ? 0 : nums[0];
if (nums.size() == 2) return max(nums[0],nums[1]);
int a = nums[0];
int b = max(a,nums[1]);
int res = 0;
for (int i = 2; i < nums.size(); i++) {
res = max(a + nums[i],b);
a = b;
b = res;
}
return max(res,a);
}
- Range Sum Query - Immutable
思路:如果直接遍歷求和的話,無法通過,所以用dp來記錄[0,j]的和,如果求[i,j]的區間和,就用dp[j]-dp[i].
public:
NumArray(vector<int> nums) {
dp.resize(nums.size()+1,0);
for (int i = 1;i < nums.size() + 1; i++) {
dp[i] = dp[i-1] + nums[i-1];
}
}
int sumRange(int i, int j) {
return dp[j+1] - dp[i];
}
private:
vector<int> dp;
- Palindromic Substrings
思路:helper函式用來計算以s[i]為中心的迴文字串個數,又因為迴文數可能是單數也可能是雙數,所以中心可能是一個數s[i],也可能是s[i]和s[i+1],所以需要helper(s,i,i,res);和helper(s,i,i+1,res);兩個計算。
int countSubstrings(string s) {
int res = 0;
for(int i = 0; i < s.size();i++) {
helper(s,i,i,res);
helper(s,i,i+1,res);
}
return res;
}
void helper(string s,int i,int j,int &res) {
while (i >= 0 && j < s.size()) {
if (s[i] != s[j])
break;
i--,j++;
res++;
}
}
思路2:dp[i][j]定義成子字串[i,j]是否為迴文串,i從n-1開始向前遍歷,j也從n-1向前遍歷直到j == i,如果i ==j,那麼[i,j]是迴文串,如果i,j相鄰或者中間隔一個數,那麼比較s[i] == s[j]?如果相等,則是迴文串,如果i和j中間隔大於一個數,那麼判斷dp[i+1][j-1]是否為真,若為真,則是迴文串。
int countSubstrings(string s) {
int n = s.size();
int res = 0;
vector<vector<bool>> dp(n,vector<bool>(n,false));
for (int i = n-1; i >= 0; i--) {
for (int j = n-1; j >= i; j--) {
if (s[i] == s[j] && (j - i <= 2 || dp[i+1][j-1]))
dp[i][j] = true;
if (dp[i][j]) res++;
}
}
return res;
}
- Arithmetic Slices
思路:求等差數列的個數。定義dp[i]為以A[i]為結尾的數列中等差數列的個數,那麼dp[i] = dp[i-1] + 1;res每次加上dp[i]即可
int numberOfArithmeticSlices(vector<int>& A) {
int n = A.size();
int res = 0;
vector<int> dp(n,0);
for (int i = 2; i < n; i++) {
if (A[i] - A[i-1] == A[i-1] - A[i-2])
dp[i] = dp[i-1] + 1;
res += dp[i];
}
return res;
}
思路2:可以不儲存所有dp值,用一個變數代替就可。
int numberOfArithmeticSlices(vector<int>& A) {
int n = A.size();
int res = 0;
int old = 0,cur = 0;
for (int i = 2; i < n; i++) {
if (A[i] - A[i-1] == A[i-1] - A[i-2]) {
cur = old + 1;
old = cur;
}
else {
old = 0;
cur = 0;
}
res += cur;
}
return res;
}
- Minimum ASCII Delete Sum for Two Strings
思路:dp[i][j]表示s1中前i個字元和s2前j個字元中,刪除的最小字元和。初始化dp[i][0]和dp[0][j].當s1[i] == s2[j-1]時,那麼不需要刪除字元,所以dp[i][j] == dp[i-1][j-1],當s1[i] != s2[j]時,那麼比較dp[i-1][j] + s1[i-1]和dp[i][j-1] + s2[j-1]大小,選擇刪除s1[i-1]或者s2[j-1].
int minimumDeleteSum(string s1, string s2) {
int m = s1.size();
int n = s2.size();
vector<vector<int>> dp(m+1,vector<int>(n+1,0));
for (int i = 1; i <= m; i++)
dp[i][0] = dp[i-1][0] + s1[i-1];
for (int j = 1; j <= n; j++)
dp[0][j] = dp[0][j-1] + s2[j-1];
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
dp[i][j] = s1[i-1] == s2[j-1] ? dp[i-1][j-1] :
min(dp[i-1][j] + s1[i-1],dp[i][j-1] + s2[j-1]);
}
}
return dp[m][n];
}
- Maximum Length of Pair Chain
思路:首先,把pairs進行排序,排序演算法根據vecSort函式,pair中尾元素進行排序(這裡為什麼不用首元素進行排序呢),也就是a[1] < b[1],然後根據貪心演算法,用一個變數end來記錄當前比較到的尾元素的值,初始化為最小值,然後遍歷的時候,如果當前鏈對的首元素大於end,那麼結果res自增1,end更新為當前鏈對的尾元素。
static bool vecSort( vector<int> &a, vector<int> &b) {
return a[1] < b[1];
}
int findLongestChain(vector<vector<int>>& pairs) {
int res = 0,end = INT_MIN;
sort(pairs.begin(),pairs.end(),vecSort);
for (auto pair : pairs) {
if (pair[0] > end) {
res++;
end = pair[1];
}
}
return res;
}
- Integer Break
思路:dp[i]表示整數i分解後最大乘積,那麼維護這個dp陣列,i被分成兩個數的和或者多個數的和。對於兩個數的和,就是用j * (i-j),多個數的和,就需要前面已經計算過的dp[j] *(i-j),最後比較。
但是這樣複雜度就是O(n^2),不是之前要求的O(n).O(n)的話需要找數學規律。
int integerBreak(int n) {
vector<int> dp(n+1,1);
int t;
for (int i = 2; i <= n; i ++) {
for (int j = 1; j < i; j++) {
t = max(j * (i-j),dp[j] * (i - j));
dp[i] = max(t,dp[i]);
}
}
return dp[n];
}
- Count Numbers with Unique Digits
思路:一位數的時候,滿足要求的是10位數,兩位數的時候,第一位可選數字為9,第二位可選數字也是9,那麼兩位數滿足要求數字是99,三位數滿足要求是998…以此類推。dp[i]表示i位數時滿足要求數字,初始化dp[0] = 1(這裡不是0,是為了後面遞推式dp[i] = dp[i-1] + (dp[i-1] - dp[i-2]) (9 - i + 2);從2滿足),然後dp[1] = 10,然後遞推式dp[i] = dp[i-1] + (dp[i-1] - dp[i-2])* (9 - i + 2);,i位數滿足要求的數字是小於等於i-1位數滿足要求的數字加上只有i位數滿足要求的數的和。
int countNumbersWithUniqueDigits(int n) {
vector<int> dp(n+1,1);
dp[1] = 10;
for (int i = 2; i < n + 1; i++) {
dp[i] = dp[i-1] + (dp[i-1] - dp[i-2])* (9 - i + 2);
}
return dp[n];
}
- 2 Keys Keyboard
思路:找到n 的因子j,然後這個因子j當作copy模板,我們再算出paste這個模板要多少次i/j.dp[i]表示i個A需要的運算元。
int minSteps(int n) {
vector<int> dp(n+1,0);
for (int i = 2; i <= n; i++) {
dp[i] = i;
for (int j = 2; j < i; j++) {
if (i % j == 0) {
dp[i] = min(dp[i],dp[j] + i / j);
}
}
}
return dp[n];
}
- Predict the Winner
思路:搜尋答案說的最多的就是Minimax演算法。這個演算法很適合用於這種交替型的博弈問題,對手雙方的策略都是一樣的,每次都選擇最優解,所以運用到了區間dp[i][j]表示在區間i和j,可以獲得的最大值。
bool PredictTheWinner(vector<int>& nums) {
int n = nums.size();
vector<vector<int>> dp(n,vector<int>(n,0));
for (int i = 0; i < n; i++) {
dp[i][i] = nums[i];
}
for (int len = 1; len < n; len++) {
for (int i = 0,j = len; j < n; i++,j++) {
dp[i][j] = max(nums[i] - dp[i+1][j],nums[j] - dp[i][j-1]);
}
}
return dp[0][n-1]>=0;
}
- Is Subsequence
思路:i和j相當於s和t的指標,如果s[i]和t[j]相等,那麼i++,j++,都向下移動一位,如果不相等,那麼只有t向下移動一位(j++),最後判斷i是不是等於s的長度。
bool isSubsequence(string s, string t) {
int m = s.size();
int n = t.size();
int i = 0,j = 0;
while (i < m && j < n) {
if (s[i] == t[j]) {
i++;
j++;
}
else {
j++;
}
}
if (i == m) {
return true;
}
return false;
}
- Unique Paths
思路:dp[i][j]表示第i行j列的所有路徑數,因為robot只能下移或者右移,所以i,j的路徑只能是[i-1][j]位置的路徑 + [i][j-1]位置的路徑。
int uniquePaths(int m, int n) {
vector<vector<int>> dp(m,vector<int>(n,1));
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
return dp[m-1][n-1];
}
- Delete and Earn
思路:首先明確一點,選擇某一個數字,那麼這個數字不管有幾個,我們都會加入最後的和當中,所以我們先統計每個數字有幾個,用cnt陣列儲存。然後dp[i]表示到i為止最大值,dp[i]有兩個選擇,要麼選擇拿i這個數,cnt[i] * i + dp[i-2],要麼不拿i這個數,dp[i-1],最後返回dp[10000]
int deleteAndEarn(vector<int>& nums) {
int n = nums.size();
vector<int> dp(10001,0);
vector<int> cnt(10001,0);
for(int i = 0; i < n; i++)
cnt[nums[i]]++;
dp[1] = cnt[1];
for (int i = 2; i < 10001; i++) {
dp[i] = max(dp[i-2] + cnt[i] * i,dp[i-1]);
}
return dp[10000];
}
- Target Sum
思路:這第一個想到的是DFS,列出來所有情況,判斷和是否為S。所以最簡單就是用DFS,對目標值每次減去或者加上當前數字,最後得到的值是否為0判斷是否滿足要求。
int findTargetSumWays(vector<int>& nums, int S) {
int res = 0;
helper(nums,S,0,res);
return res;
}
void helper(vector<int>& nums,int S,int start,int& res) {
if (start >= nums.size()) {
if (S == 0)
res++;
return;
}
helper(nums,S-nums[start],start+1,res);
helper(nums,S+nums[start],start+1,res);
}
- Longest Palindromic Subsequence
思路:找最大子序列,可以不連續(和最長子字串不同)。dp[i][j]表示從i到j的最長迴文字串,如果s[i] == s[j],那麼dp[i][j] = dp[i+1][j-1] + 2;如果不相等,那麼 dp[i][j] = max(dp[i+1][j],dp[i][j-1]);
int longestPalindromeSubseq(string s) {
int n = s.size();
vector<vector<int>> dp(n,vector<int>(n,0));
for (int i = n - 1; i >= 0; i--) {
dp[i][i] = 1;
for (int j = i + 1; j < n; j++) {
if (s[i] == s[j])
dp[i][j] = dp[i+1][j-1] + 2;
else {
dp[i][j] = max(dp[i+1][j],dp[i][j-1]);
}
}
}
return dp[0][n-1];
}
- Unique Binary Search Trees
思路:二叉搜尋樹,對於根節點root來說,左邊的一定小於root,右邊的大於root,所以如果給定一個數n,說明n個數進行BST,那麼左子樹節點個數為k(0<=k<=n-1),右子樹節點個數為n-k-1;dp[i]表示i個數BST的可能構造數,那麼dp[n] += dp[k] * dp[n-k-1] (0<=k<=n-1).最後返回dp[n]就是結果。
int numTrees(int n) {
vector<int> dp(n+1,0);
dp[0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 0; j < i; j++) {
dp[i] += dp[j] * dp[i - j - 1];
}
}
return dp[n];
}
64.Minimum Path Sum
思路:dp[i][j]表示i行j列最小路徑和,因為只能向右和向下移動,所以dp[i][j] = min (dp[i-1][j],dp[i][j-1]) + grid[i][j];也就是說,dp[i][j]只能來自於它上面的塊和左邊的塊,所以比較這兩個大小,取其小的那一塊就可以。程式碼中需要先初始化第一行和第一列dp值。
int minPathSum(vector<vector<int>>& grid) {
int n = grid.size();
int m = grid[0].size();
vector<vector<int>> dp = grid;
for (int i = 1; i < n; i++){
dp[i][0] = dp[i-1][0] + grid[i][0];
}
for (int i = 1; i < m; i++) {
dp[0][i] = dp[0][i-1] + grid[0][i];
}
for (int i = 1; i < n; i ++) {
for (int j = 1; j < m; j++) {
dp[i][j] = min (dp[i-1][j],dp[i][j-1]) + grid[i][j];
}
}
return dp[n-1][m-1];
}
377.Combination Sum IV
思路:第一想法是DFS,但是超時,所以還是用DP。dp[i] 表示和為i時,組合數,那麼dp[i] = dp[i-nums[0] ] + dp[i - nums[1]] + …dp[i - nums[size-1]].用程式碼表示就是dp[i] += dp[i - num];
int combinationSum4(vector<int>& nums, int target) {
int n = nums.size();
vector<int> dp(target+1,0);
dp[0] = 1;
sort(nums.begin(),nums.end());
for(int i = 1; i <= target; i++) {
for (auto num : nums) {
if (num <= i)
dp[i] += dp[i - num];
}
}
return dp[target];
}
Maximum Length of Repeated Subarray
思路:dp[i][j]表示陣列A中前i個字元和陣列B前j個字元最長公共子陣列的個數,如果A[i] == B[j],那麼dp[i][j]等於dp[i-1][j-1] + 1,最後找到dp中最大值即可。其中有個小技巧,因為是從0開始計算,但是i-1和j-1不能是負數,所以i,j從1計數,但是比較還是i-1,j-1,相當於還是0,只是儲存結果從[1][1]開始的。
int findLength(vector<int>& A, vector<int>& B) {
int m = A.size();
int n = B.size();
int res = 0;
vector<vector<int>> dp(m+1,vector<int>(n+1,0));
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (A[i-1] == B[j-1]) {
dp[i][j] = dp[i-1][j-1] + 1;
res = max(res,dp[i][j]);
}
}
}
return res;
}
72`edit distance
思路:
int minDistance(string word1, string word2) {
int m = word1.size();
int n = word2.size();
// vector<vector<int>> dp(m+1,vector<int>(n+1,0));
int dp[m+1][n+1];
for (int i = 0; i < m + 1; i++)
dp[i][0] = i;
for (int i = 0; i < n + 1; i++)
dp[0][i] = i;
for (int i = 1; i < m + 1; i++) {
for (int j = 1; j < n + 1; j++) {
if (word1[i-1] == word2[j-1]) {
dp[i][j] = dp[i-1][j-1];
}
else {
dp[i][j] = min(dp[i-1][j],min(dp[i][j-1],dp[i-1][j-1])) + 1;//這裡一定要注意,min函式只接受兩個引數,必須寫兩個min
}
}
}
return dp[m][n];
}
相關文章
- leetcode題解(動態規劃)LeetCode動態規劃
- Leetcode 題解演算法之動態規劃LeetCode演算法動態規劃
- [leetcode] 動態規劃(Ⅰ)LeetCode動態規劃
- LeetCode 動態規劃 House Robber 習題LeetCode動態規劃
- [LeetCode] 動態規劃題型總結LeetCode動態規劃
- [LeetCode解題] -- 動態規劃二 [ 子串、子序列問題 ]LeetCode動態規劃
- Leetcode 題解系列 -- 股票的最大利潤(動態規劃)LeetCode動態規劃
- [leetcode 1235] [動態規劃]LeetCode動態規劃
- 動態規劃解題方法動態規劃
- LeetCode:動態規劃+貪心題目整理LeetCode動態規劃
- leetcode總結——動態規劃LeetCode動態規劃
- 動態規劃 擺花 題解動態規劃
- [LeetCode 困難 動態規劃+LIS問題]354. 俄羅斯套娃信封問題LeetCode動態規劃
- leetcode-動態規劃總結LeetCode動態規劃
- 動態規劃專題動態規劃
- 好題——動態規劃動態規劃
- 動態規劃題單動態規劃
- Leetcode 編輯距離(動態規劃)LeetCode動態規劃
- 【leetcode】741 摘櫻桃(動態規劃)LeetCode動態規劃
- leetcode演算法題解(Java版)-16-動態規劃(單詞包含問題)LeetCode演算法Java動態規劃
- 演算法刷題:LeetCode中常見的動態規劃題目演算法LeetCode動態規劃
- 【LeetCode動態規劃#12】詳解買賣股票I~IV,經典dp題型LeetCode動態規劃
- 動態規劃練習題動態規劃
- 動態規劃做題思路動態規劃
- LeetCode 分割回文串II(動態規劃)LeetCode動態規劃
- LeetCode入門指南 之 動態規劃思想LeetCode動態規劃
- LeetCode 343. 整數拆分--動態規劃LeetCode動態規劃
- BIRT 中如何根據引數動態拼接 SQLSQL
- 【LeetCode】55. 跳躍遊戲 (動態規劃)LeetCode遊戲動態規劃
- 動態規劃解0-1揹包問題動態規劃
- 【動態規劃】揹包問題動態規劃
- 我的動態規劃題單動態規劃
- 做題記錄 --- 動態規劃動態規劃
- 揹包問題----動態規劃動態規劃
- 動態規劃之子序列問題動態規劃
- 德魯週記10--15天從0開始刷動態規劃(leetcode動態規劃題目型別總結)動態規劃LeetCode型別
- SpringBoot中根據屬性動態註冊Spring BeanSpring BootBean
- 力扣-動態規劃全解力扣動態規劃