• leetcode Ch2-Dynamic Programming I


    一、

    1. Edit Distance

     1 class Solution
     2 {
     3 public:
     4     int minDistance(string t1,string t2)
     5     {
     6         int len1=t1.size(),len2=t2.size();
     7         vector<vector<int>> dp(len1+1,vector<int>(len2+1,0));
     8         for(int i=0;i<=len1;i++) dp[i][0]=i;
     9         for(int i=0;i<=len2;i++) dp[0][i]=i;
    10         for(int i=1;i<=len1;i++)
    11         {
    12             for(int j=1;j<=len2;j++)
    13             {
    14                 dp[i][j]=min(min(dp[i-1][j]+1,dp[i][j-1]+1),dp[i-1][j-1]+(t1[i-1]==t2[j-1]?0:1));
    15             }
    16         }
    17         return dp[len1][len2];
    18     }
    19 };
    View Code

    需要注意的细节是dp里的下标和字符串t中的下标不是一致的(Line14),是差了1的。即dp[i][j]所对应的是t1中的第i-1位和t2中的第j-1
    位,即t1[i-1],t2[j-1]。因为dp中的下标代表的是对应字符串的长度。

    #2: 又漏了种情况。当word1和word2末尾元素不相等时,有3种可能的操作:除dp[i-1][j]+1,dp[i][j]+1之外,还有 dp[i-1][j-1]+1。 

    2. Distinct Subsequences

     1 class Solution
     2 {
     3 public:
     4     int numDistinct(string s,string t)
     5     {
     6         int sLen=s.size(),tLen=t.size();
     7         vector<vector<int>> dp(sLen+1,vector<int>(tLen+1,0));
     8         for(int i=0;i<=sLen;i++) dp[i][0]=1;
     9         for(int i=1;i<=sLen;i++)
    10         {
    11             for(int j=1;j<=tLen;j++)
    12             {
    13                 dp[i][j]=dp[i-1][j]+(s[i-1]==t[j-1]? dp[i-1][j-1]:0 );        
    14             }
    15         }
    16         return dp[sLen][tLen];
    17     }
    18 };
    View Code

    递推式不难,关键是正确理解题意和初始化。

    题目说的是T是S任意删掉若干字符得到的结果。那么当T为空时,从S中只有一种删法能够得到T(那就是删光)。所以dp[i][0]都是1. 这一步初始化很重要。

     dp[i][j]是S长度为i(从下标0开始),T长度为j(从下标0开始)所对应的删法的个数。

    易出错。

    #2: 忘记递推关系了; 初始化的时候是=1;

    3. Scramble String

    用vector,不用数组 (推荐)

     1 class Solution {
     2 public:
     3     bool isScramble(string s1, string s2) {
     4         int N = s1.size();
     5         if (N != s2.size()) {
     6             return false;
     7         }
     8         vector<vector<vector<bool>>> f(N + 1, vector<vector<bool>>(N, vector<bool>(N, false)));
     9         f[0][0][0] = true;
    10         for (int i = 0; i < N; i++) {
    11             for (int j = 0; j < N; j++) {
    12                 f[1][i][j] = (s1[i] == s2[j]);
    13             }
    14         }
    15         for (int n = 1; n <= N; n++) {
    16             for (int i = 0; i + n <= N; i++) {
    17                 for (int j = 0; j + n <= N; j++) {
    18                     for (int k = 1; k < n; k++) {
    19                         if ((f[k][i][j] && f[n - k][i + k][j + k]) || (f[k][i][j + n - k] && f[n - k][i + k][j])) {
    20                             f[n][i][j] = true;
    21                             break;
    22                         }
    23                     }
    24                 }
    25             }
    26         }
    27         return f[N][0][0];
    28     }
    29 };
    View Code

    用数组(不推荐)

     1 class Solution
     2 {
     3 public:
     4     bool isScramble(string s1,string s2)
     5     {
     6         const int N=s1.size();
     7         if(N!=s2.size()) return false;
     8         bool f[N+1][N][N];
     9         fill_n(&f[0][0][0],(N+1)*N*N,false);
    10         for(int i=0;i<N;i++)
    11             for(int j=0;j<N;j++)
    12                 f[1][i][j]=(s1[i]==s2[j]);
    13         for(int n=2;n<=N;n++)
    14         {
    15             for(int i=0;i<=N-n;i++)
    16             {
    17                 for(int j=0;j<=N-n;j++)
    18                 {
    19                     for(int k=1;k<n;k++)
    20                     {
    21                         if((f[k][i][j]&&f[n-k][i+k][j+k])||(f[k][i][j+n-k]&&f[n-k][i+k][j]))
    22                         {
    23                             f[n][i][j]=true;
    24                             break;
    25                         }
    26                     }
    27                 }
    28             }
    29         }
    30         return f[N][0][0];
    31     }
    32 };
    View Code
    简单的说,就是s1和s2是scramble的话,那么必然存在一个在s1上的长度l1,将s1分成s11和s12两段,同样有s21和s22。
    那么要么s11和s21是scramble的并且s12和s22是scramble的;要么s11和s22是scramble的并且s12和s21是scramble的。
    springfor的总结不错。

    这道题很多细节容易出错,要谨慎。

    注意:这个代码在VS下通不过,虽然能过OJ。

    #2: 忘记对 f[1][i][j] 的初始化了。而且写的巨慢。

    注意:k是从1到n-1的,不能从0开始,那会导致越界。

    二、最长递增子序列 LIS

    见此整理,三。'

    #2: 老是重复犯一个错误!!!每次应用binarySearch的时候都用到了nums上,应该是用到存储当前位置最小元素的数组B上!!!

    三、矩阵链乘

     1 int matrixChain(vector<pair<int,int>> &vp)
     2 {
     3     int len = vp.size();
     4     vector<vector<int>> dp(SIZE, vector<int>(SIZE, INT_MAX));
     5     for (int i = 0; i<len; i++) dp[i][i] = 0;
     6     for (int L = 2; L <= len; L++)
     7     {
     8         for (int i = 0; i <= len - L; i++)
     9         {
    10             int j = i + L - 1;
    11             int minV = INT_MAX;
    12             for (int k = i; k < j; k++)
    13             {
    14                 if (dp[i][k] + dp[k + 1][j] + vp[i].first*vp[k].second*vp[j].second<minV)
    15                     minV = dp[i][k] + dp[k + 1][j] + vp[i].first*vp[k].second*vp[j].second;
    16             }
    17             dp[i][j] = minV;
    18         }
    19     }
    20     return dp[0][len - 1];
    21 }
    View Code

    需要注意的是Line12,k<j,不能是k<=j. 因为我们的划分是划分成[i...k][k+1...j],所以对于后半段需要满足k+1<=j,即k必须小于j。

    完整代码:

    不能首尾相连版:

     1 #include <iostream>
     2 #include <vector>
     3 #include <algorithm>
     4 #include <climits>
     5 using namespace std;
     6 
     7 #define SIZE 1000
     8 void init(vector<pair<int, int>> &vp)
     9 {
    10     int n;
    11     cin >> n;
    12     while (n--)
    13     {
    14         int x, y;
    15         cin >> x >> y;
    16         vp.push_back(make_pair(x, y));
    17     }
    18 }
    19 
    20 int matrixChain(vector<pair<int,int>> &vp)
    21 {
    22     int len = vp.size();
    23     vector<vector<int>> dp(SIZE, vector<int>(SIZE, INT_MAX));
    24     for (int i = 0; i<len; i++) dp[i][i] = 0;
    25     for (int L = 2; L <= len; L++)
    26     {
    27         for (int i = 0; i <= len - L; i++)
    28         {
    29             int j = i + L - 1;
    30             int minV = INT_MAX;
    31             for (int k = i; k < j; k++)
    32             {
    33                 if (dp[i][k] + dp[k + 1][j] + vp[i].first*vp[k].second*vp[j].second<minV)
    34                     minV = dp[i][k] + dp[k + 1][j] + vp[i].first*vp[k].second*vp[j].second;
    35             }
    36             dp[i][j] = minV;
    37         }
    38     }
    39     return dp[0][len - 1];
    40 }
    41 
    42 int main()
    43 {
    44     vector<pair<int, int>> vp;
    45     init(vp);
    46     cout << matrixChain(vp) << endl;
    47 }
    View Code

    可以首尾相连版:

     1 #include <iostream>
     2 #include <vector>
     3 #include <algorithm>
     4 #include <climits>
     5 using namespace std;
     6 
     7 struct mat {
     8     int rowNum;
     9     int colNum;
    10 };
    11 
    12 void init(vector<mat> &v) {
    13     int n;
    14     cin >> n;
    15     while (n--) {
    16         mat tmp;
    17         cin >> tmp.rowNum >> tmp.colNum;
    18         v.push_back(tmp);
    19     }
    20 }
    21 
    22 int matMultiply(vector<mat> &v) {
    23     int len = v.size();
    24     if (len == 0) {
    25         return 0;
    26     }
    27     vector<vector<int> > dp(2 * len, vector<int>(2 * len, INT_MAX));
    28     for (int i = 0; i < 2 * len; i++) {
    29         dp[i][i] = 0;
    30     }
    31     int minV = INT_MAX;
    32     for (int L = 2; L <= len; L++) {
    33         for (int i = 0; i <= 2 * len - L; i++) {
    34             int j = i + L - 1;
    35             for (int k = i; k < j; k++) {
    36                 int tmp = dp[i][k] + dp[k + 1][j] + v[i % len].rowNum * v[k % len].colNum * v[j % len].colNum;
    37                 dp[i][j] = min(dp[i][j], tmp);
    38             }
    39             if (L == len) {
    40                 minV = min(minV, dp[i][j]);
    41             }
    42         }
    43     }
    44     return minV;
    45 }
    46 
    47 int main() {
    48     vector<mat> v;
    49     init(v);
    50     cout << matMultiply(v) << endl;
    51     return 0;
    52 }
    View Code

     reference

    #2: 犯的错误:if(L == len) 放错位置,放在了i循环外面。

    四、最长公共子序列 LCS

     1 #include <iostream>
     2 #include <vector>
     3 #include <string>
     4 using namespace std;
     5 
     6 int lcs(string t1, string t2, vector<vector<int>> &trace)
     7 {
     8     int len1 = t1.size(), len2 = t2.size();
     9     vector<vector<int>> dp(t1.size() + 1, vector<int>(t2.size() + 1, 0));
    10     for (int i = 1; i <= len1; i++)
    11     {
    12         for (int j = 1; j <= len2; j++)
    13         {
    14             if (t1[i - 1] == t2[j - 1])
    15             {
    16                 dp[i][j] = dp[i - 1][j - 1] + 1;
    17                 trace[i][j] = 0;
    18             }
    19             else
    20             {
    21                 if (dp[i - 1][j]>dp[i][j - 1])
    22                 {
    23                     dp[i][j] = dp[i - 1][j];
    24                     trace[i][j] = 1;
    25                 }
    26                 else
    27                 {
    28                     dp[i][j] = dp[i][j - 1];
    29                     trace[i][j] = 2;
    30                 }
    31             }
    32         }
    33     }
    34     return dp[len1][len2];
    35 }
    36 
    37 void print(vector<vector<int>> &trace, int i, int j, string t1)
    38 {
    39     if (i == 0 || j == 0) return;
    40     if (trace[i][j] == 0)
    41     {
    42         print(trace, i - 1, j - 1,t1);
    43         cout << t1[i-1];
    44     }
    45     else if (trace[i][j] == 1)
    46         print(trace, i - 1, j, t1);
    47     else if(trace[i][j]==2)
    48         print(trace, i, j - 1, t1);
    49 }
    50 
    51 int main()
    52 {
    53     string t1;
    54     string t2;
    55     cin >> t1 >> t2;
    56     vector<vector<int>> trace(t1.size()+1, vector<int>(t2.size()+1, -1));
    57     lcs(t1, t2, trace);
    58     print(trace, t1.size(), t2.size(), t1);
    59     //cout << endl;
    60 }
    View Code

    这里仍然是需要注意下标问题。dp[i][j]里的下标仍然代表的是长度,即对应t1[i-1],t2[j-1]. 不过trace的下标和dp一样,也是长度。这里构造出LCS序列时,可以如代码中那样利用递归,也可以while(dp[i][j]) if(dp[i][j]==dp[i-1][j]) i--; ........... 用while循环来做。(refer to bnuzhanyu in 51nod)

    reference and 算法概论,算法导论

    #2: 忘记了怎么递归地output。

    五、

    1. 最长重复子串 Longest Repeat Substring (LRS)

    最长重复子串是单字符串问题。本题中默认为允许重叠。

    直观做法 O(n^3)

     1 #include <string>
     2 #include <vector>
     3 #include <iostream>
     4 using namespace std;
     5 
     6 int comp(string str, int i, int j)
     7 {
     8     int len = 0;
     9     while (i<str.size() && j<str.size() && str[i] == str[j])
    10     {
    11         len++;
    12         i++; j++;
    13     }
    14     return len;
    15 }
    16 int lrs(string str,int &maxIndex)
    17 {
    18     int maxV = 0;
    19     for (int i = 0; i<str.size(); i++)
    20     {
    21         for (int j = i + 1; j<str.size(); j++)
    22         {
    23             int len = comp(str, i, j);
    24             if (len>maxV) {
    25                 maxV = len;
    26                 maxIndex = i;
    27             }
    28         }
    29     }
    30     return maxV;
    31 }
    32 
    33 void output(string str, int maxIndex,int len)
    34 {
    35     while (len--)
    36     {
    37         cout << str[maxIndex++];
    38     }
    39 }
    40 
    41 int main()
    42 {
    43     string str("banana");
    44     int maxIndex = 0;
    45     int len= lrs(str,maxIndex);
    46     output(str,maxIndex,len);
    47     return 0;
    48 }
    View Code

    refer1

    refer2

    后缀数组做法:

    最长公共子序列|最长公共子串|最长重复子串|最长不重复子串|最长回文子串|最长递增子序列|最大子数组和

    2. Longest Substring Without Repeating Characters [最长不重复子串]

     1 class Solution {
     2 public:
     3     int lengthOfLongestSubstring(string str) {
     4         vector<bool> exist(256, false);
     5         int n = str.size();
     6         int i = 0, j = 0;
     7         int maxlen = 0;
     8         while (j < n) {
     9             if (exist[str[j]]) {
    10                 maxlen = max(maxlen, j - i);
    11                 while (str[i] != str[j]) {
    12                     exist[str[i]] = false;
    13                     i++;    
    14                 }
    15                 i++;
    16                 j++;
    17             } else {
    18                 exist[str[j]] = true;
    19                 j++;
    20             }
    21         }
    22         maxlen = max(maxlen, n - i);
    23         return maxlen;
    24     }
    25 };
    View Code

    ref

    六、

    1. Unique Binary Search Trees

     1 class Solution
     2 {
     3 public:
     4     int numTrees(int n)
     5     {
     6         vector<int> g(n+1,0);
     7         g[0]=g[1]=1;
     8         for(int i=2;i<=n;i++)
     9         {
    10             for(int j=0;j<i;j++)
    11             {
    12                 g[i]+=g[j]*g[i-1-j];
    13             }
    14         }
    15         return g[n];
    16     }
    17 };
    View Code

    g[n]表示对于长为n的序列能有多少个UniqueBST。循环里的 i 表示序列长度,依次从短向长计算,直到计算出最终结果g[n].

    g[n]=g[0]*g[n-1]+g[1]*g[n-2]+...+g[n-1]*g[0]

    ref 解释的非常清楚。

    2. Unique Binary Search Trees II

     1 class Solution
     2 {
     3 public:
     4     vector<TreeNode*> generateTrees(int n)
     5     {
     6         return dfs(1,n);
     7     }
     8     vector<TreeNode*> dfs(int min,int max)
     9     {
    10         vector<TreeNode*> res;
    11         if(min>max) 
    12         {
    13             res.push_back(NULL);//表示空树,不可缺少。否则会影响下面对leftSub,rightSub的循环。
    14             return res;
    15         }
    16         for(int i=min;i<=max;i++)
    17         {
    18             vector<TreeNode*> leftSub=dfs(min,i-1);
    19             vector<TreeNode*> rightSub=dfs(i+1,max);
    20             for(int j=0;j<leftSub.size();j++)
    21             {
    22                 for(int k=0;k<rightSub.size();k++)
    23                 {
    24                     TreeNode* root=new TreeNode(i);
    25                     root->left=leftSub[j];
    26                     root->right=rightSub[k];
    27                     res.push_back(root);
    28                 }
    29             }
    30         }
    31         return res;
    32     }
    33 };
    View Code

    对于Line11-15,当min>max时表示空树。空树必须要加进去一个NULL来表示出来,否则,对于下面分别从leftSub和rightSub取出一个元素组成两个子树时,如果一边(假设为leftSub)为空且没有表示出空树来的话,这一层for循环就不能执行了,那对于有n-1个节点的rightSub的各种形态都没法执行循环列出来了。整个双层for循环都没法用了。

    ref

    #2: 想不出怎么实现的了。而且Line24如果放在两层循环的外面是会WA的,还没明白为什么。

    七、

    1. Maximum Subarray 最大子数组和

    code1: [美观]

     1 class Solution
     2 {
     3 public:
     4         int maxSubArray(vector<int> &nums)
     5         {
     6             int dp=0,res=INT_MIN;
     7             for(int i=0;i<nums.size();i++)
     8             {
     9                 dp=max(nums[i],dp+nums[i]);
    10                 res=max(res,dp);
    11             }
    12             return res;
    13         }
    14 };
    View Code

    code2:[不够美观]

     1 class Solution
     2 {
     3 public:
     4     int maxSubArray(vector<int> &nums)
     5     {
     6         int sum=0;
     7         int maxV=INT_MIN;
     8         for(int i=0;i<nums.size();i++)
     9         {
    10             if(sum<0) sum=0;
    11             sum+=nums[i];
    12             if(sum>maxV) maxV=sum;
    13         }
    14         return maxV;
    15     }
    16 };
    View Code

    采用dp来做的话,dp[n]代表从arr[0]到arr[n]的最大子数组和。它的子问题dp[n-1]是从arr[0]到arr[n-1]的最大子数组和。注意,起点始终是arr[0].

    看一下july列出的扩展问题以及勇幸的整理

    1. 如果数组是二维数组,同样要你求最大子数组的和列?
    2. 如果是要你求子数组的最大乘积列?
    3. 如果同时要求输出子段的开始和结束列?

    另外,关于如何用divide and conquer来解此问题,详见此贴

    2. 环形最大连续数组和

     1 int maxConsSum(const vector<int> &arr)
     2 {
     3     int dp=0,res=0;
     4     for(int i=0;i<arr.size();i++)
     5     {
     6         dp=max(arr[i],dp+arr[i]);
     7         res=max(res,dp);
     8     }
     9     return res;
    10 }
    11 
    12 int minConsSum(const vector<int> &arr)
    13 {
    14     int dp=0,res=0;
    15     for(int i=0;i<arr.size();i++)
    16     {
    17         dp=min(arr[i],dp+arr[i]);
    18         res=min(dp,res);
    19     }
    20     return res;
    21 }
    22 
    23 
    24 int maxConsSum2(const vector<int> &arr) {
    25     int sum=0;
    26     for(int i=0;i<arr.size();i++)
    27         sum+=arr[i];
    28     int m1=maxConsSum(arr);
    29     int m2=minConsSum(arr);
    30     int res=max(m1,sum-m2);
    31     return res;
    32 }
    View Code

    ref1   ref2

    一开始的思路和ref2里刚开始说的那样,想着首尾拼接起来成一个长数组。但是貌似需要O(n*n)才行(难道是列举出长度为arr.size()的这n个数组,分别对其执行最大子段和MaxSubArray的O(n)算法?还没进行实现。)后来参考了ref2和ref1的代码,如果跨越了末尾就用(数组元素之和-最小子段和),如果没跨越就用(最大子段和)。对其取max即可。

    注意一个与上题不同的细节。这题里说可以允许有空段,而上题里要求是至少含有一个元素。因此res的初值一个为INT_MIN,一个为0.

    另外,再看一下ref3提到的几个拓展问题

    可以理解为:若环形最大子段和需要跨越首尾,那么最小子段和必然不需要跨越。举个极端的例子就是,首尾元素都是正数,其余元素都是负数。

    八、Climbing Stairs

    空间复杂度O(n):【非最优】

     1 class Solution
     2 {
     3 public:
     4     int climbStairs(int n)
     5     {
     6         vector<int> dp(n+1,0);
     7         dp[0]=dp[1]=1;
     8         for(int i=2;i<=n;i++)
     9         {
    10             dp[i]=dp[i-1]+dp[i-2];
    11         }
    12         return dp[n];
    13     }
    14 };
    View Code

    若不能开辟数组的话。

    空间复杂度O(1):

     1 class Solution
     2 {
     3 public:
     4     int climbStairs(int n)
     5     {
     6         int prev=0,curr=1;
     7         int tmp=0;
     8         for(int i=1;i<=n;i++)
     9         {
    10             tmp=curr;
    11             curr+=prev;
    12             prev=tmp;        
    13         }
    14         return curr;
    15     }
    16 };
    View Code

    使用3个变量即可。和Fibonacci一样。

    九、

    1. Unique Paths

    空间复杂度O(mn) 【非最优】

     1 class Solution
     2 {
     3 public:
     4     int uniquePaths(int m,int n)
     5     {
     6         vector<vector<int>> dp(m,vector<int>(n,0));
     7         for(int i=0;i<m;i++)
     8             dp[i][0]=1;
     9         for(int i=0;i<n;i++)
    10             dp[0][i]=1;
    11         for(int i=1;i<m;i++)
    12         {
    13             for(int j=1;j<n;j++)
    14             {
    15                 dp[i][j]=dp[i-1][j]+dp[i][j-1];
    16             }
    17         }
    18         return dp[m-1][n-1];
    19     }
    20 };
    View Code

    空间复杂度O(n) 【非最优】

     1 class Solution
     2 {
     3 public:
     4     int uniquePaths(int m,int n)
     5     {
     6         vector<int> dp(n,1);
     7         for(int i=1;i<m;i++)
     8         {
     9             for(int j=1;j<n;j++)
    10             {
    11                 dp[j]=dp[j]+dp[j-1];
    12             }
    13         }
    14         return dp[n-1];
    15     }
    16 };
    View Code

    等式左边的dp[j]代表dp[i][j],等式右边的dp[j]代表dp[i-1][j]. 而dp[j-1]代表dp[i][j-1].

    可参考 ref1  ref2 学习一下滚动数组。

     利用公式 【最优】

     1 class Solution
     2 {
     3 public:
     4     int uniquePaths(int m,int n)
     5     {
     6         int N=m+n-2;
     7         int k=min(n-1,m-1);
     8         double res=1.0;
     9         for(int i=1;i<=k;i++)
    10             res*=(double)(N-i+1)/i;
    11         return round(res);                    
    12     }
    13 };
    View Code

    注意:Line10必须加double,否则等号右边精度就丢失了;Line11不能用(int),要用round,这样得到的结果还能四舍五入到准确值,否则用int的话有可能就和准确结果差了1

    ps: round实现如下:

    double round(double number)
    {
        return number < 0.0 ? ceil(number - 0.5) : floor(number + 0.5);
    }
    View Code

    ref      绝对严格的准确解参考soulmach。

    2. Unique Paths II

     1 class Solution
     2 {
     3 public:
     4     int uniquePathsWithObstacles(vector<vector<int>> &obstacleGrid)
     5     {
     6         if(obstacleGrid.empty()) return 0;
     7         int m=obstacleGrid.size();
     8         int n=obstacleGrid[0].size();
     9         vector<vector<int>> dp(m,vector<int>(n,0));
    10         for(int i=0;i<m;i++)
    11         {
    12             if(obstacleGrid[i][0]==1)
    13                 break;
    14             dp[i][0]=1;
    15         }
    16         for(int i=0;i<n;i++)
    17         {
    18             if(obstacleGrid[0][i]==1)
    19                 break;
    20             dp[0][i]=1;
    21         }
    22         for(int i=1;i<m;i++)
    23         {
    24             for(int j=1;j<n;j++)
    25             {
    26                 if(obstacleGrid[i][j]==1)
    27                     dp[i][j]=0;
    28                 else
    29                     dp[i][j]=dp[i-1][j]+dp[i][j-1];
    30             }
    31         }
    32         return dp[m-1][n-1];
    33     }
    34 };
    View Code

    注意Line22,24,一定要从1开始。从0开始就错了,因为后面需要递推i-1,j-1。

    滚动数组法节省空间:

    3. Minimum Path Sum

     1 class Solution
     2 {
     3 public:
     4     int minPathSum(vector<vector<int>> &grid)
     5     {
     6         if(grid.empty()) return 0;
     7         int m=grid.size(),n=grid[0].size();
     8         vector<vector<int>> dp(m,vector<int>(n,0));
     9         dp[0][0]=grid[0][0];
    10         for(int i=1;i<m;i++)
    11             dp[i][0]=dp[i-1][0]+grid[i][0];
    12         for(int i=1;i<n;i++)
    13             dp[0][i]=dp[0][i-1]+grid[0][i];
    14         for(int i=1;i<m;i++)
    15         {
    16             for(int j=1;j<n;j++)
    17             {
    18                 dp[i][j]=min(dp[i-1][j],dp[i][j-1])+grid[i][j];
    19             }
    20         }
    21         return dp[m-1][n-1];
    22     }
    23 };
    View Code

    4. Dungeon Game

     1 class Solution
     2 {
     3 public:
     4     int calculateMinimumHP(vector<vector<int>> &dungeon)
     5     {
     6         if(dungeon.empty()) return 1;
     7         int m=dungeon.size(),n=dungeon[0].size();
     8         vector<vector<int>> dp(m,vector<int>(n,0));
     9         dp[m-1][n-1]=max(1,1-dungeon[m-1][n-1]);
    10         for(int i=m-2;i>=0;i--)
    11             dp[i][n-1]=max(1,dp[i+1][n-1]-dungeon[i][n-1]);
    12         for(int i=n-2;i>=0;i--)
    13             dp[m-1][i]=max(1,dp[m-1][i+1]-dungeon[m-1][i]);
    14         for(int i=m-2;i>=0;i--)
    15             for(int j=n-2;j>=0;j--)
    16                 dp[i][j]=max(1,min(dp[i+1][j],dp[i][j+1])-dungeon[i][j]);
    17         return dp[0][0];
    18     }
    19 };
    View Code

    dp[i][j]: 在走进dungeon[i][j]之前至少应有的hp值。

    一开始自己写的太罗嗦了,一堆判断语句,结果其实就用一句max(1,dp[][]-dungeon[][])就行了。

     ref    ref2

    #2: 特殊处理的行和列分别是 m-1行和 n-1列, 不是0!

    此外,递推出错。最重要的递推公式是max(1, min(.....)), 别想当然。

    十、

    1. Best Time to Buy and Sell Stock

    code1:

     1 class Solution
     2 {
     3 public:
     4     int maxProfit(vector<int>& prices)
     5     {
     6         if(prices.size()<2) return 0;
     7         int dp=0,minV=prices[0];
     8         for(int i=1;i<prices.size();i++)
     9         {    
    10             dp=max(dp,prices[i]-minV);
    11             minV=min(minV,prices[i]);
    12         }
    13         return dp;
    14     }
    15 }; 
    View Code

    code2:

     1 class Solution
     2 {
     3 public:
     4     int maxProfit(vector<int> &prices)
     5     {
     6         if(prices.size()==0) return 0;
     7         int min=prices[0];
     8         int res=0;
     9         for(int i=1;i<prices.size();i++)
    10         {
    11             if(prices[i]<min) min=prices[i];
    12             if(prices[i]-min>res) res=prices[i]-min;
    13         }
    14         return res;
    15     }
    16 };
    View Code

    和最大连续子数组和类似。子问题都是以当前位置作为结尾(卖出点)时的最优解。取这N个最优解中的最大值。但是MaxSubArray里是一定包括当前位置的,本题里不一定包括当前位置,只是指定一个范围。

    2. Best Time to Buy and Sell Stock III

     1 class Solution
     2 {
     3 public:
     4     int maxProfit(vector<int> &prices)
     5     {
     6         if(prices.size()<2) return 0;
     7         const int n=prices.size();
     8         vector<int> f(n,0);
     9         vector<int> g(n,0);
    10         int minV=prices[0],maxV=prices[n-1];
    11         for(int i=1;i<n;i++)
    12         {
    13             f[i]=max(f[i-1],prices[i]-minV);
    14             minV=min(minV,prices[i]);
    15         }
    16         for(int i=n-2;i>=0;i--)
    17         {
    18             g[i]=max(g[i+1],maxV-prices[i]);
    19             maxV=max(maxV,prices[i]);
    20         }
    21         int res=0;
    22         for(int i=0;i<n;i++)
    23         {
    24             res=max(f[i]+g[i],res);
    25         }
    26         return res;
    27     }
    28 };
    View Code

    f[i]是记录[0..i]中的最大收益,是从前向后算的;g[i]是记录[i..n-1]中的最大收益,是从后向前算的。

    【注意】不要忘了第一句prices为空时的操作!细节很重要!pay attention to corner case !

    ref:soulMach.

    本题是最多允许两次交易。看看扩展到最多k次交易的一般情况。reference    【最大M子段和

    3.  Best Time to Buy and Sell Stock IV

    ref1      ref2       ref3       ref4  

    十一、 

    1. House Robber

    方法一:O(n) space

     1 class Solution
     2 {
     3 public:
     4     int rob(vector<int> &nums)
     5     {    
     6         const int n=nums.size();
     7         if(n==0) return 0;
     8         vector<int> dp(n+1,0);
     9         dp[0]=0;
    10         dp[1]=nums[0];
    11         for(int i=2;i<=n;i++)
    12         {
    13             dp[i]=max(dp[i-1],dp[i-2]+nums[i-1]);
    14         }
    15         return dp[n];
    16     }
    17 };
    View Code

    递推公式:dp[i] = max(dp[i-1], dp[i-2]+nums[i-1])

    其中这里的i指的是从下标0开始的长为i的序列能rob到的最大值。注意长度为i的序列对应的末尾元素应是nums[i-1],别忘了减1.

    之前感觉递推公式不太对,因为毕竟这里的dp[i]是表示的从0到下标i这个区间范围内所能取到的最大值,不一定包含末尾元素。这样的话dp[i-2]就有可能不包含nums[i - 3],而是以nums[i - 4]结尾,因此在它的基础上可以加上nums[i - 2],并在这种方案下取到最大值。但是后来想明白了,其实这种情况是已经包含在dp[i-1]这种情况里了。dp[i-1]所不能涵盖的就是dp[i-2]+nums[i-1]这种情况。因此最终在dp[i-1]和dp[i-2]+nums[i-1]之间取个较大值即可。

    总结起来就是,dp[i]的大多数情况都能被dp[i-1]所代替。dp[i-1]唯一代替不了的方案是以nums[i-1]为结尾元素的方案,即dp[i-2] + nums[i-1]. 将这两大方案取个较大值即可。

    ref

    #2: dp[i]里的i是表示的长度。所以开辟的vector是n+1大小的。但是在line11处却用的是i < n. 应该是 i <= n.

    方法二:O(1) space

     1 class Solution
     2 {
     3 public:
     4     int rob(vector<int> &nums)
     5     {
     6         if(nums.size()==0) return 0;
     7         int odd=0,even=0;
     8         for(int i=0;i<nums.size();i++)
     9         {
    10             if(i%2==0)
    11                 even=max(odd,even+nums[i]);
    12             else
    13                 odd=max(even,odd+nums[i]);
    14         }
    15         return even>odd?even:odd;
    16     }
    17 };
    View Code

    odd是之前在奇数位达到的最大值,even是之前在偶数位达到的最大值。

    follow up: 如果首尾相连呢?

    2. House Robber II

    code 1:

     1 class Solution
     2 {
     3 public:
     4     int rob(vector<int> &nums)
     5     {
     6         if(nums.size()==0) return 0;
     7         if(nums.size()==1) return nums[0];
     8         int odd=0,even=0;
     9         for(int i=0;i<nums.size()-1;i++)
    10         {        
    11             if(i%2==0)
    12                 even=max(odd,even+nums[i]);
    13             else
    14                 odd=max(even,odd+nums[i]);
    15         }
    16         int res1=max(even,odd);
    17         even=odd=0;
    18         for(int i=1;i<nums.size();i++)
    19         {        
    20             if(i%2==0)
    21                 even=max(odd,even+nums[i]);
    22             else
    23                 odd=max(even,odd+nums[i]);
    24         }
    25         int res2=max(even,odd);
    26         return max(res1,res2);
    27     }
    28 };
    View Code

    code 2:

     1 class Solution {
     2 public:
     3     int rob(vector<int> &nums) {
     4         int n = nums.size();
     5         if (n < 1) {
     6             return 0;
     7         }
     8         if (n == 1) {
     9             return nums[0];
    10         }
    11         vector<int> dp(n + 1, 0);
    12         int res1 = 0, res2 = 0;
    13         dp[1] = nums[0];
    14         for (int i = 2; i < n; i++) {
    15             dp[i] = max(dp[i - 1], dp[i - 2] + nums[i - 1]);
    16         }
    17         res1 = dp[n - 1];
    18         fill_n(dp.begin(), dp.size(), 0);
    19         dp[1] = nums[1];
    20         for (int i = 2; i < n; i++) {
    21             dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]);
    22         }
    23         res2 = dp[n - 1];
    24         return max(res1, res2);
    25     }
    26 };
    View Code

    如果首尾相连,其实就意味着首和尾最多只能有1个(也可能都没有,反正不能同时出现)。所以就对上一题的算法跑两遍,一遍去掉头,一遍去掉尾。取这两次结果的较大值即可。

    十二、 Triangle

     1 class Solution
     2 {
     3 public:
     4     int minimumTotal(vector<vector<int>> &triangle)
     5     {
     6         int n=triangle.size();
     7         vector<vector<int>> dp(n,vector<int>(n,0));
     8         for(int i=0;i<=n-1;i++)
     9             dp[n-1][i]=triangle[n-1][i];
    10         for(int i=n-2;i>=0;i--)
    11         {
    12             for(int j=0;j<=i;j++)
    13             {
    14                 dp[i][j]=min(dp[i+1][j],dp[i+1][j+1])+triangle[i][j];
    15             }
    16         }
    17         return dp[0][0];
    18     }
    19 };
    View Code

     从下向上计算确实比从上向下要简单很多,没有额外的边界条件需要考虑。

    滚动数组版: 

    十三、

    1. Word Break

    先来个dfs版的。一阵子不练dfs就生疏了。

     1 class Solution
     2 {
     3 public:
     4     bool wordBreak(string s, unordered_set<string> &wordDict)
     5     {
     6         return dfs(s, wordDict, 0);
     7     }
     8     bool dfs(string s, unordered_set<string> &wordDict, int start)
     9     {
    10         if (start == s.size())
    11             return true;
    12         for (int i = start; i<s.size(); i++)
    13         {
    14             if (wordDict.count(s.substr(start, i - start + 1))>0)
    15             {
    16                 if (dfs(s, wordDict, i + 1))
    17                     return true;
    18             }
    19         }
    20         return false;
    21     }
    22 };
    View Code

    DP:

     1 class Solution
     2 {
     3 public:
     4     bool wordBreak(string s, unordered_set<string> &wordDict)
     5     {
     6         vector<bool> dp(s.size()+1,0);
     7         dp[0]=1;
     8         for(int i=1;i<=s.size();i++)
     9         {
    10             for(int j=0;j<i;j++)    
    11             {
    12                 if(dp[j]&&wordDict.count(s.substr(j,i-j))>0)
    13                 {
    14                     dp[i]=true;
    15                     break;
    16                 }
    17             }
    18         }
    19         return dp[s.size()];
    20     }
    21 };
    View Code

    i表示当前子串的长度(从下标0开始),j表示此时把子串分割成的左半段的长度。dp[i]就表示从0开始长度为i的子串是不是满足wordBreak的。

    ref     自底向上,由小到大的构造出来。

    2. Word Break II

    常规dfs版:【TLE】

     1 class Solution
     2 {
     3 public:
     4     vector<string> wordBreak(string s,unordered_set<string> &wordDict)
     5     {
     6         dfs(s,wordDict,0);
     7         return result;
     8     }
     9     vector<string> result;
    10     string path;
    11     void dfs(string s,unordered_set<string> &wordDict,int start)
    12     {
    13         if(start==s.size())    
    14         {
    15             result.push_back(path);
    16             return;
    17         }
    18         for(int i=start;i<s.size();i++)
    19         {
    20             if(wordDict.count(s.substr(start,i-start+1))>0)
    21             {
    22                 if(!path.empty()) path+=" ";
    23                 path+=s.substr(start,i-start+1);
    24                 dfs(s,wordDict,i+1);
    25                 path.resize(path.size()-(i-start+1));
    26                 if(!path.empty()) path.resize(path.size()-1);
    27             }
    28         }
    29     }
    30 };
    View Code

    改进版dfs--dp剪枝版:(除了利用word break里的函数外,只比上面的 [常规dfs版] 多了line6这一句话)

     1 class Solution
     2 {
     3 public:
     4     vector<string> wordBreak(string s,unordered_set<string> &wordDict)
     5     {
     6         if(!isWordBreak(s,wordDict)) return result;
     7         dfs(s,wordDict,0);
     8         return result;
     9     }
    10 private:
    11     vector<string> result;
    12     string path;
    13     void dfs(string s,unordered_set<string> &wordDict,int start)
    14     {
    15         if(start==s.size())    
    16         {
    17             result.push_back(path);
    18             return;
    19         }
    20         for(int i=start;i<s.size();i++)
    21         {
    22             if(wordDict.count(s.substr(start,i-start+1))>0)
    23             {
    24                 if(!path.empty()) path+=" ";
    25                 path+=s.substr(start,i-start+1);
    26                 dfs(s,wordDict,i+1);
    27                 path.resize(path.size()-(i-start+1));
    28                 if(!path.empty()) path.resize(path.size()-1);
    29             }
    30         }
    31     }
    32     
    33     bool isWordBreak(string s, unordered_set<string> &wordDict)
    34     {
    35         vector<bool> dp(s.size()+1,0);
    36         dp[0]=1;
    37         for(int i=1;i<=s.size();i++)
    38         {
    39             for(int j=0;j<i;j++)    
    40             {
    41                 if(dp[j]&&wordDict.count(s.substr(j,i-j))>0)
    42                 {
    43                     dp[i]=true;
    44                     break;
    45                 }
    46             }
    47         }
    48         return dp[s.size()];
    49     }
    50 };
    View Code

    参考该博客,dp的作用只有1行,line6处。

    纯dp版:

    refer to discuss

    十四、

    1. Largest Rectangle in Histogram

     1 class Solution
     2 {
     3 public:
     4     int largestRectangleArea(vector<int> &height)
     5     {
     6         stack<int> s;
     7         height.push_back(0);
     8         int i=0,res=0;
     9         while(i<height.size())
    10         {
    11             if(s.empty()||height[i]>=height[s.top()])
    12                 s.push(i++);
    13             else
    14             {
    15                 int tmp=s.top();
    16                 s.pop();
    17                 res=max(res,height[tmp]*(s.empty()?i:i-s.top()-1));
    18             }
    19         }
    20         return res;
    21     }
    22 };
    View Code

     关键是对于一个特定的柱体,如何最大限度的向左右扩展。

    首先看向右扩展。假设当前需要出栈的柱体的下标是x,那么当它要出栈时,说明当前遍历到的下标i对应的柱体高度h[i]小于h[x].(其实也说明了下标i之前的柱体高度都是大于h[x]的) 所以当前柱体向右扩展最多能扩展到i的前一个,即下标i-1对应的柱体。

    下面看向左扩展。从当前要出栈的柱体x开始向左依次找,第一个高度小于h[x]的柱体即为向左扩展的左边界(不包含)。而第一个高度小于h[x]的柱体恰恰是栈中紧挨着它的那个,即把柱体x出栈之后的栈顶元素s.top()(不包含)。特例:若此时栈已经空了,那么左边界就是下标0(包含)。

    所以,当栈非空时,柱体x向左扩展的边界是s.top()+1(包含),向右扩展的边界是i-1(包含),介于这两个之间共有 i-s.top()-1 个柱体。

         当栈为空时,向左扩展的边界是0(包含),右边界不变,共有 个柱体。

    ref

    #2: Line11 忘了加上 if(s.empty()) 这个判断。这会导致RE. Line17也忘了考虑s.empty();

    2. Maximal Rectangle

     1 class Solution
     2 {
     3 public:
     4     int maximalRectangle(vector<vector<char>> &matrix)
     5     {
     6         if(matrix.empty()) return 0;
     7         int m=matrix.size(),n=matrix[0].size();
     8         vector<int> height(n,0);
     9         int res=0;
    10         for(int i=0;i<m;i++)
    11         {
    12             for(int j=0;j<n;j++)
    13             {
    14                 if(matrix[i][j]=='0')
    15                     height[j]=0;
    16                 else
    17                     height[j]+=1;
    18             }
    19             res=max(res,largestRectangleArea(height));
    20         }
    21         return res;
    22     }
    23 
    24     int largestRectangleArea(vector<int> &height)
    25     {
    26         stack<int> s;
    27         height.push_back(0);
    28         int i=0,res=0;
    29         while(i<height.size())
    30         {
    31             if(s.empty()||height[i]>=height[s.top()])
    32                 s.push(i++);
    33             else
    34             {
    35                 int tmp=s.top();
    36                 s.pop();
    37                 res=max(res,height[tmp]*(s.empty()?i:i-s.top()-1));
    38             }
    39         }
    40         return res;
    41     }
    42 };
    View Code

    先预处理再做,是一个很好的技巧。

    学以致用。预处理出高度来,再利用上一题的代码。

    ref

    3. Trapping Rain Water

     1 class Solution
     2 {
     3 public:
     4     int trap(vector<int> &height)
     5     {
     6         int n=height.size();
     7         if(n==0) return 0;
     8         vector<int> leftMost(n,0);
     9         vector<int> rightMost(n,0);
    10         int maxV=0,res=0;
    11         for(int i=0;i<n;i++)
    12         {
    13             leftMost[i]=maxV;
    14             maxV=max(maxV,height[i]);    
    15         }
    16         maxV=height[n-1];
    17         for(int i=n-1;i>=0;i--)
    18         {
    19             rightMost[i]=maxV;
    20             maxV=max(maxV,height[i]);
    21         }
    22         for(int i=0;i<n;i++)
    23         {
    24             if(min(leftMost[i],rightMost[i])>height[i])
    25                 res+=min(leftMost[i],rightMost[i])-height[i];
    26         }
    27         return res;        
    28     }
    29 };
    View Code

    规律:对每个小柱子X而言,它头顶上会不会有水取决于它左边所有柱子里最高的那个柱子A以及它右边所有柱子里最高的那个柱子B。A和B较矮的那个如果高过当前这个柱子X,那么这个小柱子X头顶上就能有 min(height[A],height[B]) - height[X] 这么高的水。

    时间O(n),空间O(n)。

    注意对n==0的边界条件的判断。【corner case !】

    ref1     ref2

    学习下空间复杂度为O(1)的解法。   ref1     ref2      ref3

    4. Container With Most Water

     1 class Solution
     2 {
     3 public:
     4     int maxArea(vector<int> &height)
     5     {
     6         if(height.empty()) return 0;
     7         int p1=0,p2=height.size()-1;
     8         int res=0;
     9         while(p1<=p2)
    10         {
    11             if(height[p1]<height[p2])
    12             {
    13                 res=max(res,height[p1]*(p2-p1));
    14                 p1++;
    15             }
    16             else
    17             {
    18                 res=max(res,height[p2]*(p2-p1));
    19                 p2--;
    20             }
    21         }
    22         return res;
    23     }
    24 };
    View Code

    比较左右两板,对于较短的板而言,无论另一块板(较长的板)如何向中间移动,都不可能取的比现在更大的面积。因此记录下此时的面积,将短板向中间移动。

  • 相关阅读:
    java_29打印流
    java_26 缓冲流
    java-27 Properties类的使用
    java_24.1文件流的应用--复制文件
    java_25 FileReader类和FileWriter类
    java_25.1字节转为字符OutputStreamWriter
    java_23 File类
    java_24 FileOutputStream类和FileInputStream类
    java_21 Set接口、HashSet类、LinkedSet类
    随机数Random和静态函数Math
  • 原文地址:https://www.cnblogs.com/forcheryl/p/4534973.html
Copyright © 2020-2023  润新知