东哥吃葡萄时竟然吃出一道算法题!

吃葡萄问题

学好算法全靠套路,认准 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 除以 NM / 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 + 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),关键思路在于如何尽可能平分。

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

_____________

我的 在线电子书 有 100 篇原创文章,手把手带刷 200 道力扣题目,建议收藏!对应的 GitHub 算法仓库 已经获得了 70k star,欢迎标星!

原文地址:https://www.cnblogs.com/labuladong/p/13945545.html