动态规划练习

目录

基本动态规划:一维

剑指 Offer 10- I. 斐波那契数列

剑指 Offer 10- II. 青蛙跳台阶问题

剑指 Offer 63. 股票的最大利润

剑指 Offer 42. 连续子数组的最大和

198. 打家劫舍

413. 等差数列划分

基本动态规划:二维

64. 最小路径和

542. 01 矩阵

221. 最大正方形


基本动态规划:一维

不能使用递归,会超时。

class Solution {
public:
    int fib(int n) {
        int mod = 1000000007;
        if(n < 2)
        {
            return n;
        }
        
        int first = 0, second =1;
        int count;
        for(int i = 2;i < n+1;i++)
        {
            count = (first+second)%mod;
            first = second;
            second = count;
        }
        return count;
    }
};

 跳上N节台阶的可能为N-1 跳一次1 和N-2 跳一次2加起来的可能。

class Solution {
public:
    int numWays(int n) {
        int mod = 1000000007;
        if(n == 0 | n == 1)
            return 1;
        int first = 1;
        int second = 1;
        int count;

        for(int i = 2; i <= n;i++)
        {
            count = (first + second)%mod;
            first = second;
            second = count;
        }

        return count;
    }
};

记录一个历史的最低价格

class Solution {
public:
    int maxProfit(vector<int>& prices) {
       int inf = 1000000000;
       int minprice = inf , maxprice = 0;
       for(int pe : prices)
       {
           maxprice = max(maxprice,pe - minprice);
           minprice = min(pe , minprice);  //记录一个历史最低价格
       }
       return maxprice;
    }
};

 

比较当前值 及其 当前值+pre的和,存为当前的pre

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int pre = 0, maxAns = nums[0];
        for(int a : nums)
        {
            pre = max(pre+a , a);
            maxAns = max(pre , maxAns);
        }
        return maxAns;
    }
};

定义一个数组 dp,dp[i] 表示抢劫到第 i 个房子时,可以抢劫的最大数量。我们考虑 dp[i],此时可以抢劫的最大数量有两种可能,一种是我们选择不抢劫这个房子,此时累计的金额即为dp[i-1];另一种是我们选择抢劫这个房子,那么此前累计的最大金额只能是 dp[i-2],因为我们不能够抢劫第 i-1 个房子,否则会触发警报机关。因此本题的状态转移方程为 dp[i] = max(dp[i-1], nums[i-1] + dp[i-2])

class Solution {
public:
    int rob(vector<int>& nums) {
        int n = nums.size();
        if(n == 0)
            return 0;
        vector<int> dp(n+1,0);   //表示抢到第i个房间之前最大金额
        
        dp[1] = nums[0];  //抢到第一个房子之前,就是nums[0]
        for(int i = 2;i <= n ;i++)
        {
            dp[i] = max(dp[i-1],dp[i-2]+nums[i-1]); 
            //第i个房子不抢 , 或者 抢 然后加上 当前房子金额nums[i-1]
        }
        return dp[n];
    }
};
class Solution {
public:
    int rob(vector<int>& nums) {
        int n = nums.size();
        if(n == 0)
            return 0;

        int cur = 0;
        int pre = 0;  
        int ppre = 0; 

        for(int i = 0;i < n ;i++)
        {
            cur = max(pre , ppre+nums[i]); 
            ppre = pre;
            pre = cur;
        }
        return cur;
    }
};

 

 子数组必定满足 num[i] - num[i-1] = num[i-1] - num[i-2]。然而由于我们对于 dp 数组的定义通常为以 i 结尾的,满足某些条件的子数组数量,而等差子数组可以在任意一个位置终结,因此此题在最后需要对 dp 数组求和。

class Solution {
public:
    int numberOfArithmeticSlices(vector<int>& nums) {
        int n = nums.size();
        if(n <= 2) return 0;
/// 以i结尾
        vector<int> dp(n ,0);

        for(int i = 2;i < n ;i++)
        {
            if(nums[i] - nums[i-1] == nums[i-1] - nums[i-2])
            {
                dp[i] = dp[i-1]+1;
            }
        }
        return accumulate(dp.begin(),dp.end(),0);
        //求出所有位置的和
    }
};

 

基本动态规划:二维

