算法 - 打家劫舍问题

题目描述

给你一个n(1 ≤ n ≤ 10),和一个长度为n的数组,在不同时选位置相邻的两个数的基础上,求该序列的最大子序列和(挑选出的子序列可以为空)。

示例1 输入 3,[1,2,3]
返回值 4

说明 : 有[],[1],[2],[3],[1,3] 4种选取方式其中[1,3]选取最优,答案为4

示例2
输入 4,[4,2,3,5]
返回值 9


尝试一:

一看这道题,马上出现 思路一:两次遍历,固定一个数,找下一个最大值,将最大的和赋值给 sum;解题思路类似于冒泡排序的思想

public long subsequence2(int n, int[] array) {
    // write code here
    long max = 0L;
    for (int i = 0; i < array.length - 2; i++) {
        int left = array[i];
        for (int j = i + 1; j < array.length; j++) {
            int right = array[j];
            max = Math.max(left + right, max);
        }
    }
    return max;
}

可惜:超时了,两次循环的时间复杂度为 O(n²)……

尝试二:

没关系,解决耗时的最大问题,就是二分法嘛,左边找个最大值,右边找个最大值,一加不就是最大值吗?解题思路类似于折半查找

public long subsequenceError(int n, int[] array) {
    if (array.length == 0) return 0;
    if (array.length == 1) return array[0];
    if (array.length == 2) return array[0] > array[1] ? array[0] : array[1];
    int leftIndex = 0;
    int rightIndex = n - 1;
    int leftMax = array[leftIndex];
    int rightMax = array[rightIndex];
    long max = 0L;
    while (leftIndex < rightIndex) {
        leftMax = leftMax > array[leftIndex] ? leftMax : array[leftIndex];
        rightMax = rightMax > array[rightIndex] ? rightMax : array[rightIndex];
        max = Math.max(max, 0L + leftMax + rightMax);
        leftIndex++;
        rightIndex--;
    }
    return max;
}

额……结果不正确,差别特别大!先吃晚饭,吃完饭就有灵感了,

尝试三:

对啊!并不能确定最大的两个值分别在左右两侧!对啊!如果两个最大值都在左侧,那通过二分的结果肯定不对啊!
那就将数组分成三段,分别找区间内最大值,找出三个最大值及其索引插入 map,最后找出最大的两个数(索引不挨着);

//……没有写,感觉不太靠谱****
public long subsequenceError3(int n, int[] array) {
    long max = 0l;
    HashMap<Integer, Integer> maxMap = new HashMap<>();
    //
    return max;
}

正文来了

看了看 C++ 的解法,上来就说要 dp,dp 是啥?我只知道 CP……

/**
* C++ 思路 题目要求是不相邻的子序列值。 什么样子会帮助满足最大呢?
* 1,序列包含尽可能多的数
* 2,序列包含尽可能大的数。
* 考虑不相邻的话,要不要加入第i个数,需要考虑的问题是它前一个 i-1 要不要加入,
* 至于i-2则不需要考虑,因为加入第i个数必然可以加入不相邻的 i-2 。换句话说,你不会跳过3个数。
* 换成代码就是 dp[i+3] = max(dp[i+2], dp[i+1]+arr[i])
*
* @param n
* @param array
* @return
*/
private long subsequenceC(int n, int[] array) {
    // write code
    long dp1 = 0, dp2 = 0;
    //dp3 记录每一次的最大值,并在将其 dp2;中,dp2 用于返回,并且记录上一次的最大值,用于这一次 dp3 的比较;
    for (int it : array) {
        long dp3 = Math.max(dp1 + it, dp2);
        dp1 = dp2;
        dp2 = dp3;
    }
    return dp2;
}

这种奇怪的交换方式,我是比较反感的,因为理解起来比较费劲,你加一个变量说人话不好吗?……


牛客上一个小伙伴给出了解题思路:【打家劫舍问题】

其实就是一个打家劫舍的问题,数组中每一个元素值就是可以偷的金额,相邻的不能偷,求能够偷出的最大金额是多少。

设置一个状态转移数组dp,dp[i]表示数组中前i个元素 所能偷的最大金额是多少
状态转移表达式:
(1)对于当前的元素arr[i],如果偷,那么dp[i] = dp[i-2] + arr[i]
(2)如果不偷,那么dp[i] = dp[i-1]

private long subsequence(int n, int[] array) {
    // write code
    long[] dp = new long[n + 1];
    dp[0] = 0;
    dp[1] = array[0];
    for (int i = 2; i <= n; i++) {
        //打劫第 6 家时,判断:dp[5] 大?还是 dp[4] + array[5] 大?;因为 array[5] 其实就是第 6 家的财产;
        //dp[i - 1] 表示的就是选择打劫上一家,放弃这一家;| 而 【dp[i - 2] + array[i - 1]】表示打劫上上家和这一家
        dp[i] = Math.max(dp[i - 1], dp[i - 2] + array[i - 1]);
        System.out.println("打劫第 " +i+" 家时,判断:dp["+(i-1)+"] 大?还是 "+"dp["+(i-2)+"] + array["+(i-1)+"] 大?" );
    }
    System.out.println(Arrays.toString(dp));
    return dp[n];
}

害,debug 都看不懂小哥是在干嘛……那就查看输出吧!最大的误区:Error:理解跑偏了,误以为只可以选择两个数,那可就永远都做不对嘛…… 害,那就有些理解了,将打劫收获的金额保存在数组中,类似于优化版的【跳台阶】,稍微提高性能,逻辑就是,在当前位置 i 时,判断是否打劫,
1.
如果打劫,那就打劫这一家 array[i - 1] + dp[i-2];
2.
如果不打劫,那金额等于 dp[i-1];


最后,我要进大厂!加油奥里给!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

liusaisaiV1

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

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

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

打赏作者

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

抵扣说明:

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

余额充值