2

I am trying to solve a Data Structures and Algorithms problem, which states that given a group of 1s and 0s, group the digits such that all 0s are together and all 1s are together. What is the minimum number of swaps required to accomplish this if one can only swap two adjacent elements? It does not matter which group is at what end.

Eg:

[0,1,0,1] = [0,0,1,1] 1 swaps

[1,1,1,1,0,1,0] = [1,1,1,1,1,0,0] 1 swaps

[1, 0, 1, 0, 0, 0, 0, 1] = = [1,1,1,0,0,0,0,0] 6 swaps

Note that this is different from the questions asked here:

Find the minimum number of swaps required such that all the 0s and all the 1s are together

I am not sorting the array, I am just trying to group all the 0s and all the 1s together and it does not matter which is at which end.

I really have no clue where to even start. Can someone help me?

  • Not sure how you're counting swaps. In your first example I can get that result with one swap, swapping elements 1 and 2. – Mark Ransom Aug 20 '20 at 22:24
  • @MarkRansom Ahh, yes this was a mistake. Edited it! Any help would be appreciated! –  Aug 20 '20 at 22:24
  • Same with the second example, plus there appears to be an extra `1` in the output. – Mark Ransom Aug 20 '20 at 22:25
  • Try Bubble Sort with just a little bit of adjustment. – Giorgi Tsiklauri Aug 20 '20 at 22:26
  • @MarkRansom Ahh! I was being careless! Sorry! –  Aug 20 '20 at 22:26
  • @GiorgiTsiklauri Can you explicate a little more? Sorry! This is my first DSA course! With a bubble sort, won't it inevitably push all the 0s to the left? –  Aug 20 '20 at 22:27
  • You can adjust your bubble sort so that it pushes greater elements on the left side. Reverse the comparison of the elements (`>` instead of `<`). Any sorting algorithm would do fine. What is the desired time complexity you want to have? – Giorgi Tsiklauri Aug 20 '20 at 22:29
  • The question did not specify, but preferably low! –  Aug 20 '20 at 22:33
  • @GiorgiTsiklauri a bubble sort would seem to be perfect, since it works exactly as the description calls for. The linked question has that as the answer. But it doesn't give any indication whether sorting forwards or reverse would be better, so you need to do it twice to get the correct answer. – Mark Ransom Aug 20 '20 at 22:42
  • @MarkRansom I actually haven't even opened that link.. because it's a bit uncomfortable to surf the SO from the small screen. :) however, the OP can introduce one more parameter as a flag, for either descending or ascending. – Giorgi Tsiklauri Aug 20 '20 at 22:45
  • @user1234 did the solution work? – Giorgi Tsiklauri Aug 20 '20 at 23:19

3 Answers3

5

Let's focus on zeroes. Each swap moves a single zero a single position closer to the final order. Then we can find the number of swaps by finding the number of displaced zeroes, and the severity of the displacement.

Let's start by assuming that the zeroes end up at the start of the array. We'll keep track of two things: count_of_ones, and displacement, both initialized to zero. Each time we find a 1, we increment count_of_ones. Each time we find a 0, we increase displacement by count_of_ones.

Then we do this in the other direction. Both ways are linear, so this is linear.

E.g. 1010001

1: count_of_ones: 0 -> 1
0: displacement: 0 -> 1
1: count_of_ones: 1 -> 2
0: displacement: 1 -> 3
0: displacement: 3 -> 5
0: displacement: 5 -> 7
1: count_of_ones: 2 -> 3

The answer for this direction is the final displacement, or 7. Going the other way we get 5. Final answer is 5.

