非负集减

时间:2019-03-01 04:45:54

标签: javascript algorithm math lodash

这适用于任何语言,但是我会在node中实现它。

我有一组整数,并且需要从该组的总和中减去一个值。

[4, 5, 6, 7, 8] - 25

如果我们从每个数字中平均减去,我们将得到:

[-1, 0, 1, 2, 3]

但是,我不希望任何数字小于0。因此,如果我正在编写算法来执行此操作,则负数将平均溢出到其余数字中,而我们现在拥有:

[0, 0, 1, 2, 3] - 1

制作结果集:

[0, 0, 1 - 0.333, 2 - 0.333, 3 - 0.333]

是否有快速的操作或一系列不涉及循环直到完成才能解决此问题的操作?

请注意,这正是我想要的结果。所有的负值甚至会溢出到其余的正值中。

我使用Lodash,所以用Lodash回答是可以接受的。

5 个答案:

答案 0 :(得分:3)

假设已设置数量,您可以减少很多循环次数

  1. 仅具有正值
  2. 集合的总和大于要减去的值
  3. 可以排序。

关于第3点,您在评论中说可以对此进行排序,所以这是我想到的算法。就变量名而言,它很冗长,但希望可以使它更清晰。总而言之,它并不太复杂:

const result = subtractFromSet([4, 5, 6, 7, 8], 25);
console.log(result)

function subtractFromSet(numberSet, totalToSubtract) {
  return numberSet.map((currentMember, index, wholeSet) => {
    const remainingElements = wholeSet.length - index
    const averageToSubtract = totalToSubtract / remainingElements;
    
    const toSubtract = Math.min(averageToSubtract, currentMember);
    //modify both the total remaining to subtract and the current element
    totalToSubtract -= toSubtract;
    return currentMember - toSubtract;
  })
}

要用语言解释,这就是发生的事情

  1. 从集合的总和中减去一个值与从每个成员中减去该值除以集合的大小相同。因此[4, 5, 6, 7, 8] - 25将是4 - 55 - 56 - 5等。
  2. 如果我们不希望出现负数,那么我们只减去当前成员。因此,在当前成员为4且平均值为25 / 5 = 5的情况下,我们只能减去4
  3. 为了分散余数,我们只需将其加回到余数中即可减去其余成员。因此,当我们到达第二个成员5时,现在的平均值为(25 - 4) / (5 - 1),因为我们无法第一次减去全部5的平均值,只能减去4,而其余元素少一。这将产生5.25

因此,当遍历每个成员并调整总和然后减去所需的平均值时,我们得到正确的结果。尽管在编程中请注意floating point arithmetic,因为它可能导致结果稍微偏离。如果您舍入足够的重要数字来满足需要,则可以避免这种情况。

最后一件事情-正如我所说,输入必须进行排序。可以像这样轻松完成:

const unsortedArray = [6, 4, 7, 8, 5];
const sortedArray = unsortedArray.sort((a, b) => a - b)

console.log(sortedArray);

我在上面的算法中忽略了它,专注于我们对已排序输入的实际操作。

该算法的总复杂度非常高-合理的排序将使您O(n log(n))的时间复杂度降低。取决于排序实现和数据集-可能会通过O(n^2) Bubblesort对5个元素进行排序,但是同样,它是5个元素,因此可能不会产生影响。运行减法算法很简单O(n)。相减的空间复杂度为O(2n),因为.map()复制了数组。我选择它是因为它比较干净,因为您仍有输入。但是,您也可以通过最少的更改就地进行减法,这将使您O(n)的空间复杂度降低。

const input = [4, 5, 6, 7, 8];
subtractFromSet(input, 25);
console.log(input);

function subtractFromSet(numberSet, totalToSubtract) {
  numberSet.forEach((currentMember, index, wholeSet) => { //using .forEach instead of .map
    const remainingElements = wholeSet.length - index
    const averageToSubtract = totalToSubtract / remainingElements;
    
    const toSubtract = Math.min(averageToSubtract, currentMember);
    //modify both the total remaining to subtract and the current element
    totalToSubtract -= toSubtract;
    wholeSet[index] = currentMember - toSubtract; //modify the array directly
  })
}

