动态规划

一、爬楼梯 LeetCode 70

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1:
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。

  1. 1 阶 + 1 阶
    
  2. 2 阶
    

示例 2:
输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
3. 1 阶 + 1 阶 + 1 阶
4. 1 阶 + 2 阶
5. 2 阶 + 1 阶
思路:
由于每次最多爬两阶,那么如果是第i阶楼梯的爬法就只与前两阶(i-1,i-2)的爬法有关。
在这里插入图片描述

class Solution {
public:
    int climbStairs(int n) {
        vector <int> dp(n+3,0);//初始化让dp前n+3个元素为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];
    }
};

动态规划原理:
在这里插入图片描述

二、打家劫舍 LeetCode 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>& nums) {
        if(nums.size()==0)
            return 0;
        if(nums.size()==1)
            return nums[0];
        vector<int> dp(nums.size(),0);//每个房间的最优策略(能够获取的最多金额)
        dp[0] = nums[0];//初始化前两个房间能够获得的最多金额
        dp[1] = max(dp[0],nums[1]);
        for(int i=2;i<nums.size();i++){//所有房间能够拥有的最多金额,要么是前一个房间那么多(即不偷这个房间)或者是前前个房间加上这个房间的金额(即偷这个房间)
            dp[i] = max(dp[i-1],dp[i-2]+nums[i]);
        }
        return dp[nums.size()-1];
    }
};

三、最大子段和 LeetCode 53

题目:
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
思路:
在这里插入图片描述

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        vector<int> dp(nums.size(),0);//存放到第i个元素最大的子序列和
        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]);//寻找到i个元素最大子序列的和
            if(max_res < dp[i])//如果第i个元素最大子序列的和比之前的大,则更新max_res
                max_res = dp[i];
        }
        return max_res;
    }
};

**四、找零钱 **

题目:
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
示例 1:
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
示例 2:
输入: coins = [2], amount = 3
输出: -1
思路:
在这里插入图片描述

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        vector<int> dp;//存放不同面额硬币组成从零到amount金额需要的个数
        for(int i=0; i<=amount; i++){
            dp.push_back(-1);
        }
        dp[0]=0;//金额零需要0个硬币
        for(int i=1;i<=amount;i++){
            for(int j=0;j<coins.size();j++)//循环各个面值找到最优解
            {
                if(i-coins[j]>=0&&dp[i-coins[j]]!=-1){//只有当前金额大于面值并且当前金额可以到达时才判断要不要给dp[i]赋值
                    if(dp[i]==-1||dp[i]>dp[i-coins[j]]+1)//如果当前金额之前是不可达或者第i个面值最优时令dp[i]=dp[i-coins[j]]+1;
                        dp[i]=dp[i-coins[j]]+1;
                }
            } 
        }
        return dp[amount];
    }
};

五、三角形 LeetCode120

题目:

思路:
在这里插入图片描述

class Solution {
public:
    int minimumTotal(vector<vector<int>>& triangle) {
        if(triangle.size()==0)
            return 0;
        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 i=0; i<dp.size();i++){//令最优值三角形最后一行等于三角形的值
            dp[dp.size()-1][i]= triangle[dp.size()-1][i];
        }
        for(int i=triangle.size()-2;i>=0;i--){//从最后一行倒推回前面的最优值
            for(int j=0;j<dp[i].size();j++){
                dp[i][j]=min(dp[i+1][j],dp[i+1][j+1]) + triangle[i][j];
            }
        }
        return dp[0][0];
    }
};

六、最长上升子序列 LeetCode 300

题目:
思路1:
在这里插入图片描述

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        if(nums.size()==0)
            return 0;
        vector<int> dp(nums.size(),0);//最长子序列长度
        dp[0] = 1;//一个元素时最长为1
        int LIS = 1;//记录最长子序列的长度
        for(int i=1;i<nums.size();i++){
            dp[i] = 1;
            for(int j=0;j < i;j++)//i之前的数如果比i小且i的最优长度也小于前面数的最优长度加1,那么dp[i]更新
            {
                if(nums[i]>nums[j]&&dp[i] < dp[j]+1)
                    dp[i]=dp[j]+1;
                if(LIS<dp[i])//是LIS始终存的是最长子序列的长度
                    LIS = dp[i];
            }
        }
        return LIS;
    }
};

