受@Nabbs评论的启发,有一种简单的方法可以在线性时间内解决这个问题:将问题转化为最大段和。
将所有的0变成1,所有的1变成-1。问题就变成了在变换后的数组中最小化数组的和。(变换后的最小和包含了变换后的数组中大多数的-1,对应原问题中的大多数1)
我们可以计算总和:
sum(after flipping) = sum(non-flipped) - sum(flipped part before flipping)
因为翻转部分的和被倒置了。现在,如果我们将未翻转部分表示如下:
sum(non-flipped) = sum(original array) - sum(flipped part before flipping)
我们发现需要将其最小化。
sum(after flipping) = sum(original array) - 2 sum(flipped part before flipping)
第一部分是一个常数,因此我们需要真正最大化翻转部分的总和。这正是最大连续子段和问题所做的。
我曾经写了一个冗长的推导来解决这个问题,时间复杂度为线性 一段时间前,所以现在我只分享代码。下面我更新了代码,以便还能存储边界。我选择 JavaScript 作为语言,因为它在浏览器中非常易于测试,而且我不必显式地声明变量 x
和 y
的类型。
var A = Array(1, 0, 1, 0, 0, 1, 0, 1);
var sum = 0;
for(var i = 0; i < A.length; i++) {
sum += A[i];
A[i] = A[i] == 0 ? 1 : -1;
}
var x = { value: 0, left: 0, right: 0 };
var y = { value: 0, left: 0 };
for (var n = 0; n < A.length; n++) {
if (y.value + A[n] >= 0) {
y.value += A[n];
} else {
y.left = n+1;
y.value = 0;
}
if (x.value < y.value) {
x.left = y.left;
x.right = n;
x.value = y.value;
}
}
alert("result = " + (sum + x.value)
+ " in range [" + x.left + ", " + x.right + "]");
您可以轻松地在浏览器中验证此操作。例如,在 Chrome 浏览器中,按 F12 键,点击控制台,然后粘贴这段代码。它应该会输出
result = 6 in range [1, 4]