所以我受到this answer的启发,请先阅读那个答案,它所属的问题与这个问题基本相同,只是在这个问题中输入字符串是循环的。该答案的关键思想是,为了找到对所有 1 进行分组的最小交换,我们只需要获取一个包含所有 1 索引的数组,该数组的中心将始终包含中心索引,然后通过以下算法计算最小交换,时间复杂度为O(n):
oneIndices = array of indices of all 1's in the input
middleOfOnesIndices = round(oneIndices.length/2)-1 // index to the center index
minimumSwaps = 0
foreach index i of oneIndices
minimumSwaps += aboluteValue(oneIndices[middleOfOneIndices]-oneIndices[i])-absoluteValue(middleOfOneIndices-i);
基于那个答案,这个问题的一个快速解决方案是在某个点打破圆圈并将其拉伸成一条直线,比如说str,然后将上述算法应用于它,然后我们
找到str 的最小互换。设n为输入循环字符串的长度,则有n可以破圈的点,这个解的最终时间复杂度为O(n^2)。
所以我试图想出一个需要O(n)时间复杂度的解决方案。在上面的算法中,决定交换的是中间1和另一个1's之间的相对距离。因为我们已经计算了str 的最小交换,有什么办法可以在其他情况下重用它?
所以我们不必在每种情况下都进行 for 循环来计算最小交换,那么时间复杂度将为O(n)。
在所有n可以破圈的可能点中,有些点是不需要的,以100011作为输入循环字符串,我们在两个不同的点上破圈,一个在索引之间0和1,另一个在1和2之间,那么我们会得到:
000111 and 001110
在上面的算法中,我们只需要计算另一个1's到中间1的相对距离,在这两种情况下,决定最小掉期的是他们的包含所有1's的公共子字符串,即111。所以为了避免这种我们重复计算相同结果的情况,我们只在每个1's之后立即打破循环。
为了重用最后的结果,我们在1's之后从左到右有序地打破圆圈,以011010110001为例,我们首先计算最小交换,然后在索引1和2之间打破它,为了简单起见,当我们把圆拉伸成直线时,我们仍然保持在我们打破圆的点之前的数字为0。
011010110001 <---before
00101011000101 <---after break it between indices 1 and 2
^^ <--- these two 0's actually don't exist, but to keep the indices of 1's to it's right
as unchanged, so we can reuse last result handily.
the array of all indices of 1's:
[1,2,4,6,7,11] <----before
[2,4,6,7,11,13] <----after break it between indices 1 and 2
我用 javascript 编写的最终解决方案具有 O(n) 时间复杂度:
function getOneIndices(inputString) {
var oneIndices = [];
for (var i=0; i<inputString.length; i++) {
if (inputString.charAt(i) === "1") {
oneIndices.push(i);
}
}
return oneIndices;
}
function getSwapInfo(inputString) {
var oneIndices = getOneIndices(inputString);
if(oneIndices.length < 2) return 0;
var minSwaps = Number.MAX_VALUE;
var distanceInInput = 0, distanceInOneIndices = 0;
var middleOfOneIndices = Math.round(oneIndices.length/2)-1;
for (var i=0; i<oneIndices.length; i++) {
distanceInInput += Math.abs(oneIndices[middleOfOneIndices]-oneIndices[i]);
distanceInOneIndices += Math.abs(middleOfOneIndices-i);
}
for(var i = 0, lastOneIndexMoved = -1, endIndexInInput = inputString.length - 1;
i <= oneIndices.length; i++){
var curSwaps = distanceInInput - distanceInOneIndices;
if(curSwaps < minSwaps) minSwaps = curSwaps;
var lastMiddleIndex = oneIndices[middleOfOneIndices];
//move the first 1 to the end as we break the circle after it.
var firstOneIndex = oneIndices[0];
oneIndices.splice(0, 1);
var lastOneIndex = endIndexInInput + firstOneIndex - lastOneIndexMoved;
oneIndices.push(lastOneIndex);
//when we move one step further, the index of the center also move one step further,
// but the length of the indices array of 1's doesn't change, we don't
//need to do middleOfOneIndices++
var diff = oneIndices[middleOfOneIndices] - lastMiddleIndex;
distanceInInput += middleOfOneIndices * diff
- (oneIndices.length - middleOfOneIndices - 1) * diff
- Math.abs(lastMiddleIndex - firstOneIndex)
+ Math.abs(oneIndices[middleOfOneIndices] - lastOneIndex);
lastOneIndexMoved = firstOneIndex;
endIndexInInput = lastOneIndex;
}
return minSwaps;
}
console.log("minimum swaps for '111111': " + getSwapInfo("111111"));
console.log("minimum swaps for '111011': " + getSwapInfo("111011"));
console.log("minimum swaps for '010001': " + getSwapInfo("010001"));
console.log("minimum swaps for '011010110001': " + getSwapInfo("011010110001"));
console.log("minimum swaps for '10000000000000011101': " + getSwapInfo("10000000000000011101"));
console.log("minmum swaps for '01001001100': " + getSwapInfo("01001001100"));