(今日头条面试题简版)写一个函数sum_subset(S,N):一个集合S里面都是正整数,求和为N的所有非空子集。

    比如{1,3,8,5,2} N=10 那么有{8, 2}, {3,5,2}

    tips:见tips.md

    答案:

    下面这个答案利用了递归的性质,具体可以参考tips。

    1. function sum_subset(S,N, path = []) {
    2. const head = S.slice(0, S.length - 1)
    3. const tail = S[S.length - 1]
    4. if(N === 0) {
    5. return [path]
    6. }
    7. if(head.length === 0) {
    8. return []
    9. }
    10. let r = []
    11. r = r.concat( sum_subset(head, N, path) )
    12. r = r.concat( sum_subset(head, N-tail, path.concat(tail)) )
    13. return r
    14. }

    另一种方法(暂时不需要会)

    这个方法提供给目前需要去今日头条面试的同学。下面这种解法叫做动态规划,本质上和上面的解法类似。但是超出了递归知识的范围,提供给有能力学习的同学。 如果发现太难可以跳过去,后面会有专门讲动态规划的课程。

    上述递归方法有一个问题,就是sum_subset中间其实有若干可以复用的更小的步骤,但是被重复计算了。因此,可以构造一种不基于递归的方法。

    设置一个二维数组 dp[i][j]代表S中前i项中存在和为j的子集的可能性,可能为1,不可能为1。

    那么对于{1,3,8,5,2} N=10 ,会形成这样一个表格(左边表头代表i,上边表头代表j)

    第一步:初始化 如下图:和为0的时候,总是存在子集(空集)和为0,因此dp[i][0] = 1

    1. 0 1 2 3 4 5 6 7 8 9 10
    2. 0 1 0 0 0 0 0 0 0 0 0 0
    3. 1 1
    4. 2 1
    5. 3 1
    6. 4 1
    7. 5 1

    第二步:继续填表

    对于任意d[i][j],如果j如果j >= S[i-1],分成两种情况(两种情况成立任意一种,那么dp[i][j] = 1
    解包含S[i] -> dp[i][j] = dp[i-1][j-S[i-1]]
    解不包含S[i] -> d[i][j] = dp[i-1][j]
    按照上述逻辑从第二行开始填表,直到结束(左边多增加了一列,是集合的数字,这样看起来比较方便)

          0  1  2  3  4  5  6  7  8  9  10
       0  1  0  0  0  0  0  0  0  0  0  0
    1  1  1  1  0  0  0  0  0  0  0  0  0
    3  2  1  1  0  1  1  0  0  0  0  0  0
    8  3  1  1  0  1  1  0  0  0  1  1  0 
    5  4  1  1  0  1  1  1  1  0  1  1  0
    2  5  1  1  1  1  1  1  1  1  1  1  1
    

    第三步:构造递归解

    • 第10列第5行有个1,那么2在结果集合中,记为{2}

    • 第8列第3、4行各有1个1,那么{2,8}和{2,5}在结果集合中。

    • {2,8}和为10,不需要再递归。 {2,5}需要继续递归。

    • 第3行有4个1,但是只有3和{2,5}合并和为10,其他都不满足条件。

    function sum_subset_dp(S, N) {
      const dp = Array.from({length : S.length + 1}, () => Array(N+1).fill(0) )
      for(let i = 0; i < S.length + 1; i++ ){
        dp[i][0] = 1
      }
    
      for(let i = 1; i < S.length + 1; i++) {
        for(let j = 1; j < N + 1; j++) {
          if( j >= S[i-1] )  {
            dp[i][j] = dp[i-1][j] || dp[i-1][j - S[i-1]]
          }else {
            dp[i][j] = dp[i-1][j]
          }
        }
      }
      return dp
    }
    
    
    function read_result_recursive(S, N, dp, path = []) {
      if( N === 0) { return [path] }
      if(N < 0) { return [] }
    
      let r = []
      for(let i = 1; i < S.length + 1; i++) {
        if( dp[i][N] && path.indexOf(S[i-1]) === -1 ) {
          r = r.concat( read_result_recursive(S, N-S[i-1], dp, path.concat(S[i-1])) )
        }
      }
      return r
    
    }
    
    const S = [1,3,8,5,2]
    const N = 10
    const dp = sum_subset_dp(S, N)
    console.log( read_result_recursive(S, N, dp) )