打家劫舍问题

这篇博客探讨了几种动态规划算法的应用,包括198.打家劫舍、53.最大子序和和152.乘积最大子数组问题。通过动态规划的思路,博主详细解释了如何建立状态转移方程,以及如何处理特殊情况如负数和0的影响。此外,还介绍了如何利用前缀和解决最小子串和问题。

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

什么是时间空间复杂度?
时间空间增长的趋势:
随着问题量级的增大,时间空间增长的趋势,就能用bigO来表示

198.打家劫舍

在这里插入图片描述
主要思路:
// dp[i]的含义:i的范围为0~size-1
// 因此对于nums[i]偷还是不偷这个问题所能产生的最大收益
代码如下:

// 198.打家劫舍问题
  int rob(vector<int>& nums)
  {
    if (nums.empty())
    {
      return 0;
    }
    int size = nums.size();
    if (size == 1)
    {
      return nums[0];
    }
    // dp[i]的含义:i的范围为0~size-1
    // 因此对于nums[i]偷还是不偷
    vector<int> dp = vector<int>(size, 0);
    dp[0] = nums[0];
    dp[1] = max(nums[0], nums[1]);
    for (int i = 2; i < size; i++)
    {
      // max(对于nums[i]偷,对于nums[i]不偷)
      dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
    }
    return dp[size - 1];
  }
53.最大子序和

在这里插入图片描述
主要思路:
可以用动态规划来做,nums[i]选或者不选

int maxSubArray(vector<int>& nums) {
        // 最值问题,最优子序列,dp[i]由dp[i-1]推导?
        // 定义dp[i] 以nums[i]结尾的最优子序列
        int n =nums.size();
        vector<int> dp(n, 0);

        // base case
        dp[0]=nums[0];
        // 递归/状态转移方程
        for(int i = 1; i<n; i++){
          dp[i] = max(dp[i-1]+nums[i], nums[i]);
        }

        // 接下来求解我们的问题
        int res=INT_MIN;
        for(int i =0; i<n; i++){
          res=max(res, dp[i]);
        }
        return res;
    }

也可以构建前缀和数组来解决这道题,当需要你给出具体的最大子序和的下标时

// 禾赛科技:最小子串和,最小子串和序列的第一个元素和最后一个元素的序列
  pair<int, int> findMinSum(vector<int>& nums)
  {
    int size = nums.size();                // size是原数组的尺寸
    vector<int> arr_sum(nums.size() + 1);  // 前缀和

    // arr_sum[0] = 0;
    for (int i = 0; i < size; i++)
    {
      arr_sum[i + 1] = arr_sum[i] + nums[i];
    }
    pair<int, int> q;
    int min = 0;
    // 遍历前缀和的数组
    for (int i = 0; i < size; i++)
    {
      for (int j = i; j < size; j++)
      {
        // 区间和[i,j]
        int sum = arr_sum[j + 1] - arr_sum[i];
        # 求最大子序和在这里改成if (sum > max)
        if (sum < min)
        {
          min = sum;
          q.first = i;
          q.second = j;
        }
      }
    }
    return q;
  }

152.乘积最大子数组

在这里插入图片描述
主要思路:
参考weiwei大神
主要思路:
这一题没法构建子序乘积,因为会有0的情况,没法构建子序的乘法
类比53题用的动态规划方法:以nums[i]结尾的子序的最大乘积为dp[i]

但是,有个情况需要考虑就是当出现负数的情况,因此增加dp的维度减少讨论,同时问题转变为无后效性,因为如果不增加维度,dp[i]可能由前面nums[i-2]为负数决定(dp[i-2])而不是只由dp[i-1]决定

状态的定义

// 明确状态:dp:以 nums[i]结尾的连续子数组的最大值
// 求乘积的最大值,示例中负数的出现,告诉我们这题和 53 题(最大子序和:对于nums[i]偷还是不偷)不一样了
// 一个正数乘以负数就变成负数,即:最大值乘以负数就变成了最小值
// 最大值和最小值是相互转换的,这一点提示我们可以把这种转换关系设计到「状态转移方程」里去
// 如何解决这个问题呢?这里常见的技巧是在「状态设计」的时候,在原始的状态设计后面多加一个维度,减少分类讨论,降低解决问题的难度。
// dp[i][j]:以 nums[i] 结尾的连续子数组的最值,计算最大值还是最小值由 j 来表示,j 就两个值
// 当 j = 0 的时候,表示计算的是最小值
// 当 j = 1 的时候,表示计算的是最大值

代码如下

int maxProduct(vector<int> &nums)
  {
    int len = nums.size();
    if(0==len) return 0;

    // dp[i][0]:以 nums[i] 结尾的连续子数组的最小值
    // dp[i][1]:以 nums[i] 结尾的连续子数组的最大值
    vector<vector<int>> dp(len, vector<int>(2));

    // base case:不管咋样,nums[0]还是要偷的
    dp[0][0]=nums[0];
    dp[0][1]=nums[0];
    for(int i=1;i<len;i++){
      if(nums[i]>=0){
        // 当nums[i]>0, 选还是不选
        dp[i][0]=min(nums[i],nums[i]*dp[i-1][0]);
        dp[i][1]=max(nums[i],nums[i]*dp[i-1][1]);
      }
      else{
        // 若nums[i]<0,那么最大值dp[i-1][1]*nums[i]变最小值
        // 最小值dp[i-1][0]*nums[i]变最大值
        dp[i][0]=min(nums[i],nums[i] * dp[i - 1][1]);
        dp[i][1]=max(nums[i],nums[i] * dp[i - 1][0]);
      }
    }

    // 只关心最大值,需要遍历
    int res=dp[0][1];
    for(int i=0;i<len;i++){
      res=max(res,dp[i][1]);
    }
    return res;
  }

在这里插入图片描述
主要思路:
在这里插入图片描述
即环状数组分成两个子数组来解决:第一个房子和最后一个房子只能选择一个来偷窃
题解

class Solution {
public:
    int dfs(vector<int> &nums)
  {
    if (nums.empty())
    {
      return 0;
    }
    int size = nums.size();
    if (size == 1)
    {
      return nums[0];
    }
    // dp[i]的含义:i的范围为0~size-1
    // 因此对于nums[i]偷还是不偷
    vector<int> dp = vector<int>(size, 0);
    dp[0] = nums[0];
    dp[1] = max(nums[0], nums[1]);
    for (int i = 2; i < size; i++)
    {
      // max(对于nums[i]偷,对于nums[i]不偷)
      dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
    }
    return dp[size - 1];
  }

  // 213.打家劫舍2
  int rob(vector<int> &nums)
  {
    if (nums.empty())
    {
      return 0;
    }
    int size = nums.size();
    if (size == 1)
    {
      return nums[0];
    }
    vector<int>::iterator start = nums.begin();
    vector<int>::iterator end = nums.end();
    vector<int> nums1(start, end - 1);
    vector<int> nums2(start + 1, end);
    return max(dfs(nums1), dfs(nums2));
  }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值