House Robber 完整版

本文深入探讨了 House Robber 系列算法,从线性状态转换到动态规划再到与树形结构的结合,提供了解决方案及代码实现,助您理解算法核心和应用。

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

这是一个从简到繁的步进题目组。

House Robber I

第一个是一维很简单的线性状态转换,题目如下:

You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed, the only constraint stopping you from robbing each of them is that adjacent houses have security system connected and it will automatically contact the police if two adjacent houses were broken into on the same night.

Given a list of non-negative integers representing the amount of money of each house, determine the maximum amount of money you can rob tonight without alerting the police.

转移方程如下:

                 /-- a[1]                     ,i == 1
                 |-- max{a[1], a[2]}          ,i == 2
        rob[i] = |       /-- rob[i-2] + a[i]
                 |-- max |                    ,i > 2
                 \       \-- rob[i-1]

思路:对当前第i个做出决策分为选择和不选择,分别得到两个子问题,比较二者最大即为原问题的最优解。代码如下:

    int robI(vector<int>& nums) {
        auto n = nums.size();
        if (n == 0) return 0;

        int * rob = new int[n];
        rob[0] = nums[0];
        rob[1] = nums[0] > nums[1] ? nums[0] : nums[1];
        int res = 0;
        for (auto i = 2; i < n; ++i)
        {
            int tmp = rob[i-2] + nums[i];
            rob[i] = rob[i-1] > tmp ? rob[i-1] : tmp;
        }
        res = rob[n-1];
        delete rob;
        return res;
    }

House Robber II

在第一题的基础上可以围成环,因此情况复杂一些:

             /-- a[i], i == j
             |
    r[i,j] = |      /-- r(i+1, j-2) + a[j]
             |--max |_/-- r(i, j-1), i != 1
             \      | \-- rob(j-1), i == 1

其中需要用到第一题的解robr[i,j] 表示从第i个到第j个元素之间的最优解。

    int robII(vector<int>& nums) {
        auto n = nums.size();
        if (n == 0) return 0;
        int * rob = new int[n];
        rob[0] = nums[0];
        rob[1] = nums[0] > nums[1] ? nums[0] : nums[1];

        vector<int> row(n, 0);
        vector<vector<int>> r(n, row);
        for (int l = 0; l < n; ++l)
        {
            for (int i = 0; i < n - l; ++i)
            {
                int j = i + l;
                if (j >= 2) rob[j] = rob[j-2] + nums[j] > rob[j-1] ? rob[j-2] + nums[j] : rob[j-1];

                if (i == j)
                {
                    r[i][j] = nums[i];
                    continue;
                }

                if (i == 0)
                {
                    int a = rob[j-1], b = r[i+1][j-2] + nums[j];
                    r[i][j] = a > b ? a : b;
                }
                else
                {
                    int choose = r[i][j-2] + nums[j], noch = r[i][j-1];
                    r[i][j] = choose > noch ? choose : noch;
                }
            }

        }
        delete rob;
        return r[0][n-1];
    }

House Robber III

题目发展到第三个变种,所有的house成为了一个二叉树,题目如下:

The thief has found himself a new place for his thievery again. There is only one entrance to this area, called the “root.” Besides the root, each house has one and only one parent house. After a tour, the smart thief realized that “all houses in this place forms a binary tree”. It will automatically contact the police if two directly-linked houses were broken into on the same night.

Determine the maximum amount of money the thief can rob tonight without alerting the police.

根据前面两个题目的思路,这里结合二叉树,需要自顶向下来递归求解。为此,需要使用一个记忆变量来记录已经计算过的子问题,否则递归子问题会变成指数式增长。代码实现如下:

    int rob(TreeNode* root) {
        map<TreeNode *, int> rob;
        return robhelper(root, rob);
    }

    int robhelper(TreeNode * p, map<TreeNode*, int> & rob)
    {
        if (!p) return 0;

        int no_choose = 0;
        if (rob.end() != rob.find(p->left)) no_choose += rob[p->left];
        else
        {
            int l = robhelper(p->left, rob);
            rob[p->left] = l;
            no_choose += l;
        }
        if (rob.end() != rob.find(p->right)) no_choose += rob[p->right];
        else
        {
            int r = robhelper(p->right, rob);
            rob[p->right] = r;
            no_choose += r;
        }

        int choose = p->val;
        if (!p->left && !p->right) return choose > no_choose ? choose : no_choose;
        if (p->left)
        {
            if (rob.find(p->left->left) != rob.end()) choose += rob[p->left->left];
            else choose += robhelper(p->left->left, rob);

            if (rob.find(p->left->right) != rob.end()) choose += rob[p->left->right];
            else choose += robhelper(p->left->right, rob);

            if (p->right)
            {
                if (rob.find(p->right->left) != rob.end()) choose += rob[p->right->left];
                else choose += robhelper(p->right->left, rob);

                if (rob.find(p->right->right) != rob.end()) choose += rob[p->right->right];
                else choose += robhelper(p->right->right, rob);
            }
        }
        else
        {
            if (rob.find(p->right->left) != rob.end()) choose += rob[p->right->left];
            else choose += robhelper(p->right->left, rob);

            if (rob.find(p->right->right) != rob.end()) choose += rob[p->right->right];
            else choose += robhelper(p->right->right, rob);
        }
        return choose > no_choose ? choose : no_choose;
    }

需要用一个辅助函数进行递归调用,使用一个map<TreeNode*, int> & rob变量来保存已经计算过的子问题,这里的索引使用的是子问题数的根节点指针。
递归调用的代码思路也很明显,分别考察当前节点是否进行选择,不选择就直接计算左右子树子问题并相加,这里计算子问题均是先在rob字典里查找,没找到的时候才递归调用。对于选择当前节点之后,就不能选择其子节点,此时需要分别考虑孙子节点的所有情况并相加,最后返回的是选择或者不选择之中的最大者。

三道题目循序渐进,从一维线性状态到二维动态规划,再到与树形结构与递归的使用结合,是一个非常好的练习案例。三道题目均来自leetcode,已全部提交AC,供需要者使用和交流~_~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值