LeetCode专题——详解搜索算法中的搜索策略和剪枝

本文始发于个人公众号:TechFlow,原创不易,求个关注


今天是LeetCode专题第20篇文章,今天讨论的是数字组合问题。


描述

给定一个int类型的候选集,和一个int类型的target,要求返回所有的数字组合,使得组合内所有数字的和刚好等于target。

注意:

  1. 所有的元素都是正数
  2. 所有元素没有重复
  3. 答案不能有重复
  4. 每一个元素可以使用若干次

样例 1:

Input: candidates = [2,3,6,7], target = 7,
A solution set is:
[
  [7],
  [2,2,3]
]

样例 2:

Input: candidates = [2,3,5], target = 8,
A solution set is:
[
   [2,2,2,2],
  [2,3,3],
  [3,5]
]

题解

我们拿到这道题还是按照老规矩来思考暴力的解法,但是仔细一想会发现好像没有头绪,没有头绪的原因也很简单,因为题目当中的一个条件:一个元素可以随意使用若干次

我们根本不知道一个元素可以使用多少次,这让我们的暴力枚举有一种无从下手的感觉。如果去掉这个条件就方便多了,因为每个元素只剩下了两个状态,要么拿要么不拿,我们可以用一个二进制的数来表示。这就引出了一个常用的表示状态的方法——二进制表示法

二进制表示法

举个例子,假如当下我们有3个数字,这3个数字都有两个状态选或者不选,我们想要枚举这3个数字的所有状态,应该怎么办?

我们当然可以用递归来实现,在每层递归当中做决策当前元素选或者不选,分别递归。但是可以不用这么麻烦,我们可以用二进制简化这个过程。这个原理非常简单,我们都知道在计算机二进制当中每一个二进制位只有两个状态0或者1,那么我们就用1表示拿,0表示不拿,那么这三个数拿或者不拿的状态其实就对应一个二进制的数字了。3位二进制,对应的数字是0到7,也就是说我们只需要遍历0到7,就可以获得这3位所有拿和不拿的状态了。

比如说我们当下遍历到的数字是5,5的二进制表示是101,我们再把1和0对应拿和不拿两种状态。那么5就可以对应上第一和第三个拿,第二个不拿的状态了。我们可以用位运算很方便地进行计算。比如我们要判断第i位是否拿了,我们可以用(1 << i),<<的意思是左移,左移一位相当于乘2,左移n位就相当于乘上了2的n次方。1对应右边起第0位,也就是最低位的二进制位,我们对它做左移i的操作就相当于乘上了 2 i 2^i 2i,那么就得到了第i位了。我们拿到了之后,只需要将它和状态state做一个二进制中的与运算,就可以得到state中第i位究竟是0还是1了。

因为在二进制当中,and运算会将两个数的每一位做与运算,运算的结果也是一个二进制数。由于我们用来进行与运算的数是(1 << i),它只有第i位为1,所以其他位进行与运算的结果必然是0,那么它和state进行与运算之后,如果结果大于0,则说明state的第i位也是1,否则则是0。这样我们就获取了state当中第i位的状态。

由于位运算是指令集的运算,在没有指令集优化的一些语言当中,它的计算要比加减乘除更快。除了快以外它最大的好处是节省空间和计算方便,这两个优点其实是一体的,我们一个一个来说。

首先来说节省空间,有了二进制表示之后,我们可以用一个32位的int来代表32个物体的0和1的所有状态。如果我们用数组来存储的话,显然我们需要一个长度为32的数组,需要的空间要大得多。这一点在单个状态下并不明显,一旦数据量很大会非常显著。尤其是在密集的IO当中,数据越轻量则传输效率越高

第二个优点是计算方便,计算方便的原因也很简单,假如我们要遍历所有的状态,如果用数组或者其他数据结构的话免不了使用递归来遍历,这样会非常麻烦。而使用二进制之后就方便了,由于我们用二进制表示了所有元素0和1的状态,我们只需要在一个整数范围做循环就可以了。就像刚才例子当中,我们要枚举3个元素的状态,我们只需要从0遍历到7即可。如果在多点元素也没问题,如果是N个元素,我们只需要从0遍历到(1 << N) - 1。

但是还有一个问题没解决,你可能会说如果我们用int来表示状态的话,最多只能表示32个物品的状态,如果更多怎么办?一个方法是使用int64,即范围更大的int,如果范围更大的int还是解决不了问题也没关系,还有一些基于同样原理实现的第三方包可以支持。但是老实说我们基本上不会碰到超过64个物品让我们枚举所有状态的情况,因为这个数字已经非常大了,几乎可以说是天荒地老也算不完。

回到问题

我相信关于二进制表示法的使用和原理,大家应该都了解了,但是本题当中元素是可以多次出现的,二进制表示法看起来并不顶用,我们怎么解决这个问题呢?难道这么大的篇幅就白写了?

当然不会白写,针对这种情况也有办法。其实很简单,因为题目当中规定所有的元素都是正数,那么对于每一个元素而言,我们最多取的数量是有限的。举个例子,比如样例当中[2, 3, 6, 7] target是7,对于元素2而言,target是7,即使可以多次使用,也最多能用上3个2。那么我们可以拓充候选集,将1个2拓充成3个,同理,我们可以继续拓充3,最后候选集变成这样:[

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值