华为OD机试102-叠积木

这个题目很有难度,解题思路大概分为两大步
第一步,算出可能的层数
第二步,利用递归来判断当前层数是否成立
其中第一步比较简单,以例题为例 3,6,6,3.总共四个数字
先将4个数字加起来 3+6+6+3 = 18
4个数字最多就是4层,其实是 3层,2层。先看 4层是否成立
18/4 结果不是整数,显然不行。 18/3 = 6 .而且6大于或等于所有元素,
说明3层是有希望的。同理 18/2=9 ,而且9大于或等于所有元素,说明9也是有希望的。
那么3层和2层都是候选层。这是第一步。
第二步稍微有点复杂。其实第二步的算法和力扣 698题, 划分为k个相等的子集是一样的,
可以参考力扣解法https://leetcode.cn/problems/partition-to-k-equal-sum-subsets/description/。
我这里的解法是将层数比作桶,比如三层,就相当于3个桶。4个数字就
相当于4个带分数的球。然后这4个球要放入3个桶,并且保证每个桶里面球的分数是6分。
具体代码如下

public class Demo102 {

    public static void main(String[] args) {
        int[] a = {3, 6, 6, 3, 6};
        System.out.println(maxFloor(a));
    }

    public static int maxFloor(int[] a) {
        int sum = 0;
        // 算出所有元素的和
        for (int i = 0; i < a.length; i++) {
            sum = sum + a[i];
        }
        // 这个排序是为了 sum / i >= a[a.length - 1]的比较
        Arrays.sort(a);
        /* 计算可能的层数
         * 比如有4个数,那我们依次假设可能的层数就是 4,3,2
         * 然后再看假设是否可能成立
         * sum % i == 0 看是否能整除
         * sum / i >= a[a.length - 1] 平均值要等于或大于最大元素
         */
        List<Integer> maybeFloors = new ArrayList<Integer>();
        for (int i = a.length; i >= 2; i--) {
            if (sum % i == 0 && sum / i >= a[a.length - 1]) {
                maybeFloors.add(i);
            }
        }
        if (maybeFloors.size() == 0) {
            return -1;
        }
        for (int i = 0; i < maybeFloors.size(); i++) {
            int k = maybeFloors.get(i);
            //依次尝试每一层是否可能
            if (canPartition(sum, a, k)) {
                return k;
            }
        }
        return -1;
    }

    public static boolean canPartition(int sum, int[] nums, int k) {
        int average = sum / k;
        int[] bucket = new int[k];
        return canPartition(nums, 0, bucket, average);
    }

    public static boolean canPartition(int[] nums, int index, int[] bucket, int average) {
        /*
         * index == nums.length说明都放完了
         * 如何证明都放完了的时候每个桶的数值恰好是average
         * 反证法
         * 首先 average * bucket.length = nums元素总和
         * 根据程序逻辑
         * 对于任意i,当 0<=i<bucket.length 必有bucket[i]<=average
         * 取j, 且0<=j<bucket.length,如果bucket[j]<average
         * 则必有bucket所有元素之和小于nums元素总和. 这和程序假定
         * 所有元素都放入了bucket矛盾.
         */
        if (index == nums.length) {
            return true;
        }
        for (int i = 0; i < bucket.length; i++) {
            // 多个桶分值一样,前面已经试了不要重复试,此处可能不好理解
            if (i > 0 && bucket[i] == bucket[i - 1]) {
                continue;
            }
            // 满足条件才能放入
            if (bucket[i] + nums[index] <= average) {
                // 放入
                bucket[i] = bucket[i] + nums[index];
                // 后续递归放入剩余的球
                if (canPartition(nums, index + 1, bucket, average)) {
                    return true;
                }
                // 上面的策略失败了,就回退,继续尝试后面的策略
                bucket[i] = bucket[i] - nums[index];
            }
        }
        return false;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值