例如,如果字符串如下所示:
011010110001
最少的相邻交换次数将是6,因为您会执行以下交换:
交换索引0和11,结果为:111010110000
交换索引3和4,结果为:111100110000
交换索引5和6,结果为:111101010000
交换索引4和5,结果为:111110010000
交换索引6和7,结果为:111110100000
交换索引5和6,结果为:111111000000
有没有一个好的算法可以找到任何1和0字符串的最小相邻交换次数?
找到一个好的起点,将所有相同价值的东西移动到那个聚类中。
注意:Python 有点生疏,目前没有设置测试(通常使用 Ruby 这些天),但应该非常接近有效的 Python,如果不是有效的话。
def longest_cluster_ending_indexes(array)
# Scan the array to find the end index of the longest cluster of the same value
index = 0
check = array[start]
best_index = 0
most_count = 1
count = 1
for item in array[1:-1]:
index += 1
if item == check:
count += 1
if count > most_count:
best_index = index
most_count = count
else:
check = item
count = 1
return (best_index - most_count, best_index)
last_seen_left,last_seen_right = longest_cluster_ending_index(array)
check = array[last_seen_right]
index_left = start_left = last_seen_left
index_right = start_right = last_seen_right
swaps = 0
while true:
index_left -= 1
if array[index_left] == check
diff = (index_left - last_seen_left) - 1
swaps += diff
last_seen_left -= 1
if array[index_right] == check
diff = (index_right - last_seen_right) - 1
swaps += diff
last_seen_right += 1
break if index_left % len(array) == start_right # Went around the array
break if index_right % len(array) == start_left # Went around the array
我们想要找到最长的一组连续值,并从该连续值的末尾开始。这可以避免像 10000000000000011101
这样的情况在索引 0
开始时给出错误的结果和糟糕的性能。相反,最好从一组大量零中的最后一个零开始。在这种情况下,您只需要对下一个零进行单个交换即可将其向后移动。
性能为 O(N)
,其中 N
是所有 1 和 0 的总数。
示例:
111000111000
这里我们有 4 组相等大小的连续值。算法应确定索引2是最佳起点(第一组的结尾)。它将扫描,找到第二组 1 并逐一移动它们。交换次数为 3 + 3 + 3 = 9。
111100011000 3 swaps. index 7 - index 3 - 1 = 3 swaps (right side)
111110001000 3 swaps. index 8 - index 4 - 1 = 3 swaps (right side)
111111000000 3 swaps. index 9 - index 5 - 1 = 3 swaps (right side)
11101100001
11101100001 # Second to last is starting index
11111000001 # index 3 and index 6, cost is 2 swaps (from the left side)
01001001100 => left start = 2 (moving left), right start = 3 (moving right)
10000101100 => Swap left and right (cost = 1 + 1 = 2 swaps)
00000011101 => Swap left and right (cost = 1 + 1 = 2 swaps)
00000011110 => One more swap
So 5 swaps.
011010110001 => Left start at 8, right start and the last index
111011100000 => 2 swaps and 1 swap = 3 swaps
011111100000 => 3 swaps
6 swaps
find_index_within_the_most_ones_or_zeros
,它是算法中最重要的部分,但在此处并没有明确定义... - trincotlongest_group_ending_index
,以更准确地描述它所寻找的内容。 - Nuclearman所以我受到了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
之间的相对距离。既然我们已经计算出了str
的最小交换次数,那么我们是否可以在其他情况下重复使用它?这样我们就不必在每种情况下进行for循环来计算最小交换次数,那么时间复杂度将为O(n)
。
在所有可能的断点中,有些是我们不需要的。以输入的循环字符串100011
为例,我们在两个不同的点上断开它,一个是在索引0
和1
之间,另一个是在1
和2
之间,然后我们会得到:
000111 and 001110
在上述算法中,我们只需要计算其他 1
距离中间的 1
的相对距离。在这两种情况下,决定最小交换次数的是它们包含所有 1
的公共子串,即为 111
。因此,为了避免重复计算同一结果的情况,我们只在每个 1
后立即打破循环。
为了重用上一次的结果,我们按照从左到右的顺序依次在每个 1
后打破循环。以 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"));
Array.prototype.splice()
的时间复杂度是 O(n)
,结果我的最终解决方案实际上需要 O(n^2)
的时间复杂度。为了将时间复杂度降至 O(n)
,我们不会每次都删除 oneIndices
中的第一个索引 1
,而是增加数组 oneIndices
的开头,并在获取 firstOneIndex
、计算 middleOfOneIndices
和 distanceInInput
时将其计入。 - Qiu Zhou
1000111
是一个有效的序列吗? - Damien