找实习之从0开始的后端学习日记【8.4】

目前进度:leetcode刷题

213. 打家劫舍 II

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。

给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。

示例 1:

输入:nums = [2,3,2]
输出:3
解释:你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。

示例 2:

输入:nums = [1,2,3,1]
输出:4
解释:你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
     偷窃到的最高金额 = 1 + 3 = 4 。

示例 3:

输入:nums = [1,2,3]
输出:3

提示:

1 <= nums.length <= 100
0 <= nums[i] <= 1000

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/house-robber-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

一开始打算拿二维数组来推导,F[i][j]代表从i→j房能偷的最大金额。则一共可以分四种情况。(是否偷i和是否偷j)。
但我们只是为了求出最后的最大值。所以可以固定起点为0,也就是将结果先分为是否偷0,然后分两种情况分别列状态转移方程。为了让0的周围有两个,且那两个对面至少还有一个,假设这里 n ≥ 4 n\ge4 n4.( n ≤ 3 n\le3 n3的情况就是取nums的最大值。)

  1. 偷0:那0的周围两个房间(1和n-1)必不能偷。所以得看2→n-2间的房间随便偷的话能偷的最大值。转换为首尾不相连接的房间求解。

    假设g(i)为从2偷到i的最大金额。 g ( 2 ) = n u m s [ 2 ] g ( i ) = m a x { g ( i − 2 ) + n u m s [ i ] , g ( i − 1 ) } g(2)=nums[2]\\g(i)=max\{g(i-2)+nums[i],g(i-1)\} g(2)=nums[2]g(i)=max{g(i2)+nums[i],g(i1)}为了计算方便令 g ( 0 ) = g ( 1 ) = 0 g(0)=g(1)=0 g(0)=g(1)=0,无实义,仅帮助g(2)从公式推出。

  2. 不偷0:则0周围两个房间(1和n-1)都有可能被偷。所以同上得看1→n-1间的房间随便偷的话能偷的最大值。转换为首尾不相连接的房间求解。

    假设h(i)为从1偷到i的最大金额。 h ( i ) = m a x { h ( i − 2 ) + n u m s [ i ] , h ( i − 1 ) } h(i)=max\{h(i-2)+nums[i],h(i-1)\} h(i)=max{h(i2)+nums[i],h(i1)}

总金额最大值 = m a x { n u m s [ 0 ] + g ( n − 2 ) , h ( n − 1 ) } =max\{nums[0]+g(n-2),h(n-1)\} =max{nums[0]+g(n2),h(n1)}我们只需要分别算出g(n-2),h(n-1)即可。
用滚动数组。
(mark一下只有本人才看得懂的草稿)
在这里插入图片描述

代码如下

class Solution {
    public int rob(int[] nums) {
        int n = nums.length;
        if(n<=3){
            int max=-1;
            for(int i = 0;i<n;i++){
                if(max<nums[i]){
                    max=nums[i];
                }
            }
            return max;
        }
        else{
            //g(n-2),n-2>=2; 2~n-2
            //h(n-1),n-1>=3; 3~n-1
            //等长
            int g0=0;
            int g1=0;
            int g2=-1;//待算
            int h1=nums[1];
            int h2=Math.max(nums[1],nums[2]);
            int h3=-1;//待算
            for(int i = 2;i<n-1;i++){
                g2 = Math.max(g0+nums[i],g1);
                h3 = Math.max(h1+nums[i+1],h2);

                g0=g1;
                g1=g2;

                h1=h2;
                h2=h3;
            }
            return Math.max(nums[0]+g2,h3);
        }
    }
}

官方题解

跟我的思路大体上一样,分的情况不一样。首先分为 n ≤ 2 n\le2 n2 n ≥ 3 n\ge3 n3。其中当 n ≥ 3 n\ge3 n3时,已知第一间房屋和最后一间房屋不能同时偷窃。所以分为

  1. 不偷窃0房,所以得看1→n-1间的房间随便偷的话能偷的最大值。
  2. 不偷窃 n − 1 n-1 n1房,所以得看0→n-2间的房间随便偷的话能偷的最大值。

我分为【偷0房】和【不偷0房】。我在【偷0房】中计算的是2→n-2的最大值g(n-2),并在最后+nums[0]. 他的【不偷窃 n − 1 n-1 n1房】中的0可偷可不偷,但会包括我的这种情况。
在这里插入图片描述
状态转移方程列了个通项的,这是个可以学习的地方。
在这里插入图片描述
代码使用一个函数实现两次计算。我傻傻的用了两组变量

class Solution {
    public int rob(int[] nums) {
        int length = nums.length;
        if (length == 1) {
            return nums[0];
        } else if (length == 2) {
            return Math.max(nums[0], nums[1]);
        }
        return Math.max(robRange(nums, 0, length - 2), robRange(nums, 1, length - 1));
    }

    public int robRange(int[] nums, int start, int end) {
        int first = nums[start], second = Math.max(nums[start], nums[start + 1]);
        for (int i = start + 2; i <= end; i++) {
            int temp = second;
            second = Math.max(first + nums[i], second);
            first = temp;
        }
        return second;
    }
}