In fact, the sum of the final displacements (starting vs ending with all zeroes) will always equal num_zeroes * num_ones. This halves the work (though it's still linear).


From the comments it seems some people didn't understand my answer. Here's a Ruby implementation to make things clearer.

def find_min_swaps(arr)
  count_of_ones = 0
  displacement = 0
  arr.each do |v|
    count_of_ones += 1 if v == 1
    displacement += count_of_ones if v == 0
  end

  count_of_zeroes = arr.length - count_of_ones
  reverse_displacement = count_of_ones * count_of_zeroes - displacement
  return [displacement, reverse_displacement].min
end

The zeroes end up on the left if displacement < reverse_displacement, either if they're equal, or the right if displacement > reverse_displacement.

Dave
  • 7,460
  • 3
  • 26
  • 39
  • So sorry, how did you get 5? or 7? –  Aug 20 '20 at 22:58
  • This is quite unclear to me.. 1) what is the final order?; 2) moves zeros where? right? left?; 3) displacements can differ depending on in what order you aim to sort the array, correspondingly, counting of the displacements would result in a different outcome; 4) why you orientate on Zeros? I'm sorry, it might be me, but I just struggle to understand even the logic of your answer.. not even getting to the level of pseudo code. You may know what you have here, but explanation is definitely quite poor.. well, at least to me. Having some snipped of code would've been great. – Giorgi Tsiklauri Aug 20 '20 at 23:23
  • 1
    @GiorgiTsiklauri Code provided. – Dave Aug 21 '20 at 02:23
  • Thanks for sharing your code, I understood most of it, can you explain how you came up with the method to calculate the displacement as disp+count_of_ones every time we find a 0, I am not sure how I would come up with this intuitively if I got this question in an interview. – Ghos3t Feb 28 '21 at 18:33
  • @Ghos3t Sure. The idea is that the all the zeroes need to move past all the ones on one side or the other of them depending whether they end up on the left or right side. E.g. 1010001111111: The first zero needs to move past one 1, but the next 3 need to move past the two 1s that are to their left. Is that clear? If not, I can try editing the answer to give more examples and explanation. – Dave Feb 28 '21 at 22:31
  • @Dave I was wondering if you can explain a bit more how you came to the solution. I think the gist of it makes sense after looking at it for a while but I don't get how you come to some realizations. For example, how did you come up with this displacement idea and "the sum of the final displacements (starting vs ending with all zeroes) will always equal num_zeroes * num_ones"? – ashkan117 Apr 27 '21 at 20:49
  • 1
    @ashkan117 The sum of displacements insight comes from the fact that, if all zeroes were on one side, moving them all to the other side would require swapping every zero with every one. The order of these swaps doesn't matter. So any state that doesn't have all the zeroes on one side can be viewed as a partial state in such a transition. – Dave Apr 27 '21 at 21:31
  • @Dave I can see that intuitively. Is there a way to prove that more formally? It's hard to see how that holds up for all partial states – ashkan117 Apr 27 '21 at 22:20
  • 1
    @ashkan117 Sounds like a good candidate for a new question here or maybe on one of the math overflow boards. – Dave Apr 28 '21 at 13:15
1

Let SUM0 be the sum of the (0-based) indexes of all the zeros, and let SUM1 be the sum of the indexes of all the ones. Every time you swap 10 -> 01, SUM0 goes down by one, and SUM1 goes up by one. They go the other way when you swap 01 -> 10.

Lets say you have N0 zeros and N1 ones. If the zeros were packed together at the start of the array, then you would have SUM0 = N0*(N0-1)/2. That's the smallest SUM0 you can have.

Since a single adjacent swap can reduce SUM0 by exactly one, it takes exactly SUM0 - N0*(N0-1)/2 swaps to pack the zeros together at the front. Similarly, it takes SUM1 - N1*(N1-1)/2 swaps to pack the ones together at the front.

Your answer is the smaller of these numbers: min( SUM0 - N0*(N0-1)/2 , SUM1 - N1*(N1-1)/2 )

Those values are all easy to calculate in linear time.

Matt Timmermans
  • 53,709
  • 3
  • 46
  • 87
0

Simple approach using Bubble Sort, which takes O(n2), would be this:

public class MainClass {

    public static void main(String[] args) {
        int[] arr = new int[]{1, 0, 0, 0, 0, 0, 0, 1, 0};
        int minSwaps = minimumSwaps(arr);
        System.out.println("Minimum swaps required: " + minSwaps);
    }

    public static int minimumSwaps(int[] array) {
        int[] arr1 = array.clone(), arr2 = array.clone();
        int swapsForRight = 0, swapsForLeft = 0;

        boolean sorted = false;

        while (!sorted) {
            sorted = true;
            for (int i = 0; i < arr1.length - 1; i++) {
                if (arr1[i + 1] < arr1[i]) {
                    int temp = arr1[i + 1];
                    arr1[i + 1] = arr1[i];
                    arr1[i] = temp;
                    sorted = false;
                    swapsForRight++;
                }
            }
        }
            
        sorted = false;
        while (!sorted) {
            sorted = true;
            for (int i = 0; i > arr2.length - 1; i++) {
                if (arr2[i + 1] < arr2[i]) {
                    int temp = arr2[i + 1];
                    arr2[i + 1] = arr2[i];
                    arr2[i] = temp;
                    sorted = false;
                    swapsForLeft++;
                }
            }
        }
        return swapsForLeft > swapsForRight ? swapsForRight : swapsForLeft;
    }
}
Giorgi Tsiklauri
  • 9,715
  • 8
  • 45
  • 66