【题目】*365. 水壶问题
有两个容量分别为 x升 和 y升 的水壶以及无限多的水。请判断能否通过使用这两个水壶,从而可以得到恰好 z升 的水?
如果可以,最后请用以上水壶中的一或两个来盛放取得的 z升 水。
你允许:
装满任意一个水壶
清空任意一个水壶
从一个水壶向另外一个水壶倒水,直到装满或者倒空
示例 1: (From the famous “Die Hard” example)
输入: x = 3, y = 5, z = 4
输出: True
示例 2:
输入: x = 2, y = 6, z = 5
输出: False
【解题思路1】BFS
1.两个杯子不会同时处于半满状态(若是与外部交互则倒满或者倒空一瓶,内部交互则把自己倒空或者倒满另一瓶).
2.若一个半满杯子水量改变,则只能是倒空或者由另一杯倒满(根据上面可知另一杯只能是满或者空,此时由外部倒满或者倒空没有意义,因为会变成双满或者一空一满或者双空的类初始状态)
3.综上所述,倒空或由外部倒满半满杯子没有意义.水的总量变化只能取决于倒空满杯子或者倒满空杯子.
import java.util.*;
class Solution {
public boolean canMeasureWater(int x, int y, int z) {
if (z == 0) {
return true;
}
if (x + y < z) {
return false;
}
Queue<Map.Entry<Integer, Integer>> queue = new ArrayDeque<>();
AbstractMap.SimpleEntry<Integer, Integer> start = new AbstractMap.SimpleEntry<>(0, 0);
queue.add(start);
Set<Map.Entry<Integer, Integer>> visited = new HashSet<>();
visited.add(start);
while (!queue.isEmpty()) {
Map.Entry<Integer, Integer> entry = queue.poll();
int curX = entry.getKey();
int curY = entry.getValue();
if (curX == z || curY == z || curX + curY == z) {
return true;
}
if (curX == 0) {
// 把第一个桶填满
addIntoQueue(queue, visited, new AbstractMap.SimpleEntry<>(x, curY));
}
if (curY == 0) {
// 把第二个桶填满
addIntoQueue(queue, visited, new AbstractMap.SimpleEntry<>(curX, y));
}
if (curY < y) {
// 把第一个桶倒空
addIntoQueue(queue, visited, new AbstractMap.SimpleEntry<>(0, curY));
}
if (curX < x) {
// 把第二个桶倒空
addIntoQueue(queue, visited, new AbstractMap.SimpleEntry<>(curX, 0));
}
// y - curY是第二个桶还可以再加的水的升数,但是最多只能加curX升水。
int moveSize = Math.min(curX, y - curY);
// 把第一个桶里的curX升水倒到第二个桶里去。
addIntoQueue(queue, visited, new AbstractMap.SimpleEntry<>(curX - moveSize, curY + moveSize));
// 反过来同理,x - curX是第一个桶还可以再加的升数,但是最多只能加curY升水。
moveSize = Math.min(curY, x - curX);
// 把第一个桶里的curX升水倒到第二个桶里去。
addIntoQueue(queue, visited, new AbstractMap.SimpleEntry<>(curX + moveSize, curY - moveSize));
}
return false;
}
private void addIntoQueue(Queue<Map.Entry<Integer, Integer>> queue,
Set<Map.Entry<Integer, Integer>> visited,
Map.Entry<Integer, Integer> newEntry) {
if (!visited.contains(newEntry)) {
visited.add(newEntry);
queue.add(newEntry);
}
}
}
【解题思路2】数学方法:GCB最大公约数,贝祖定理
每次操作只会让桶里的水总量增加 x,增加 y,减少 x,或者减少 y。
操作水壶的时候,两个水壶不可能同时都是半满的。如果某个水壶是半满的,另外一个肯定是满的或者空的。而且如果某个水壶是半满的(此时另外一个就是空的或者满的),就不能直接把这个水壶填满,也不能把这个半满的水倒掉,因为这会回到初始状态,这么做没有意义。
因此,我们可以认为每次操作只会给水的总量带来 x 或者 y 的变化量。因此我们的目标可以改写成:找到一对整数 a, b,使得
ax+by=z
而只要满足 z≤x+y,且这样的 a, b 存在,那么我们的目标就是可以达成的。这是因为:
若 a≥0,b≥0,那么显然可以达成目标。
若 a<0,那么可以进行以下操作:
- 往 y 壶倒水;
- 把 y 壶的水倒入 x 壶;
- 如果 y 壶不为空,那么 x 壶肯定是满的,把 x 壶倒空,然后再把 y 壶的水倒入 x 壶。
重复以上操作直至某一步时 x 壶进行了 a 次倒空操作,y 壶进行了 b 次倒水操作。
若b<0,方法同上,x 与 y 互换。
而贝祖定理告诉我们,ax+by=z 有解当且仅当 z 是 x,y 的最大公约数的倍数。因此我们只需要找到 x, y 的最大公约数并判断 z 是否是它的倍数即可。
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 % gcb(x,y) == 0;
}
int gcb(int x,int y){
return y == 0 ? x : gcb(y,x%y);
}
}