ctci/05. Bit manipulation/5.3. Flip Bit to Win.md

5.8 KiB

5.3. Flip Bit to Win

You have an integer and you can flip exactly one bit from a 0 to a 1. Write code to find the length of the longest sequence of 1s you could create. Input: 1775, or 11011101111, Output: 8

Brute force idea: find all 0s, flip all of them, count all 1s sequences (how?), output max.

Another idea: find longest sequence of 1s as of now, flip adjacent 0 and count length, flip other 0 (if not end/beginning of sequence) and count length, output max.

But how to find first 0? and count consecutive 1s? And how to make it efficient?

Hint 1

Start with a brute force solution for each

Flipping function, need the position of the 0 bit.

boolean setBit(int num, int i) {
    return num | (1 << i);
}

Length of the Longest Consecutive 1s in Binary Representation. Bit magic.

#include<bits/stdc++.h>
using namespace std;
  
int maxConsecutiveOnes(int num) {
    // Initialize result
    int count = 0;
  
    // Count the number of iterations to reach num = 0.
    while (num!=0) {
        // This operation reduces length
        // of every sequence of 1s by one.
        num = (num & (num << 1));
        count++;
    }
  
    return count;
}
  11101111   (x)
& 11011110   (x << 1)
----------
  11001110   (x & (x << 1))
    ^    ^
    |    |
trailing 1 removed

The operation x = (x & (x << 1)) reduces length of every sequence of 1s by one in binary representation of x. If we keep doing this operation in a loop, we end up with x = 0. The number of iterations required to reach 0 is the length of the longest consecutive sequence of 1s.

If I find all 0s, flip them, I can use this to find the longest sequence.

Number of leading zeros in binary representation of a given number.

#include <vector>
using namespace std;

bool findPosof0(int num) {
  int i = sizeof(num) * 8; // number of bits in num
  vector<int> positions;

  while (i--) {
    // checking if i th bit of num is 1
    if (!(num & (1 << i))) {
      positions.push_back(i);
    }
    num = (num << 1);
  }
  // found first 0
  return positions;
}

int num = 17;
int max1s = 0;
// finds all positions of 0
vector<int> positions0 = findPosOf0(num);

for (pos : positions0) {
  // flip the first 0, returns a boolean so don't know if this works
  num = setBit(num, first0);
  max1s = max(max1s, maxConsecutiveOnes(num));
}

// count consecutive 1s
cout << max1s << endl;

Time complexity O(2n) and space complexity O(2n) (n=# bits)

Hints

Get Next: Picture a binary number-something with a bunch of 1s and Os spread out throughout the number. Suppose you flip a 1 to a 0 and a 0 to a 1. In what case will the number get bigger? In what case will it get smaller?

Flipping 1 to 0 makes it smaller, and vice versa.

Flipping a O to a 1 can merge two sequences of 1s-but only if the two sequences are separated by only one 0

Exactly.

Each sequence can be lengthened by merging it with an adjacent sequence (if any) or just flipping the immediate neighboring zero. You just need to find the best choice.

So the two sequences of 1 separated by the one 0. In my algorithm I just find all 0s, flip and count, flip and count, and get the max. This takes O(n) time and O(n) space.

Try to do it in linear time, a single pass, and 0(1) space.

I could if I had a string, but I guess I can't convert it to a string. I'd start counting all the 1s until finding a 0, keep a flag, count all 1s. When next 0 is found, flag=0, max1s=num, num=0, repeat. and then return max1s. But I don't know how to do that with logic operations.

Solution

Brute force

Count all sequences of 1s and 0s, start from right to left and in each iteration either add one to the count of 1s, or add one to the count of 0s, or switch. How to switch? int searchingFor = n & 1; If n ends with 1, then searchingFor=1. if not, 0. Returns the rightest bit in the number. Then, move the number by n >>>= 1;

  • At the end of the function, we end up with [1, 2, 3, 3, 1]. Starting from right to left, there's one 0, then two 1s, then three 0s, etc.
  • Then we search for the longest sequence. Iterate the sequence, get the # zeros in the sequence and # ones in left and right, if inside of bounds.
  • If the amount of 0s is 1, then we can merge. And the sequence length is length of 1s to the left + 1 + length of 1s to the right.
  • If there's more than one 0, add 1 to the longest sequence of 1s. 1110011, 3 > 2 --> 3 + 1 = 4. longest sequence of 1s would be of length 4.
  • Update the maximum sequence length iteratively
  • The end

This is kind of what I had thought of.

Optimal algorithm

O(b) time complexity, b=#num_bits and O(1) space. To reduce the space usage, note that we don't need to hang on to the length of each sequence the entire time. We only need it long enough to compare each 1s sequence to the immediately preceding 1s sequence.

We canjust walk through the integer doing this, tracking the current 1s sequence length and the previous 1s sequence length. When we see a 0, update previousLength (see code in link).

  • If the next next bit is a 1 (by doing n & 2, or checking if the last 2 bits are 10 or 00), previousLength should be set to currentLength.
  • If the next next bit is a 0, then we can't merge these sequences together. So, set previousLength to 0.

currentLength keeps track of the sequences of 1 without any unflipping. Each time we encounter a 0, we check for the next bit and update the previous and current lengths, O(1) space complexity.