LeetCode中動態規劃題解合集(根據難易程度))

碼農凱凱發表於2018-08-09

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);
    }
  1. 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;
    }
  1. 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;
    }
  1. 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);
    }
  1. 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;
  1. 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;
    }
  1. 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;
    }
  1. 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];
    }
  1. 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;
    }
  1. 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];
    }
  1. 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];
    }
  1. 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];
    }
  1. 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;
    }
  1. 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;
    }
  1. 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];
    }
  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];
        
    }
  1. 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);
    }
  1. 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];
    }
  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];
    }

相關文章