思路2:
在这里插入图片描述
在这里插入图片描述

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        if(nums.size()==0)
            return 0;
        vector<int> stack;//存放数组的栈
        stack.push_back(nums[0]);
        for(int i=1;i < nums.size();i++){
            if(nums[i] > stack.back())    //将比栈顶大的元素放进栈中
                stack.push_back(nums[i]);
            else{
                    int pos = binary_search(stack, nums[i]);
                    stack[pos] = nums[i];
            }
        }
        return stack.size();
    }
    int binary_search(vector<int> nums,int target){//二分法优化位置查找
        int index = -1;//目标位置
        int begin = 0;
        int end = nums.size()-1;
        while(index == -1){
            int mid = (begin + end)/2;
            if(nums[mid]==target)
                index = mid;
            else if(nums[mid] < target){
                if(mid==nums.size()-1 || nums[mid+1] > target )
                        index = mid+1;
                begin = mid+1;
            }
            else if(nums[mid]>target){
                if(mid==0||nums[mid-1]<target){
                    index = mid;
                }
                end = mid-1;
            }
        }
        return index;
    }
};

**注意:**二分查找法时,边界条件要先判断

七、最小路径和

题目:
给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
示例:
输入:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
输出: 7
解释: 因为路径 1→3→1→1→1 的总和最小。
**思路:**与第五题三角形相似,从左上角到右下角寻求最优解

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        if(grid.size()==0)
            return 0;
        vector<vector<int>> dp;//最优路径值
        for(int i =0;i<grid.size();i++){//初始化dp
            dp.push_back(vector<int>());
            for(int j=0;j<grid[i].size();j++){
                dp[i].push_back(0);
            }
        }
        dp[0][0] = grid[0][0];
        for(int i=1;i<grid[0].size();i++)//将第一行求出
            dp[0][i]=dp[0][i-1]+grid[0][i];
        for(int i=1;i<grid.size();i++){
            dp[i][0]=dp[i-1][0]+grid[i][0];//第一列
            for(int j = 1;j<grid[0].size();j++){
                dp[i][j] = min(dp[i-1][j],dp[i][j-1])+grid[i][j];
            }
        }
        return dp[grid.size()-1][grid[0].size()-1];
    }
};

八、地牢游戏

题目:
一些恶魔抓住了公主(P)并将她关在了地下城的右下角。地下城是由 M x N 个房间组成的二维网格。我们英勇的骑士(K)最初被安置在左上角的房间里,他必须穿过地下城并通过对抗恶魔来拯救公主。
骑士的初始健康点数为一个正整数。如果他的健康点数在某一时刻降至 0 或以下,他会立即死亡。
有些房间由恶魔守卫,因此骑士在进入这些房间时会失去健康点数(若房间里的值为负整数,则表示骑士将损失健康点数);其他房间要么是空的(房间里的值为 0),要么包含增加骑士健康点数的魔法球(若房间里的值为正整数,则表示骑士将增加健康点数)。
为了尽快到达公主,骑士决定每次只向右或向下移动一步。
编写一个函数来计算确保骑士能够拯救到公主所需的最低初始健康点数。
例如,考虑到如下布局的地下城,如果骑士遵循最佳路径 右 -> 右 -> 下 -> 下,则骑士的初始健康点数至少为 7。
在这里插入图片描述
说明:
骑士的健康点数没有上限。

任何房间都可能对骑士的健康点数造成威胁,也可能增加骑士的健康点数,包括骑士进入的左上角房间以及公主被监禁的右下角房间。
**思路:**与上一提相比这道题增加了限制条件,那就是要保证骑士的生命值为1,并且所走路径要使骑士的生命值伤害最低,因此不便再从左上至右下,而是要从右下至左上;同时还要保证其生命值为1

class Solution {
public:
    int calculateMinimumHP(vector<vector<int>>& dungeon) {
          if(dungeon.size()==0)
            return 0;
        vector<vector<int>> dp;//最优路径值
        for(int i =0;i<dungeon.size();i++){//初始化dp
            dp.push_back(vector<int>());
            for(int j=0;j<dungeon[i].size();j++){
                dp[i].push_back(0);
            }
        }
        //初始化
        dp[dungeon.size()-1][dungeon[0].size()-1] = max(1,1-dungeon[dungeon.size()-1][dungeon[0].size()-1]);//右下角的值
        for(int i=dungeon.size()-2;i>=0;i--)//将最后一列求出
            dp[i][dungeon[0].size()-1]=max(1,dp[i+1][dungeon[0].size()-1]-dungeon[i][dungeon[0].size()-1]);
        for(int i=dungeon[0].size()-2;i>=0;i--){
            dp[dungeon.size()-1][i]=max(1,dp[dungeon.size()-1][i+1]-dungeon[dungeon.size()-1][i]);//最后一行
            for(int j = dungeon.size()-2;j>=0;j--){//其他位置
                int dp_min = min(dp[j+1][i],dp[j][i+1]);//选出右边和下边的最小值,求需要保证的生命值
                dp[j][i] = max(1,dp_min-dungeon[j][i]);
            }
        }
        return dp[0][0];
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值