破解打家劫舍:动态规划与二分查找的高效算法

目录

198. 打家劫舍

 解法一:一维动态规划

 解法二:二维动态规划

213. 打家劫舍 II

思路分析

代码实现

337. 打家劫舍 III

思路分析

代码实现

2560. 打家劫舍 IV

思路分析 

参考博客


198. 打家劫舍

如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 ,求今晚能够偷窃到的最高金额。

 解法一:一维动态规划

class Solution {
    
    public int rob(int[] nums) {

        if (nums == null || nums.length == 0) {
            return 0;
        }

        int n =  nums.length;
        int dp[] = new int[n]; 

        if (n == 1) {
            return nums[0];
        }

        dp[0] = nums[0]; 
        dp[1] =  Math.max(nums[0],nums[1]); 

       for(int i=2;i<n;i++){

            // 不偷这个房子,那么总金额就是偷到前一个房子为止的最大金额,即 dp[i-1]。
            // 偷这个房子,那么总金额就是这个房子的金额加上偷到前前一个房子为止的最大金额,即 nums[i] + dp[i-2]。
            dp[i] = Math.max(dp[i-1], nums[i] + dp[i-2]);
          
       }

       return dp[n-1];

    }
}

 解法二:二维动态规划

参考这篇博客的方法三LeetCode122之股票买卖的最好时机(相关话题:动态规划,记忆搜索,状态机,贪心算法)_最适合买入股票算法题-CSDN博客

class Solution {
    public int rob(int[] nums) {

       int n =  nums.length;
       //dp[i][0]表示不偷第i个房间,dp[i][1]表示偷第i个房间
       int dp[][] = new int[n][2]; 

       
        dp[0][0] = 0; 
        dp[0][1] = nums[0]; 

       for(int i=1;i<n;i++){

            dp[i][0] =  Math.max(dp[i-1][0] , dp[i-1][1]);
            dp[i][1] =  dp[i-1][0] + nums[i];

       }
       return Math.max(dp[n-1][0],dp[n-1][1]);

    }
}

213. 打家劫舍 II

这个地方所有的房屋都 围成一圈 ,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 , 求今晚能够偷窃到的最高金额。

思路分析

  1. 第一次考虑时,我们假设偷了第一间房屋,因此不能偷最后一间房屋。这样,我们就只考虑从第一间房屋到倒数第二间房屋的数组范围,即 nums[0] 到 nums[n-2]。
  2. 第二次考虑时,我们假设不偷第一间房屋,这样就可以偷最后一间房屋。这时,我们考虑的数组范围是从第二间房屋到最后一间房屋,即 nums[1] 到 nums[n-1]。

代码实现

基于(Python)


class Solution:
    def rob(self, nums: List[int]) -> int:
        # 辅助函数,用于处理不成环的情况
        def rob_linear(houses):
            n = len(houses)
            if n == 1:
                return houses[0]
            if n == 2:
                return max(houses[0], houses[1])
            
            dp = [0] * n
            dp[0] = houses[0]
            dp[1] = max(houses[0], houses[1])
            for i in range(2, n):
                dp[i] = max(dp[i-1], dp[i-2] + houses[i])
            return dp[-1]

        # 如果房屋数量小于3,直接返回最大金额的房子
        n = len(nums)
        if n == 1:
            return nums[0]
        if n == 2:
            return max(nums[0], nums[1])

        # 第一次考虑时,我们假设偷了第一间房屋,因此不能偷最后一间房屋。这样,我们就只考虑从第一间房屋到倒数第二间房屋的数组范围,即 nums[0] 到 nums[n-2]。
        #第二次考虑时,我们假设不偷第一间房屋,这样就可以偷最后一间房屋。这时,我们考虑的数组范围是从第二间房屋到最后一间房屋,即 nums[1] 到 nums[n-1]。
        return max(rob_linear(nums[:-1]), rob_linear(nums[1:]))

337. 打家劫舍 III

root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。

思路分析

  • 如果偷当前节点,那么子节点就不能偷
  • 如果不偷当前节点,那么子节点可以偷也可以不偷,取决于哪种选择更优
  • 如果函数只返回一个值可能会超时,需要引入缓存,可以考虑函数分别返回访问当前节点的最大收益,和不访问当前节点最大收益数组

代码实现

只有一个返回值加缓存的实现

class Solution {

    public int rob(TreeNode root) {
         
        // 使用HashMap来存储节点在被访问和未被访问时的最大值
        Map<TreeNode, Integer>[] memo = new Map[2];
        memo[0] = new HashMap<>();  // 不偷当前节点的情况
        memo[1] = new HashMap<>();  // 偷当前节点的情况
        int maxProfit =  Math.max(helpRob(root,1,memo), helpRob(root,0,memo));
 
        return maxProfit;
    }