定义一个同样是二维的 dp 数组,其中 dp[i][j] 表示从左上角开始到 (i, j) 位置的最优路径的数字和。因为每次只能向下或者向右移动,我们可以很容易得到状态转移方程 dp[i][j] =min(dp[i-1][j], dp[i][j-1]) + grid[i][j],其中 grid 表示原数组。

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int m = grid.size();  // m x n
        int n = grid[0].size();  //
        vector<vector<int>> dp(m,vector<int>(n,0));  //状态转移方程:dp表示到达(i,j)位置的最短路径

        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];  //返回到达右下角的最短路径
    }
};

 

 

 从左上到右下进行一次动态搜索,然后在进行一次从右下到左上的一次动态搜索,来完成四个方向的查找。

 class Solution {
public:
    vector<vector<int>> updateMatrix(vector<vector<int>>& mat) {
        int m = mat.size();
        int n = mat[0].size();
        vector<vector<int>> dp(m,vector(n, INT_MAX-1));

        for(int i = 0;i < m;i++)
        {
            for(int j = 0;j < n;j++)
            {
                if(mat[i][j] == 0) 
                {
                    dp[i][j] = 0;
                }
                else{
                    if(i > 0)
                    {
                        dp[i][j] = min(dp[i][j] , dp[i-1][j]+1);
                    }
                    if(j > 0)
                    {
                        dp[i][j] = min(dp[i][j] , dp[i][j-1] + 1);
                    }
                    //此时dp存储的是INT_MAX-1和上、左+1的最小值;
                }
            }
        }//此时除了【0,0】,应该除了距离就是INT_MAX

        for(int i = m-1 ;i >= 0;i--)
        {
            for(int j = n-1 ;j >= 0;j--)
            {
                if(mat[i][j] != 0)
                {
                    if( i < m -1)
                    {
                        dp[i][j] = min(dp[i][j] ,dp[i + 1][j] + 1);
                    }
                    if(j < n - 1)
                    {
                        dp[i][j] = min(dp[i][j] ,dp[i][j+1] + 1);
                    }
                }  //此时dp存储的是刚刚的dp与下、右+1的最小值。
            }
        }
        return dp;  
    }
};
//如果使用上题解法,(0,0)为1时会出现问题

 


方法一:暴力解法 

class Solution {
public:
    int maximalSquare(vector<vector<char>>& matrix) {
        int m = matrix.size();
        int n = matrix[0].size();
        if(m == 0 || n == 0)
        {
            return 0;
        }
        int maxSide = 0;

        for(int i = 0; i < m ;i++)
        {
            for(int j = 0;j < n;j++)
            {
                if(matrix[i][j] == '1')    //如果遇到一个1作为正方形的左上角
                {
                    maxSide = max(maxSide,1);    //存储最大
                    int currentMaxSide = min(m-i,n-j);    //计算当前可能的最大正方形边长

                    for(int k = 1; k < currentMaxSide ;k++)
                    {
                        //判断未来的每一行是否都为1
                        bool flag = true;
                        if(matrix[i+k][j+k] == '0') //判断
                        {
                            break;
                        }
                        for(int m = 0;m < k;m++)  //判断新增的行列是否为1
                        {
                            if(matrix[i+k][j+m] == '0' || matrix[i+m][j+k] == '0')
                            {
                                flag = false;
                                break;
                            }
                        }
                        if(flag)
                        {
                            maxSide = max(maxSide ,k+1);
                        }else{
                            break;
                        }
                    }
                }
            }
        }
        int maxSquare = maxSide * maxSide;
        return maxSquare;
    }
};

方法二:动态规划——定义一个二维 dp 数组,其中 dp[i][j] 表示满足题目条件的、以 (i, j) 为右下角的正方形或者长方形的属性。

class Solution {
public:
    int maximalSquare(vector<vector<char>>& matrix) {
        int m = matrix.size();
        int n = matrix[0].size();
        if(m == 0 || n == 0)
        {
            return 0;
        }
        int maxSide = 0;
        vector<vector<int>> dp(m,vector<int>(n));
        //dp[i][j]表示一(i,j)为右下角的正方向或者长方形的边长
        for(int i = 0; i < m;i++)
        {
            for(int j = 0; j < n;j++)
            {
                if(matrix[i][j] == '1')
                {
                    if(i == 0||j == 0)
                    {
                        dp[i][j] = 1;
                    }else{
                        dp[i][j] = min(min(dp[i-1][j], dp[i][j-1]) , dp[i-1][j-1]) + 1;
                    }
                    maxSide = max(maxSide,dp[i][j]);
                }
            }
        }
        int maxSquare = maxSide * maxSide;
        return maxSquare;
    }
};

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值