我的LeetCode:https://leetcode-cn.com/u/ituring/
我的LeetCode刷题源码[GitHub]:https://github.com/izhoujie/Algorithmcii
LeetCode 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-我无法证明的一生二、二生四、四生万物算法...
- 我的想法:x、y、x+y、|x-y|是四个基本数(去重后可能更少),若再更相递减能再获得一个不重复数(总数大于4个),说明由xy能获得的z范围是[1,x+y],否则z的值只能由四个基本数组合获得;
- 按想法实现的算法通过了,效率不高,但是我好像不会证明这个东西;
- 看了官解,感觉我这个跟贝祖定理有点靠近...有可以证明我这个想法数学实现的大佬麻烦指教一下;
思路2-贝祖定理+GCD(算法思路说明来自LeetCode官方解答)
预备知识:贝祖定理
我们认为,每次操作只会让桶里的水总量增加 x,增加 y,减少 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 是否是它的倍数即可。
复杂度分析
时间复杂度:O(log(min(x,y))),取决于计算最大公约数所使用的辗转相除法。
空间复杂度:O(1),只需要常数个变量。
总结:数学很强大,数学是算法的基石啊...
算法源码示例
package leetcode;
import java.util.Iterator;
import java.util.TreeSet;
/**
* @author ZhouJie
* @date 2020年3月21日 下午1:59:07
* @Description: 365. 水壶问题
*
*/
public class LeetCode_0365 {
}
class Solution_0365 {
/**
* @author: ZhouJie
* @date: 2020年3月21日 下午2:39:59
* @param: @param x
* @param: @param y
* @param: @param z
* @param: @return
* @return: boolean
* @Description: 1-除去xy本身,x y互相加减及递归一次的加减值组成的set,
* 若set总数大于4说明可以生成1~x+y之间的任意z数,否则z只能是set的某两个散列数的组合
* ::这个无法证明...但是算法通过了,虽然效率不高。
*
*/
public boolean canMeasureWater_1(int x, int y, int z) {
if (z == 0 || z == x || z == y) {
return true;
}
if (z > (x + y)) {
return false;
}
TreeSet<Integer> treeSet = new TreeSet<Integer>();
int a = Math.abs(x + y);
int b = Math.abs(x - y);
int c = Math.abs(x - b);
int d = Math.abs(y - b);
treeSet.add(a);
treeSet.add(b);
treeSet.add(c);
treeSet.add(d);
treeSet.add(x);
treeSet.add(y);
treeSet.remove(0);
int min = treeSet.first();
int size = treeSet.size();
if (size > 4) {
return true;
} else if (z < min) {
return false;
} else {
Iterator<Integer> ite = treeSet.iterator();
while (ite.hasNext()) {
if (treeSet.contains(z - ite.next().intValue())) {
return true;
}
}
return false;
}
}
/**
* @author: ZhouJie
* @date: 2020年3月21日 下午3:16:54
* @param: @param x
* @param: @param y
* @param: @param z
* @param: @return
* @return: boolean
* @Description: 2-贝祖定理+GCD
*
*/
public boolean canMeasureWater_2(int x, int y, int z) {
if (z == 0 || z == x || z == y) {
return true;
} else if (z > (x + y)) {
return false;
} else {
return z % gcd(x, y) == 0;
}
}
private int gcd(int x, int y) {
return y == 0 ? x : gcd(y, x % y);
}
}