    private int helpRob(TreeNode root, int canRob, Map<TreeNode, Integer>[] memo) {


         if(root==null){
            return 0;
         }

          // 检查缓存是否已有结果
        if (memo[canRob].containsKey(root)) {
            return memo[canRob].get(root);
        }

        int maxProfit = 0;

         if(canRob==1){
           
              // 如果偷当前节点,那么子节点就不能偷
             maxProfit =  Math.max(maxProfit ,root.val + helpRob(root.left,0,memo) + helpRob(root.right,0,memo));

         }else{


            int temp1 =  helpRob(root.left,0,memo);
            int temp2 =  helpRob(root.left,1,memo);
            int temp3 =  helpRob(root.right,0,memo);
            int temp4 =  helpRob(root.right,1,memo);

            // 如果不偷当前节点,那么子节点可以偷也可以不偷,取决于哪种选择更优
            maxProfit = Math.max(temp1+temp3,temp1+temp4);
            maxProfit = Math.max(maxProfit,temp2+temp3);
            maxProfit = Math.max(maxProfit,temp2+temp4);

         }

        // 将结果存入缓存
        memo[canRob].put(root, maxProfit);
        return maxProfit;
    }

}

返回值是数组的实现

class Solution {
    public int rob(TreeNode root) {
        int[] result = robSub(root);
        return Math.max(result[0], result[1]);
    }

     //res[0] 表示不偷当前节点时的最大值。
     //res[1] 表示偷当前节点时的最大值。
    private int[] robSub(TreeNode node) {
        if (node == null) {
            return new int[2];
        }

        int[] left = robSub(node.left);
        int[] right = robSub(node.right);

        int[] res = new int[2];
        // 如果不偷当前节点,那么子节点可以偷也可以不偷,取决于哪种选择更优
        res[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);

        // 如果偷当前节点,那么子节点就不能偷
        res[1] = node.val + left[0] + right[0];

        return res;
    }
}

2560. 打家劫舍 IV

沿街有一排连续的房屋。每间房屋内都藏有一定的现金。现在有一位小偷计划从这些房屋中窃取现金。由于相邻的房屋装有相互连通的防盗系统,所以小偷 不会窃取相邻的房屋 。小偷的 窃取能力 定义为他在窃取过程中能从单间房屋中窃取的 最大金额 。给你一个整数数组 nums 表示每间房屋存放的现金金额。形式上,从左起第 i 间房屋中放有 nums[i] 美元。另给你一个整数 k ,表示窃贼将会窃取的 最少 房屋数。小偷总能窃取至少 k 间房屋。返回小偷的 最小 窃取能力。

思路分析 

单调性如果小偷可以在某个窃取能力x下窃取至少k间房屋,那么他也能在任何大于x的能力下做到同样的事情。这是因为增加窃取能力意味着更多房屋的金额成为可选项。这种单调递增关系是二分查找可行性的关键。

参考博客

LeetCode887之鸡蛋掉落(相关话题:动态规划,二分法)_鸡蛋掉落二分法是否有效率提高的空间?包括空间效率和时间效率。-CSDN博客

class Solution {
    public int minCapability(int[] nums, int k) {
        // 初始化二分搜索的边界
        int lower = Arrays.stream(nums).min().getAsInt(); // 找到数组中的最小值,作为最低可能的窃取能力
        int upper = Arrays.stream(nums).max().getAsInt(); // 找到数组中的最大值,作为最高可能的窃取能力

        // 执行二分搜索
        while (lower <= upper) {
            int middle = (lower + upper) / 2; // 计算中间值作为当前的窃取能力
            int count = 0; // 用来计数在当前窃取能力下可以窃取的不相邻房屋数
            boolean visited = false; // 用于标记上一间房屋是否已被窃取

            // 遍历房屋数组,判断在当前窃取能力下的窃取情况
            for (int x : nums) {
                if (x <= middle && !visited) {
                    // 如果当前房屋的金额不超过middle,并且上一间房屋没有被窃取
                    count++; // 增加计数
                    visited = true; // 标记当前房屋已被窃取
                } else {
                    // 如果当前房屋的金额超过middle或上一间房屋已被窃取
                    visited = false; // 重置visited,表示当前房屋没有被窃取
                }
            }

            // 判断是否能满足至少窃取k间房屋的条件
            if (count >= k) {
                // 如果可以窃取的房屋数量大于等于k
                upper = middle - 1; // 降低窃取能力的上界,尝试找到更小的窃取能力
            } else {
                // 如果不能满足窃取至少k间房屋
                lower = middle + 1; // 增加窃取能力的下界,尝试更高的窃取能力
            }
        }

        // 当lower > upper时,lower即为所求的最小窃取能力
        return lower;
    }
}

  • 22
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

数据与算法架构提升之路

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

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

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

打赏作者

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

抵扣说明:

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

余额充值