打家劫舍使得动态规划理解简单多啦!

11 阅读5分钟

前言:

大家好,我是Felix, 祝大家周末快乐,但是休息的同时也不要忘记学习哦。今天我们来认识一下动态规划,相信在算法的学习中,动态规划是逃不掉的。但是一般动态规划都是有点难以理解的,但是,今天这个打家劫舍的例子应该可以很好的帮助大家理解动态规划。

打家劫舍

某个街道,有很多排列在一起的商铺,某天晚上,一个小偷来到了这里。但是已知,他不能够偷取两个连在一起的商铺,否则警报就会响起,他就要吃牢饭了。每个商铺的金额也是不一样的,求:小偷如何才能做到抢钱最多呢?

例如我有一个数组[1,2,3,100],此时,小偷能够获得的最大金额就是102。

如何去解题

我们先来浅浅分析一下,小偷在偷取东西的时候,应该不能只看眼前的利益,就好比我上面那个例子,最后一家店有100快,这个是一定要偷的,所以后面的会影响前面的选择,那这如何解决呢?

我们试着找一下规律。

- 你要偷取的街道,只有一家店

这个时候毋庸置疑,就是直接偷取这个店

- 你要偷取的街道,只有两家店

偷取金额较多的那家店

- 你要偷取的街道,只有三家店

这个时候,就要比较了,是第一家+第三家和第二家的金额进行比较,谁大偷谁

- 你要偷取的街道,只有四家店(两种情况分析,谁大偷谁)

偷了前一家店的情况下的金额

偷了第四家和偷了前一家的前一家的金额相加

。。。。

这个时候,我相信大家能够看出来规律了,我要不要偷这家,取决于偷了前一家的情况下有的金额,或者是偷了前一家的前一家的金额加上当前这家的金额哪个大,我就偷哪个。

所以说,递归的感觉迎面而来了。

public class RobMoney {

    public static void main(String[] args) {
        int [] nums = {1,2,3,4};
        System.out.println(maxMoney(nums, 3));
    }

    /**
     * 这里我们写一个递归方法
     * @param nums  表示每个店铺的金额
     * @param index 表示偷取第index店所能获取到的最大金额
     * @return
     */
    public static int maxMoney(int [] nums, int index) {
        //这里就是递归的出口,只偷取到第0家店时,最大的金额就是这几点的洁
        if (nums == null || index < 0)
            return 0;
        if (index == 0)
            return nums[0];

        //这里就是最大值取决于偷了当前店,加前一家的前一家,或者不偷当前点,就是偷到前一家的值
        return Math.max(maxMoney(nums, index - 1), maxMoney(nums, index - 2) + nums[index]);

    }
}

那么代码其实还有优化的空间吗

其实还是有的,因为我们在计算前面的某家的偷盗金额时候,重复计算了,完全可以保存下来,后面想用直接取值即可。这个时候,就需要正向的去考虑这个问题了。看如下代码。

public static int maxMoneyReverse(int [] nums) {
    int n = nums.length;
    int [] dp = new int[n];

    //只抢到第一家
    dp[0] = nums[0];

    //只抢到第二家
    dp[1] = Math.max(nums[0], nums[1]);

    //后续抢到的就需要判断,两种情况了
    for (int i = 2;i < n;i ++) {
        dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);
    }

    return dp[n - 1];
}

好的,这就是打家劫舍,我们回过头来,其实你不知不觉中已经使用到了动态规划了,那什么是动态规划呢?

我把这个问题抛给了ChatGPT, 以下是它的回答

我目前无法直接生成图像或动画,但可以帮助你设计一个小偷角色的概念或者为你描述如何制作这个动画。你可以使用像Adobe Animate、Blender、Toon Boom等工具来将这个设计变为动画。以下是一个小偷动画图的设计思路:

1. 角色设计

外貌特点:

  • 身材: 小偷可以是一个身手灵活的瘦高型人物,穿着黑色的衣服以便在夜间行动。
  • 面部: 带着面罩遮住下巴和嘴巴,眼睛里露出一丝狡猾的光芒。
  • 道具: 背包,或者拿着一个手电筒,或者带着一个撬锁工具。
  • 鞋子: 穿着软底鞋,以便轻巧地行走。

2. 动作设计

常见的偷盗行为:

  • 潜行: 小偷蹑手蹑脚,趴在墙角,注意四周环境。动作要灵活、快速。
  • 打开门: 拿着撬锁工具,轻轻地撬开门锁,耳朵紧贴门上,时刻警惕声音。
  • 偷东西: 在柜子里搜寻贵重物品,快速抓起目标物品放进包里。
  • 逃跑: 一边跑,一边回头张望,躲避追赶。

3. 动画场景

  • 背景: 可以选择夜晚的城市巷道、豪宅的后花园或富人区的窗台。背景颜色要偏向暗色调,比如黑色、深蓝、灰色等。
  • 光效: 可以添加手电筒的光束效果,聚焦到小偷的动作上。
  • 音乐和音效: 加入一些紧张的背景音乐和环境音效,例如风声、脚步声等。

4. 动画流程

  • 帧数: 动画的帧数取决于你需要的流畅度,通常24帧/秒适合高质量动画。
  • 分镜: 将动画分成几个镜头:例如“潜行进入”、“撬锁”,“偷东西”,“逃跑”等,每个镜头都需要精细的动作设计。

你可以将这些设计思路输入到动画制作工具中,利用软件逐帧绘制或者使用现成的动画库进行组合。

其实,它说的是有些复杂的,但是本质就是通过复杂问题的细分来解决最终的问题。但是这里要明白,它和贪心算法有本质的区别:

- 贪心算法的局部最优达到整体最优。
- 动态规划的问题一般局部最优达不到总体最优的。

OK,这就是本周的算法分享啦!