leetcode 动态规划

目录

53 最大子序和

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例:

输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

进阶:

如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。

class Solution {
public:
    int maxSubArray(vector<int>& nums) 
    {
        vector<int> dp(nums.size()+6,0);
        dp[0]=nums[0];
        int max_res=dp[0];
        
        for(int i=1;i<nums.size();i++)
        {
            dp[i]=max(dp[i-1]+nums[i],nums[i]);
            if(max_res<dp[i])
            {
                max_res=dp[i];
            }
        }
        return max_res;
        
    }
};

62 不同路径(回到目录

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。

问总共有多少条不同的路径?

例如,上图是一个7 x 3 的网格。有多少可能的路径?

说明:m 和 n 的值均不超过 100。

示例 1:

输入: m = 3, n = 2
输出: 3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向右 -> 向下
2. 向右 -> 向下 -> 向右
3. 向下 -> 向右 -> 向右
示例 2:

输入: m = 7, n = 3
输出: 28

方法1

//动态规划
class Solution {
public:
    int uniquePaths(int m, int n) 
    {
        vector<vector<int> >dp(m,vector<int>(n,0));
        for(int i=0;i<m;i++)
        {
            dp[i][0]=1;
        }
        for(int j=0;j<n;j++)
        {
            dp[0][j]=1;
        }
        for(int i=1;i<m;i++)
        {
            for(int j=1;j<n;j++)
            {
                dp[i][j]=dp[i][j-1]+dp[i-1][j];
            }
        }
        return dp[m-1][n-1];
    }
};

方法2

这跟之前那道 Climbing Stairs 爬梯子问题 很类似,那道题是说可以每次能爬一格或两格,问到达顶部的所有不同爬法的个数。而这道题是每次可以向下走或者向右走,求到达最右下角的所有不同走法的个数。那么跟爬梯子问题一样,我们需要用动态规划Dynamic Programming来解,我们可以维护一个二维数组dp,其中dp[i][j]表示到当前位置不同的走法的个数,然后可以得到递推式为:dp[i][j] = dp[i - 1][j] + dp[i][j - 1],这里为了节省空间,我们使用一维数组dp,一行一行的刷新也可以,代码如下:

class Solution {
public:
    int uniquePaths(int m, int n) 
    {
        vector<int> dp(n,1);
        for(int i=1;i<m;i++)
        {
            for(int j=1;j<n;j++)
            {
                dp[j]=dp[j]+dp[j-1];
            }
        }
        return dp[n-1];
    }
};

63 不同路径 II(回到目录

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。

现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?

网格中的障碍物和空位置分别用 1 和 0 来表示。

说明:m 和 n 的值均不超过 100。

示例 1:

输入:
[
  [0,0,0],
  [0,1,0],
  [0,0,0]
]
输出: 2
解释:
3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:
1. 向右 -> 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右 -> 向右
class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) 
    {
        if(obstacleGrid.empty() || obstacleGrid[0].empty() || obstacleGrid[0][0]==1)
         {
             return 0;
         }
        int m=obstacleGrid.size();
        int n=obstacleGrid[0].size();
        vector<vector<int> > dp(m,vector<int>(n,0));
        for(int i=0;i<m;i++)
        {
            for(int j=0;j<n;j++)
            {
                if(obstacleGrid[i][j]==1)
                {
                    dp[i][j]=0;
                }
                else if(i==0 && j==0)
                {
                    dp[i][j]=1;
                }
                else if(i==0 && j>0)
                {
                    dp[i][j]=dp[i][j-1];
                }
                else if(i>0 && j==0)
                {
                    dp[i][j]=dp[i-1][j];
                }
                else if(i>0 && j>0)
                {
                    dp[i][j]=dp[i][j-1]+dp[i-1][j];
                }
            }
        }
        return dp[m-1][n-1];
    }
};

