是否找到n个参数的所有可能的组合(正负)和?

时间:2019-07-02 04:40:32

标签: javascript algorithm addition subtraction

我正在尝试构建一个带有可变数量参数的函数。

该函数接受n个输入并计算所有可能的加法和减法之和,例如如果参数是1,2,3

1 + 2 + 3
1-2-3
1 + 2-3-
1-2 + 3

最后,该函数输出最接近零的和。在这种情况下,答案仅为0。

我在解决如何循环n个参数以使用+和-运算符的所有可能组合时遇到很多问题。

我设法构建了一个将所有变量相加或相减的函数,但是我一直坚持如何处理各种+和-,特别是在考虑多个可能的变量时。

var sub = 0;
var add = 0;

function sumAll() {
  var i;

  for (i = 0; i < arguments.length; i++) {
    sub -= arguments[i];
  }
  for (i = 0; i < arguments.length; i++) {
    add += arguments[i];
  }
  return add;
  return sub;
};
console.log(add, sub); // just to test the outputs

对于任何给定数量的输入(总是整数,正负),我想计算+和-的所有可能排列。欢迎提出将总和与零进行比较的建议,尽管我还没有尝试过,但宁愿先询问这一部分。谢谢。

5 个答案:

答案 0 :(得分:2)

我会遍历数字的可能。例如,如果有3个自变量,则有3位,并且这些位可表示的最高数字是2 ** 3 - 1或7(当所有3位都已设置时,111或1 + 2 + 4)。然后,从0迭代到7,并检查是否设置了每个位索引。

例如,在第一次迭代中,当数字为0时,位为000,对应于+++-将所有3个参数加起来。

在第二次迭代中,当数字为1时,位为001(对应于-++),因此减去第一个自变量,再加上其他两个自变量。

第三次迭代将具有2010+-+

第三次迭代将具有3011+--

第三次迭代将具有4100-++

继续模式直到结束,同时跟踪到目前为止到目前为止最接近零的总数。

如果愿意,如果总计为0,也可以立即返回。

const sumAll = (...args) => {
  const limit = 2 ** args.length - 1; // eg, 2 ** 3 - 1 = 7
  let totalClosestToZeroSoFar = Infinity;
  for (let i = 0; i < limit; i++) {
    // eg '000', or '001', or '010', or '011', or '100', etc
    const bitStr = i.toString(2).padStart(args.length, '0');
    let subtotal = 0;
    console.log('i:', i, 'bitStr:', bitStr);
    args.forEach((arg, bitPos) => {
      if (bitStr[args.length - 1 - bitPos] === '0') {
        console.log('+', arg);
        subtotal += arg;
      } else {
        console.log('-', arg);
        subtotal -= arg;
      }
    });
    console.log('subtotal', subtotal);
    if (Math.abs(subtotal) < Math.abs(totalClosestToZeroSoFar)) {
      totalClosestToZeroSoFar = subtotal;
    }
  }
  return totalClosestToZeroSoFar;
};

console.log('final', sumAll(1, 2, 3));

您可以通过将[args.length - 1 - bitPos]替换为[bitPos]来“简化”以获得相同的结果,但看起来会更加混乱-例如3011,或+--)将变为110--+)。

它要短很多,而没有所有日志可以证明代码正在按预期运行:

const sumAll = (...args) => {
  const limit = 2 **  args.length - 1;
  let totalClosestToZeroSoFar = Infinity;
  for (let i = 0; i < limit; i++) {
    const bitStr = i.toString(2).padStart(args.length, '0');
    let subtotal = 0;
    args.forEach((arg, bitPos) => {
      subtotal += (bitStr[bitPos] === '0' ? -1 : 1) * arg;
    });
    if (Math.abs(subtotal) < Math.abs(totalClosestToZeroSoFar)) {
      totalClosestToZeroSoFar = subtotal;
    }
  }
  return totalClosestToZeroSoFar;
};

console.log('final', sumAll(1, 2, 3));

您可以通过任意选择第一个数字的符号来将操作次数减少一半。例如。目前,对于sumAll(9, 1)89 - 1)和-81 - 9)的答案都是有效的,因为它们都接近0。无论输入如何,如果+-产生一个最接近0的数字,那么-+也会这样做,只是带有相反的符号。同样,如果++---产生一个最接近0的数字,那么--+++也会产生一个相反的符号。通过为第一个数字选择一个符号,您可能会强制计算结果仅具有一个 符号,但这不会影响算法的结果与0的距离。

没有太大的改进(例如,10个参数,2 ** 10 - 1-> 1023迭代改进为2 ** 9 - 1-> 511迭代),但这是有的。

const sumAll = (...args) => {
  let initialDigit = args.shift();
  const limit = 2 **  args.length - 1;
  let totalClosestToZeroSoFar = Infinity;
  for (let i = 0; i < limit; i++) {
    const bitStr = i.toString(2).padStart(args.length, '0');
    let subtotal = initialDigit;
    args.forEach((arg, bitPos) => {
      subtotal += (bitStr[bitPos] === '0' ? -1 : 1) * arg;
    });
    if (Math.abs(subtotal) < Math.abs(totalClosestToZeroSoFar)) {
      totalClosestToZeroSoFar = subtotal;
    }
  }
  return totalClosestToZeroSoFar;
};

