求数组元素的所有排列方式

74 阅读2分钟

背景:这是在刷leetcode时,经常遇到的算法。

  • 给一个数组 [1,2,3],求元素的所有排列方式

  • eg: [1,2,3],它的排列方式有六种,123132213231312321

  • 所有排列方式的总数为 3*2*1 = 3! = arr.length!,数组长度的阶乘

  • 所有排列方式的总数超过千万时,计算量就会非常恐怖

实现思路

  • 遍历数组,将数组转为对象,再遍历对象+回溯,得出所有排列组合

  • 第一步,通过arr获取所有排列方式的对象obj

// 此方法将数组所有可能的排列方式,转换为对象的形式
// 递归到最后一个位置时,使用 # 作为特殊标识,下一步需要使用
// 这个方法的运行时长与arr的长度有关,长度小于等于10都正常,大于10就会非常恐怖

const getObj = (arr) => {
    if (arr.length == 1) return { '#': arr[0] }
    const obj = {}
    for (const key of arr) {
        if (!obj[key]) {
            obj[key] = getObj(arr.filter(v => v != key))
        }
    }
    return obj
}
getObj(arr)
  • 第二步,将对象obj转回数组allKeys
const allKeys = []
const getKeyArr = (obj) => {
    for (const key in obj) {
        if (key == '#') {
            allKeys.push(obj[key], '-')
            return obj[key]
        } else {
            allKeys.push(key)
            getKeyArr(obj[key])
        }
    }
}
getKeyArr(allobj)
  • 第三步,将allKeys组装成最后需要的样子
// 这里 curItem.slice(0, -v.length) 很重要,需要理解上一步的 allKeys.push(obj[key], '-')
// 把 allKeys 打印出来看着结合理解
const len = arr.join('').length
let curItem = ''
const allChild = allKeys
    .join('')
    .split('-')
    .slice(0, -1)
    .map(v => {
        curItem = v.length == len ? v : curItem.slice(0, -v.length) + v
        return curItem
    })
  • 核心逻辑到这里就完了,但是给的数组中可能会有相同的元素

  • 这种情况可以在getObj前处理数组arr,对其中重复的元素添加特殊字符,将其变成唯一元素,在第三步之后再通过正则去掉特殊字符还原元素

// 对其中重复的元素添加特殊字符,将其变成唯一元素
let id = 1, ids = []
const uniWords = (arr) => {
    const obj = {}
    for (let i = 0; i < arr.length; i++) {
        if (!obj[arr[i]]) {
            obj[arr[i]] = arr[i]
        } else {
            const uid = '&' + id + '&'
            id++
            arr[i] = arr[i] + uid
            ids.push(arr[i])
        }
    }
}
...
...
...
// 通过正则去掉特殊字符还原元素
const allUniChild = allChild.map(v => {
    return v.replaceAll(/&\d+&/g, '')
})

---------------- 2025-02-18

第二种方式,还是原来的思路,代码上有点区别

const test = () => {

            const obj = {}
            const fn = (obj, nums) => {
                for (const item of nums) {
                    const arr = nums.filter((v) => v != item)//[1,1,5]这种重复的数据需要优化
                    if (arr.length == 0) {
                        obj[item] = item
                    } else {
                        obj[item] = {}
                        fn(obj[item], arr)
                    }
                }
            }
            fn(obj, nums)
            console.log(obj)

            const _res = []
            const fn2 = (obj, arr) => {
                for (const key in obj) {
                    if (typeof obj[key] != 'object') {
                        arr.push(key)
                        _res.push(arr)
                    } else {
                        const arr2 = [...arr, key]
                        fn2(obj[key], arr2)
                    }
                }
            }
            fn2(obj, [])
            console.log(_res)
            
        }

        const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9]
        //最多九个数字,十个数字就需要五秒左右
        console.time('test')
        test(nums)
        console.timeEnd('test')