数组组合排序

一 目录

不折腾的前端,和咸鱼有什么区别

目录
一 目录
二 前言
三 一维数组排列组合
四 多维数组排列组合
五 参考文献

二 前言

本文会介绍:

  1. 一维数组排列组合

  2. 多维数组排列组合

三 一维数组排列组合

已知存在一个数字类型数组,该数组中的每个数字都不相同,例如:

  • [1, 2, 3]

  • [2, 4, 6]

求该数组中各个数字可能具有的组合,例如:

  • [1, 2, 3][ [1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1] ]

  • [2, 4, 6][ [2, 4, 6], [2, 6, 4], [4, 2, 6], [4, 6, 2], [6, 2, 4], [6, 4, 2] ]

那么我们该怎么求解呢?看代码:

const arrange = (arr) => {
  // 1. 设置结果集
  const result = [];
  
  // 2. 递归
  const ergodic = (res) => {
    // 2.1 循环数组 arr
    for (let i = 0; i < arr.length; i++) {
      // 2.1.1 判断已有的 res 数组中不存在当前元素
      // 并且 res 数组差一位就凑齐 arr 的长度了
      if (!res.includes(arr[i]) && res.length === arr.length - 1) {
        result.push([...res, arr[i]]);
        continue;
      }
      // 2.1.2 如果已有的 res 数组不包含当前元素
      // 则添加进来
      if (!res.includes(arr[i])) {
        ergodic([...res, arr[i]]);
      }
    }
  }
  ergodic([], 0);

  // 3. 返回最终结果
  return result;
};

console.log(arrange([2, 4, 6]));
// [ [2, 4, 6], [2, 6, 4], [4, 2, 6], [4, 6, 2], [6, 2, 4], [6, 4, 2] ]

我们通过递归的形式,就可以获取到一维数组的排列组合,这对小伙伴们来说,应该不是问题。

四 多维数组排列组合

在上面,我们求解了一维数组的排列组合,假如我们需要二维甚至多维数组的排列组合,我们要怎么做呢?

下面 jsliang 逐步给你介绍下~

题目:

现在有一批手机,它有各种信息:

  • 颜色:['白色', '黑色', '金色']

  • 内存大小:['16G', '32G', '64G']

  • 版本:['联通', '移动', '电信']

要求:编写一个算法,实现 颜色 + 内存大小 + 版本的组合,类似于:

  • ['白色', '16G', '联通']

  • ['白色', '32G', '联通']

  • ['黑色', '16G', '电信']

  • ……

请输出所有的组合。

const combine = (arr) => {
  
};

// 后端给出的数据
const colorList = ['白色', '黑色', '金色'];
const sizeList = ['16G', '32G', '64G'];
const versionList = ['联通', '移动', '电信'];

console.log(combine([colorList, sizeList, versionList]));

题解:

首先,我们需要知道的是,我们并不确定数组给出的长度,只知道里面是个二维数组,可能有颜色、内存大小、版本、优惠套餐等内容。

然后,我们据此可以先将基本代码列出来:

const combine = (arr) => {
  // 1. 设置结果集
  const result = [];

  // 2. 遍历收集
  // do something
  
  // 3. 返回结果
  return result;
};

const colorList = ['白色', '黑色', '金色'];
const sizeList = ['16G', '32G', '64G'];
const versionList = ['联通', '移动', '电信'];

console.log(
  combine([
    colorList,
    sizeList,
    versionList,
  ])
);

思路最简单的是暴力无疑,如果我们知道 arr 中具体有多少长度,那么我们可以按照正常的思路编写:

const combine = (arr) => {
  // 1. 设置结果集
  const result = [];

  // 2. 遍历收集
  for (let i = 0; i < arr[0].length; i++) {
    for (let j = 0; j < arr[1].length; j++) {
      for (let k = 0; k < arr[2].length; k++) {
        result.push([arr[0][i], arr[1][j], arr[2][k]]);
      }
    }
  }
  
  // 3. 返回结果
  return result;
};

const colorList = ['白色', '黑色', '金色'];
const sizeList = ['16G', '32G', '64G'];
const versionList = ['联通', '移动', '电信'];

console.log(
  combine([
    colorList,
    sizeList,
    versionList,
  ])
);

这样,我们就通过简单的方法获取到了组合:

