demand

requirements are very simple to describe, there are three arrays like this:

let names = ["iPhone",'iPhone xs']

let colors = ['black','white']

let storages = ['64g','256g']

needs to enumerate all their combinations, and finally get such an array:

[

  ["iPhone X", "黑色", "64g"],

  ["iPhone X", "黑色", "256g"],

  ["iPhone X", "白色", "64g"],

  ["iPhone X", "白色", "256g"],

  ["iPhone XS", "黑色", "64g"],

  ["iPhone XS", "黑色", "256g"],

  ["iPhone XS", "白色", "64g"],

  ["iPhone XS", "白色", "256g"],

]

Since these attribute arrays are indeterminate, they cannot be solved simply by a triple violent loop.

Idea

If we choose recursive tracing to solve this problem, then the most important problem is to design our recursive function

Idea decomposition

For the examples given above, for example, our current attribute array is names, colors, storages, first we will process the names array
Obviously, for each attribute array, you need to traverse it and select one by one before combining with each item in the next array.

The recursive function we designed receives two parameters

  • index corresponds to the subscript currently being processed, whether it is names or colors or storage.
  • prev The result of the previous recursion has been spliced, such as ['iphoneX','black']

Enter the recursive function:

  1. Processing the subscript 0 of the attribute array: Suppose we select iphone XS in the first loop. At this time, we have an unfinished result state. Suppose we call it prev, at this time prev = ['iphone Xs'].
  2. Processing the subscript 1 of the attribute array: Then it is processed to the colors array, and we have prev. When traversing colors, we continue to recursively concatenate prev into prev.concat(color), which is ('iphoneXs',' Black'] This continues to pass this prev to the next recursion
  3. Processing the subscript 2 of the attribute array: Then it is processed to the storages array and we have the prev of name + color. When traversing the storages, we continue to recursively concatenate the prev into prev.concat(storage), which is ['iPhoneXS' ,'Black','64g'], and at this time we find that the subscript of the processed attribute array has reached the end, then put it into the global result variable res as a result

coding to achieve

let names = ['iphoneX',"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 -1

        for(let val of chunk){

            let cur = prev.concat(val)

            // ['iphoneX','黑色','64g'],['iphoneX','黑色','256g'],['iphoneX','白色','64g']

            if(isLast){

                // 如果已经处理到数组的最后一项 则把拼接的结果放入返回值中

                res.push(cur)

            }else{

                helper(chunkIndex+1,cur)

            }

        }

    }

    //从属性数组下标为0开始处理

    // 并且此时的prev是一个空数组

    helper(0,[])

    return res

}

console.log(combine(names,colors,storages));

["iphoneX", "黑色", "64g"]

["iphoneX", "黑色", "256g"]

["iphoneX", "白色", "64g"]

["iphoneX", "白色", "256g"]

["iPhone XS", "黑色", "64g"]

["iPhone XS", "黑色", "256g"]

["iPhone XS", "白色", "64g"]

["iPhone XS", "白色", "256g"]

Universal template

Given two integers n and k, return all possible combinations of k numbers in 1...n
Input: n = 4, k = 2
Output:

[

  [2,4],

  [3,4],

  [2,3],

  [1,2],

  [1,3],

  [1,4],

]

Answer

let combine = function (n,k){

    let ret = []

    let helper = (start,prev)=>{

        let len = prev.length

        if(len === k){

            ret.push(prev)

            return //[[1,2]]

        }

        for(let i = start;i<=n;i++){

            helper(i+1,prev.concat(i))

            //helper(2,[1]) [1,2]

            //helper(3,[1]), [1,3]

            //helper(4,[1]) [1,4]

            //helper(3,[2])  [2,3]

            //helper(4,[2])[2,4]

            // helper(4,[3])[3,4]

        }

    }

    helper(1,[])

    return ret

}
  • It can be seen that this question is so similar to our code for solving e-commerce permutations and combinations. Only need to design a recursive helper function that accepts the starting position of the start arrangement and the last stitching result of prev as a parameter.
  • Then for each start subscript start, the value corresponding to the start position is spliced first, and then the other remaining subscripts are used as the starting point for the next splicing.
  • When the splicing array of the intermediate state of prev reaches the required length k of the problem, it is put into the result array

optimization

  • In this solution, there are some recursive branches that are obviously impossible to obtain results.
  • Every time we recurse, we will try all the items <= n as start. Assuming that the length of the array we require is k=3,
  • The maximum value is n=4 and we use prev = [1], and then use n=4 as start as the starting point of the recursion
  • Then obviously it is impossible to get the result, because if n=4, only 4 items can be spliced, at most
  • Just spell it into [1,4], it is impossible to satisfy the condition of k=3, so before entering the recursion
  • Just decisively reduce these waste branches. This is called branch

