leetcode-动态规划

编辑 / leetcode / 发布于2020-10-08 / 更新于2023-03-16 / 阅读 225

5. 最长回文子串

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

  • 暴力搜索,失败,耗费时间太长。
  • 动态规划,通过。

使用动态规划时首先要找到题目的状态转移方程,以字符串的长度进行增长。

方程: P(i,j) = P(i+1,j-1) && s[i]==s[j] (s是字符串)

    class Solution {
    public:
        bool Is(string s,int st,int len)
        {
            for(int i=0;i<len/2;i++)
            {
                if(s[st+i]!=s[st+len-i-1])
                    return false;
            }
            return true;
        }
        string longestPalindrome(string s) {
            int len = s.length();
            int max=-1;
            string res;
            vector<vector<bool>> dp(len,vector<bool>(len));
            for(int l=0;l<len;l++)
            {
                for(int i=0;i+l<len;i++)
                {
                    if(l==0)
                    {
                        dp[i][i+l]=true;
                    }
                    else if(l==1)
                    {
                        dp[i][i+l]= s[i]==s[i+l];
                    }
                    else
                    {
                        dp[i][i+l] = dp[i+1][i+l-1] && s[i]==s[i+l];
                    }
                    if(dp[i][i+l] &&l>max)
                    {
                        max=l;
                        res=s.substr(i,max+1);
                    }
                }
            }
            return res;         
        }
    };

32. 最长有效括号

动态规划算法。

1

  • dp[i][j] = n ,代表从i - j 是有效括号字符串 且长度为n。
  • 列出状态转移方程:
    dp[i][j] =
    • if dp[i+1][j-1]>0 && s[i]==' ( ' && s[j] ==' ) ' then dp[i][j] = dp[i+1][j-1]+2
    • for k = i+1 : j-1
      if s[i][k]>0 && s[k+1][j]>0 then dp[i][j] = dp[i][k]+dp[i+1][j]
  • 编写算法,发现时间复杂度有点高,最后有三个用例时间超时。
  • 总结: 可以发现 n=j-i+1,这就说明,j在此处有些多余,改进时可将j去掉。

2

  • dp[i]=n , 代表截至到 i 的有效括号字符串长度是 n 。
  • 状态转移方程:
    dp[i] = if s[i]==')'
    • if s[i-1] == '(' then dp[i] = dp[i-2] + 2
    • else if s[i - dict[i-1]-1]=='(' then dp[i] = dp[i-1] + 2 + dp[i - dict[i-1]-2]
    class Solution {
    public:
        int longestValidParentheses(string s) {
            int len = s.length();
            vector<int> dict(len,0);
            int max = 0;
            for(int i=1;i<len;i++)
            {
                if(s[i]==')')
                {
                    if(s[i-1]=='(')
                    {
                        dict[i] = i>1?dict[i-2]+2:2;
                    }
                    else if(i - dict[i-1]-1>=0 &&s[i - dict[i-1]-1]=='(')
                    {
                        dict[i] = dict[i-1]+2;
                        if(i - dict[i-1]-2>=0 &&dict[i - dict[i-1]-2]>0)
                        {
                            dict[i]+=dict[i - dict[i-1]-2];
                        }
                    }
                    max = dict[i]>max?dict[i]:max;
                }
            }
            return max;
        }         
    };

62. 不同路径

  • 方程:$dp[i][j] = dp[i-1][j] + dp[i][j-1]$

  • 注意,对于第一行 dp[0][j],或者第一列 dp[i][0],由于都是在边界,所以只能为 1 !!!

class Solution {
public:
    int uniquePaths(int m, int n) {
        int *dp = (int *)malloc(sizeof(int)*m*n);
        for(int i=0;i<m;i++)
        {
            for(int j=0;j<n;j++)
            {
                if(i==0||j==0)
                {
                    dp[i*n+j] =1;
                }
                else
                {
                    dp[i*n+j] =dp[i*n+j-n]+dp[i*n+j-1] ;
                }
            }
        }
        return dp[m*n-1];
    }
};

数学方法: 机器人会走 m-1 + n-1 步,其中一定需要向下走 m-1 步,所以有 $C_{m+n-2} ^ $

64. 最小路径和

  • 类似于62题,唯一的区别就是对于路径增加了权重,从而无法使用数学方法解答了。

  • 方程:$dp[i][j] = min(dp[i-1][j],dp[i][j-1]) + grid[i][j]$

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int m = grid.size();
        int n = grid[0].size();
        vector<vector<int>> dp(m,vector<int>(n));

        for(int i=0;i<m;i++)
        {
            for(int j=0;j<n;j++)
            {
                if(i==0&&j==0)
                {
                    dp[i][j] = grid[i][j];
                }
                else if(i==0)
                {
                    dp[i][j]=dp[i][j-1]+grid[i][j];
                }
                else if(j==0)
                {
                    dp[i][j]=dp[i-1][j]+grid[i][j];
                }
                else
                {
                    dp[i][j]  = min(dp[i-1][j],dp[i][j-1])+grid[i][j];
                }
            }
        }
        return dp[m-1][n-1];

    }
};

