LeetCode 2312.卖木头

给你两个整数 m 和 n ,分别表示一块矩形木块的高和宽。同时给你一个二维整数数组 prices ,其中 prices[i] = [hi, wi, pricei] 表示你可以以 pricei 元的价格卖一块高为 hi 宽为 wi 的矩形木块。

每一次操作中,你必须按下述方式之一执行切割操作,以得到两块更小的矩形木块:

  • 沿垂直方向按高度 完全 切割木块,或
  • 沿水平方向按宽度 完全 切割木块

在将一块木块切成若干小木块后,你可以根据 prices 卖木块。你可以卖多块同样尺寸的木块。你不需要将所有小木块都卖出去。你 不能 旋转切好后木块的高和宽。

请你返回切割一块大小为 m x n 的木块后,能得到的 最多 钱数。

注意你可以切割木块任意次。

输入:m = 3, n = 5, prices = [[1,4,2],[2,2,7],[2,1,3]]
输出:19
解释:上图展示了一个可行的方案。包括:
- 2 块 2 x 2 的小木块,售出 2 * 7 = 14 元。
- 1 块 2 x 1 的小木块,售出 1 * 3 = 3 元。
- 1 块 1 x 4 的小木块,售出 1 * 2 = 2 元。
总共售出 14 + 3 + 2 = 19 元。
19 元是最多能得到的钱数。

示例 2:

输入:m = 4, n = 6, prices = [[3,2,10],[1,4,2],[4,1,3]]
输出:32
解释:上图展示了一个可行的方案。包括:
- 3 块 3 x 2 的小木块,售出 3 * 10 = 30 元。
- 1 块 1 x 4 的小木块,售出 1 * 2 = 2 元。
总共售出 30 + 2 = 32 元。
32 元是最多能得到的钱数。
注意我们不能旋转 1 x 4 的木块来得到 4 x 1 的木块。

解析:对于任意一块木板,我们都有可以选择是把整颗木板进行出售,还是进行切割。

1.当选择把木板进行出售时,题目要求的得到最大的价格,在此我们肯定是要选择最大价格的并且要此时木板的尺寸要大于选择出售的价格,对于此问题,运用贪心策略,对价格进行从大到小进行排序,这样可以更早发现想要的价格。

2.选择进行切割时,我们可以有许多方案。观察题目给出的价格是整型,所以我们切割的最小单位就是1,每一次切割,可以把木板分割成两部分。同时,切割也要分为横着切和竖着切,因为题目要求不能将木板进行旋转。

        2.1.横着切。对于横着切,我们可以每一格为单位遍历切割,但是当切到一半后,接下来切割的两部分与之前切割的方案相同。所以,我们可以选择切割到一半就停止,而且,我们没有必要从第一个格子进行切割。

        2.2.竖着切。与横着切原理相同,就不一一进行赘述了。

方法一:暴力递归

class Solution {
    public static long sellingWood(int m, int n, int[][] prices) {
        int minW = Integer.MAX_VALUE;
        int minH = Integer.MAX_VALUE;
        int flag =0;
        for(int i= 0;i<prices.length;i++){
            if(prices[i][1]==prices[i][0]&&prices[i][1]==1)
                flag = prices[i][2];
            minW = Math.min(minW,prices[i][1]);
            minH = Math.min(minH,prices[i][0]);
        }
        //价格排序
        Arrays.sort(prices, (o1, o2) -> (o2[2] - o1[2]));

        return Math.max(process(prices, n, m,minH,minW), (long) m *n*flag);
    }

    public static long process(int[][] prices, int w, int h,int minH,int minW) {
        if (w < minW || h < minH)
            return 0;
        //分三种情况
        //1.直接要这个版
        int pt = 0;
        for (int[] price : prices) {
            if (price[0] <= h && price[1] <= w) {
                pt = price[2];
                break;
            }
        }

        long l1 = process(prices, 0, 0,minH,minW) + pt;

        //2.不要这个版,横着切
        long l2 = 0;
        for (int i = 1; i <= h / 2; i++) {
            l2 = Math.max(l2, process(prices, w, h - i,minH,minW) + process(prices, w, i,minH,minW));
        }
//
        //3.要这个版,竖着切
        long l3 = 0;
        for (int i = 1; i <= w / 2; i++)
            l3 = Math.max(l3, process(prices, w - i, h,minH,minW) + process(prices, i, h,minH,minW));

        return Math.max(l1, Math.max(l2, l3));
    }
}