console.log('final', sumAll(1, 2, 3));

答案 1 :(得分:1)

变量参数要求与算法无关,这似乎是问题的关键。如果需要,可以使用spread syntax代替arguments

对于算法,如果参数编号可以为正数或负数,那么一个很好的起点是朴素的蛮力O(2 n )算法。对于每个可能的操作位置,我们在该位置添加一个加号来递归,而在添加负号时分别递归。在备份调用树的过程中,选择最终导致方程最接近零的任何选择。

代码如下:

const closeToZero = (...nums) =>
  (function addExpr(nums, total, i=1) {
    if (i < nums.length) {
      const add = addExpr(nums, total + nums[i], i + 1);
      const sub = addExpr(nums, total - nums[i], i + 1);
      return Math.abs(add) < Math.abs(sub) ? add : sub;
    }
    
    return total;
  })(nums, nums[0])
;

console.log(closeToZero(1, 17, 6, 10, 15)); // 1 - 17 - 6 + 10 + 15

现在,问题是这是否正在执行额外的工作。我们可以找到overlapping subproblems吗?如果是这样,我们可以记住以前的答案并在表中查找它们。问题部分在于负数:基于已经针对数组的给定块解决的子问题,如何确定我们是否离目标越来越近并不明显。

我会将其留给读者自己做练习,然后自己考虑一下,但是似乎还有优化的余地。这是related question,在此期间可能会提供一些见识。

答案 2 :(得分:1)

这也称为partition problem的变体,即我们正在寻找将参数划分为两部分之间的最小差异(例如[1,2]和[3之间的差异]为零)。这是一种枚举我们可以创建并选择最小差异的方法:

function f(){
  let diffs = new Set([Math.abs(arguments[0])])
  for (let i=1; i<arguments.length; i++){
    const diffs2 = new Set
    for (let d of Array.from(diffs)){
      diffs2.add(Math.abs(d + arguments[i]))
      diffs2.add(Math.abs(d - arguments[i]))
    }
    diffs = diffs2
  }
  return Math.min(...Array.from(diffs))
}

console.log(f(5,3))
console.log(f(1,2,3))
console.log(f(1,2,3,5))

答案 3 :(得分:0)

我花了很多时间在这项技能上,所以在数组中的每个项目之间加上符号。这对我来说似乎是最自然的方法。

const input1 = [1, 2, 3]
const input2 = [1, 2, 3, -4]
const input3 = [-3, 6, 0, -5, 9]
const input4 = [1, 17, 6, 10, 15]

const makeMatrix = (input, row = [{ sign: 1, number: input[0] }]) => {
  if(row.length === input.length) return [ row ]
  const number = input[row.length]
  return [
    ...makeMatrix(input, row.concat({ sign: 1, number })),
    ...makeMatrix(input, row.concat({ sign: -1, number }))
  ]
}

const checkMatrix = matrix => matrix.reduce((best, row) => {
  const current = {
    calculation:  row.map((item, i) => `${i > 0 ? item.sign === -1 ? "-" : "+" : ""}(${item.number})`).join(""),
    value: row.reduce((sum, item) => sum += (item.number * item.sign), 0)
  }
  return best.value === undefined || Math.abs(best.value) > Math.abs(current.value) ? current : best
})

const processNumbers = input => {
  console.log("Generating matrix for:", JSON.stringify(input))
  const matrix = makeMatrix(input)
  console.log("Testing the following matrix:", JSON.stringify(matrix))
  const winner = checkMatrix(matrix)
  console.log("Closest to zero was:", winner)
}
processNumbers(input1)
processNumbers(input2)
processNumbers(input3)
processNumbers(input4)

答案 4 :(得分:0)

我喜欢加入这个谜语:)

问题可以描述为 f n = f n-1 + a n * x n < / sub> ,其中 x X ,而 a 0 ,...,a n {-1,1}

对于单个情况: X * A = y

对于所有情况 X(*)TA = Y,TA = [A n!,...,A 0 ] < / p>

现在我们有n!不同的A

//consider n < 32
// name mapping TA: SIGN_STATE_GENERATOR, Y: RESULT_VECTOR, X: INPUT    

const INPUT = [1,2,3,3,3,1]
const SIGN_STATE_GENERATOR = (function*(n){
       if(n >= 32) throw Error("Its working on UInt32 - max length is 32 in this implementation")
       let uint32State = -1 >>> 32-n;
       while(uint32State){
          yield uint32State--;
       }
})(INPUT.length)

const RESULT_VECTOR = []
let SIGN_STATE = SIGN_STATE_GENERATOR.next().value
while (SIGN_STATE){
  RESULT_VECTOR.push(
    INPUT.reduce(
      (a,b, index) => 
      a + ((SIGN_STATE >> index) & 1 ? 1 : -1) * b,
      0
    )
  )
  SIGN_STATE = SIGN_STATE_GENERATOR.next().value
}
console.log(RESULT_VECTOR)