蓝桥杯零基础冲国赛-第3天

动态规划题目思维训练

1、[乘积最大子数组](152. 乘积最大子数组 - 力扣(LeetCode) (leetcode-cn.com))

给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。

一、回忆动态规划解题步骤:状态定义、状态转移、边界条件、编程

二、根据解题步骤进行思考:

​ 状态定义。一上来就定义一个dp[n]。然后就开始思考这个dp[n]的含义是什么。思考含义首先是从题目入手,此题的问题是乘积最大的连续子数组,那么dp[n]的含义首先是第n处的乘积最大连续子数组。

​ 状态转移。如果做题做得多,就会发现,根据动态规划无后效性,dp[n] 由 dp[n - 1]推出来,第n处的乘积最大连续子数组是由第n - 1处的乘积最大连续子数组。那么dp[n - 1] 怎么到dp[n]。根据题目主要谓语(乘积)可以得到:dp[n] = dp[n -1] * nums[n]。那么问题来了,nums[n]可能是负数或者正数或者零,那么一个最大数乘以一个正数为一个更大的数,乘以一个负数为一个更小的数,明显一个nums[n]会对一个数产生两种结果,因此可以想到状态定义不够严谨,不够严谨意思就是说这个定义可能可以求出结果。所以要回到状态定义的步骤。

​ 状态定义。根据上一步可以得到一个结论。就是在n处有两种情况,一个是最大乘积一个是最小乘积,所以在n处有两种状态,所以要定义为dp[n] [2],dp[n] [0]为在n处的最大乘积,dp[n] [1] 为最小乘积。

​ 状态转移。同理,dp[n] [0] 和dp[n] [1]都是dp[n - 1] [0] 和dp[n - 1] [1]得到,得到dp[n - 1] [0] * nums[n] 和dp[n - 1] [1] * nums[n],这两个结果一个是最大乘积一个是最小乘积。

​ 边界条件。因为动态规划有无后效性,所以一般确定开始的边界值,确定dp[0] [0] 和dp[0] [1]

​ 编程:

int maxProduct(vector<int>& nums) {
        int n = nums.size();
        int dp[n][2];
        dp[0][0] = dp[0][1] = nums[0];
        int ans = nums[0];
        for (int i = 1; i < n; i++) {
            int val1 = dp[i - 1][0] * nums[i];
            int val2 = dp[i - 1][1] * nums[i];
            int val3 = nums[i];
            dp[i][0] = max(val1, max(val2, val3));
            dp[i][1] = min(val1, min(val2, val3));
            ans = max(ans, dp[i][0]); 
        }
        return ans;
    }

2、[打家劫舍](198. 打家劫舍 - 力扣(LeetCode) (leetcode-cn.com))

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

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

一、回忆动态规划解题步骤:状态定义、状态转移、边界条件、编程

二、根据解题步骤进行思考:

​ 状态定义。定义一个dp[n],分析其含义。首先定义为dp[n]为偷在n处能偷窃到的最高金额。

​ 状态转移。dp[n] 由dp[n - 1]得到,因为dp[n]定义为在n处偷,则n - 1处就是不偷,那么n - 2处可能偷也可能不偷,如果n - 2处偷,那么n - 3处就不偷,但如果n - 2处不偷,n - 3处可能偷也可能不偷,依次类推。所以状态定义还是不严谨。所以要返回到状态定义。

​ 状态定义。从上一步可以得到一个结论,在每个房子都有两种情况就是偷或者不偷。所以定义为dp[n] [2],dp[n] [0] 为不偷金额,dp[n] [1]为偷的最大金额

​ 状态转移。不偷的情况dp[n] [0] 就是dp[n - 1] [0] 和dp[n - 1] [1] 中的一个,偷的情况就是dp[n - 1] [0] + nums[i]

​ 编码:

int rob(vector<int>& nums) {
        int n = nums.size();
        int dp[n][2];
        dp[0][0] = 0, dp[0][1] = nums[0];
        for (int i = 1; i < n; i++) {
            dp[i][0] = max(dp[i - 1][0], dp[i - 1][1]);
            dp[i][1] = dp[i - 1][0] + nums[i];
        }
        return max(dp[n - 1][0], dp[n - 1][1]);
    }

总结:动态规划是一个基于生活常识的算法,只有多练,增加见识才可以在状态定义和状态转移中灵活应用。而且会发现,在解动态规划过程,状态定义总不会一下就会是好的,在做动态规划题目时,最关键的是不断得去状态转移和状态定义,在状态转移过程如果发现当前定义得状态在转移过程有推不完的转移,那么说明状态定义存在一定问题。

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

扑天鹰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值