吃葡萄问题
学好算法全靠套路,认准 labuladong 就够了!
如果你在迎战秋招,东哥悄悄告诉你一些 笔试中的套路。
相关推荐:
读完本文,你可以去力扣拿下如下题目:
-----------
今天在牛客网上做了一道叫做「吃葡萄」的题目,非常有意思。
有三种葡萄,每种分别有 a, b, c
颗,现在有三个人,第一个人只吃第一种和第二种葡萄,第二个人只吃第二种和第三种葡萄,第三个人只吃第一种和第三种葡萄。
现在给你输入 a, b, c
三个值,请你适当安排,让三个人吃完所有的葡萄,算法返回吃的最多的人最少要吃多少颗葡萄。
题目链接:
https://www.nowcoder.com/questionTerminal/14c0359fb77a48319f0122ec175c9ada
牛客网的题目形式和力扣不一样,我去除输入和输出的处理,题目核心就是让你实现这样一个函数:
// 输入为三种葡萄的颗数,可能非常大,所以用 long 型
// 返回吃的最多的人最少要吃多少颗葡萄
long solution(long a, long b, long c);
PS:我认真写了 100 多篇原创,手把手刷 200 道力扣题目,全部发布在 labuladong的算法小抄,持续更新。建议收藏,按照我的文章顺序刷题,掌握各种算法套路后投再入题海就如鱼得水了。
题目解析
首先来理解一下题目,你怎么做到使得「吃得最多的那个人吃得最少」?
可以这样理解,我们先不管每个人只能吃两种特定葡萄的约束,你怎么让「吃得最多的那个人吃得最少」?
显然,只要平均分就行了,每个人吃 (a+b+c)/3
颗葡萄。即便不能整除,比如说 a+b+c=8
,那也要尽可能平均分,就是说一个人吃 2 颗,另两个人吃 3 颗。
综上,「吃得最多的那个人吃得最少」就是让我们尽可能地平均分配,而吃的最多的那个人吃掉的葡萄颗数就是 (a+b+c)/3
向上取整的结果,也就是 (a+b+c+2)/3
。
PS:向上取整是一个常用的算法技巧。大部分编程语言中,如果你想计算 M
除以 N
,M / N
会向下取整,你想向上取整的话,可以改成 (M+(N-1)) / N
。
好了,刚才在讨论简单情况,现在考虑一下如果加上「每个人只能吃特定两种葡萄」的限制,怎么做?
也就是说,每个人只能吃特定两种葡萄,你也要尽可能给三个人平均分配,这样才能使得吃得最多的那个人吃得最少。
这可复杂了,如果用 X, Y, Z
表示这三个人,就会发现他们组成一个三角关系:
你让某一个人多吃某一种葡萄,就会产生连带效应,想着就头疼,这咋整?
思路分析
反正万事靠穷举呗,我一开始想了下回溯算法暴力穷举的可能性:
对于每一颗葡萄,可能被谁吃掉?有两种可能呗,那么我写一个回溯算法,把所有可能穷举出来,然后求个最值行不行?
理论上是可行的,但是暴力算法的复杂度一般都是指数级,如果你以葡萄为「主角」进行穷举,看看变量 a, b, c
都是 long 型的数据,这个复杂度已经让我脊梁沟冒冷汗了。
那么这道题还是得取巧,思路还是要回到如何「尽可能地平均分配」上面,那么事情就变得有意思起来。
如果把葡萄的颗数 a, b, c
作为三条线段,它们的大小作为线段的长度,想一想它们可能组成什么几何图形?我们的目的是否可以转化成「尽可能平分这个几何图形的周长」?
三条线段组成的图形,那不就是三角形嘛?不急,我们小学就学过,三角形是要满足两边之和大于第三边的,假设 a < b < c
,那么有下面两种情况:
PS:我认真写了 100 多篇原创,手把手刷 200 道力扣题目,全部发布在 labuladong的算法小抄,持续更新。建议收藏,按照我的文章顺序刷题,掌握各种算法套路后投再入题海就如鱼得水了。
如果 a + b > c
,那么可以构成一个三角形,只要取每条边的中点,就一定可以把这个三角形的周长平分成三份,且每一份都包含两条边:
也就是说,这种情况下,三个人依然是可以平均分配所有葡萄的,吃的最多的人最少可以吃到的葡萄颗数依然是 (a+b+c+2)/3
。
如果 a + b <= c
,这三条边就不能组成一个封闭的图形了,那么我们可以将最长边 c
「折断」,也就是形成一个四边形。
这里面有两种情况:
对于情况一,a + b
和 c
的差距还不大的时候,可以看到依然能够让三个人平分这个四边形,那么吃的最多的人最少可以吃到的葡萄颗数依然是 (a+b+c+2)/3
。
随着 c
的不断增大,就会出现情况二,此时 c > 2*(a+b)
,由于每个人口味的限制,为了尽可能平分,X
最多吃完 a
和 b
,而 c
边需要被 Y
或 Z
平分,也就是说此时吃的最多的人最少可以吃到的葡萄颗数就是 (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),关键思路在于如何尽可能平分。
谁又能想到,吃个葡萄得借助几何图形?也许这就算法的魅力吧...
_____________
我的 在线电子书 有 100 篇原创文章,手把手带刷 200 道力扣题目,建议收藏!对应的 GitHub 算法仓库 已经获得了 70k star,欢迎标星!