前言

前段时间在掘金看到一个热帖 《今天又懒得加班了,能写出这两个算法吗?带你去电商公司写商品中心》,里面提到了一个比较有意思故事,大意就是一个看似比较简单的电商 sku 的全排列组合算法,但是却有好多人没能顺利写出来。有一个毕业生小伙子在面试的时候给出了思路,但是进去以后还是没写出来,羞愧跑路~

其实排列组合是一个很经典的算法,也是对递归回溯法的一个实践运用,本篇文章就以带你学习一个标准「排列组合求解模板」,耐心看完,你会有更多收获。

需求

需求描述起来很简单,有这样三个数组:

let names = ["iPhone X", "iPhone XS"]let colors = ["黑色", "白色"]let storages = ["64g", "256g"]

需要把他们的所有组合穷举出来,最终得到这样一个数组:

;[["iPhone X", "黑色", "64g"],["iPhone X", "黑色", "256g"],["iPhone X", "白色", "64g"],["iPhone X", "白色", "256g"],["iPhone XS", "黑色", "64g"],["iPhone XS", "黑色", "256g"],["iPhone XS", "白色", "64g"],["iPhone XS", "白色", "256g"],
]

由于这些属性数组是不定项的,所以不能简单的用三重的暴力循环来求解了。

思路

如果我们选用递归回溯法来解决这个问题,那么最重要的问题就是设计我们的递归函数。

思路分解

以上文所举的例子来说,比如我们目前的属性数组就是:namescolorsstorages,首先我们会处理 names 数组,很显然对于每个属性数组,都需要去遍历它,然后一个一个选择后再去和 下一个数组的每一项进行组合。

我们设计的递归函数接受两个参数:

  • index 对应当前正在处理的下标,是 names 还是 colors 或是 storage

  • prev 上一次递归已经拼接成的结果,比如 ['iPhone X', '黑色']

进入递归函数:

  1. 处理属性数组的下标0:假设我们在第一次循环中选择了 iPhone XS,那此时我们有一个未完成的结果状态,假设我们叫它 prev,此时 prev = ['iPhone XS']

  2. 处理属性数组的下标1:那么就处理到 colors 数组的了,并且我们拥有 prev,在遍历 colors 的时候继续递归的去把 prev 拼接成 prev.concat(color),也就是 ['iPhone XS', '黑色'] 这样继续把这个 prev 交给下一次递归。

  3. 处理属性数组的下标2:那么就处理到 storages 数组的了,并且我们拥有了 name + colorprev,在遍历 storages 的时候继续递归的去把 prev 拼接成 prev.concat(storage),也就是 ['iPhone XS', '黑色', '64g'],并且此时我们发现处理的属性数组下标已经到达了末尾,那么就放入全局的结果变量 res 中,作为一个结果。

编码实现

