365 水壶问题(广度优先搜索、数学推导-裴蜀定理)

44 篇文章 1 订阅
36 篇文章 1 订阅

1. 问题描述:

有两个容量分别为 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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/water-and-jug-problem

2. 思路分析:

① 之前有做过类似的题目,蓝桥杯的分酒问题:
有4个红酒瓶子,它们的容量分别是:9升, 7升, 4升, 2升
开始的状态是 [9,0,0,0],也就是说:第一个瓶子满着,其它的都空着。
允许把酒从一个瓶子倒入另一个瓶子,但只能把一个瓶子倒满或把一个瓶子倒空,不能有中间状态。这样的一次倒酒动作称为1次操作。假设瓶子的容量和初始状态不变,对于给定的目标状态,至少需要多少次操作才能实现?

这道题目与分酒问题也是类似的,比较经典的解决办法是使用bfs,能够使用bfs的题目特点:从当前初始状态到最后一个状态所经历的最少的步数,对于这道题目恰恰如此,所以我们可以使用bfs来解决(也可以使用dfs来解决,但是当数据量太大的话会超时所以比较好的处理办法是使用bfs),因为使用的是bfs,所以需要借助于队列,在python语言中,可以使用collections.deque()声明一个双端队列,然后加入初始状态(0, 0),在循环中弹出队首元素,需要声明一个set集合来记录之前已经存在的状态防止重复访问,将当前状态能够转换得到的并且在set集合中没有的状态加入到双端队列中,也就是模拟出这些状态并且加入到双端队列中,其中涉及到的几个状态如下:

① 倒满x ② 倒满y     ③ 清空x ④ 清空y           ⑤ 将x倒入到y, x为空  ⑥ 将x倒入到y,y满      ⑦ 将y倒入到x,y空 ⑧  将y倒入到x,x满

② 除了使用bfs解决的思路之外,力扣的官方提供了一个很优秀的数学思路解法,关键的一点是需要理解每一次水量的变化都是x或者是y的,所以问题最终就可以转换为是否存在两个int类型的整数使得:

ax + by = z方程有整数解,判断方程组是否有解的方法可以使用裴蜀定理:
ax + by = z 有解当且仅当 z 是 x, y 的最大公约数的倍数,这个解法真的很优秀非常值得学习

3. 代码如下:

import collections


class Solution:
    def canMeasureWater(self, x: int, y: int, z: int) -> bool:
        queue = collections.deque()
        queue.append((0, 0))
        rec = set()
        rec.add((0, 0))
        if x + y == z: return True
        while queue:
            poll = queue.popleft()
            pollx, polly = poll[0], poll[1]
            if pollx == z or polly == z or pollx + polly == z: return True
            # 尝试平行状态
            # x倒满
            if (x, polly) not in rec:
                queue.append((x, polly))
                rec.add((x, polly))
            # y倒满
            if (pollx, y) not in rec:
                queue.append((pollx, y))
                rec.add((pollx, y))
            # 将x倒空
            if pollx + polly <= y:
                t1 = 0
                t2 = pollx + polly
                if (t1, t2) not in rec:
                    queue.append((t1, t2))
                    rec.add((t1, t2))
            # 将y倒空
            if pollx + polly <= x:
                t1 = pollx + polly
                t2 = 0
                if (t1, t2) not in rec:
                    queue.append((t1, t2))
                    rec.add((t1, t2))
            # 将x清空
            if (0, polly) not in rec:
                queue.append((0, polly))
                rec.add((0, polly))
            # 将y清空
            if (pollx, 0) not in rec:
                queue.append((pollx, 0))
                rec.add((pollx, 0))
            # 将y倒满
            if polly < y <= pollx + polly:
                t1 = x - y + polly
                t2 = y
                if (t1, t2) not in rec:
                    queue.append((t1, t2))
                    rec.add((t1, t2))
            # 将x倒满
            if pollx < x <= pollx + polly:
                t1 = x
                t2 = y - x + pollx
                if (t1, t2) not in rec:
                    queue.append((t1, t2))
                    rec.add((t1, t2))
        return False

在编写代码的过程中可能有的时候需要跟踪变化的过程,对于广度优先搜索来说我们每一次加入相邻节点的时候都是可以在上一个状态的基础上添加到达当前状态的路径,对于这道题目来说可以使用字符串添加从初始状态到达目标状态的路径,所以我们在加入相邻节点的时候可以在弹出节点的路径上添加当前目标节点的字符串,这样每到达一个节点都可以知道到达当前节点的路径,这样到达最后一个状态的时候就可以得到一条完整的路径了,下面是具体的代码:

import collections


class Solution:
    def canMeasureWater(self, x: int, y: int, z: int) -> bool:
        queue = collections.deque()
        # 使用一个字符串来记录从初始状态到目标
        s = "" + str((0, 0)) + ","
        queue.append((0, 0, s))
        rec = set()
        rec.add((0, 0))
        if x + y == z: return True
        while queue:
            poll = queue.popleft()
            pollx, polly, pollz = poll[0], poll[1], poll[2]
            if pollx == z or polly == z or pollx + polly == z:
                print(pollz[0:-1])
                return True
            if (x, polly) not in rec:
                # 添加到队列中的元祖的第三个参数用来记录到达当前节点(x1, y1)的路径
                queue.append((x, polly, pollz + str((x, polly)) + ","))
                rec.add((x, polly))
            if (pollx, y) not in rec:
                queue.append((pollx, y, pollz + str((pollx, y)) + ","))
                rec.add((pollx, y))
            if pollx + polly <= y:
                t1 = 0
                t2 = pollx + polly
                if (t1, t2) not in rec:
                    queue.append((t1, t2, pollz + str((t1, t2)) + ","))
                    rec.add((t1, t2))
            if pollx + polly <= x:
                t1 = pollx + polly
                t2 = 0
                if (t1, t2) not in rec:
                    queue.append((t1, t2, pollz + str((t1, t2)) + ","))
                    rec.add((t1, t2))
            if (0, polly) not in rec:
                queue.append((0, polly, pollz + str((0, polly)) + ","))
                rec.add((0, polly))
            if (pollx, 0) not in rec:
                queue.append((pollx, 0, pollz + str((pollx, 0)) + ","))
                rec.add((pollx, 0))
            if polly < y <= pollx + polly:
                t1 = x - y + polly
                t2 = y
                if (t1, t2) not in rec:
                    queue.append((t1, t2, pollz + str((t1, t2)) + ","))
                    rec.add((t1, t2))
            if pollx < x <= pollx + polly:
                t1 = x
                t2 = y - x + pollx
                if (t1, t2) not in rec:
                    queue.append((t1, t2, pollz + str((t1, t2)) + ","))
                    rec.add((t1, t2))
        return False

数学推导:

import math


class Solution:
    # 更相减损术求解a,b的最大公约数
    def gcd(self, a: int, b: int) -> int:
        while a != b:
            if a > b:
                a = a - b
            else:
                b = b - a
        return a

    # ax + by = z
    def canMeasureWater(self, x: int, y: int, z: int) -> bool:
        if x + y < z:
            return False
        if x == 0 or y == 0:
            return z == 0 or x + y == z
        return z % self.gcd(x, y) == 0

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值