吃葡萄问题

GitHub

培养框架思维,真正爱上算法!关注公众号查看更新文章👆

相关推荐:

读完本文,你不仅学会了算法套路,还可以顺便去 LeetCode 上拿下如下题目:

吃葡萄


今天在牛客网上做了一道叫做「吃葡萄」的题目,非常有意思。

有三种葡萄,每种分别有 a, b, c 颗,现在有三个人,第一个人只吃第一种和第二种葡萄,第二个人只吃第二种和第三种葡萄,第三个人只吃第一种和第三种葡萄。

现在给你输入 a, b, c 三个值,请你适当安排,让三个人吃完所有的葡萄,算法返回吃的最多的人最少要吃多少颗葡萄

题目链接:

https://www.nowcoder.com/questionTerminal/14c0359fb77a48319f0122ec175c9ada

牛客网的题目形式和力扣不一样,我去除输入和输出的处理,题目核心就是让你实现这样一个函数:

// 输入为三种葡萄的颗数,可能非常大,所以用 long 型
// 返回吃的最多的人最少要吃多少颗葡萄
long solution(long a, long b, long c);

题目解析

首先来理解一下题目,你怎么做到使得「吃得最多的那个人吃得最少」?

可以这样理解,我们先不管每个人只能吃两种特定葡萄的约束,你怎么让「吃得最多的那个人吃得最少」?

显然,只要平均分就行了,每个人吃 (a+b+c)/3 颗葡萄。即便不能整除,比如说 a+b+c=8,那也要尽可能平均分,就是说一个人吃 2 颗,另两个人吃 3 颗。

综上,「吃得最多的那个人吃得最少」就是让我们尽可能地平均分配,而吃的最多的那个人吃掉的葡萄颗数就是 (a+b+c)/3 向上取整的结果,也就是 (a+b+c+2)/3

PS:向上取整是一个常用的算法技巧。大部分编程语言中,如果你想计算 M 除以 NM / N 会向下取整,你想向上取整的话,可以改成 (M+(N-1)) / N

好了,刚才在讨论简单情况,现在考虑一下如果加上「每个人只能吃特定两种葡萄」的限制,怎么做?

也就是说,每个人只能吃特定两种葡萄,你也要尽可能给三个人平均分配,这样才能使得吃得最多的那个人吃得最少。

这可复杂了,如果用 X, Y, Z 表示这三个人,就会发现他们组成一个三角关系:

你让某一个人多吃某一种葡萄,就会产生连带效应,想着就头疼,这咋整?

思路分析

反正万事靠穷举呗,我一开始想了下回溯算法暴力穷举的可能性:

对于每一颗葡萄,可能被谁吃掉?有两种可能呗,那么我写一个回溯算法,把所有可能穷举出来,然后求个最值行不行?

理论上是可行的,但是暴力算法的复杂度一般都是指数级,如果你以葡萄为「主角」进行穷举,看看变量 a, b, c 都是 long 型的数据,这个复杂度已经让我脊梁沟冒冷汗了。

那么这道题还是得取巧,思路还是要回到如何「尽可能地平均分配」上面,那么事情就变得有意思起来

如果把葡萄的颗数 a, b, c 作为三条线段,它们的大小作为线段的长度,想一想它们可能组成什么几何图形?我们的目的是否可以转化成「尽可能平分这个几何图形的周长」?

三条线段组成的图形,那不就是三角形嘛?不急,我们小学就学过,三角形是要满足两边之和大于第三边的,假设 a < b < c,那么有下面两种情况:

如果 a + b > c,那么可以构成一个三角形,只要在这个三角形中间画一个顶点都在边 a, b, c 上的等边三角形,这三点就一定可以把这个三角形的周长平分成三份,且每一份都包含两条边:

也就是说,这种情况下,三个人依然是可以平均分配所有葡萄的,吃的最多的人最少可以吃到的葡萄颗数依然是 (a+b+c+2)/3

如果 a + b <= c,这三条边就不能组成一个封闭的图形了,那么我们可以将最长边 c「折断」,也就是形成一个四边形。

这里面有两种情况:

对于情况一,a + bc 的差距还不大的时候,可以看到依然能够让三个人平分这个四边形,那么吃的最多的人最少可以吃到的葡萄颗数依然是 (a+b+c+2)/3

随着 c 的不断增大,就会出现情况二,此时 c > 2*(a+b),由于每个人口味的限制,为了尽可能平分,X 最多吃完 ab,而 c 边需要被 YZ 平分,也就是说此时吃的最多的人最少可以吃到的葡萄颗数就是 (c+1)/2,即平分 c 边向上取整。

以上就是全部情况,翻译成代码如下:

long solution(long a, long b, long c) {
    long[] nums = new long[]{a, b, c};
    Arrays.sort(nums);
    long sum = a + b + c;

    // 能够构成三角形,可完全平分
    if (nums[0] + nums[1] > nums[2]) {
        return (sum + 2) / 3;
    }
    // 不能构成三角形,平分最长边的情况
    if (2 * (nums[0] + nums[1]) < nums[2]) {
        return (nums[2] + 1) / 2;
    }
    // 不能构成三角形,但依然可以完全平分的情况
    return (sum + 2) / 3;
}

至此,这道题就被巧妙地解决了,时间复杂度仅需 O(1),关键思路在于如何尽可能平分。

谁又能想到,吃个葡萄得借助几何图形?也许这就算法的魅力吧...

_____________

《labuladong 的算法小抄》已经出版,关注公众号「labuladong」查看详情;后台回复关键词「进群」可加入算法群,大家一起刷题/内推

Copyright © labuladong 2020 all right reserved,powered by Gitbook该文件修订时间: 2021-03-17 21:09:29

results matching ""

    No results matching ""