Loading

【LeetCode】365. 水壶问题

题目链接:

365. 水壶问题

题目描述:

有两个容量分别为 x 升和 y 升的水壶以及无限多的水。请判断能否通过使用这两个水壶,从而可以得到恰好 z 升的水?

如果可以,最后请用以上水壶中的一或两个来盛放取得的 z 升水。

你允许:

  • 装满任意一个水壶
  • 清空任意一个水壶
  • 从一个水壶向另外一个水壶倒水,直到装满或者倒空

示例:

示例 1:

输入:x = 3, y = 5, z = 4
输出:true

示例 2:

输入:x = 2, y = 6, z = 5
输出:false

思路:

先放一个预备知识:

裴蜀定理(又称贝祖定理):维基百科百度百科

简单来讲就是:

若 x, y 是整数,且它们的最大公约数为 d,那么对于任意的整数 a, b,ax+by 都一定是 d 的倍数。特别地,一定存在整数 a,b,使 ax+by=d 成立。

值得注意的是:a,b 可以是负数。

下面的内容是官方题解,我觉得十分精彩,就搬过来了。原链接:官方题解

另外,题目条件十分值得认真审阅:

  1. 最后请用以上水壶中的一或两个来盛放取得的 z 升水:那么如果 x + y < z,直接 false
  2. 根据题目给出的三个倒水的条件,可以得到这样的结论:每次操作只会让桶里的水总量增加 x 或增加 y或减少 x或者减少 y

你可能认为这有问题:如果往一个不满的桶里放水,或者把它排空呢?那变化量不就不是 x 或者 y 了吗?接下来解释这一点:

  • 首先要清楚,在题目所给的操作下,两个桶不可能同时有水且不满。因为观察所有题目中的操作,操作的结果都至少有一个桶是空的或者满的;

  • 其次,对一个不满的桶加水是没有意义的。因为如果另一个桶是空的,那么这个操作的结果等价于直接从初始状态给这个桶加满水;而如果另一个桶是满的,那么这个操作的结果等价于从初始状态分别给两个桶加满;

  • 再次,把一个不满的桶里面的水倒掉是没有意义的。因为如果另一个桶是空的,那么这个操作的结果等价于回到初始状态;而如果另一个桶是满的,那么这个操作的结果等价于从初始状态直接给另一个桶倒满。

因此,可以认为每次操作只会给水的总量带来 x 或者 y 的变化量。因此目标可以改写成:

找到一对整数 a, b,使得 \(ax+by=z\)

而只要满足 \(z\leq x+y\),且这样的 a, b 存在,那么我们的目标就是可以达成的。这是因为:

  • \(a\geq 0, b\geq 0\),那么显然可以达成目标。

  • \(a\lt 0\),那么可以进行以下操作:

    1. 往 y 壶倒水;
    2. 把 y 壶的水倒入 x 壶;
    3. 如果 y 壶不为空,那么 x 壶肯定是满的,把 x 壶倒空,然后再把 y 壶的水倒入 x 壶。

    重复以上操作直至某一步时 x 壶进行了 a 次倒空操作,y 壶进行了 b 次倒水操作。

  • \(b\lt 0\),方法同上,x 与 y 互换。

而贝祖定理告诉我们,\(ax+by=z\) 有解当且仅当 z 是 x, y 的最大公约数的倍数。因此

只需要找到 x, y 的最大公约数并判断 z 是否是它的倍数即可

另附:辗转相除法求最大公约数

辗转相除法是递归算法,一句话概括这个算法就是:两个整数的最大公约数,等于其中较小的数 和两数相除余数 的最大公约数
比如 10 和 25,25 除以 10 商 2 余 5,那么 10 和 25 的最大公约数,等同于 10 和 5 的最大公约数。

代码实现:

class Solution {
    public boolean canMeasureWater(int x, int y, int z) {
        if (x + y < z) {
            return false;
        }
        if (x == 0 || y == 0) {
            return z == 0 || x + y == z;
        }
        // 裴蜀定理,又称贝祖定理
        return z % gcd(x, y) == 0;
    }
    // 辗转相除法求最大公约数
    private int gcd(int a, int b) {
        return (a % b == 0) ? b : gcd(b, a % b);
    }
}
posted @ 2020-03-21 11:39  江南笑书生  阅读(229)  评论(0编辑  收藏  举报