LeetCode算法练习——动态规划入门(三)

本文深入解析了LeetCode上的经典算法题目,包括打家劫舍系列和股票交易系列。通过详细的示例和代码实现,阐述了如何运用动态规划解决这些问题,旨在帮助读者掌握动态规划的核心思想。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

LeetCode198. 打家劫舍 && LeetCode 面试题 17.16. 按摩师

打家劫舍

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

示例 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 。

按摩师

一个有名的按摩师会收到源源不断的预约请求,每个预约都可以选择接或不接。在每次预约服务之间要有休息时间,因此她不能接受相邻的预约。给定一个预约请求序列,替按摩师找到最优的预约集合(总预约时间最长),返回总的分钟数。

示例 1:

输入: [1,2,3,1]
输出: 4
解释: 选择 1 号预约和 3 号预约,总时长 = 1 + 3 = 4。

示例 2:

输入: [2,1,4,5,3,1,1,3]
输出: 12
解释: 选择 1 号预约、 3 号预约、 5 号预约和 8 号预约,总时长 = 2 + 4 + 3 + 3 = 12。

这两题看似不同,实则相同,一个是不能抢劫相邻的房子,一个是不能连续两天预约,而起点同样可以从索引为0和索引为1的位置开始。我们得出状态转移方程:dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]),dp[i - 2] + nums[i]很容易理解,最后一步完成前的上一次服务为两天前的服务/与上一次抢劫的房子相隔一个房子,而dp[i - 1]的含义为:当我们从索引0位置以题设方式到达i - 1的位置时,同样可以从索引1位置开始以题设方式到达i终点位置时,故取dp[i - 1]表示该情况。

class Solution {
public:
    int rob(vector<int>& nums) {
    //int massage(vector<int>& nums) {
        int length = nums.size();
        vector<int> dp(length + 1, 0);
        if(length == 0)  return 0;
        if(length == 1) return nums[0];
        dp[0] = nums[0];
        dp[1] = max(nums[0], nums[1]); 
        for(int i = 2; i < length; i++){
            dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
        }
        return dp[length - 1];
    }

LeetCode213. 打家劫舍 II

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

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

示例 1:

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

示例 2:

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

分析:这个题打家劫舍1的区别在于,房屋是环形的,这意味着,第一个房间和最后一个房间不能同时偷窃。这就意味着,将原来的问题转化为两个子问题:偷窃1 ~ n-1个房间的最大值以及偷窃 2~n 个房间的最大值。最终,取二者的最大值即为解因此可以对上一个问题进行两次dp即可。

class Solution {
public:
    int rob(vector<int>& nums) {
        //偷窃1 ~ n-1个房间的最大值以及偷窃 2 ~ n 个房间的最大值。最终,取二者的最大值即为解。
        int length = nums.size();
        vector<int> dp(length + 1, 0);
        if(length == 0)  return 0;
        if(length == 1) return nums[0];
        dp[0] = nums[0];
        dp[1] = max(nums[0], nums[1]); 
        //1 ~ n-1的最大值
        for(int i = 2; i < length - 1; i++){
            dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
        }
        int res = dp[length - 2];
        //2 ~ n的最大值
        dp[0] = 0;
        dp[1] = nums[1];
        for(int i = 2; i < length; i++){
            dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
        }
        return max(res, dp[length - 1]);
    }
};

LeetCode121. 买卖股票的最佳时机 && 剑指 Offer 63. 股票的最大利润

给定一个数组,它的第 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。

核心思路是用当前价格减去之前最小的价格,即记录当前索引最大利润的dp[i] = price[i] - min{price[0] ~ price[i - 1]},怎么来表示这个 min{price[0] ~ price[i - 1]}呢——将设置初值为dp[1] = prices[1] - prices[0],我们会巧妙地发现:

  • dp[2] = price[2] - min(price[1] - dp[1], price[1]),问题转换成dp[2] = price[2] - min(price[0], price[1])
  • dp[3] = price[3] - min(price[2] - dp[2], price[2]),问题转换成dp[3] = price[3] - min(min(price[0], price[1]),price[2])
  • 以此类推加入循环后dp[i] = prices[i] - min(prices[i - 1] - dp[i - 1], prices[i - 1])就表示为dp[i] = price[i] - min{price[0] ~ price[i - 1]}
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int length = prices.size();
        if(length <= 1) return 0;
        vector<int> dp(length + 1, 0);
        dp[1] = prices[1] - prices[0];
        int res = max(0, dp[1]);
        for(int i = 2; i < length; i++){
            //dp[i] = price[i] - min{price[0] ~ price[i - 1]}
            dp[i] = prices[i] - min(prices[i - 1] - dp[i - 1], prices[i - 1]);
            res = max(res, dp[i]);
        }
        return res;
    }
};

LeetCode122. 买卖股票的最佳时机 II

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
     随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3

此题和上一题思想类似,我们需要设置一个dp数组来记录当前位置所能获得的最大利润:

  • 当prices[i] <= prices[i - 1]时,dp[i] = dp[i - 1]
  • 当prices[i] > prices[i - 1]时,dp[i] = dp[i - 1] + (prices[i] - prices[i - 1])
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int length = prices.size();
        if(length <= 1) return 0;
        vector<int> dp(length + 1, 0);
        for(int i = 1; i < prices.size(); i++){
            if(prices[i] > prices[i - 1])       //dp[i - 1] + 当前的增量
                dp[i] = dp[i - 1] + (prices[i] - prices[i - 1]);
            else
                dp[i] = dp[i - 1];              //dp[i]记录之前位置的最大值
        }
        return dp[prices.size() - 1];
    }
};

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值