121. 买卖股票的最佳时机

  • 方程:$dp[i] = max(dp[j] + price[i] - price[j]) (j = i-1 : 0)$
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int len =prices.size();
        vector<int> dp(len,0);
        int max = 0;
        for(int i=1;i<len;i++)
        {
            for(int j=i-1;j>=0;j--)
                {
                    if(prices[i]>=prices[j])
                    {
                        dp[i] = dp[j] +prices[i]-prices[j];
                        break;
                    }
                }
            max = max<dp[i]?dp[i]:max;
        }
        return max;

    }
};
  • $dp[i]=max(dp[i-1],prices[i]-min)$
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        if(prices.size()==0)
            return 0;
        vector<int> dp(prices.size(),0);
        int _min = prices[0];
        for(int i=1;i<prices.size();i++)
        {
            dp[i] = max(dp[i-1],prices[i]-_min);
            _min = min(_min,prices[i]);
        }   
        return dp[prices.size()-1];
    }
};

198. 打家劫舍

  • 方程 $dp[i] = max(dp[i-1],dp[i-2]+nums[i]);$
  • 本题目中我们不需要全部放的dp数组,所以还可以进行空间优化,
class Solution {
public:
    int rob(vector<int>& nums) {
        int size = nums.size();
        if(size==0)
            return 0;
        if(size==1)
            return nums[0];
        vector<int> dp(size,0);
        dp[0] = nums[0];
        dp[1] = max(nums[0],nums[1]);
        for(int i=2;i<size;i++)
        {
            dp[i] = max(dp[i-1],dp[i-2]+nums[i]);
        }
        return dp[size-1];
    }
};

338. 比特位计数

  • 主要的是需要找到状态转移函数,其他的就好办了,可以通过观察i分别和 i-1,i/2的关系来找到方程。
  • $dp[i]=\begin
    dp[i-1] + 1,&&{i \bmod 2} \not={0}\
    dp[i/2],&&{i \bmod 2}={0}
    \end
    $
  • 对上述方程还可以进行进一步优化, $dp[i] = dp[i/2] + i\bmod2$
class Solution {
public:
    vector<int> countBits(int num) {
        vector<int> ret(num+1,0);
        for(int i=0;i<=num;i++)
        {
            ret[i] = i&1? ret[i-1]+1:ret[i/2];
        }
        return ret;
    }
};

96. 不同的二叉搜索树

  • 主要还是要找到状态转移函数。
  • 思路是将一个大的树分为两个小的树分别计算,然后左右两边相乘在求和。如n的节点的树,以第1个节点为根节点,分成左边0个节点,右边n-1节点的树,以第2个节点为根节点,分成左边1个节点,右边n-2节点的树,以第3个节点为根节点,分成左边2个节点,右边n-3节点的树。
  • $dp[i] = \displaystyle\sum_^ dp[j]*dp[i-1-j]$
class Solution {
public:
    int numTrees(int n) {
        vector<int> dp(n+1,1);
        for(int i=1;i<=n;i++)
        {
            int sum=0;
            for(int j=0;j<i;j++)
            {
                sum+=dp[j]*dp[i-1-j];
            }
            dp[i] = sum;
        }
        return dp[n];
    }
};

279. 完全平方数

  • 动态规划 $dp[i] = min(dp[i-k]+1) , k\in sq$ (sq为平方数的序列)
class Solution {
public:
    int numSquares(int n) {
        vector<int> sq;
        vector<int> dp(n+1);
        for (int i = 1; i * i <= n; i++)
        {
            sq.push_back(i * i);
        }
        for (int i = 1; i < n + 1; i++)
        {
            int _min = i;
            for (int j = 0; j < sq.size() && i - sq[j] >= 0; j++)
            {
                _min = min(_min, dp[i - sq[j]] + 1);
            }
            dp[i] = _min;
        }
        return dp[n];
    }
};

309. 最佳买卖股票时机含冷冻期

  • $dp[i] = max{dp[j]+max{prices[i]-prices[k](k= j+2 \backsim i-1)}}(j= 0 \backsim i-1)$ 时间复杂度$O(n^2)$
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        if(prices.size()<2)
            return 0;
        vector<int> dp(prices.size());
        int m1=prices[0];
        m1 = min(m1,prices[1]);
        if(prices.size()>2)
            m1 = min(m1,prices[2]);
        dp[1] = max(0,prices[1]-prices[0]);
        
        for(int i=2;i<prices.size();i++)
        {
            int m = prices[i];
            for(int j=i-2;j>0;j--)
            {
                m = min(m,prices[j+2]);
                dp[i]= max(dp[i],(dp[j]+prices[i]-m));
            }
            dp[i]= max(dp[i],dp[i-1]);
            dp[i]= max(dp[i],prices[i]-m1);
        }   
        return dp[prices.size()-1];
    }
};
  • 另外一种,带有三种状态转换的动态规划,时间复杂度O(n)。见解析