let combine = function (n,k){

    let ret = []

    let helper = (start,prev)=>{

        let len = prev.length

        if(len === k){

            ret.push(prev)

            return

        }

        // 还有rest个位置待填补

        let rest = k - prev.length

        for(let i = start;i<=n;i++){

            if(n-i+1<rest){

                continue

            }

            helper(i+1,prev.concat(i))

        }

    }

    helper(1,[])

    return ret

}

Similar question type

Given an integer array nums that may contain repeated elements, return all possible subsets of the array (power set)
Note: The solution cannot contain duplicate subsets

输入: [1,2,2]

输出:

[

  [2],

  [1],

  [1,2,2],

  [2,2],

  [1,2],

  []

]

pruning 1616e8663bcf2f is similar to the previous one. If you find that the remaining number is not enough to make up the target length during the loop, just cut off

var subsetsWithDup = function(nums){

    let n = nums.length

    let res = []

    if(!n){

        return res

    }

    nums.sort()

    let used = {}

    let helper = (start,prev,target)=>{ //0,[],2

        if(prev.length === target){

            let key = genKey(prev)

            if(!used[key]){

                res.push(prev)

                used[key] = true

            }

            return

        }

        for(let i = start; i<= n;i++){

            let rest = n - i

            let need = target - prev.length

            if(rest<need){

                continue

            }

            helper(i + 1,prev.concat(nums[i]),target)//1,[1],2     2,[2],2  3,[2],2

                                                    // 2,[1,2],2,  2,[2,2],2

                                                    //1,[1,]3    2,[2],3    3,[3],3

                                                    //2,[1,2],3   3,[2,2],3  

                                                    //3,[1,2,3],3  



        }

    }

  for(let i = 1;i<=n;i++){

      helper(0,[],i) //0,[],3

  }

  return [[],...res]

}

function genKey(arr){

    return arr.join('~')

}

Array sum

Given an array of candidates and a target number target, find out all the combinations in candidates that can make the number and the target. Each number in candidates can only be used once in each combination.

illustrate:
All numbers (including the target number) are positive integers
The solution set cannot contain repeated combinations


示例 1:

输入: candidates = [10,1,2,7,6,1,5], target = 8,

所求解集为:

[

  [1, 7],

  [1, 2, 5],

  [2, 6],

  [1, 1, 6]

]

示例 2:

输入: candidates = [2,5,2,1,2], target = 5,

所求解集为:

[

  [1,2,2],

  [5]

]
Ideas
  • Similar to the above idea, except that there is no need to consider the repeated use of the same element. The starting point for each recursive start should be prevStart + 1.
  • Since there may be multiple identical elements in the array, they may generate the same solution. For example, when [1,1,7] is used to make up 8, it may use 1 and 7 with subscripts of 0 to make up 8, or use 1 and 7 marked 1 go to 8
  • Therefore, it is necessary to judge whether the solution has been generated through the unique key before liberating it into the array, but considering the situation of [1,2,1,2,7] to make up 10, it may generate [1,2 ,7] and [2,1,7]
  • Such a solution with a different order but the same result does not meet the requirements of the problem, so a simple method is to sort the array first and then solve it so that there will be no solutions with the same order and the same.
  • At this time, you only need to do a simple array splicing to generate key[1,2,7]->1~2~7
/**

 * @param {number[]}candidates

 * @param {number} target

 * @return {number[][]}

 */

let combinationSum2 = function(candidates,target){

    let res = []

    if(!candidates.length){

        return res

    }

    candidates.sort()

    let used = {}

    let helper = (start,prevSum,prevArr) =>{

        // 由于全是正整数  所以一旦和大于目标值了  直接结束本次递归即可

        if(prevSUm >target){

            return

        }

        // 目标值达成

        if(prevSum === target){

            let key = genkey(prevArr)

            if(!used[key]){

                res.push(prevArr)

                used[key] = true

            }

            return

        }

        for(let i = start;i<candidates.length; i++){

            // 这里还是继续从start本身开始  因为多个重复值是允许的

            let cur = candidates[i]

            let sum = prevSum + cur

            let arr = prevArr.concat(cur)

            helper(i + 1,sum,arr)

        }

    }

    helper(0,0,[])

    return res

}

let genKey = (arr)=> arr.join('~')

HappyCodingTop
526 声望847 粉丝

Talk is cheap, show the code!!