给定一个集合,如 {3, 4, 5, -3, 100, 1, 89, 54, 23, 20},将它分成两个子集,每个是它的一半大小。 如果集合元素是奇数,那么有一个自己会比另一个子集大1。

    集合用数组S表示,写一个方法tug(S),使得S切分成两个子集后,两个子集元素和的差值(绝对值)最小。

    比如{3, 4, 5, -3, 100, 1, 89, 54, 23, 20}可以拆分成 {4, 100, 1, 23, 20}和{3, 5, -3, 89, 54},和分别是148,148,差值为0。

    {23, 45, -34, 12, 0, 98, -99, 4, 189, -1, 4}可以拆分成{45, -34, 12, 98, -1}和{23, 0, -99, 4, 189, 4},和分别是120和121,差值为1。

    示例

    1. tug([23, 45, -34, 12, 0, 98, -99, 4, 189, -1, 4])
    2. // [ [45 ,-34, 12 ,98 ,-1], [23, 0, -99, 4, 189 ,4] ]

    提示:见tips.md

    答案:

    枚举集合S中的所有子集(可以利用类似全排列)的方法,利用决策树选中所有规模为一半的子集,然后求和。

    1. const sum = (arr) => arr.reduce((a,b) => a + b, 0)
    2. function tug(S) {
    3. const total = sum(S)
    4. let min = Infinity
    5. let list = null
    6. /* 递归枚举所有的情况 */
    7. function tug_util(S, decisions = []) {
    8. if(decisions.length === ~~(S.length / 2)) {
    9. const s = sum(decisions.map(i => S[i]))
    10. const t = Math.abs(total - 2*s) // 两个子集和的差值(绝对值)
    11. if(min > t) {
    12. min = t
    13. list = [
    14. decisions.map(i => S[i]),
    15. [...Array(S.length)].map((_, i)=>i).filter(i => decisions.indexOf(i) === -1).map(i => S[i])
    16. ]
    17. }
    18. return
    19. }
    20. const start = decisions.length > 0 ? decisions[decisions.length - 1] : -1
    21. for(let i = start + 1; i < S.length; i++) {
    22. tug_util(S, decisions.concat(i))
    23. }
    24. }
    25. tug_util(S)
    26. return list
    27. }