let names = ["iPhone X", "iPhone XS"]let colors = ["黑色", "白色"]let storages = ["64g", "256g"]let combine = function (...chunks) {let res = []let helper = function (chunkIndex, prev) {let chunk = chunks[chunkIndex]let isLast = chunkIndex === chunks.length - 1for (let val of chunk) {let cur = prev.concat(val)if (isLast) {// 如果已经处理到数组的最后一项了 则把拼接的结果放入返回值中res.push(cur)} else {helper(chunkIndex + 1, cur)}}}// 从属性数组下标为 0 开始处理// 并且此时的 prev 是个空数组helper(0, [])return res
}console.log(combine(names, colors, storages))

递归树图

画出以 iPhone X 这一项为起点的递归树图,当然这个问题是一个多个根节点的树,请自行脑补 iPhone XS 为起点的树,子结构是一模一样的。

万能模板

为什么说这种接法是排列组合的「万能模板呢」?来看一下 LeetCode 上的 77. 组合 问题,这是一道难度为 medium 的问题,其实算是比较有难度的问题了:

问题

给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。

示例:

输入: n = 4, k = 2
输出:
[[2,4],[3,4],[2,3],[1,2],[1,3],[1,4],
]

解答

let combine = function (n, k) {let ret = []let helper = (start, prev) => {let len = prev.lengthif (len === k) {ret.push(prev)return}for (let i = start; i <= n; i++) {helper(i + 1, prev.concat(i))}}helper(1, [])return ret
}

可以看出这题和我们求解电商排列组合的代码竟然如此相似。只需要设计一个接受 start排列起始位置、prev上一次拼接结果为参数的递归 helper函数,

然后对于每一个起点下标 start,先拼接上 start位置对应的值,再不断的再以其他剩余的下标作为起点去做下一次拼接。当 prev 这个中间状态的拼接数组到达题目的要求长度 k后,就放入结果数组中。

剪枝

在这个解法中,有一些递归分支是明显不可能获取到结果的,我们每次递归都会循环到 不停的尝试 <= n的所有项,尝试作为start,假设我们要求的数组长度 k = 3,最大值 n = 4

而我们以 prev = [1],再去以 n = 4start 作为递归的起点,那么显然是不可能得到结果的,因为 n = 4 的话就只剩下 4这一项可以拼接了,最多也就拼接成 [1, 4],不可能满足 k = 3 的条件。

所以在进入递归之前,就果断的把这些“废枝”给减掉。

let combine = function (n, k) {let ret = []let helper = (start, prev) => {let len = prev.lengthif (len === k) {ret.push(prev)return}// 还有 rest 个位置待填补let rest = k - prev.lengthfor (let i = start; i <= n; i++) {if (n - i + 1 < rest) {continue}helper(i + 1, prev.concat(i))}}helper(1, [])return ret
}

相似题型

当然,力扣中可以套用这个模板的相似题型还有很多,而且大多数难度都是 medium的,比如快手的面试题子集 II-90,可以看出排列组合的递归解法还是有一定的难度的。

我在维护的 LeetCode 题解仓库 中已经按标签筛选好 「递归与回溯」类型的几道题目和解答了,感兴趣的小伙伴也可以一起攻破它们。

总结

排列组合问题并不是空中楼阁,在实际工作中也会经常遇到这种场景,掌握了递归回溯的标准模板当然不是为了让你死记硬背套公式,而是真正的理解它。遇到需要递归解决的问题。

  1. 画出递归树状图,找出递归公式。

  2. 对于不可能达成条件的分支递归,进行合理的「剪枝」。

希望阅读完本篇文章的你,能对递归和排列组合问题有进一步的理解和收获。

最后

  1. 我是黑叔,关注我,跑得更快噢!

亲,点这涨工资 

想进某电商公司?建议学会电商 sku 的全排列算法!相关推荐

  1. 前端电商 sku 的全排列算法

    前端电商 sku 的全排列算法 什么是sku 聊聊常见的需求 解决思路 思路分解 上代码 什么是sku 针对电商而言: 1.SKU是指一款商品,每款都有出现一个SKU,便于电商品牌识别商品. 2.一款 ...

  2. 前端电商 SKU 的全排列算法很难吗?学会这个套路,彻底掌握排列组合。

    前言 前段时间在掘金看到一个热帖 <今天又懒得加班了,能写出这两个算法吗?带你去电商公司写商品中心>,里面提到了一个比较有意思故事,大意就是一个看似比较简单的电商 sku 的全排列组合算法 ...

  3. 【转载】前端电商 sku 的全排列算法

    需求 需求描述起来很简单,有这样三个数组: let names = ["iPhone X", "iPhone XS"] let colors = [" ...

  4. 本想制衡经销商价格,不料成为黄牛货源地!茅台电商公司被迫解散!

    说起白酒中的贵族,一定要谈到茅台,茅台是世界三大名酒之一,许久以来,茅台成为了人们口中的白酒奢侈品,有许多人买茅台并不是为了喝,而是为了送礼或者收藏,甚至是屯着等涨价再卖出去,变成了一个理财产品. 因 ...

  5. 程序员想进大公司?学会这门编程知识,决定你能进什么样的企业!

    对于程序员来讲,很多技术真正掌握之后,都能影响甚至说改变一个人的命运,比如:python.AI.DL.算法等等,但是如果只让你选择其中的一项基础知识,你会选择哪个呢? 如果是我, 我会选--数据结构与 ...

  6. 亚马逊——不一样的电商公司

    其一: 电商公司就是电子商务公司.电子商务通俗的说就是利用电子工具进行各种商务活动,如网上购物.在线电子支付等.可以说电子商务是传统商业活动的电子化和网络化.离我们最近的就是网购了,通常我们会在淘宝. ...

  7. 奉劝那些刚参加工作的学弟学妹们:要想进大厂,这些并发编程核心技能是你必须要掌握的!!(建议收藏)

    大家好,我是冰河~~ 最近有很多学弟学妹问我:冰河,并发编程要学哪些内容呀?我看你CSDN博客的的[精通高并发系列]更新了很多高并发编程的技术文章,你是怎么学习的呢?后面你还会更新吗?啥时候更新完呀? ...

  8. OKR分解/360对齐的方法详解-电商公司

    这篇文章从「公司级OKR」[部门级OKR][个人级OKR]出发,涉及到销售部门.研发部门.CSM部门以及相关的职能部门参与其中,以理论方法 + 案例的形式,介绍OKR的分解和360对齐,阅读时间约5m ...

  9. 初创跨境电商公司,让Callnovo的海外客服成为您出海的桨

    蓝牙耳机跨境出海大促当天销售额有8万美金! 下单率竟然超89%! 好评率100%,还有买家洋洋洒洒写长文好评信,他们是怎么做到的? 让我们来看一看这篇好评信,到底写了些什么. 从顾客的反馈中,可以看到 ...

最新文章

  1. 爬虫之 lxml模块的安装与使用示例
  2. 信号建模与参数估计作业重新计算
  3. 2020年下信息系统项目管理师合格分数线通知
  4. CRMEB后台前端文档说明
  5. C语言学习之输入任意年份,判断是否为闰年
  6. 使用ImpromptuInterface反射方便的创建自定义DfaGraphWriter
  7. 1043 幸运号码 数位DP
  8. 智力问答 46倒计时
  9. 论文赏析[ACL18]基于RNN和动态规划的线性时间成分句法分析
  10. Windows 系统软件有哪些「必备」软件?
  11. Tuple和 ValueTuple
  12. Android 视频直播 ( 从快播到直播,从高清到无码 )十年视频开发项目
  13. 千兆路由器和百兆路由器
  14. 一根竹子,4天的生长过程
  15. 从零开始学GIMP:一.从基本图形开始
  16. 诛仙账号合并服务器,《诛仙3》10/17 服务器数据互通公告
  17. 关于六边形地图的生成算法
  18. Python(17)python使用tkinter实现一个简单的CSGO幸运转盘抽奖游戏
  19. IntelliJ idea (最新版)激活方法
  20. select2 新增全选功能

热门文章

  1. 关于使用preparestatement来实现模糊查询
  2. 关于大疆御2行业进阶版(M2EA)热红外照片温度信息提取问题
  3. 马布里和穆里尼奥的回归
  4. 从零开始制作人脸表情的数据集
  5. 计算机什么应用函数计算,计算机应用基础Excel函数计算
  6. EPC模式和施工总承包的区别
  7. 【洛谷T7243】【CJOJ2225】【BYVoid S3】珠光宝气阁(潜入辛迪加)
  8. tarjan算法 割点割边强联通 算法讲解模板 自用整理
  9. PC键盘在Mac下Command/Option键切换
  10. 《仙剑奇侠传3》流程攻略4