答案 1 :(得分:1)

不清楚“效率”在这个问题上是什么意思。

对于算法,您可以

  • .map()输入数组,以输入arr整数或小数减去n数除以输入.lengthmap

    的数组
  • .filter()数组的
  • map结果转换为值大于0not

  • 的元素 将.filter()数组中的
  • map个元素转换为大于或等于0.matches

  • 的元素
  • .reduce() not数组,其元素少于0subtotal

  • subtotal传递到Math.abs()以获取整数的绝对值,除以matches.length减去not.length以获得要应用于输入{{ 1}}

  • N映射,其中minuend值小于或等于.map()返回0,否则返回值减去0,其中N链接以满足描述为预期输出的3个十进制数字的要求,并使用add .toFixed(3)运算符转换为数字,结果

  • 返回+

result

答案 2 :(得分:1)

无需排序,但可以使用递归。

let output = substract( [6,5,7,4,8], 25, 5)
console.log(output)
output = substract( [6,5,7,4,8.5], .10, 5)
console.log(output)
function substract(array, substractThis, length) {
	let delta = substractThis/length;
	let toSubstract;
	let out = array.map(ele => {
		toSubstract =  Math.min(ele, delta);
		substractThis -= toSubstract;
		ele = ele - toSubstract;
		length -= !ele;
		return ele;
	});
	if(substractThis < 1) {
		return out
	}
	return substract(out, substractThis, length)
}

答案 3 :(得分:0)

我提供了使用递归的解决方案。它不需要对数组进行预排序即可工作,但是这将导致算法无法利用某些优势。解释在代码内...

const spreadSubtract = (arr, val) =>
{
    // Get the numbers of positive (> 0) elements on the array.

    let len = arr.filter(x => x > 0).length;

    // Get the new subtract value.

    let sub = val / len;

    // Stop condition: if the positive (> 0) elements on the
    // array minus the subtract value are all positive (> 0),
    // then return the mapped array.

    if (!arr.some(x => x > 0 && x - sub < 0))
    {
        return arr.map(x => x > 0 ? x - sub : x);
    }
    
    // Get the minimum element on the array that will be
    // negative when subtracting <sub> from it.

    sub = Math.min(...arr.filter(x => x > 0 && x - sub < 0));

    // Proceed to next iteration of the recursion.

    return spreadSubtract(
        arr.map(x => x > 0 ? x - sub : x),
        val - sub * len
    );
}

// OP example:
console.log(spreadSubtract([4, 5, 6, 7, 8], 25));

// Another test:
console.log(spreadSubtract([0, 9, 1, 5, 3], 11));

// Test subtract greater than the sum of array values:
console.log(spreadSubtract([4, 0, 1, 2, 5], 20));
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}

这是当数组以升序排序时可以使用的一种方法:

const spreadSubtract = (arr, val) =>
{
    let avgSub = val / arr.length;

    return arr.map((x, idx) =>
    {
        if (x < avgSub)
        {
            avgSub = (val -= x) / (arr.length - idx - 1);
            return 0;
        }
        else
        {
            return x - avgSub;
        }
    });
}

// Tests
let test1 = [[4, 5, 6, 7, 8], 25];
let test2 = [[0, 9, 1, 5, 3], 11];
let test3 = [[4, 0, 1, 2, 5], 20];

// Note array are sorted first.
console.log(spreadSubtract(test1[0], test1[1]));
console.log(spreadSubtract(test2[0].sort(), test2[1]));
console.log(spreadSubtract(test3[0].sort(), test3[1]));
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}

答案 4 :(得分:-1)

您可以对有序数组采用单循环方法,并查看变化的值以进行分散。

function subtract(array, delta) {
    return array.map((v, i, { length }) => {
        var d = delta / (length - i);
        delta -= Math.min(v, d);
        return Math.max(0, v - d);
    });
}

console.log(subtract([4, 5, 6, 7, 8], 25));