题目链接:
题目描述:
有两个容量分别为 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 可以是负数。
下面的内容是官方题解,我觉得十分精彩,就搬过来了。原链接:官方题解
另外,题目条件十分值得认真审阅:
- 最后请用以上水壶中的一或两个来盛放取得的 z 升水:那么如果
x + y < z
,直接false
; - 根据题目给出的三个倒水的条件,可以得到这样的结论:每次操作只会让桶里的水总量增加
x
或增加y
或减少x
或者减少y
。
你可能认为这有问题:如果往一个不满的桶里放水,或者把它排空呢?那变化量不就不是 x 或者 y 了吗?接下来解释这一点:
-
首先要清楚,在题目所给的操作下,两个桶不可能同时有水且不满。因为观察所有题目中的操作,操作的结果都至少有一个桶是空的或者满的;
-
其次,对一个不满的桶加水是没有意义的。因为如果另一个桶是空的,那么这个操作的结果等价于直接从初始状态给这个桶加满水;而如果另一个桶是满的,那么这个操作的结果等价于从初始状态分别给两个桶加满;
-
再次,把一个不满的桶里面的水倒掉是没有意义的。因为如果另一个桶是空的,那么这个操作的结果等价于回到初始状态;而如果另一个桶是满的,那么这个操作的结果等价于从初始状态直接给另一个桶倒满。
因此,可以认为每次操作只会给水的总量带来 x 或者 y 的变化量。因此目标可以改写成:
找到一对整数 a, b,使得 (ax+by=z)。
而只要满足 (zleq x+y),且这样的 a, b 存在,那么我们的目标就是可以达成的。这是因为:
-
若 (ageq 0, bgeq 0),那么显然可以达成目标。
-
若 (alt 0),那么可以进行以下操作:
- 往 y 壶倒水;
- 把 y 壶的水倒入 x 壶;
- 如果 y 壶不为空,那么 x 壶肯定是满的,把 x 壶倒空,然后再把 y 壶的水倒入 x 壶。
重复以上操作直至某一步时 x 壶进行了 a 次倒空操作,y 壶进行了 b 次倒水操作。
-
若 (blt 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);
}
}