作者:LeetCode-Solution
链接:https://leetcode.cn/problems/house-robber-ii/solution/da-jia-jie-she-ii-by-leetcode-solution-bwja/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

740. 删除并获得点数

给你一个整数数组 nums ,你可以对它进行一些操作。

每次操作中,选择任意一个 nums[i] ,删除它并获得 nums[i] 的点数。之后,你必须删除 所有 等于 nums[i] - 1 和 nums[i] + 1 的元素。

开始你拥有 0 个点数。返回你能通过这些操作获得的最大点数。

示例 1:

输入:nums = [3,4,2]
输出:6
解释:
删除 4 获得 4 个点数,因此 3 也被删除。
之后,删除 2 获得 2 个点数。总共获得 6 个点数。

示例 2:

输入:nums = [2,2,3,3,3,4]
输出:9
解释:
删除 3 获得 3 个点数,接着要删除两个 2 和 4 。
之后,再次删除 3 获得 3 个点数,再次删除 3 获得 3 个点数。
总共获得 9 个点数。

提示:

1 <= nums.length <= 2 * 104
1 <= nums[i] <= 104

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/delete-and-earn
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

![在这里插入图片描述](https://img-blog.csdnimg.cn/ae66eb15616e4934a8adda5304b929a3.png
步骤分为

  1. 构成point数组
  2. 把point数组当成nums求偷东西问题。用滚动数组。
class Solution {
    public int deleteAndEarn(int[] nums) {
        Arrays.sort(nums);
        //第一遍循环确定长度
        int n = 1;
        for(int i = 1 ;i<nums.length;i++){
            if(nums[i]==nums[i-1]+1){//如果当前是前一个的相邻数
                n+=1;                
            }
            else if(nums[i]>nums[i-1]+1){//如果当前比前一个数大多了
                n+=2;
            }
        }
        //第二次遍历构造point数组
        int[] point = new int[n];
        point[0]=nums[0];
        int j = 0;
        for(int i = 1 ;i<nums.length;i++){
            if(nums[i]==nums[i-1]){//如果当前数和前一个相等
                point[j]+=nums[i];
            }
            else if(nums[i]==nums[i-1]+1){//如果当前是前一个的相邻数
                j+=1;
                point[j]=nums[i];         
            }
            else{//如果当前比前一个数大多了
                j+=1;
                point[j]=0;
                j+=1;
                point[j]=nums[i];
            }
        }
        //利用point数组,转化为偷东西问题,使用滚动数组
        if(n==1){return point[0];}
        // if(n==2){return Math.max(point[0],point[1])}//转为用公式计算
        int a = 0;//f-1
        int b = point[0];//f0
        int c = -1;//f1 本应该为临界条件,为了计算方便造了个f-1,这个就可以用公式计算了。
        for(int i = 1;i<n;i++){
            c = Math.max(a+point[i],b);
            a = b;
            b = c;
        }
        return c;

    }
}

官方题解

官方题解跟我基本差不多,都是转换成小偷问题,不过没有为了省空间用固定的int数组,或者重构一个point数组过滤多余的0。

方案1的动规基本就是我的最初没有优化的思想。

方案2则是直接将不连续的分成子问题了,相当于他边遍历边计算了,没有像我这样copy一份到point再一口气算,我相当于重复遍历。这点比我的要好,值得学习。

方案2的代码

class Solution {
    public int deleteAndEarn(int[] nums) {
        int n = nums.length;
        int ans = 0;
        Arrays.sort(nums);
        List<Integer> sum = new ArrayList<Integer>();
        sum.add(nums[0]);
        int size = 1;
        for (int i = 1; i < n; ++i) {
            int val = nums[i];
            if (val == nums[i - 1]) {
                sum.set(size - 1, sum.get(size - 1) + val);
            } else if (val == nums[i - 1] + 1) {
                sum.add(val);
                ++size;
            } else {
                ans += rob(sum);
                sum.clear();
                sum.add(val);
                size = 1;
            }
        }
        ans += rob(sum);
        return ans;
    }

    public int rob(List<Integer> nums) {
        int size = nums.size();
        if (size == 1) {
            return nums.get(0);
        }
        int first = nums.get(0), second = Math.max(nums.get(0), nums.get(1));
        for (int i = 2; i < size; i++) {
            int temp = second;
            second = Math.max(first + nums.get(i), second);
            first = temp;
        }
        return second;
    }
}

作者:LeetCode-Solution
链接:https://leetcode.cn/problems/delete-and-earn/solution/shan-chu-bing-huo-de-dian-shu-by-leetcod-x1pu/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

总结

第一题学到了:状态转移方程列了两个的话,可以看看能不能化成一个通项。

第二题主要是没想到可以写一个rob函数将隔开的nums化解成一个个子问题,在遍历的过程中就解。我还是习惯于最后统一处理了。结果还是通项能力。要加强这方面的思想。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值