[dfs] [数学] 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
最开始摸拟了一些情况,找两个壶的最大值,让小的给大的灌水,最后会有溢出的水,比如3给5灌水,最后会溢出1,也就是小的能留下1的水,把大的倒空,1进大的在到一杯小的就得到4,然后巴拉巴拉记录从两个状态能导出的一些值,用背包那种思想f[i]表示水为i能不能达到,搞了一个小时能过一半,发现大的也能给小的灌水,是真的傻
dfs
最后总结一下发现只有六种操作可以选
填满x 填满y
清空x 清空y
把x剩余的倒入y 把y剩余的倒入x
所以暴力dfs,因为可能会有重复,所以用一个unordered_set保存出现过的情况,因为递归的过程可能过多会爆栈,就用一个栈来存储摸拟,当然时间复杂度和空间复杂度双双爆炸
官方题解:
using PII = pair<int, int>;
class Solution {
public:
bool canMeasureWater(int x, int y, int z) {
stack<PII> stk;
//11新特性,在插入时直接构造,效率更高
stk.emplace(0,0);
//unordered_set基于hash实现,
//只支持的build-in类型和string类型,别的类型要自定义hash函数,定义一个lambda表达式
auto hash_function = [](const PII& rhs){return hash<int>()(rhs.first) ^ hash<int>()(rhs.second);};
//decltype推测类型,和auto有一些区别,有一点就是不会真正去执行
unordered_set<PII, decltype(hash_function)> seen(0, hash_function);
while(!stk.empty()){
if(seen.count(stk.top())){
stk.pop();
continue;
}
seen.emplace(stk.top());
//17特性,大佬告诉我叫结构化绑定声明,和解包差不多
auto [remian_x, remian_y] = stk.top();
stk.pop();
if(remian_x == z || remian_y == z || remian_x + remian_y == z) return true;
//填满x
stk.emplace(x, remian_y);
//填满y
stk.emplace(remian_x, y);
//清空x
stk.emplace(0, remian_y);
//清空y
stk.emplace(remian_x, y);
//把x剩余的倒入y
stk.emplace(remian_x - min(remian_x, y - remian_y), remian_y + min(remian_x, y - remian_y));
//把y剩余的倒入x
stk.emplace(remian_x + min(remian_y, x - remian_x), remian_y - min(remian_y, x - remian_x));
}
return false;
}
};
数学解法
这就比较厉害了,就不怎么懂了
也是一个整体的思想,从总水量来看,只有四种情况:
增加x,增加y,减少x,减少y
剩下的情况比如往不满的壶里加水,这是没有意义的,因为只能加满
把一个不满的倒空,这也是没有意义的
因为只有x或者y的变化,这样问题就转化成能不能找到两个整数a和b使
ax + by = z
满足x+y>=z,并且ab存在
对于a>=0,b>=0,显然成立,非0时应该就是a=b=1应该
对于a<0
也就是需要倒掉一些a体积的水来达到,以样例3,5,4为例
3 * (-2) + 5 * 2 = 4 减两次3,加两次5
5装满倒进3,5剩2,倒掉3,5的2进3,5装满,倒进3,5剩4,倒掉3
总结下来就是
1.往y壶倒水
2.把y壶倒进x种
3.如果y不空,那么x一定是满的,把x的水倒掉然后把y的水倒进x
重复知道x倒空a次,y倒空b次
b小于0同理
而贝祖定理告诉我们,ax+by=z 有解当且仅当 z 是 x, y的最大公约数的倍数。因此我们只需要找到 x, y的最大公约数并判断 z是否是它的倍数即可。
这我就不懂了记住就得了
class Solution {
public:
int gcd(int a, int b){
return b == 0 ? a : gcd(b, a % b);
}
bool canMeasureWater(int x, int y, int z) {
if(x + y < z) return false;
//对付101,100这样的情况
if(!x || !y) return z == 0 || x + y == z;
return z % gcd(x, y) == 0;c
}
};