到测试用例23就超出时间限制了,对算法进行改进,可以观察到,每次递归调用了大量的重复,所以选择将这些重复存储起来,不用多次进行递归运算。

方法二:记忆化搜索

class Solution {
    public static long sellingWood(int m, int n, int[][] prices) {
        int minW = Integer.MAX_VALUE;
        int minH = Integer.MAX_VALUE;
        int flag =0;
        for(int i= 0;i<prices.length;i++){
            if(prices[i][1]==prices[i][0]&&prices[i][1]==1)
                flag = prices[i][2];
            minW = Math.min(minW,prices[i][1]);
            minH = Math.min(minH,prices[i][0]);
        }
        //价格排序
        Arrays.sort(prices, (o1, o2) -> (o2[2] - o1[2]));
        long[][] dp = new long[n+1][m+1];

        return Math.max(process(prices, n, m,minH,minW,dp), (long) m *n*flag);
    }

    public static long process(int[][] prices, int w, int h,int minH,int minW,long[][] dp) {
        if(dp[w][h]!=0)
            return dp[w][h];
        if (w < minW || h < minH)
            return 0;
        //分三种情况
        //1.直接要这个版
        int pt = 0;
        for (int[] price : prices) {
            if (price[0] <= h && price[1] <= w) {
                pt = price[2];
                break;
            }
        }

        long l1 = process(prices, 0, 0,minH,minW,dp) + pt;

        //2.不要这个版,横着切
        long l2 = 0;
        for (int i = minH; i <= h / 2; i++) {
            l2 = Math.max(l2, process(prices, w, h - i,minH,minW,dp) + process(prices, w, i,minH,minW,dp));
        }
//
        //3.要这个版,竖着切
        long l3 = 0;
        for (int i = minW; i <= w / 2; i++)
            l3 = Math.max(l3, process(prices, w - i, h,minH,minW,dp) + process(prices, i, h,minH,minW,dp));

        dp[w][h] = Long.max(l1,Long.max(l2,l3));
        return dp[w][h];
    }
}

到测试用例46就超出时间限制了,对算法进行改进,可以观察到,该递归的每一个后面的结果都依赖前面的结果,可以将算法改为循环迭代进行。

方法三:动态规划

class Solution {
    public static long sellingWood(int m, int n, int[][] prices) {
        int minW = Integer.MAX_VALUE;
        int minH = Integer.MAX_VALUE;
        int flag =0;
        for(int i= 0;i<prices.length;i++){
            if(prices[i][1]==prices[i][0]&&prices[i][1]==1)
                flag = prices[i][2];
            minW = Math.min(minW,prices[i][1]);
            minH = Math.min(minH,prices[i][0]);
        }
        //价格排序
        Arrays.sort(prices, (o1, o2) -> (o2[2] - o1[2]));
        long[][] dp = new long[n+1][m+1];
        for(int w = minW;w<=n;w++){
            for(int h = minH;h<=m;h++){
                //分三种情况
                //1.直接要这个版
                int pt = 0;
                for (int[] price : prices) {
                    if (price[0] <= h && price[1] <= w) {
                        pt = price[2];
                        break;
                    }
                }

                long l1 =  pt;

                //2.不要这个版,横着切
                long l2 = 0;
                for (int i = minH; i <= h / 2; i++) {
                    l2 = Math.max(l2, dp[w][h-i]+dp[w][i]);
                }

                //3.要这个版,竖着切
                long l3 = 0;
                for (int i = minW; i <= w / 2; i++)
                    l3 = Math.max(l3, dp[w-i][h] + dp[i][h]);

                dp[w][h] = Long.max(l1,Long.max(l2,l3));
            }
        }

        return dp[n][m];
    }
}

终于通过了,不写了,可以发现迭代过程中的循环有些是没有必要的,还可以进行优化。。。。。。。。。。。。。

  • 42
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值