• [leetcode 双周赛 11] 1231 分享巧克力


    1231 Divide Chocolate 分享巧克力

    问题描述

    你有一大块巧克力,它由一些甜度不完全相同的小块组成。我们用数组 sweetness 来表示每一小块的甜度。

    你打算和 K 名朋友一起分享这块巧克力,所以你需要将切割 K 次才能得到 K+1 块,每一块都由一些 **连续 **的小块组成。

    为了表现出你的慷慨,你将会吃掉 总甜度最小 的一块,并将其余几块分给你的朋友们。

    请找出一个最佳的切割策略,使得你所分得的巧克力 总甜度最大,并返回这个 最大总甜度

    示例 1:

    输入: sweetness = [1,2,3,4,5,6,7,8,9], K = 5
    输出: 6
    解释: 你可以把巧克力分成 [1,2,3], [4,5], [6], [7], [8], [9]。

    示例 2:

    输入: sweetness = [5,6,7,8,9,1,2,3,4], K = 8
    输出: 1
    解释: 只有一种办法可以把巧克力分成 9 块。

    示例 3:

    输入: sweetness = [1,2,2,1,2,2,1,2,2], K = 2
    输出: 5
    解释: 你可以把巧克力分成 [1,2,2], [1,2,2], [1,2,2]。

    提示:

    • 0 <= K < sweetness.length <= 10^4
    • 1 <= sweetness[i] <= 10^5

    思路

    • 读题
    1. 将数组分成K+1份, 每份独立且包含是一连续子序列
    2. 计算每份元素之和, 使得最小那份是在所有可能分割方案中最大

    贪心

    假设必定有一最佳答案ans, 它的情况是:

    • 0 < ans <= sum(sweetness)/(K+1)
    • 每个分块之和必定 >= ans
    • 以ans为界限分割的块数量 >= (K+1)

    先设ans=avg(sweetness, K+1), 期望最完美的情况下, 大家分到的一样多, 最少的肯定也是所有方案中最多的
    如果以此做界限切割不到K+1块, 则缩减期望ans--
    每次都假设当前期望ans是最佳切割方案的答案, 不断-1逼近正确答案

    • 贪心切割
      每次累加之和大于等于阈值, 就认为这是最佳划分, 作为1块
      最终判断是否能切割到(K+1)块

    贪心+二分

    最佳答案ans还有一个特性:

    • ans 是最佳答案
    • ans+1 不是答案
    • ans-1 是答案
      ans为界限, 大于它的都无法做出预期切割方案, 小于等于它的都可以做出切割方案, 其中等于它就是最佳切割方案

    这样就有一个二分的特性
    我们可以在一个确定有正确答案的范围内, 使用二分搜索, 不断调整范围区间, 直至找到正确答案
    其中判断正确答案的位置时:

    • [left, right] mid = (left + right) >> 1
    • checkout(mid)
    1. 可以通过 left = mid+1 向右逼近最佳
    2. 不可以通过 right = mid-1 向左逼近最佳
    • checkout 就是之前的贪心切割

    代码实现

    贪心

    /**
     * 超出时间限制 avg调整过慢
     */
    class Solution {
        public int maximizeSweetness(int[] sweetness, int K) {
            // sum 该数组总和
            int sum = 0;
            for (int swt : sweetness) {
                sum += swt;
            }
    
            // avg 如果平均分给K+1个人
            int avg = sum / (K + 1);
            while (avg > 0) {
                // cur 当前分块个数
                // curSum 每个分块的大小
                int cur = 0, curSum = 0;
                for (int swt : sweetness) {
                    curSum += swt;
                    System.out.printf("cut:%d K:%d curSum:%d avg:%d
    ", cur, K, curSum, avg);
                    if (curSum >= avg) {
                        System.out.println();
                        // 从第0块开始切 (cur++ > K or ++cur > (K+1))
                        if (cur++ >= K) {
                            return avg;
                        }
                        curSum = 0;
                    }
                }
                // 以步长为1的"速度"向最佳答案靠近
                avg--;
            }
    
            return avg;
        }
    }
    

    贪心+二分

    class Solution {
        public int maximizeSweetness(int[] sweetness, int K) {
            // ans 返回答案
            // left 二分左值
            // right 二分右值 大小为1e4*1e5
            int ans = 0, left = 0, right = (int) 1e9 + 50;
    
            // 最佳甜度必定在[left, right]区间内
            while (left <= right) {
                int mid = (left + right) >> 1;
                // 检测: 以mid为界限, 大于它的都不可以, 小于等于则可以
                if (check(sweetness, K + 1, mid)) {
                    // 最佳mid值在后半段 [mid+1, right]
                    left = mid + 1;
                    ans = Math.max(mid, ans);
                } else {
                    // 最佳mid值在前半段 [left, mid-1]
                    right = mid - 1;
                }
            }
    
            return ans;
        }
    
        private boolean check(int[] arr, int len, int threshold) {
            // cur 在分块总和满足阈值threshold的情况下 可切块数量 --> k+1
            // sum 单分块之和
            int cur = 0, sum = 0;
    
            for (int a : arr) {
                sum += a;
                // 该连续块之和符合阈值 予以分块
                if (sum >= threshold) {
                    cur++;
                    sum = 0;
                }
            }
    
            // 如果在阈值threshold下 可以分成k+1块 该切割策略符合题意
            return cur >= len;
        }
    }
    
    • 改进: 缩小二分查找范围
    /**
     * 同上相同的思路
     * 不过使用二叉搜索的方式 并且进行了'剪枝'(最终结果必定不超过平均值, 缩小了二叉搜索范围)
     */
    class Solution {
        public int maximizeSweetness(int[] sweetness, int K) {
            int sum = 0;
            for (int swt : sweetness) {
                sum += swt;
            }
    
            // right 采用平均值
            int left = 0, right = sum / (K + 1), ans = 0;
    
            while (left <= right) {
                int mid = (left + right) >> 1, cur = 0, curSum = 0;
                System.out.printf("[left:%d mid:%d right:%d]
    ", left, mid, right);
                for (int swt : sweetness) {
                    curSum += swt;
                    System.out.printf("[cut:%d K:%d] [curSum:%d mid:%d]
    ", cur, K, curSum, mid);
                    if (curSum >= mid) {
                        if (cur++ >= K) {
                            break;
                        }
                        curSum = 0;
                    }
                }
    
                if (cur > K) {
                    ans = Math.max(ans, mid);
                    left = mid + 1;
                } else {
                    right = mid - 1;
                }
            }
    
            return ans;
        }
    }
    
    input:
    [1,2,3,4,5,6,7,8,9]
    5
    output:
    [left:0 mid:3 right:7]
    [cut:0 K:5] [curSum:1 mid:3]
    [cut:0 K:5] [curSum:3 mid:3]
    [cut:1 K:5] [curSum:3 mid:3]
    [cut:2 K:5] [curSum:4 mid:3]
    [cut:3 K:5] [curSum:5 mid:3]
    [cut:4 K:5] [curSum:6 mid:3]
    [cut:5 K:5] [curSum:7 mid:3]
    [left:4 mid:5 right:7]
    [cut:0 K:5] [curSum:1 mid:5]
    [cut:0 K:5] [curSum:3 mid:5]
    [cut:0 K:5] [curSum:6 mid:5]
    [cut:1 K:5] [curSum:4 mid:5]
    [cut:1 K:5] [curSum:9 mid:5]
    [cut:2 K:5] [curSum:6 mid:5]
    [cut:3 K:5] [curSum:7 mid:5]
    [cut:4 K:5] [curSum:8 mid:5]
    [cut:5 K:5] [curSum:9 mid:5]
    [left:6 mid:6 right:7]
    [cut:0 K:5] [curSum:1 mid:6]
    [cut:0 K:5] [curSum:3 mid:6]
    [cut:0 K:5] [curSum:6 mid:6]
    [cut:1 K:5] [curSum:4 mid:6]
    [cut:1 K:5] [curSum:9 mid:6]
    [cut:2 K:5] [curSum:6 mid:6]
    [cut:3 K:5] [curSum:7 mid:6]
    [cut:4 K:5] [curSum:8 mid:6]
    [cut:5 K:5] [curSum:9 mid:6]
    [left:7 mid:7 right:7]
    [cut:0 K:5] [curSum:1 mid:7]
    [cut:0 K:5] [curSum:3 mid:7]
    [cut:0 K:5] [curSum:6 mid:7]
    [cut:0 K:5] [curSum:10 mid:7]
    [cut:1 K:5] [curSum:5 mid:7]
    [cut:1 K:5] [curSum:11 mid:7]
    [cut:2 K:5] [curSum:7 mid:7]
    [cut:3 K:5] [curSum:8 mid:7]
    [cut:4 K:5] [curSum:9 mid:7]
    

    参考资源

    第 11 场双周赛 全国排名
    【算法实况】旅游期间打一场双周赛 - 力扣双周赛 - LeetCode Biweekly 11

  • 相关阅读:
    Android:Butter Knife 8.0.1配置
    webAPI获得链接客户端IP地址
    解决WebClient或HttpWebRequest首次连接缓慢问题
    Android Studio中有用的快捷键栏
    SQL十进制和十六进制相互转换
    SQL分组查询每组前几条数据
    POJ 1011 Sticks
    POJ 1144 Network(割点)
    求无向图中的割边(桥)
    POJ 2553 The Bottom of a Graph
  • 原文地址:https://www.cnblogs.com/slowbirdoflsh/p/11738134.html
Copyright © 2020-2023  润新知