虽然现在保研成功了,我也深知了算法的重要性,还是每天刷一道吧,防止老年痴呆 = =
一、题目大意
有两个容量分别为 x升 和 y升 的水壶以及无限多的水。请判断能否通过使用这两个水壶,从而可以得到恰好 z升 的水?
如果可以,最后请用以上水壶中的一或两个来盛放取得的 z升 水。
你允许:
- 装满任意一个水壶
- 清空任意一个水壶
- 从一个水壶向另外一个水壶倒水,直到装满或者倒空
来源:力扣(LeetCode)
链接:原题链接
PS:不好意思,之前说很讨厌LeetCode,到以工作为目的的刷题了,还是来了,真香 ~
二、题目思路以及AC代码
这道题有两个思路进行求解,其实准确的说是三个,下面我就一一来说。下面均假设B水壶容量y > A水壶容量x
解法1:
首先,显然该题可以用BFS进行解决,状态也显而易见,就是两个水壶的状态,暂且称其为A,B水壶,初始状态为(0, 0),因为两个水壶都是空的,然后在处于每个状态的时候,可以进行题目中提到的三种操作,产生新的状态,对应的也就是新的状态进栈,之后就是经典的BFS了。
解法2:
同样还是用BFS解决,但比解法1稍微快捷方便一点。解法1中,我们将两个水壶的状态共同作为BFS的状态,但是注意题目要求的是求得两个水壶的总水量是否可以达到z,那么我们这时就可以将总水量作为状态进行BFS求解。
初始状态为0,因为总水量是0,然后对于每个状态来说,总水量都可以进行+x,+y,-x,-y的操作(当然要保证在0 ~ x+y的范围内),当然要记住这里不限制必须操作只一次,因为有的状态变化可能不能一次达成。
解法3:
这里就没有采用BFS求解了,因为由解法2我们知道,两个水壶的总水量必定可以表示成 ax + by 的形式,其中 a 和 b 均为整数。
因为我们从一开始,每回加水,必定加x或者y,有人可能会有疑问,如果B水壶先装满水,倒到A水壶里面,那么B水壶里面就剩一点,再倒不就不是全部的y了吗?但是此时确实全部的x,因为总水量是x + y,而你在加水之前是 y。每回倒水,倒水之后,必定剩下 x 或 y。每回两个壶相互倒水,水的总量必定不会变。
当我们知道了两个水壶总量必定是 ax + by 之后,就需要知道一个定理。
贝祖定理:若x,y是整数,且gcd(x,y)=d,那么对于任意的整数a,b,ax+by都一定是d的倍数,特别地,一定存在整数a,b,使ax+by=d成立。
所以思路也很明确,我们只需要求 x 和 y 的最大公约数,然后看 z 是否是其的倍数即可。
因为我在做题的时候就只写了第二种解法,下面就先贴这种解法的代码吧,之后有时间再把另外两种补上:
// 解法 2
#include <queue>
#include <unordered_set>
class Solution {
public:
bool canMeasureWater(int x, int y, int z) {
if (z == 0) return true;
if (z > (x + y)) return false;
unordered_set<int> vis;
queue<int> q;
q.push(0);
vis.insert(0);
while (!q.empty()) {
int n = q.front(); q.pop();
if (n + x <= x + y && vis.find(n+x) == vis.end()) {
if (n+x == z) return true;
vis.insert(n+x);
q.push(n+x);
}
if (n + y <= x + y && vis.find(n+y) == vis.end()) {
if (n+y == z) return true;
vis.insert(n+y);
q.push(n+y);
}
if (n - x >= 0 && vis.find(n-x) == vis.end()) {
if (n-x == z) return true;
vis.insert(n-x);
q.push(n-x);
}
if (n - y >= 0 && vis.find(n-y) == vis.end()) {
if (n-y == z) return true;
vis.insert(n-y);
q.push(n-y);
}
}
return false;
}
};