[dp]leetcode 198. House Robber

输入:一个数组nums,每一个元素nums[i]表示第i个房间的价值。
输出:一个抢劫犯能抢到又不会被警察发现的最大价值。
规则:如果抢劫犯抢了相邻房间,那么报警装置就会触发,警察会得到通知。
分析:我可以从第0个房间开始,可能抢,也可能不抢,一直到最后一个房间,得到的价值取最大值就是结果。用回溯法吧。

public class HouseRobber198 {
    private int[] nums;
    private int max = 0;
    public int rob(int[] nums) {
        this.nums = nums;
        max = 0;
        rob(0,0,true);
        return max;
    }

    private void rob(int idx,int value,boolean robable){
        if(idx==nums.length){
            max = Math.max(max,value);
        }else{
            //System.out.println(idx+" "+robbed+" "+value);
            int idx2 = robable?1:0;
            if(robable){
                rob(idx+1,value+nums[idx],false);
            }
            rob(idx+1,value,true);
        }
    }
}

分析2:这样写完,画决策分析树,或者打印idx,robbed,value,就会发现有些是重复的。2 false 重复了两次。因为要计算的是最大价值,所以只需要保留一个最大值。所以我们可以使用缓存减少计算量。

0 false 0
1 true 1
2 false 1
3 true 4
3 false 1
1 false 0
2 true 2
3 false 2
2 false 0
3 true 3
3 false 0
	private int[] nums;
    private int max = 0;
    private int[][] memo;
    public int rob(int[] nums) {
        this.nums = nums;
        max = 0;
        memo = new int[nums.length][2];
        rob(0,0,true);
        return max;
    }

    private void rob(int idx,int value,boolean robable){
        if(idx==nums.length){
            max = Math.max(max,value);
        }else{
            //System.out.println(idx+" "+robbed+" "+value);
            int idx2 = robable?1:0;
            if(value<memo[idx][idx2]){
                return;
            }
            memo[idx][idx2]=value;
            if(robable){
                rob(idx+1,value+nums[idx],false);
            }
            rob(idx+1,value,true);
        }
    }

分析3:把以上代码改为迭代版本。把变量名memo改为dp。

public int rob(int[] nums) {
        if(nums==null || nums.length==0) return 0;
        int n = nums.length;
        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]);
    }

看着写出来的dp代码我发现可以这样整理自己的逻辑。

给定一个数组nums={1,2,3,1}。
如果只有第0个房间,可能抢也可能不抢。但这基本是废话,不抢显然拿不到价值。
  抢,得1,
  不抢得0。
  取最大值,得到的价值是1。
如果只有第0个和第1个 房间。对于房间1可能抢,也可能不抢。
  如果抢第1个房间,那得到的价值是2,因为第0个房间不抢的价值是0。
  如果不抢第1个房间,那得到的价值是1,因为第0个房间抢,得1,不抢得0,取最大值。Max(抢第1个房间,不抢第1个房间)=2。

如果只有第0个、第1个 ,第2个房间。对于房间2可能抢,也可能不抢。
  如果不抢第2个房间,那最后拿到的价值就是Max(抢第1个房间,不抢第1个房间)=2。
  如果抢第2个房间,那最后拿到的价值就是不抢第1个房间的价值+nums[2]=1+3=4。
  那最大的答案是Max(不抢第2个房间,抢第2个房间)=4。
分析到这里我们知道在新增一个房间的时候与上一个房间的抢和不抢获得的价值有关系。最后答案是在当前房间号抢和不抢状态取最大值。这样基本上dp方程就出来了。

我们用dp[i]记录第i个房间的状态,dp[i][0]表示第i个房间不抢的价值,dp[i][1]代表第i个房间抢后的价值。
dp[i][0]=max(dp[i-1][0],dp[i-1][1])
dp[i][1]=dp[i-1][0]+nums[i]

嗯,这就是我的思考方式,是从回溯算法开始,不断优化,最后整理到dp,然后总结出dp应该这样思考。当然还缺少一步是内存优化。只与上一步的状态有关系,可以用变量表示,而无需数组。

	public int robV4(int[] nums) {
        if(nums==null || nums.length==0) return 0;
        int n = nums.length;
        int v0 = 0 ,v1=nums[0];
        for(int i=1;i<n;i++){
            int tmp0 = Math.max(v0,v1);
            int tmp1 = v0+nums[i];
            v0=tmp0;
            v1=tmp1;
        }
        return Math.max(v0,v1);
    }
发布了148 篇原创文章 · 获赞 35 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/flying_all/article/details/103225849
今日推荐