【问题标题】:Given a circular string of 0 and 1, find minimum no. of adjacent swaps to bring all 1s together给定一个由 0 和 1 组成的圆形字符串,求最小编号。将所有 1 放在一起的相邻掉期
【发布时间】:2021-02-03 01:23:15
【问题描述】:

如果给定一个循环(意味着您可以用 sizeOfString-1 交换 0)字符串 1 和 0,那么显示将所有 1 组合在一起所需的相邻交换的最小数量的好算法是什么。 1 不需要在字符串中的任何特定位置分组。只需将它们分组在任何提供最少相邻交换数量的位置即可。

例如,如果字符串看起来像这样......

011010110001

相邻交换的最小数量为 6,因为您将执行以下交换:

交换索引 0 和 11,得到:111010110000

交换索引 3 和 4,得到:111100110000

交换索引 5 和 6,得到:111101010000

交换索引 4 和 5,得到:111110010000

交换索引 6 和 7,得到:111110100000

交换索引 5 和 6,得到:111111000000

谁有一个很好的算法来找到任何 1 和 0 的字符串的最小相邻交换数?

【问题讨论】:

  • 根据您的描述,我会说最小值是 2 而不是 6。交换 3-7 和交换 5-11。如果你的例子是正确的,也许你需要更好地表达这件事。
  • 当你说1(和0)必须分组时,是不是以循环方式?换句话说,1000111 是一个有效的序列吗?
  • 它们必须是相邻的交换,它们必须彼此相邻
  • 是的 1000111 是一个有效的序列
  • 我认为在这个stackoverflow entry 中与接受的答案相同的问题。不知道对你有帮助吗?

标签: string algorithm sorting


【解决方案1】:

所以我受到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作为输入循环字符串,我们在两个不同的点上破圈,一个在索引之间01,另一个在12之间,那么我们会得到:

000111  and  001110

在上面的算法中,我们只需要计算另一个1's到中间1相对距离,在这两种情况下,决定最小掉期的是他们的包含所有1's的公共子字符串,即111。所以为了避免这种我们重复计算相同结果的情况,我们只在每个1's之后立即打破循环。

为了重用最后的结果,我们在1's之后从左到右有序地打破圆圈,以011010110001为例,我们首先计算最小交换,然后在索引12之间打破它,为了简单起见,当我们把圆拉伸成直线时,我们仍然保持在我们打破圆的点之前的数字为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"));

【讨论】:

  • 没有注意到javascript中Array.prototype.splice()的时间复杂度是O(n),然后我的最终解决方案实际上是O(n^2)。为了将时间复杂度降低到O(n),我们不会每次都删除oneIndices1的第一个索引,而是增加数组oneIndices的开头,并在得到firstOneIndex时计算@ 987654361@ 和 distanceInInput.
【解决方案2】:

找到一个好的起点,将所有具有相同价值的东西移向该集群。

注意:Python 有点生疏,无法在 atm 测试它(现在通常与 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

我们想找到一个最长的运行,并从该运行的末尾开始。如果您从索引0 开始,这可以防止类似:10000000000000011101 给出不正确的结果糟糕的性能。相反,最好从大组零中的最后一个 0 开始。在这种情况下,您只需在下一个零上进行一次交换即可将其向后移动。

性能为O(N),其中N 是1 和0 的总数。

示例: 111000111000

这里我们有 4 个大小相等的运行。算法应确定索引 2 是最佳起点(第一组的结尾)。它将扫描,找到第二组并将它们一个接一个地移动。掉期为 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)

嗯,这个提出了一个很好的观点,必须调整算法以从最佳集群进行双向搜索(而不是仅仅向右),虽然现在它确实做了大约 2 倍的严格需要的工作(它可能会中途结束围绕在阵列的另一侧,循环)。

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,这是算法中最重要的部分,但这里没有明确定义......
  • 10000000000000011101 只需要 2 次交换,最后 1 个向左交换 10000000000000011110 然后第一个 1 向左交换,我们得到 00000000000000011111 所以这不是一个最佳解决方案
  • @trincot 添加了缺少的功能。还将其重命名为longest_group_ending_index,以便更准确地了解它要查找的内容。
  • 注意到我们需要从最长的簇向左和向右移动。
猜你喜欢
  • 1970-01-01
  • 2015-10-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-17
相关资源
最近更新 更多