查找数组总和中的哪些数字到目标总和 [重复]

Posted

技术标签:

【中文标题】查找数组总和中的哪些数字到目标总和 [重复]【英文标题】:Find Which Numbers in an Array Sum to Target Sums [duplicate] 【发布时间】:2020-08-30 07:41:59 【问题描述】:

做了一点会计工作,遇到了一个我认为我可以用 javascript 快速解决的问题——然后我就难住了。我需要找到一个数组中的所有数字,当它们相加时将产生我的目标总和。

这是我的输入:

// numbersToSum total = 11319
var numbersToSum = [276, 1872, 1345, 1355, 1400, 1300, 1200, 51, 1020, 500, 400, 300, 100, 100, 100];

// targetSums total = 11319
var targetSums = [1627, 4775, 4917];

// Keep track of the total sum for convenience
var totalSum = 11319;

// Store the solutions
var targetSumsSolutions = [
    1627: [],
    4775: [],
    4917: [],
];

// Do some magic
...

// Output the solutions
console.log(targetSumSolutions);

我开始使用基本的 for 循环将数字加在一起进行迭代,但意识到这是一个更复杂的问题,因为 numbersToSum 条目的多次相加可能会产生目标总和,但考虑到所有总和,这可能并不准确我需要找到。任何人都知道解决这个问题的简单方法吗?

【问题讨论】:

您要查找的是数组元素总和为所需值的所有组合。对于超过 100 个值的数组,这可能会占用大量处理器资源,因此您需要实施一些过滤策略来减少可能组合的数量(例如,删除所有大于您要查找的总和的值)。 而且这些不仅仅是数字的,对吧? 还有Find all possible subset combos in an array? 【参考方案1】:

这是一个有趣的解决方案。

首先,我们从数组构建一个“求和映射”,使用类似于子集求和的经典动态规划解决方案的过程。

对于您可以创建的每个可能的总和,此映射包含该总和中可能的最后一个数字的索引列表。

然后我们可以使用同一张地图直接生成所有可能的方法来获得所有可能的总和,而无需任何更多的搜索/回溯。

请注意,输出可能非常大,但对于您似乎感兴趣的输入类型,总和图并没有那么大。

我不知道您需要这些大列表来做什么,但也许您只想存储 sum map 并根据需要生成列表。这可以为您节省大量内存。

// numbersToSum total = 11319
var numbersToSum = [276, 1872, 1345, 1355, 1400, 1300, 1200, 51, 1020, 500, 400, 300, 100, 100, 100];

// targetSums total = 11319
var targetSums = [1627, 4775, 4917];

// Keep track of the total sum for convenience
var totalSum = 11319;

// Build a "sum map" from a list of positive integers
// For each sum <= maxtarget, the returned map will provide
// the list of indexes that could be the last number in that sum
// From this map, the complete set of possible sums for any value
// can be easily determined.
function buildSumMap(nums, maxtarget)

    //all accessible sums <= maxtarget
    let sumList=[0];
    //for each accessible sum, the indexes of possible final numbers, in order
    let sumMap=
        0:[]
    
    for (let i=0; i<nums.length; ++i) 
        for (let previ=sumList.length-1; previ>=0; --previ) 
            let newsum = sumList[previ]+nums[i];
            if (newsum <= maxtarget) 
                let list = sumMap[newsum];
                if (!list) 
                    //previously inaccessible sum
                    sumList.push(newsum);
                    list = [];
                    sumMap[newsum] = list; 
                
                list.push(i);
            
        
    
    return sumMap;


// Get all the derivations of a given target sum, using a sum map
// only indexes < indexLimit will be considered
function getSetsThatSum(nums, sumMap, target, indexLimit)

    if (target==0) 
        return [[]];
    
    let list = sumMap[target];
    if (!(list && list.length)) 
        return [];
    
    let ret=[];
    for (let i=0; i<list.length; i++) 
        let lastindex = list[i];
        if (lastindex >= indexLimit) 
            break;
        
        let val = nums[lastindex];
        getSetsThatSum(nums, sumMap, target-val, lastindex).forEach(prevsum => 
            ret.push([...prevsum, val]);
        );
    
    return ret;


let sumMap = buildSumMap(numbersToSum, 5000);

// Store the solutions
var targetSumsSolutions = 
    1627: getSetsThatSum(numbersToSum, sumMap, 1627, numbersToSum.length),
    4775: getSetsThatSum(numbersToSum, sumMap, 4775, numbersToSum.length),
    4917: getSetsThatSum(numbersToSum, sumMap, 4917, numbersToSum.length),
;

console.log(targetSumsSolutions);