[
  [ '白色', '16G', '联通' ], [ '白色', '16G', '移动' ], [ '白色', '16G', '电信' ],
  [ '白色', '32G', '联通' ], [ '白色', '32G', '移动' ], [ '白色', '32G', '电信' ],
  [ '白色', '64G', '联通' ], [ '白色', '64G', '移动' ], [ '白色', '64G', '电信' ],
  [ '黑色', '16G', '联通' ], [ '黑色', '16G', '移动' ], [ '黑色', '16G', '电信' ],
  [ '黑色', '32G', '联通' ], [ '黑色', '32G', '移动' ], [ '黑色', '32G', '电信' ],
  [ '黑色', '64G', '联通' ], [ '黑色', '64G', '移动' ], [ '黑色', '64G', '电信' ],
  [ '金色', '16G', '联通' ], [ '金色', '16G', '移动' ], [ '金色', '16G', '电信' ],
  [ '金色', '32G', '联通' ], [ '金色', '32G', '移动' ], [ '金色', '32G', '电信' ],
  [ '金色', '64G', '联通' ], [ '金色', '64G', '移动' ], [ '金色', '64G', '电信' ],
]

但是,如果我们不确定传入的字段有多少,二维数组的长度有多少呢?就好比:

const combine = (arr) => {

};

const colorList = ['白色', '黑色', '金色'];
const sizeList = ['16G', '32G', '64G'];
const versionList = ['联通', '移动', '电信'];
const setMealList = ['套餐一', '套餐二', '套餐三'];

console.log(
  combine([
    colorList,
    sizeList,
    versionList,
    setMealList,
  ])
);

这时候我们就需要 4 次 for() 循环进行嵌套处理?

如果是 5 个,那就 5 次 for() 循环嵌套处理?

这无疑是令人细思极恐的,那么,有没有法子简化下操作呢?

有的,通过递归:

const combine = (arr) => {
  // 1. 如果是 [] 或者 [colorList] 这种单个的,则直接返回
  if (arr.length < 2) {
    return arr[0];
  } else { // 2. 两两组合得到最终结果
    let tempResult = [];
    for (let i = 0; i < arr[0].length; i++) {
      for (let j = 0; j < arr[1].length; j++) {
        // 2.1 如果是单个 * 单个,则返回两者组合
        if (typeof arr[0][i] === 'string') {
          tempResult.push([arr[0][i], arr[1][j]]);
        } else {
          // 2.2 如果前面已经组合过了,通过 ... 解构数组
          tempResult.push([...arr[0][i], arr[1][j]]);
        }
      }
    }
    // 2.3 重组数组
    arr = [tempResult, ...arr.slice(2)];
    // 2.4 返回最终结果
    return combine(arr);
  }
};

const colorList = ['白色', '黑色', '金色'];
const sizeList = ['16G', '32G', '64G'];
const versionList = ['联通', '移动', '电信'];
const setMealList = ['套餐一', '套餐二', '套餐三'];

console.log(
  combine([
    colorList,
    sizeList,
    versionList,
    setMealList,
  ])
); 

输出:

