面试题2:水壶问题,使用BFS广度优先算法解决

有两个容量分别为 x升 和 y升 的水壶以及无限多的水。请判断能否通过使用这两个水壶,从而可以得到恰好 z升 的水?

如果可以,最后请用以上水壶中的一或两个来盛放取得的 z升 水。

你允许:

装满任意一个水壶
清空任意一个水壶
从一个水壶向另外一个水壶倒水,直到装满或者倒空

示例1:

输入: x = 3, y = 5, z = 4
输出: True

示例2:

输入: x = 2, y = 6, z = 5
输出: False

为简单起见,以 x = 1, y = 3, z = 2 为例,来看下如何构思:
当 x = 1, y = 3 时,一共有八种状态
(0,0), (1,0)
(0,1), (1,1)
(0,2), (1,2)
(0,3), (1,3)

既然时图的广度优先搜索,我们先来看下如何构图:
在这里插入图片描述关于图片的说明:

·节点表示两个水壶的状态
·边表示操作方法:分别为倒满A/B,倒空A/B,A倒入B,B倒入A 六种方法。
·这是一个有向图,因为有些状态并不能护互为转移。比如 (1,1) 和 (1,0)。

简单介绍下BFS: 该过程总是从一个或若干个起始点开始,沿着边像水波一样逐层向外遍历,直到所有的点已被访问或者到达目标状态在这里插入图片描述编程上可以使用一个队列维护需要扩散的点,以及一个set或者数组维护已经被访问过的点来实现:

1.初始时,队列和set均为空。将起始点放入队列及set。
2.如果队列为空则 bfs 结束。
3.弹出队首元素并访问其周围元素,设为 p。
4.如果p为目标状态则 bfs 结束。
5.访问 p 周围的元素,将不在set中的元素放入队列及set。跳转第 2 步。

Java没有原生的Pair,所以我用Map.Entry替代了。leetcode上需要额外加上import语句。

class Solution {
    int capacityX;
    int capacityY;
    public boolean canMeasureWater(int x, int y, int z) {
        capacityX = x;
        capacityY = y;
        Set<Long> set = new HashSet<>();
        Queue<Long> dfs = new LinkedList<>();   //遍历链表
        dfs.add(0L);
        while(!dfs.isEmpty()){
            Long l = dfs.poll();
            if(set.contains(l)){
                continue;
            }
            x = (int)(l>>32);
            y = (int)l.intValue();
            if(x==z||y==z||x+y==z){
                return true;
            }  
            set.add(l);
            if(x!=0&&y!=0){
                dfs.add((long)y);
                dfs.add(((long)x)<<32);
            }
            if(x!=capacityX){
                dfs.add(combineToLong(capacityX,y));
                if(y!=0){
                     dfs.add(combineToLong(Math.min(x+y,capacityX),Math.max(y-capacityX+x,0)));
                }
            }
            if(y!=capacityY){
                dfs.add(combineToLong(x,capacityY));
                if(x!=0){
                    dfs.add(combineToLong(Math.max(x-capacityY+y,0),Math.min(x+y,capacityY)));
                }
            }
        }
        return false;
    } 
    public long combineToLong(int a,int b){
        long res = (long)a;
        res = res<<32;
        return res|b;
    } 
}

数学算法

这道题目要求的返回值是真或者假,并非求解倒水操作的集合。这时若是有数学方法能够算出是否有解,至少可以避免遍历所费的空间。

我们先来观察题目给出的例子,若是我们思考倒水的过程,可以像下方所示图来操作:
在这里插入图片描述通过观察更多的方案,可以看出这些操作序列有以下几个特点:

每次注水,不会将两个水壶都打满,这样的情况在初始状态中即有所判断,即是否满足x+y==z
两个壶相互倒水的过程中,结果必然有一个空壶或者一个满壶,因为若是存在两个非空或非满的壶,则说明倒水的过程中没有刻度把控(只有全倒出,或者倒满才知道每个壶中水量的确定刻度)
每次装水的过程中,不会向非空壶内装水。若是有此操作,则相当于返回红框内的初始状态,或者两个壶都满
根据以上的观察,可以得出结论:两个壶内总水量每次的变化要么是x,要么是y,最终水量为ax+by(a、b为装水次数,若为负值,则说明该壶的水量都是从另一个壶中得来)

再回到题干,既然最终水量为ax+by,则只需判断是否存在a、b,满足:
ax + by = z
根据祖定理可知,判断该线性方程是否有解需要需要判断z是否为x,y最大公约数的倍数。此时为题转化为了求解最大公约数,而该问题可以使用gcd算法(辗转相除法)。最终代码如下

  class Solution {
    public boolean canMeasureWater(int x, int y, int z) {
        if(x==z||y==z||x+y<=z){
            if(x+y<z){
                return false;
            }
            return true;
        }
        return x>y?(z%gcd(x,y))==0:(z%gcd(y,x))==0;
    }
    public int gcd(int x,int y){
        return y==0?x:gcd(y,x%y);
    } 
}
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值