3
votes

If given a circular(meaning you can swap 0 with sizeOfString-1) string of 1's and 0's, what's good algorithm to show the minimum number of adjacent swaps needed to group all of the 1's together. The 1's don't need to be grouped at any specific place in the string. They just need to be grouped in whatever place provides for the minimum number of adjacent swaps.

For example, if the string looks like this...

011010110001

The minimum number of adjacent swaps would be 6, because you'd do the following swaps:

Swap indices 0 and 11, resulting in: 111010110000

Swap indices 3 and 4, resulting in: 111100110000

Swap indices 5 and 6, resulting in: 111101010000

Swap indices 4 and 5, resulting in: 111110010000

Swap indices 6 and 7, resulting in: 111110100000

Swap indices 5 and 6, resulting in: 111111000000

Anyone have a good algorithm for finding the minimum number of adjacent swaps for any string of 1's and 0's?

2
From your description i would say that the minimum would be 2 not 6. Swap 3-7 and swap 5-11. Maybe you need to phrase the thing a bit better if your example is correct.Ralf
When you say that the 1 (and the 0) must be grouped, is it in a circular way?. In other words, is 1000111 a valid sequence?Damien
they need to be adjacent swaps they must be next to each otherKosta
yes 1000111 is a valid sequenceKosta
I think the same question with accepted answer in this stackoverflow entry . I don't know is it help you ?ibra

2 Answers

1
votes

So I was inspired by this answer, please read that answer first, the question it belongs to is basically the same as this one, except that in this question the input string is circular. The key idea in that answer is, in order to find the minimum swaps to group all 1's, we just need to get an array of all indices of 1's, the center of that array will always hold the center index, then calculate the minimum swaps by following algorithm, the time complexity is 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);

Based on that answer, a quick solution for this question is to break the circle at some point and stretch it to be a straight line, let's say str, and then apply the above algorithm to it, then we find the minimum swaps for str. Let n be the length of input circular string, then there are n points where we can break the circle, and the final time complexity for this solution will be O(n^2).

So I was trying to come up with a solution that takes O(n) time complexity .In the algorithm above, what determines the swaps is the relative distances between the middle 1 and the other 1's.Since we already calculated the minimum swaps for str, is there any way we can reuse it in other cases? so we don't have to do a for-loop to calculate minimum swaps in every case, then the time complexity will be O(n).

Of all n possible points where we can break the circle, there are some points we don't need, take 100011 as the input circular string, we break it at two different points, one is between the indices 0 and 1, and the other is between 1 and 2, then we will get:

000111  and  001110

in the above algorithm, we only need to calculate the relative distances from the other 1's to the middle 1, in this two cases, what determines the minimum swaps is their common substring that contains all 1's, which is 111. So to avoid this kinda cases where we repeatedly calculate the same result, we only break the circle immediately after every 1's.

To reuse last result, we break the circle immediately after 1's from left to right orderly, take 011010110001 as an example, we first calculate the minimum swaps, then break it between indices 1 and 2, for the sake of simplicity, when we stretch the circle to be a straight line, we still keep the numbers as 0's before the point where we break the circle.

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

My final solution written in javascript that has O(n) time complexity :

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"));
0
votes

Find a good starting point and move everything of the same value towards that cluster.

Note: Python is a bit rusty and not setup to test it atm (typically work with Ruby these days), but should be pretty close to valid python, if not valid.

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

We want to find a run of the longest size, and start at the end of that run. This prevents something like: 10000000000000011101 from giving the incorrect results bad performance if you started at index 0. Instead it would be better to start at the last 0 in the big group of zeros. In that case, you just need to do a single swap on the next zero to move it backwards.

Performance is O(N), where N is the total number of ones and zeros.

Examples: 111000111000

Here we have 4 runs of equal size. Algorithm should determine that index 2 is the best place to start (end of first group). It will scan, find the second group of ones and move them over 1 by one. Swaps is 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)

Hmmm this one raises a good point, algorithm had to be adjusted to search bidirectionally from the best cluster (instead of just going right), fixed though now it does do about 2x the work it strictly needs to (it could end halfway around on the other side of the array, circularly).

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