1. 问题描述:
有两个容量分别为 x升和 y升的水壶以及无限多的水。请判断能否通过使用这两个水壶,从而可以得到恰好z升的水?
如果可以,最后请用以上水壶中的一或两个来盛放取得的z升水。
你允许:
- 装满任意一个水壶
- 清空任意一个水壶
- 从一个水壶向另外一个水壶倒水,直到装满或者倒空
示例 1:
输入: x = 3, y = 5, z = 4
输出: True
示例 2:
输入: x = 2, y = 6, z = 5
输出: False
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/water-and-jug-problem
2. 思路分析:
① 分析题目可以知道最容易想到的是递归(也可以使用宽搜,思路都是一样的),在递归的时候枚举所有可能操作下的状态,由题可知,对应的操作下总共有八个平行的状态,分别是:① 将A倒满 ② 将B倒满 ③ 将A清空 ④ 将B清空 ⑤ 将A倒B并且A有剩余 ⑥ 将A倒B并且A倒空 ⑦ 将B倒A且B有剩余 ⑧ 将B倒A且B倒空,在递归的时候尝试这八种平行状态即可,为了避免在递归的时候递归之前已经求解过的状态我们需要标记一下已经被访问过的状态,因为使用的是python语言所以可以使用python中的字典标记,使用元组表示两个容器的状态,键表示元组对应的状态,值为int类型表示当前的状态已经标记。只有当前操作下新的状态在之前没有递归过的时候我们才往下递归,并且我们可以写一个有返回值的递归这样当我们往下递归求解的过程发现可以通过x与y得到z那么就可以直接返回True,也即找到一个满足条件的状态就返回True,这样就可以避免重复性的递归。
② 除了①中递归求解所有可能方案的思路之外,还有一种比较好的是使用数学的思路求解。分析题目可以知道两个水壶不可能同时是既不空也不满的状态,所以水壶的水与外界交换的总共有四种情况,分别是+ a, -a, +b,-b(a, b为水壶的体积),只有这四种状态才对应着最优解,所以问题就转化为判断方程ax + by = c是否有解,判断方程是否有解我们可以使用裴蜀定理:方程有解的充要条件是a, b的最大公约数是c的倍数,也即判断c是否能够整除a, b的最大公约数即可。并且方程有解的时候我们需要判断是否存在一种合法的操作序列使得方程有解。我们知道0 <= c <= a + b的,当c = 0时肯定满足条件所以c > 0 且c <= a + b所以只需要判断0 < c <= a + b的情况即可,c > 0说明a, b对应的操作次数中至少有一个是大于0的,假设x > 0,当y > 0时说明将a, b倒满即可,当x > 0且y <= 0时说明不断将a倒满,将b倒掉,使得最终的次数满足ax + by = c成立。所以当方程有解的时候我们是可以构造成一种合法的操作序列的,所以只要是方程有解我们就可以知道一种合法的操作序列,也即直接判断方程是否有解即可。
3. 代码如下:
递归:
import collections
class Solution:
# 使用dfs搜索
def dfs(self, dic: collections.defaultdict(int), x1, y1, x, y, z):
# 通过x与y最终可以得到z直接返回True
if x1 == z or y1 == z or x1 + y1 == z: return True
# 尝试可能的状态
# 将A倒满
if (x, y1) not in dic:
dic[(x, y1)] = 1
# 我们只要是找到一个满足条件的就直接返回
if self.dfs(dic, x, y1, x, y, z): return True
# 将B倒满
if (x1, y) not in dic:
dic[(x1, y)] = 1
if self.dfs(dic, x1, y, x, y, z): return True
# 将A清空
if (0, y1) not in dic:
dic[(0, y1)] = 1
if self.dfs(dic, 0, y1, x, y, z):return True
# 将B清空
if (x1, 0) not in dic:
dic[(x1, 0)] = 1
if self.dfs(dic, x1, 0, x, y, z): return True
# 将A倒B并且A有剩余
if x1 >= y - y1 and (x1 - y + y1, y) not in dic:
dic[(x1 - y + y1, y)] = 1
self.dfs(dic, x1 - y + y1, y, x, y, z)
# 将A倒B并且A倒空
if x1 < y - y1 and (0, x1 + y1) not in dic:
dic[(0, y1 + x1)] = 1
self.dfs(dic, 0, x1 + y1, x, y, z)
# 将B倒A且B有剩余
if y1 >= x - x1 and (x, y1 - x + x1) not in dic:
dic[(x, y1 - x + x1)] = 1
if self.dfs(dic, x, y1 - x + x1, x, y, z): return True
# 将B倒A且B倒空
if y1 < x - x1 and (x1 + y1, 0) not in dic:
dic[(y1 + x1, 0)] = 1
if self.dfs(dic, x1 + y1, 0, x, y, z): return True
return False
def canMeasureWater(self, x: int, y: int, z: int) -> bool:
# 使用字典来标记状态是否被访问过
dic = collections.defaultdict(int)
return self.dfs(dic, 0, 0, x, y, z)
可以使用一个字符串变量来记录中间满足条件的结果:
import collections
class Solution:
def dfs(self, dic: collections.defaultdict(int), x1, y1, x, y, z):
# 通过x与y最终可以得到z, 注意两个瓶子的水加起来等于z也是满足条件的
if x1 == z or y1 == z or x1 + y1 == z: return True
if (x, y1) not in dic:
dic[(x, y1)] = 1
if self.dfs(dic, x, y1, x, y, z): return True
if (x1, y) not in dic:
dic[(x1, y)] = 1
if self.dfs(dic, x1, y, x, y, z): return True
if (0, y1) not in dic:
dic[(0, y1)] = 1
if self.dfs(dic, 0, y1, x, y, z):return True
if (x1, 0) not in dic:
dic[(x1, 0)] = 1
if self.dfs(dic, x1, 0, x, y, z): return True
if x1 >= y - y1 and (x1 - y + y1, y) not in dic:
dic[(x1 - y + y1, y)] = 1
self.dfs(dic, x1 - y + y1, y, x, y, z)
if x1 < y - y1 and (0, x1 + y1) not in dic:
dic[(0, y1 + x1)] = 1
self.dfs(dic, 0, x1 + y1, x, y, z)
if y1 >= x - x1 and (x, y1 - x + x1) not in dic:
dic[(x, y1 - x + x1)] = 1
if self.dfs(dic, x, y1 - x + x1, x, y, z): return True
if y1 < x - x1 and (x1 + y1, 0) not in dic:
dic[(y1 + x1, 0)] = 1
if self.dfs(dic, x1 + y1, 0, x, y, z): return True
return False
def canMeasureWater(self, x: int, y: int, z: int) -> bool:
dic = collections.defaultdict(int)
return self.dfs(dic, 0, 0, x, y, z)
数学:
class Solution:
# 求解最大公约数模板
def gcd(self, a: int, b: int):
return a if b == 0 else self.gcd(b, a % b)
def canMeasureWater(self, a: int, b: int, c: int) -> bool:
if c > a + b: return False
return c >= 0 and c % self.gcd(a, b) == 0