[
  [ '白色', '16G', '联通', '套餐一' ], [ '白色', '16G', '联通', '套餐二' ], [ '白色', '16G', '联通', '套餐三' ],
  [ '白色', '16G', '移动', '套餐一' ], [ '白色', '16G', '移动', '套餐二' ], [ '白色', '16G', '移动', '套餐三' ],
  [ '白色', '16G', '电信', '套餐一' ], [ '白色', '16G', '电信', '套餐二' ], [ '白色', '16G', '电信', '套餐三' ],
  [ '白色', '32G', '联通', '套餐一' ], [ '白色', '32G', '联通', '套餐二' ], [ '白色', '32G', '联通', '套餐三' ],
  [ '白色', '32G', '移动', '套餐一' ], [ '白色', '32G', '移动', '套餐二' ], [ '白色', '32G', '移动', '套餐三' ],
  [ '白色', '32G', '电信', '套餐一' ], [ '白色', '32G', '电信', '套餐二' ], [ '白色', '32G', '电信', '套餐三' ],
  [ '白色', '64G', '联通', '套餐一' ], [ '白色', '64G', '联通', '套餐二' ], [ '白色', '64G', '联通', '套餐三' ],
  [ '白色', '64G', '移动', '套餐一' ], [ '白色', '64G', '移动', '套餐二' ], [ '白色', '64G', '移动', '套餐三' ],
  [ '白色', '64G', '电信', '套餐一' ], [ '白色', '64G', '电信', '套餐二' ], [ '白色', '64G', '电信', '套餐三' ],
  [ '黑色', '16G', '联通', '套餐一' ], [ '黑色', '16G', '联通', '套餐二' ], [ '黑色', '16G', '联通', '套餐三' ],
  [ '黑色', '16G', '移动', '套餐一' ], [ '黑色', '16G', '移动', '套餐二' ], [ '黑色', '16G', '移动', '套餐三' ],
  [ '黑色', '16G', '电信', '套餐一' ], [ '黑色', '16G', '电信', '套餐二' ], [ '黑色', '16G', '电信', '套餐三' ],
  [ '黑色', '32G', '联通', '套餐一' ], [ '黑色', '32G', '联通', '套餐二' ], [ '黑色', '32G', '联通', '套餐三' ],
  [ '黑色', '32G', '移动', '套餐一' ], [ '黑色', '32G', '移动', '套餐二' ], [ '黑色', '32G', '移动', '套餐三' ],
  [ '黑色', '32G', '电信', '套餐一' ], [ '黑色', '32G', '电信', '套餐二' ], [ '黑色', '32G', '电信', '套餐三' ],
  [ '黑色', '64G', '联通', '套餐一' ], [ '黑色', '64G', '联通', '套餐二' ], [ '黑色', '64G', '联通', '套餐三' ],
  [ '黑色', '64G', '移动', '套餐一' ], [ '黑色', '64G', '移动', '套餐二' ], [ '黑色', '64G', '移动', '套餐三' ],
  [ '黑色', '64G', '电信', '套餐一' ], [ '黑色', '64G', '电信', '套餐二' ], [ '黑色', '64G', '电信', '套餐三' ],
  [ '金色', '16G', '联通', '套餐一' ], [ '金色', '16G', '联通', '套餐二' ], [ '金色', '16G', '联通', '套餐三' ],
  [ '金色', '16G', '移动', '套餐一' ], [ '金色', '16G', '移动', '套餐二' ], [ '金色', '16G', '移动', '套餐三' ],
  [ '金色', '16G', '电信', '套餐一' ], [ '金色', '16G', '电信', '套餐二' ], [ '金色', '16G', '电信', '套餐三' ],
  [ '金色', '32G', '联通', '套餐一' ], [ '金色', '32G', '联通', '套餐二' ], [ '金色', '32G', '联通', '套餐三' ],
  [ '金色', '32G', '移动', '套餐一' ], [ '金色', '32G', '移动', '套餐二' ], [ '金色', '32G', '移动', '套餐三' ],
  [ '金色', '32G', '电信', '套餐一' ], [ '金色', '32G', '电信', '套餐二' ], [ '金色', '32G', '电信', '套餐三' ],
  [ '金色', '64G', '联通', '套餐一' ], [ '金色', '64G', '联通', '套餐二' ], [ '金色', '64G', '联通', '套餐三' ],
  [ '金色', '64G', '移动', '套餐一' ], [ '金色', '64G', '移动', '套餐二' ], [ '金色', '64G', '移动', '套餐三' ],
  [ '金色', '64G', '电信', '套餐一' ], [ '金色', '64G', '电信', '套餐二' ], [ '金色', '64G', '电信', '套餐三' ] ]

以上,通过两两相组的形式,我们可以获取到最终的组合。

当然,jsliang 知道小伙伴们不会满足以此,找了一种更简单的写法:

推入数据的时候需要 result.join(',').split(','),这点可以优化一下,这里就不逼逼了。

const combine = (arr) => {
  const results = [];
  const ergodic = (index, result = []) => {
    for (let i = 0; i < arr[index].length; i++) {
      result[index] = arr[index][i];
      if (index != arr.length - 1) {
        ergodic(index + 1, result);
      } else {
        results.push(result.join(',').split(','));
      }
    }
  }
  ergodic(0, []);

  return results;
};

const colorList = ['白色', '黑色', '金色'];
const sizeList = ['16G', '32G', '64G'];
const versionList = ['联通', '移动', '电信'];
const setMealList = ['套餐一', '套餐二', '套餐三'];

console.log(
  combine([
    colorList,
    sizeList,
    versionList,
    setMealList,
  ])
);

五 参考文献

  • 【博客园】凌云之翼《多个数组的组合排序算法》

  • 【简书】少苏菇凉《js数组排列组合(收录一种简单的方法)》

  • 【微信公众号】程序员黑叔《又懒得加班了,带你去电商公司写商品中心》


不折腾的前端,和咸鱼有什么区别!

jsliang 会每天更新一道 LeetCode 题解,从而帮助小伙伴们夯实原生 JS 基础,了解与学习算法与数据结构。

浪子神剑 会每天更新面试题,以面试题为驱动来带动大家学习,坚持每天学习与思考,每天进步一点!

扫描上方二维码,关注 jsliang 的公众号(左)和 浪子神剑 的公众号(右),让我们一起折腾!

jsliang 的文档库 由 梁峻荣 采用 知识共享 署名-非商业性使用-相同方式共享 4.0 国际 许可协议进行许可。
基于https://github.com/LiangJunrong/document-library上的作品创作。
本许可协议授权之外的使用权限可以从 https://creativecommons.org/licenses/by-nc-sa/2.5/cn/ 处获得。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值