198. House Robber 关于动态规划问题(Dynamic programming)的学习、思考与记录

本文以LeetCode中的House Robber问题为例,详细介绍了如何通过动态规划解决问题,并逐步优化算法,包括递归、带备忘录的递归、自底向上的迭代方法及减少空间复杂度的技巧。

动态规划(Dynamic programming)

定义

任何数学递推公式都可以直接转换为递归算法,但是基本现实是编译器常常不能正确对待递归算法,结果导致低效的程序。当怀疑出现这样的情况时,我们必须再给编译器提供一些帮助,即将递归算法转换为非递归算法,让后者把那些子问题的答案系统的记录在一个表内。利用这种方法的一种技巧叫做动态规划。

以LeetCode.198. House Robber为例进行一下算法的优化。

大多数的问题可以按照下面的顺序进行解决:
1.找到递归关系,得到递归关系式。
2.自顶向下的递归。(top-down)
3.自顶向下的递归 + 存储记录。(top-down)
4.转化为非递归 + 存储记录。(自下向上 bottom-up)
5.非递归 + n 个变量存储(bottom-up)

第一步:找到递归关系。
题中盗贼有两个选择:
(1)洗劫当前的房子 i;(2)不洗劫当前的房子。
若选(1)则意味着他不能洗劫前一个房子i-1,但是他可以对前面的i-2个房子进行有选择的洗劫。
若选(2)则意味着他可以对前面的i-1个房子进行有选择的洗劫。

因此递归关系为:

rob(i) = Math.max( rob(i - 2) + currentHouseValue, rob(i - 1) )

第二步:自顶向下的递归。(top-down)
将第一步的递归关系式转换如下:

public int rob(int[] nums) {
    return rob(nums, nums.length - 1);
}
private int rob(int[] nums, int i) {
    if (i < 0) {
        return 0;
    }
    return Math.max(rob(nums, i - 2) + nums[i], rob(nums, i - 1));
}

由于上述算法会重复的处理相同的子问题导致计算复杂度会随输入规模快速增长,问题多一个输入,那么计算的规模就要乘上2,所以时间复杂度为2^n这个级别,所以此方案放在leetcode上会超时,因此需要优化。

第三步:自顶向下的递归 + 存储记录。(top-down)

class Solution {
    int[] memo;
    public int rob(int[] nums) {
        memo = new int[nums.length];
        Arrays.fill(memo, -1);
        return rob(nums, nums.length - 1);
    }

    private int rob(int[] nums, int i) {
        if (i < 0) {
            return 0;
        }
        //这一步的判断可以防止重复计算相同的子问题
        if (memo[i] >= 0) {
            return memo[i];
        }
        int result = Math.max(rob(nums, i - 2) + nums[i], rob(nums, i - 1));
        memo[i] = result;
        return result;
    }
}

这个算法就好很多了,时间复杂度和空间复杂度都为O(n),下一步优化可以试着摆脱递归堆栈。

第四步:转化为非递归 + 存储记录。(自下向上 bottom-up)
主体还是上面提到的那个关系,只是我们将子问题的结果放在了一个临时数组中。临时数组中存放的都是子问题的最优解。比如memo [1]里面存放的就是前两个元素中最大值,即最优解。当计算前三个数中最优解的时候,我只要做一个选择,即要不要选择nums[2]这个元素,选择的话,那么我就从memo [2-2]中得到最优解,加起来就是当前最优解,不选择的话,就从memo [2-1]中选择最优解。依次下去,tmp中最后一个值就是整个序列中组合的最优解了。

这里注意一下,这里其实是记忆化搜索的思想来实现的,我们可以注意到,其实是自顶向下来看的,从第一个数字来一直推到最后。然而动态规划的思想是从底向上的,参见第一个递归版本的实现。我们先考虑的是最终的n,而不是考虑从0开始。所以在设计思想上是有所区别的,但是又是非常类似,有的人将他们归位一类,我想,它们在大多数场景下可以互换的化,可以认为都是广义上的DP算法吧,因为DP毕竟只是一种思想,正过来实现反过来实现也未尝不可。

我们用一个数组来存放子问题的最优解,大大降低了时间复杂度,leetcode上也顺利通过。其实这个是一种记忆化搜索的思想,上面这个用了一个数组,其实完全没有必要用数组,用两个变量即可。

class Solution {
    public int rob(int[] nums) {
        if(nums.length <= 0){
            return 0;
        }
        if(nums.length == 1){
            return nums[0];
        }
        int[] memo = new int[nums.length];
        memo[0] = nums[0];
        memo[1] = nums[0] > nums[1] ? nums[0] : nums[1];
        for(int i=2;i<nums.length;i++){
            int A = memo[i-2] + nums[i];
            int B = memo[i-1];
            int max = A > B ? A : B;
            memo[i] = max;
        }
        return memo[nums.length-1];
    }
}

第五步:.非递归 + n 个变量存储(bottom-up)

class Solution {
    public int rob(int[] nums) {
        if(nums.length <= 0){
            return 0;
        }
        if(nums.length == 1){
            return nums[0];
        }
        
        int a = nums[0];
        int b = nums[0] > nums[1] ? nums[0] : nums[1];
        for(int i=2;i<nums.length;i++){
            int A = a + nums[i];
            int B = b;
            int max = A > B ? A : B;
            a = b;
            b = max;
        }
        return b;
    }
}
源码地址: https://pan.quark.cn/s/a4b39357ea24 欧姆龙触摸屏编程软件MPTST 5.02是专门为欧姆龙品牌的工业触摸屏而研发的编程解决方案,它赋予用户在直观界面上构建、修改以及排错触摸屏应用程序的能力。 该软件在工业自动化领域具有不可替代的地位,特别是在生产线监视、设备操控以及人机互动系统中发挥着核心作用。 欧姆龙MPTST(Machine Process Terminal Software Touch)5.02版本配备了多样化的功能,旨在应对不同种类的触摸屏项目要求。 以下列举了若干核心特性:1. **图形化编程**:MPTST 5.02采用图形化的编程模式,允许用户借助拖拽动作来设计屏幕布局,设定按钮、滑块、指示灯等组件,显著简化了编程流程,并提升了工作效率。 2. **兼容性**:该软件能够适配欧姆龙的多个触摸屏产品线,包括CX-One、NS系列、NJ/NX系列等,使用户可以在同一个平台上完成对不同硬件的编程任务。 3. **数据通信**:MPTST 5.02具备PLC(可编程逻辑控制器)进行数据交互的能力,通过将触摸屏作为操作界面,实现生产数据的显示输入,以及设备状态的监控。 4. **报警事件管理**:软件中集成了报警和事件管理机制,可以设定多种报警标准,一旦达到预设条件,触摸屏便会展示对应的报警提示,助力操作人员迅速做出响应。 5. **模拟测试**:在设备实际连接之前,MPTST 5.02支持用户进行脱机模拟测试,以此验证程序的正确性稳定性。 6. **项目备份恢复**:为了防止数据遗失,MPTST 5.02提供了项目文件的备份及还原功能,对于多版本控制团队协作具有显著价值。 7. **多语言支持**:针对全球化的应...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值