输出:

 '1627': 
   [ [ 276, 1300, 51 ],
     [ 276, 1200, 51, 100 ],
     [ 276, 51, 500, 400, 300, 100 ],
     [ 276, 1200, 51, 100 ],
     [ 276, 51, 500, 400, 300, 100 ],
     [ 276, 1200, 51, 100 ],
     [ 276, 51, 500, 400, 300, 100 ] ],
  '4775': 
   [ [ 1355, 1200, 1020, 500, 400, 300 ],
     [ 1355, 1400, 1020, 500, 400, 100 ],
     [ 1355, 1400, 1020, 500, 400, 100 ],
     [ 1355, 1300, 1020, 500, 400, 100, 100 ],
     [ 1355, 1400, 1020, 500, 300, 100, 100 ],
     [ 1355, 1400, 1020, 500, 400, 100 ],
     [ 1355, 1300, 1020, 500, 400, 100, 100 ],
     [ 1355, 1400, 1020, 500, 300, 100, 100 ],
     [ 1355, 1300, 1020, 500, 400, 100, 100 ],
     [ 1355, 1400, 1020, 500, 300, 100, 100 ],
     [ 1355, 1200, 1020, 500, 400, 100, 100, 100 ],
     [ 1355, 1300, 1020, 500, 300, 100, 100, 100 ],
     [ 1355, 1400, 1020, 400, 300, 100, 100, 100 ] ],
  '4917': 
   [ [ 1872, 1345, 1200, 500 ],
     [ 1872, 1345, 1300, 400 ],
     [ 1872, 1345, 1400, 300 ],
     [ 1872, 1345, 1200, 400, 100 ],
     [ 1872, 1345, 1300, 300, 100 ],
     [ 1872, 1345, 1200, 400, 100 ],
     [ 1872, 1345, 1300, 300, 100 ],
     [ 1872, 1345, 1200, 300, 100, 100 ],
     [ 1872, 1345, 1200, 400, 100 ],
     [ 1872, 1345, 1300, 300, 100 ],
     [ 1872, 1345, 1200, 300, 100, 100 ],
     [ 1872, 1345, 1200, 300, 100, 100 ],
     [ 1872, 1345, 1400, 100, 100, 100 ] ] 

【讨论】:

【参考方案2】:

这是Subset Sum problem的经典实例,著名的NP。尽管有很多方法可以解决它,但我将在这里坚持回溯技术。请注意,这种方法将在第一个正确答案时停止。

代码将变为:

// numbersToSum total = 11319
var numbersToSum = [276, 1872, 1345, 1355, 1400, 1300, 1200, 51, 1020, 500, 400, 300, 100, 100, 100];

// targetSums total = 11319
var targetSums = [1627, 4775, 4917];

function subsetSum(items, target) 
  const input = items.map(() => 0);
  const algortihmResponse = subsetSumBacktrack(items, input, target, 0, 0);
  return items.filter((element, index) => algortihmResponse[index] === 1);


function subsetSumBacktrack(items, response, target, partialSum, totalCheckedElements) 
  // Here, we compute the maximum number our partialSum could achieve after adding all the remaining elements
  const totalPossibleSum = items
    .slice(totalCheckedElements)
    .reduce((a, b) => a + b, 0);

  // If we've reached our target, just return our response
  if (partialSum == target) 
    return response;
  //  If we've passed our target, just return null. There's no need to continue
   else if (partialSum > target) 
    return null;
  // If even after summing all the remaining elements we won't reach our target, just return null. There's no need to continue
   else if (partialSum + totalPossibleSum < target) 
    return null;
   else 
    // Here comes the magic in it. Here we check the next number that will either sum to a number smaller than our target or even, if we are lucky enough, reach the target;
    for (const v of [1, 0]) 
      response[totalCheckedElements] = v;
      const x = subsetSumBacktrack(items, response, target, partialSum + response[totalCheckedElements] * items[totalCheckedElements], totalCheckedElements + 1);
      if (x != null) 
        return x;
      
    
    return null;
  


const targetSumsSolutions = targetSums.map(target => subsetSum(numbersToSum, target));
console.log(targetSumsSolutions);

此外,请注意这是一个非常密集的解决方案,在最坏的情况下,我们基本上暴力破解数组的每个可能子集。

【讨论】:

这太不可思议了

以上是关于查找数组总和中的哪些数字到目标总和 [重复]的主要内容,如果未能解决你的问题,请参考以下文章

React JS获取数组中数字的总和

39. 组合总和

39. 组合总和

39. 组合总和

查找具有给定总和的数字列表的所有组合

组合总和--力扣