需求

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

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,可以看出排列组合的递归解法还是有一定的难度的。

样式体现

尺寸:5.0寸、4.5寸
型号:土豪金、红、黑
内存:128G、64G

后台可选的规格:

[["4.5寸", "红", "64G"],["5.0寸", "土豪金", "128G"],    // ["5.0寸", "黑", "128G"]
]

现在这几种类型一共有2 * 3 * 2 = 12种排列组合,然而只有3种组合是正确的(其中还要排除库存为0的情况)一开始先保存好每一个按钮和每一列的位置进入一个List<List<TagEnable>> 的数组中,TagEnable记录着每一个按钮的状态(0代表者正常,1代表选中,2代表不可选(库存为0||无规格));然后当用户点击的时候,用Map<Integer,String> 记录选中的按钮和文字;并把每一个按钮先设置为不可点击,之后根据文字去对按钮进行设置状态。
这种算法是比较直接的一种实现,但是很繁琐,循环嵌套循环,可以简单分析下算法复杂度,如果sku属性组合元素的总和数用m来表示,可选的数据的长度是n的话,那么算法的步骤大概是m*n,这看起来好像不怎么复杂;不过,每次判断一个sku组合是否和result中的 组合匹配,却不是一个简单的过程,实际上,这可以看做是一个字符串匹配的一个算法了, 最简单的还是使用正则匹配,m * n次正则匹配,这样就不怎么快了吧。正则表达式很不稳定,万一sku组合中有一些特殊字符,就可能导致一个正则匹配没能匹配到我们想要的表达式。
而且,当用户全部选中的时候,根据这种算法,只会出现一种情况,就是未选中的全部都变成不可选(即变成灰色),这大大影响了用户的体验。如下图:

如果第一条数据[“5.0寸”, “黑”, “128G”]可选,
那么以下的组合肯定存在:

5.0寸
黑
128G
5.0寸、黑
5.0寸、128G
黑、128G
5.0寸、黑、128G

所以,我们可以利用sku算法取出所有的组合,再根据这些组合的集合,记为U,去判断用户点击按钮时,去设置其他按钮的状态;

例如:当用户进行如下的选择:5.0寸、128G
那么如何判断 4.5寸这个按钮的状态呢?只需判断4.5寸、128G是否可选(集合U是否存在(4.5寸-128G)这个组合并且库存不为0),以此类推:

4.5寸:   U是否包含于 4.5寸-128G   false
土豪金:  U是否包含于 5.0寸-土豪金-128G  true
红:     U是否包含于 5.0寸-红-128G  false
黑:     U是否包含于 5.0寸-黑-128G   true
64G:    U是否包含于 5.0寸-64G    false

总结

排列组合问题并不是空中楼阁,在实际工作中也会经常遇到这种场景,掌握了递归回溯的标准模板当然不是为了让你死记硬背套公式,而是真正的理解它。遇到需要递归解决的问题。
画出递归树状图,找出递归公式。
对于不可能达成条件的分支递归,进行合理的「剪枝」。
希望阅读完本篇文章的你,能对递归和排列组合问题有进一步的理解和收获。

转载自公众号「前端从进阶到入院」

【转载】前端电商 sku 的全排列算法相关推荐

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

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

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

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

  3. 想进某电商公司?建议学会电商 sku 的全排列算法!

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

  4. JS每日一题:前端电商 sku 全排列的递归回溯算法

    本文内容:https://github.com/sl1673495/blogs/issues/50 需求 需求描述起来很简单,有这样三个数组: let names = ["iPhone X& ...

  5. Vue 的电商 SKU 表单配置组件

    下载地址Vue 的电商 SKU 表单配置组件,多条件筛选计算价格和库存. dd:

  6. spu是什么计算机软件,电商sku和spu的区别

    大家好,我是时间财富网智能客服时间君,上述问题将由我为大家进行解答. 电商sku和spu的区别是: 1.SPU:标准化产品单元.SPU=Standard Product Unit(标准化产品单元).S ...

  7. 基于Vue实现电商SKU组合算法问题

    这个相对来说比较麻烦,还涉及到了下面"属性图片"的循环,但关键点还是在SKU组合的代码上面 以下是基于element-ui和vue的精简版demo代码: html <div& ...

  8. 电商sku组合查询状态细究与实现

    作者 | 朱徽 最近做到一个需求,需要做一个类似于京东或者淘宝等电商的商品详情页,其中有一个功能就是商品SKU的选择查询问题 如上图,网络类型.机身颜色.套餐类型.存储容量这些每一个都是一个 SKU属 ...

  9. 小兔鲜儿Vue3.0前端电商项目实战

    小兔鲜儿Vue3.0实现了电商平台主线业务功能,电商首页.一级分类.二级分类.商品详情.购物车.结算.支付.个人中心.订单管理.订单详情. 项目采用前后端分离模式,前台使用了VUE3.0技术栈构建,自 ...

最新文章

  1. usaco Prime Palindromes
  2. 机器人如何懂得人类感情
  3. 证明Ax=0的最小二乘解是ATA的最小特征值对应的特征向量(||x||=1)
  4. ElasticSearch Client详解
  5. C++/CLI思辨录之Object的对象布局
  6. java 日志切面_自定义注解+面向切面整合的日志记录模块(一)
  7. java项目收获总结_java开发项目收获心得
  8. 【工具】adb下载安装及基本操作
  9. Desktop Aquarium Wallpaper‪s for mac(高清水族馆屏保)
  10. android 解码 gif 时间,Android 平台实现Gif 图像解码并播放代码及组件
  11. 大数据时代的 10 个重大变化
  12. 微信小号来了!同一个手机号可注册两个微信号
  13. html 5 flash 播放器开发
  14. Life's A Struggle
  15. 自控力之意志力的本能:人生来就能抵制奶酪蛋糕的诱惑
  16. wlh- beagle bone 通过uboot tftp 加载zImage 设备树 及 nfs 挂载根文件系统
  17. Java中的@Test注解
  18. yolov5 继续训练
  19. 萌新向Python数据分析及数据挖掘 第二章 pandas 第一节 pandas使用基础QA 1-15
  20. 【微信小程序】1、SpringBoot整合WxJava开启消息推送

热门文章

  1. 计算机组成原理之字和字节
  2. 5只猫5天吃5只老鼠,200分钟捉200只老鼠,需要多少只猫
  3. 分享Atom必备插件
  4. 【Android-kotlin】kt语法中when 的使用方式类似switch
  5. 计算机网络 第一章、概述
  6. IT6722A 宽范围高精度稳压电源技术参数
  7. Jython、JRuby 和 Groovy
  8. 远瑞服务器无影响,泰拉瑞亚加mod之后如何搭建服务器联机
  9. 命令模式 :Command(转自阿良.NET)
  10. HMS生态的华为主题风格太多了