64 最小路径和(回到目录

给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

说明:每次只能向下或者向右移动一步。

示例:

输入:
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
输出: 7
解释: 因为路径 1→3→1→1→1 的总和最小。

代码1:

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) 
    {
        if(grid.size()==0) return 0;
        int row=grid.size();
        int col=grid[0].size();
        vector<vector<int> >dp(row,vector<int>(col,0));
        dp[0][0]=grid[0][0];
        for(int j=1;j<col;j++)
        {
            dp[0][j]=grid[0][j]+dp[0][j-1];
            
        }
        for(int i=1;i<row;i++)
        {
            dp[i][0]=grid[i][0]+dp[i-1][0];
            for(int j=1;j<col;j++)
            {
                dp[i][j]=min(dp[i-1][j],dp[i][j-1])+grid[i][j];
            }
        }
        return dp[row-1][col-1];
    }
};

代码2:

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int m = grid.size();
        int n = grid[0].size();
        if(m == 0 || n == 0)
            return 0;
        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];
    }
};

70 爬楼梯答(回到目录

假设你正在爬楼梯。需要 n 步你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

注意:给定 n 是一个正整数。

示例 1:
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1.  1 步 + 1 步
2.  2 步

示例 2:
输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
1.  1 步 + 1 步 + 1 步
2.  1 步 + 2 步
3.  2 步 + 1 步
class Solution {
public:
    int climbStairs(int n) 
    {
        vector<int> dp(n,0);
        dp[1]=1;
        dp[2]=2;
        for(int i=3;i<n;i++)
        {
            dp[i]=dp[i-1]+dp[i-2];
        }
        return dp[n];
    }
};

91 解码方法

一条包含字母 A-Z 的消息通过以下方式进行了编码:

'A' -> 1
'B' -> 2
...
'Z' -> 26
给定一个只包含数字的非空字符串,请计算解码方法的总数。

示例 1:

输入: "12"
输出: 2
解释: 它可以解码为 "AB"(1 2)或者 "L"(12)。
示例 2:

输入: "226"
输出: 3
解释: 它可以解码为 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。

这道题要求解码方法,跟之前那道 Climbing Stairs 爬梯子问题 非常的相似,但是还有一些其他的限制条件,比如说一位数时不能为0,两位数不能大于26,其十位上的数也不能为0,出去这些限制条件,根爬梯子基本没啥区别,也勉强算特殊的斐波那契数列,当然需要用动态规划Dynamci Programming来解。建立一位dp数组,长度比输入数组长多多2,全部初始化为1,因为斐波那契数列的前两项也为1,然后从第三个数开始更新,对应数组的第一个数。对每个数组首先判断其是否为0,若是将改为dp赋0,若不是,赋上一个dp值,此时相当如加上了dp[i - 1], 然后看数组前一位是否存在,如果存在且满足前一位不是0,且和当前为一起组成的两位数不大于26,则当前dp值加上dp[i - 2], 至此可以看出来跟斐波那契数组的递推式一样,代码如下:

class Solution {
public:
    int numDecodings(string s) {
        if (s.empty() || (s.size() > 1 && s[0] == '0')) return 0;
        vector<int> dp(s.size(), 0);
        dp[0] = 1;
        for (int i = 1; i < dp.size(); ++i) {
            dp[i] = (s[i - 1] == '0') ? 0 : dp[i - 1];
            if (i > 1 && (s[i - 2] == '1' || (s[i - 2] == '2' && s[i - 1] <= '6'))) {
                dp[i] += dp[i - 2];
            }
        }
        return dp.back();
    }
};

96 不同的二叉搜索树(回到目录

给定一个整数 n,求以 1 … n 为节点组成的二叉搜索树有多少种?

示例:

输入: 3
输出: 5
解释:
给定 n = 3, 一共有 5 种不同结构的二叉搜索树:

   1         3     3      2      1
    \       /     /      / \      \
     3     2     1      1   3      2
    /     /       \                 \
   2     1         2                 3

二分查找树的定义是,左子树节点均小于root,右子树节点均大于root,所以可以用递推的方法,把dp[i]表示i个数能够构成的二叉搜索树的个数.初始化边界值是 dp[0]=1,dp[1]=1,dp[2]=2。
当i>=3的时候,若以j为root结点,dp[j-1]等于root结点左边的j-1个结点能构成的BST个数.
dp[i-j]等于root结点右边i-j个结点能构成的BST个数。因为j+1i的种数和0i-j的种数一样,所以就是dp[i-j]所以dp[j-1] * dp[i-j]等于以j为root结点能构成的BST种数
j可以取1~i中的任意一个值,把这些所有计算出来的总数相加就是v[i]的值


                    1                        n = 1

                2        1                   n = 2
               /          \
              1            2
  
   1         3     3      2      1           n = 3
    \       /     /      / \      \
     3     2     1      1   3      2
    /     /       \                 \
   2     1         2                 3
class Solution {
public:
    int numTrees(int n) 
    {
        vector<int> dp(n+1);
        dp[0]=1;
        dp[1]=1;
        dp[2]=2;
        for(int i=3;i<=n;i++)
        {
            for(int j=1;j<=i;j++)
            {
                dp[i] +=dp[i-1]*dp[i-j];
            }
        }
        return dp[n];
    }
};

120 三角形最小路径和(回到目录

给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。

例如,给定三角形:

[
     [2],
    [3,4],
   [6,5,7],
  [4,1,8,3]
]

自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。

使用常规的动态规划解法

class Solution {
public:
    int minimumTotal(vector<vector<int>>& triangle) 
    {
        vector<vector<int> > dp;
        for(int i=0;i<triangle.size();i++)
        {
            dp.push_back(vector<int>());
            for(int j=0;j<triangle[i].size();j++)
            {
                dp[i].push_back(0);
            }
        }
        for(int j=0;j<dp[dp.size()-1].size();j++)
        {
            dp[dp.size()-1][j]=triangle[dp.size()-1][j];
        }
        
        for(int i=triangle.size()-2;i>=0;i--)
        {
            for(int j=0;j<=triangle[i].size();j++)
            {
                dp[i][j]=min(dp[i+1][j],dp[i+1][j+1])+triangle[i][j];
            }
        }
        return dp[0][0];
    }
};

使用原地覆盖

class Solution {
public:
    int minimumTotal(vector<vector<int>>& triangle) 
    {
        for(int i=triangle.size()-2;i>=0;i--)
        {
            for(int j=0;j<=i;j++)
            {
                triangle[i][j]=min(triangle[i+1][j],triangle[i+1][j+1])+triangle[i][j];
            }
        }
        return triangle[0][0];
    }
};

121 买卖股票的最佳时机(回到目录

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。

注意你不能在买入股票前卖出股票

示例 1:

输入: [7,1,5,3,6,4]
输出: 5

解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。

注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。

示例 2:

输入: [7,6,4,3,1]
输出: 0

解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

分析:prices数组是每只股票的当天的价格
遍历整个prices数组
minvalue 中保存从0到当前所有股价中最低的价格
ans 中保存minvalue到当前所有股价中能卖掉的最高的价格
最后 return ans;

代码1:


class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int minvalue = 99999999, ans = 0;
        for(int i = 0; i < prices.size(); i++) {
            minvalue = min(minvalue, prices[i]);
            ans = max(ans, prices[i] - minvalue);
        }
        return ans;
    }
};

代码2:

class Solution {
public:
    int maxProfit(vector<int>& prices) 
    {
        if(prices.size()<=1) return 0;
        int max_pro=0;
        int temp_min=prices[0];
        int len=prices.size();
        for(int i=1;i<len;i++)
        {
            if(temp_min>prices[i])
            {
                temp_min=prices[i];
            }
            else
            {
                int temp_max=prices[i]-temp_min;
                max_pro=max(max_pro,temp_max);
            }
        }
        return max_pro;
    }
};

198 打家劫舍

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。

示例 1:

输入: [1,2,3,1]
输出: 4
解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
     偷窃到的最高金额 = 1 + 3 = 4 。
示例 2:

输入: [2,7,9,3,1]
输出: 12
解释: 偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
     偷窃到的最高金额 = 2 + 9 + 1 = 12 
// 动态规划
class Solution {
public:
    int rob(vector<int> &num) {
        if (num.size() <= 1) return num.empty() ? 0 : num[0];
        vector<int> dp = {num[0], max(num[0], num[1])};
        for (int i = 2; i < num.size(); ++i) {
            dp.push_back(max(num[i] + dp[i - 2], dp[i - 1]));
        }
        return dp.back();
    }
};

213 打家劫舍 II(回到目录

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。

示例 1:

输入: [2,3,2]
输出: 3
解释: 你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。
示例 2:

输入: [1,2,3,1]
输出: 4
解释: 你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
     偷窃到的最高金额 = 1 + 3 = 4 。

在打家劫舍(198)的基础上改动,先去掉第一个,计算一下最高金额,然后只去掉最后一个,计算最高金额。然后比较两种方式的结果,取较大值。

class Solution {//动态规划
public:
    int rob(vector<int>& nums) 
    {
        if(nums.size()<=1) return nums.empty()? 0: nums[0];
        return max(rob(nums,0,nums.size()-1),rob(nums,1,nums.size()));
    }
private:
    int rob(vector<int> &nums, int start,int end)
    {
        if(end-start<=1) return nums[start];
        vector<int> dp(end,0);
        dp[start]=nums[start];
        dp[start+1]=max(nums[start+1],nums[start]);
        for(int i=start+2;i<end;i++)
        {
            dp[i]=max(dp[i-1],dp[i-2]+nums[i]);
        }
        return dp[dp.size()-1];
    }
};

279 完全平方数(回到目录

给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。

示例 1:

输入: n = 12
输出: 3 
解释: 12 = 4 + 4 + 4.
示例 2:

输入: n = 13
输出: 2
解释: 13 = 4 + 9.

分析:建立一个n+1长度的数组dp,dp[i]表示i这个数构成平方和需要数字的最小个数。
当jxj小于i的时候,temp中保存j从1开始每加1得到的dp[i-jxj] + 1的最小值
当jXj=i的时候,dp[i]的值就直接为1。从2一直到n可以计算出dp[i]的所有值。。
最后return dp[n]的值。

class Solution {
public:
    int numSquares(int n) {
        vector<int> dp(n+1);
        dp[1] = 1;
        for(int i = 2; i <= n; i++) {
            int temp = 99999999;
            for(int j = 1; j * j <= i; j++) {
                if(j * j == i) {
                    temp = 1;
                    break;
                }
                temp = min(temp, dp[i-j*j] + 1);
            }
            dp[i] = temp;
        }
        return dp[n];
    }
};

303 区域和检索 - 数组不可变(回到目录

给定一个整数数组 nums,求出数组从索引 i 到 j (i ≤ j) 范围内元素的总和,包含 i, j 两点。

示例:

给定 nums = [-2, 0, 3, -5, 2, -1],求和函数为 sumRange()

sumRange(0, 2) -> 1
sumRange(2, 5) -> -1
sumRange(0, 5) -> -3

说明:

你可以假设数组不可变。
会多次调用 sumRange 方法。
动态规划

class NumArray {
public:
    NumArray(vector<int> nums) 
    {
        dp=nums;
        for(int i=1;i<nums.size();i++)
        {
            dp[i]=dp[i]+dp[i-1];
        }
    }
    
    int sumRange(int i, int j) 
    {
        if(i==0)
        {
            return dp[j];
        }
        else
        {
            return dp[j]-dp[i-1];
        }
    }
private:
    vector<int> dp;
};

/**
 * Your NumArray object will be instantiated and called as such:
 * NumArray obj = new NumArray(nums);
 * int param_1 = obj.sumRange(i,j);
 */

304 二维区域和检索 - 矩阵不可变(回到目录

给定一个二维矩阵,计算其子矩形范围内元素的总和,该子矩阵的左上角为 (row1, col1) ,右下角为 (row2, col2)。

上图子矩阵左上角 (row1, col1) = (2, 1) ,右下角(row2, col2) = (4, 3),该子矩形内元素的总和为 8。

示例:

给定 matrix = [
  [3, 0, 1, 4, 2],
  [5, 6, 3, 2, 1],
  [1, 2, 0, 1, 5],
  [4, 1, 0, 1, 7],
  [1, 0, 3, 0, 5]
]

sumRegion(2, 1, 4, 3) -> 8
sumRegion(1, 1, 2, 2) -> 11
sumRegion(1, 2, 2, 4) -> 12
class NumMatrix {
public:
    vector<vector<int> > v;
    NumMatrix(vector<vector<int> > matrix) {
        int m = matrix.size();
        if(matrix.empty())
            return ;
        int n = matrix[0].size();
        v= vector<vector<int> > (m, vector<int>(n));
        for(int i = 0; i < m; i++) {
            for(int j = 0; j < n; j++) {
                if(i == 0 && j == 0) {
                    v[i][j] = matrix[0][0];
                } else if(i == 0) {
                    v[i][j] = v[i][j-1] + matrix[i][j];
                } else if(j == 0) {
                    v[i][j] = v[i-1][j] + matrix[i][j];
                } else {
                    v[i][j] = v[i-1][j] + v[i][j-1] + matrix[i][j] - v[i-1][j-1];
                }
            }
        }
    }
 
    int sumRegion(int row1, int col1, int row2, int col2) {
        if(row1 == 0 && col1 == 0) {
            return v[row2][col2];
        } else if(row1 == 0) {
            return v[row2][col2] - v[row2][col1-1];
        } else if(col1 == 0) {
            return v[row2][col2] - v[row1-1][col2];
        } else {
            return v[row2][col2] - v[row1-1][col2] - v[row2][col1-1] + v[row1-1][col1-1];
        }
    }
};

300 最长上升子序列(回到目录

给定一个无序的整数数组,找到其中最长上升子序列的长度。

示例:

输入: [10,9,2,5,3,7,101,18]
输出: 4 
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
class Solution {
public:
    int lengthOfLIS(vector<int>& nums) 
    {
        if(nums.size()==0) return 0;
        vector<int> dp(nums.size(),0);
        dp[0]=1;
        int res=1;
        for(int i=1;i<dp.size();i++)
        {
            dp[i]=1;
            if(nums[i]>nums[i-1])
            {
                dp[i]=dp[i-1]+1;
            }
            if(res<dp[i])
            {
                res=dp[i];
            }
            
        }
        return res;
    }
};

338 比特位计数(回到目录

给定一个非负整数 num。对于 0 ≤ i ≤ num 范围中的每个数字 i ,计算其二进制数中的 1 的数目并将它们作为数组返回。

示例 1:

输入: 2
输出: [0,1,1]
示例 2:

输入: 5
输出: [0,1,1,2,1,2]

方法:假设构造一颗二叉树,根节点为1,从左到右从上到下分别是1,2,3,4…的二进制,可以发现如下规律:左子树是给根节点在末尾加0,右结点是给根节点在末尾加1,可得10和11;后来,10成为根节点,它左子树是100,右子树是101;11为根节点的树,左子树是110,右子树是111,同样是左边加0,右边加1…也就是当前数为偶数就在左子树,加0;如果当前数是奇数就在右子树,加1。如下图:

dp[0]设置为0,dp[1]就是dp[0001(1)右移一位数(是0000)]+1,dp[2]是dp[0010(2)右移一位数(是1)]+0,dp[3]是dp[(0011(3)右移一位数(即是0001)]+1,dp[4]是dp[0100(4)右移一位数(0010)]+0。剩余的往下递推。

class Solution {
public:
    vector<int> countBits(int num) 
    {
        vector<int> dp(num+1);
        dp[0]=0;
        for(int i=1;i<=num;i++)
        {
            dp[i]=dp[i>>1]+i%2;
        }
        return dp;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值