Leetcode practice – bit operation (division of two numbers, numbers that appear only once, repeated DNA sequences, etc.)

Time:2021-9-14

preface

The operation of 0 and 1 is the bottom operation of the computer. All programs, no matter what language they are written in, must be transformed into a language that the machine can understand, that is, binary for basic operations, and these basic operations are the bit operations we want to talk about today. Because of the support of hardware, the computer is much faster than the ordinary decimal calculation. It is an important skill to realize the ordinary operation with the method of bit operation, which can greatly improve the program performance.

brief introduction

There are more than seven bit operations in the computer. Here, take Java as an example to explain the seven bit operators that need to be used in Java.

Symbol describe rule give an example
& Bitwise AND If the left and right sides are 1 at the same time, get 1, otherwise it is 0 1&1=1
1&0=0
| Bitwise OR As long as one of the left and right sides is 1, it returns 1, and it returns 0 only when both are 0 1|0=1
0|0=0
^ Bitwise XOR When the left and right sides are the same, it is 0, and the difference is 1 1^1=0
0^0=0
1^0=1
~ Bitwise inversion 1 becomes 0, 0 becomes 1 ~1=0
~0=1
<< Signed shift left Keep the highest symbol, move the other bits to the left, and fill in the right with 0 1<<2=4
>> Signed right shift Keep the highest bit symbol, move other bits to the right, and supplement with 0 except the highest bit 5>>1=2
-10>>1=-5
>>> unsigned right shift All bits move to the left, and the high bits are filled with 0 5>>>1=2
-10>>>1=2147483643

Now that we talk about bit operations, we have to mention that in JavaintFor the representation of type numbers, Java’s int is a total of 32 bits, of which the 32nd symbol flag bit. If it is 0, it represents a positive number, and 1 represents a negative number. For example, the representation method of 1 is0000 0000 0000 0000 0000 0000 0000 0001, the maximum can only be the result of setting the first 31 bits to 1, that is, $2 ^ {31} – 1 $; For the representation of negative numbers, it should be noted that instead of directly changing the highest bit of the corresponding positive number from 0 to 1, all bits are reversed and then added by one. The purpose of this is to make the sum of two binary numbers directly added to 0, such as-1The representation of is1111 1111 1111 1111 1111 1111 1111 1111In this way, the final result of the binary addition of the two numbers is 0 (the highest bit overflow is discarded). The minimum value of a negative number is $2 ^ {31} $, and the absolute value is one bit higher than the maximum value of a positive number. Because this negative number is special, binary is expressed as1000 0000 0000 0000 0000 0000 0000 0000, after taking the inverse plus one, it’s still itself.

By the way, here is a small skill of lower level operation, in which the principle can be guessed by yourself

Serial number formula Explain(nfrom0(start)
1 num |= 1<<n takenumThe firstnBit set to1
2 num &= ~(1<<n) takenumThe firstnBit set to0
3 num ^= 1<<n takenumThe firstnBit inversion
4 num & (1<<n) == (1<<n) inspectnumThe firstnWhether the bit is1
5 num = num & (num-1) takenumLowest1Set0
6 num & -num getnumLowest1
7 num &= ~((1<<n+1)-1) takenummostnPosition right0
8 num &= (1<<n)-1 takenumThe firstnLeft position0

More operation related skills can be seen here.


Here’s the floating point typefloatIf you have a clear representation in Java, you can skip this part directly. Floating point numbers are 4 bytes in total, which can be expressed by the following formula:

$$ (-1)^s \times m \times 2^{e-127} $$

among

  • s, the 31st bit. If it is 1, it represents a negative number, and 0 represents a positive number
  • eThe 30th ~ 23rd bits refer to digits, representing an unsigned integer, with a maximum of 255
  • m, bits 22-0, mantissa, including hidden1, indicating decimal places, the following invalid 0 should be removed

Like floating point numbers0.15625The representation in binary is0011 1110 0010 0000 0000 0000 0000 0000(using java code)Integer.toBinaryString(Float.floatToIntBits(0.15625F))The binary representation of floating-point numbers can be obtained, but the highest bit is omitted0), where

  • 31st place0, indicating a positive number
  • The 30th to 23rd,011 1110 0Exponential bit, decimal is124, minus 127-3
  • Bits 22-0,010 0000 0000 0000 0000 0000Mantissa, rounding off the number after the decimal0Later get01, plus the default 11.01(decimal in binary)
  • To sum up, it is obtained according to the formula0.15625The binary formula of is $(- 1) ^ 0 \ times (1.01) \ times2 ^ {- 3} = 0.00101 $, and then converting binary to decimal becomes $2 ^ {- 3} + 2 ^ {- 5} = 0.125 + 0.03125 = 0.15625$

The calculation method of double type is the same as that of float, except that the index bit of double has 11 bits and the mantissa bit has 52 bits. 1023 needs to be subtracted when calculating the index. Compared with float, the accuracy of double is much higher. If you don’t care about space consumption, you’d better use double.


Leetcode related topics

Now we know the basic computer operation and the binary representation of numbers, so how to complete the calculation of decimal numbers through binary bit operation? Let’s find out one by one.

371 sum of two integers

Leetcode question 371 sum of two integers

Not usedoperator+and-, calculate two integers​​​​​abSum of.

Example 1:

Input: a = 1, B = 2
Output: 3

Example 2:

Input: a = – 2, B = 3
Output: 1

There are more than 1900 dislikes on leetcode’s U.S. website. It seems that everyone has great opinions on this topic, but let’s take this opportunity to review how computers perform addition operations.

Review the decimal addition that we began to learn in primary school, such as15+7, lowest5+7obtain12Yes10Obtained by taking mold2, carry1, and then add the high bits1+0Plus carry1You get a high result2, put it together22。 This involves two numbers. One is the low order obtained by adding, that is5+7Results obtained2, the second is carry1。 In binary calculation, it is necessary to obtain the low order and carry of the result through bit operation. For different cases, use a table to show that the two numbers areaandb

a b Low position carry
0 0 0 0
1 0 1 0
0 1 1 0
1 1 0 1

As you can see from the table above,Low = a ^ BCarry = A & B。 This calculation may last many times. Recall that in decimal calculation, if the carry is always greater than 0, we have to calculate later. The same is true here. As long as the carry is not 0, we have to repeatedly calculate the low and carry (we need to move the carry to the left before the next calculation, so that the carry can operate with the higher bit) 。 At this timeaandbIt is the low bit and carry just calculated, which are expressed in the code of simple addition iteration:

public int getSum(int a, int b) {
    if (a==0) return b;
    if (b==0) return a;
    int lower;
    int carrier;
    while (true) {
        lower = a^b;    //  Calculate low order
        carrier = a&b;  //  Calculate carry
        if (carrier==0) break;
        a = lower;
        b = carrier<<1;
    }
    return lower;
}

29 divide two numbers

This is the division of two numbers in question 19 of leetcode

Given two integers, the divisordividendSum divisordivisor。 Dividing two numbers requires no multiplication, division and divisionmodOperator.

Returns the divisordividendDivide by divisordivisorGet the quotient.

Example 1:

Input: divide = 10, division = 3
Output: 3

Example 2:

Input: divide = 7, division = – 3
Output: – 2

explain:

  • Both the dividend and divisor are 32-bit signed integers.
  • Divisor is not   0
  • Suppose that our environment can only store 32-bit signed integers with a value range of $[− 2 ^ {31},   2^{31}   − 1]$。 In this question, $2 ^ {31} is returned if the division result overflows   − 1$。

In fact, this problem can be easily associated with the decimal division we often use. Here is an example to illustrate how to apply the idea of decimal division to binary.

Leetcode practice - bit operation (division of two numbers, numbers that appear only once, repeated DNA sequences, etc.)

Pictures are used33divide6, corresponding to binary100001divide110, there are three steps:

  1. Divide the divisor from110Start shifting left and right0Until the maximum ratio is found100001Small numbers11000(right in the figure)0It has been omitted). At this time, it moves two bits to the left, that is, times100(binary), the remainder is1000
  2. Again110Move left to maximum ratio1000Small numbers, at this time, are themselves, which is equivalent to multiplying by1(moving left)0Bits), the remainder is11
  3. Because the remainder is bigger than the divisor110If it is smaller, you can directly stop the operation and calculate the results from the above two stepsmultiplier100and1)Add up to our final result(101)。

Here we use java code to implement this logic:

public int divide(int dividendInt, int divisorInt) {
    int shiftedDivisor;                   //  Shifted divisor
    int quotient = 0;                     //  Record the quotient obtained by division
    int remainder = dividendInt;

    while (remainder>=divisorInt) {
        int tempQuotient = 1;             //  Temporary business
        dividendInt = remainder;          //  Process the remainder of the previous round
        shiftedDivisor = divisorInt;      //  Reset divisor
        while (dividendInt>=shiftedDivisor) {
            shiftedDivisor <<=1;
            tempQuotient <<= 1;
        }
        quotient += tempQuotient >> 1;    //  Cumulative calculated quotient
        remainder = dividendInt - (shiftedDivisor >> 1); //  The displacement priority is lower than the minus sign, and parentheses should be used
    }
    return quotient;
}

Through the loop method, we get the implementation logic of the division we want, but this method is only for both divisor and dividendPositive numberFor example, the divisor is-100The divisor is10perhaps-10The returned result is0! about-100divide-10In this example, when comparing the size of the remainder and the shifted divisor, if they are both positive, it is normal to stop the cycle when the remainder is smaller than the divisor, but when they are all negative, there is a problem in doing so. The remainder is larger than the divisor, and the quotient is0Therefore, the unequal sign direction of the termination conditions of the above two loops can be changed.

public int divide(int dividendInt, int divisorInt) {
    int shiftedDivisor;
    int quotient = 0;
    int remainder = dividendInt;

    While (remain < = divisorint) {// note that it becomes less than or equal to
        int tempQuotient = 1;
        dividendInt = remainder;
        shiftedDivisor = divisorInt;
        While (dividedint < = shifteddivisor) {// note that it becomes less than or equal to
            shiftedDivisor <<=1;
            tempQuotient <<= 1;
        }
        quotient += tempQuotient >> 1;
        remainder = dividendInt - (shiftedDivisor >> 1);
    }
    return quotient;
}

Now we have both positive numbers and negative numbers. What about a positive number and a negative number? Then change the symbol to the same, and finally change it back when the result is returned. The title here kindly reminds us that we should consider the boundary problem when considering the conversion of symbols-2147483648The particularity of this value, that is, be careful when negative numbers become positive numbers, but positive numbers can become negative numbers at will, so it’s not as good as direct negative operation (refer to this article). Negative numbers are also used when accumulating the quotient obtained by each division-1To avoid overflow problems (e.g-2147483648divide1Spillover problem).

When shifting the divisor, it should also be noted that the divisor cannot overflow after the shift. The best way to use is to judge whether the divisor is less than half of the minimum number before the shift (if it is shifted with a positive number, it should be judged whether it is greater than half of the maximum number). If it is less than, it will overflow in the next shift, At this time, the loop can only be terminated directly.

When judging the sign of the result, use the trick of one lower bit operation. When the XOR operation is mentioned above, if it is the same, it will be returned0, otherwise1Therefore, XOR the highest bits of two numbers. If yes0It means that the result is a positive sign, otherwise it is a negative sign. In addition, there is an extreme case when the divisor takes the minimum value-2147483648And divisor-1The result is2147483648It will overflow because there is only such a special case that can be directly excluded. The remaining numbers will not overflow anyway. The sorted code is as follows:

public int divide(int dividendInt, int divisorInt) {
    if (dividendInt == Integer.MIN_VALUE && divisorInt == -1) {
        return Integer.MAX_ VALUE;         //  For extreme cases, exclude directly and return the maximum value of int
    }
    boolean negSig =
            ((dividendInt ^ divisorInt) & Integer.MIN_VALUE) == Integer.MIN_ VALUE;  //  Determine whether the result is negative

    dividendInt = dividendInt > 0 ? - dividendInt : dividendInt;                     //  The divisor takes a negative value
    divisorInt = divisorInt > 0 ? - divisorInt : divisorInt;                         //  The divisor takes a negative value

    int shiftedDivisor;                   //  Shifted divisor
    int quotient = 0;                     //  Record the quotient obtained by division
    int remainder = dividendInt;
    int minShiftDivisor = Integer.MIN_ VALUE >> 1; //  Prevent overflow. If it is greater than this value, it cannot be shifted to the left

    while (remainder<=divisorInt) {
        int tempQuotient = -1;             //  Temporary quotients are all treated as negative numbers
        dividendInt = remainder;           //  Process the remainder of the previous round
        shiftedDivisor = divisorInt;       //  Reset divisor
        While (dividedint < = (shifteddivisor < < 1) // perform the next calculation only when it is smaller than the divisor
                &&Shifteddivisor > = minshiftdivisor) {// judge whether overflow occurs after shifting
            shiftedDivisor <<=1;
            tempQuotient <<= 1;
        }
        quotient += tempQuotient;                        //  Cumulative calculated quotient
        remainder = dividendInt - shiftedDivisor;        //  Get the remainder and the next round as a new divisor
    }

    return negSig?quotient:-quotient;     //  If it is a negative number, it will return the result directly. If it is not, it will be transformed into a positive number
}

191. Number of bit 1

Number of bit 1 in question 191 of leetcode

Write a function. The input is an unsigned integer and returns the number of digits in its binary expression1Number of (also known as Hamming weight).

Example 1:

Input:00000000000000000000000000001011
Output:3
Explanation: input binary string00000000000000000000000000001011There are three digits’ 1 ‘in.

Example 2:

Input:00000000000000000000000010000000
Output:1
Explanation: input binary string00000000000000000000000010000000A total of one digit is’ 1 ‘.

The simplest way for anyone to solve this problem is to shift directly. In Java, you can directly use unsigned right shift to judge whether the lowest bit is right or not each time1, judge as1Just record all the times.

public int hammingWeight(int n) {
    int count = 0;
    for (int i = 0; i < 32; i++) {
        count += n&1;  //  Accumulate 1
        n>>>=1;        //  Cycle shift right 1 bit
    }
    return count;
}

But there is a more interesting solution to this problem. First, for a binary number, such as10100, obtained after subtracting one10011, compare the two numbers and find the lowest bit of the original number1The number on the right of does not change, while the number on the right0It’s all turned into1, and this1Become0, if the original number and the number minus one are bitwise and, the lowest number will be1Set zero.

So there’s a magical idea, if we want to put the numbersaLowest1become0Without changing other bits, directly througha&(a-1)You can get it, and put all the1It’s all turned into0Is not the number of bits1Have you got the number of?

For example, for numbers101, aftera&(a-1) = 101&100 = 100, just do it again100&011 = 0, a total of two operations, and the final result becomes0

The simple code is as follows:

public int hammingWeight(int n) {
    int count = 0;    //  Count the number of times to set 0
    while (n!=0) {
        count ++;
        n = n&(n-1);  //  Set the lowest 1 to 0
    }
    return count;
}

It seems that the above two algorithms can achieve the purpose we want, but we don’t know the efficiency. Let’s test and compare the built-in computing of Java1Number method)

private void test() {
    int t = 10000000;                        //  Ten million times
    long s = System.currentTimeMillis();
    for (int i = 0; i < t; i++) {
        hammingWeight1(-3);
    }
    System.out.println("Java builtin:\t" + (System.currentTimeMillis()-s));
    s = System.currentTimeMillis();
    for (int i = 0; i < t; i++) {
        hammingWeight2(-3);
    }
    System. Out. Println ("set the lowest 1 to 0: \ T" + (system. Currenttimemillis() - s));
    s = System.currentTimeMillis();
    for (int i = 0; i < t; i++) {
        hammingWeight3(-3);
    }
    System. Out. Println ("shift right comparison: \ T" + (system. Currenttimemillis() - s));
}

//Java built-in method
public int hammingWeight1(int n) {
    return Integer.bitCount(n);
}

//Set the lowest 1 to 0
public int hammingWeight2(int n) {
    int count = 0;
    while (n!=0) {
        count ++;
        n = n&(n-1);
    }
    return count;
}

//Shift right comparison
public int hammingWeight3(int n) {
    int count = 0;
    for (int i = 0; i < 32; i++) {
        count += n&1;  //  Accumulate 1
        n>>>=1;        //  Cycle shift right 1 bit
    }
    return count;
}

The output results are as follows:

Java builtin:   10
Lowest 1 set 0: 150
Shift right comparison: 10

Although the above is only the result of one test (the result is different every time you run the test), you can see the lowest position0The efficiency of this method is one order of magnitude slower than the ordinary shift operation or the built-in method. I guess the reason should ben=n&(n-1)This operation is time-consuming(n=n&(n-1)It’s actually two steps, the first stepint a = n-1, step twon=n&a), andn&1perhapsn>>>=1Both operations are much faster.

Back to the test, I found that the built-in Java method is sometimes much faster than shifting to the right, which is interesting. We have to see how it is implemented:

public static int bitCount(int i) {
    // HD, Figure 5-2
    i = i - ((i >>> 1) & 0x55555555);
    i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
    i = (i + (i >>> 4)) & 0x0f0f0f0f;
    i = i + (i >>> 8);
    i = i + (i >>> 16);
    return i & 0x3f;
}

After reading the sentencewhat the fuckCome to my mind, what’s this writing?! What immortal algorithm is this! Let’s take a closer look at the algorithm and decompose each step, assuming that our input is-1

Public static int bitcount (int i) {// I = Binary of 11 11 11 11 11 11 11 11 11 - 1
    // HD, Figure 5-2                                //     01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01  
    i = i - ((i >>> 1) & 0x55555555);                //  I = 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
                                                     //     00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11  
    i = (i & 0x33333333) + ((i >>> 2) & 0x33333333); //  I = 0100 0100 0100 0100 0100 0100 0100 0100 0100 (2) sum every 4 bits
                                                     //     0000 1111 0000 1111 0000 1111 0000 1111
    i = (i + (i >>> 4)) & 0x0f0f0f0f;                //  I = 000010000 000010000 (3) sum every 8 bits

    i = i + (i >>> 8);                               //  I = 0000110000000000 0001000000010000 (4) sum every 16 bits

    i = i + (i >>> 16);                              //  I = 00000100000010000001000000100000010000000000 (5) sum every 32 bits

    return i & 0x3f;                                 //  I = 00000000000000000000000000000 (6) take the lowest 6 digits
}

There are six steps in this process, and the main idea isDivide and conquer, calculate every 2, 4, 8, 16, 32 bits1Number of:

  1. The first and most important step is to count the numbers in every 2 digits1Number of, such as binary11There are two1It should become a statistical result1011-((11>>>1)&01) = 10),10perhaps01Should become0110-((10>>>1)&01) = 0101-((01>>>1)&01) = 01
  2. Count the number in every four digits1The number of, that is, the two digits calculated above1The number and sum of, for example01 00, by shift and mask0011Bitwise and, become0000and0001Then sum it up0001
  3. Statistics of every 8-bit binary1The number of binary digits in this step is different from that in the above step. Instead of adding with a mask first, add first and then mask. The main difference is that in the previous step, the addition of two two binary digits may overflow. After using a mask, you will get wrong results, such as10 10, add to get01 00Then use00 11Bitwise and get00 00Not what we want. When calculating the number of 8 bits, it will not overflow, because it is in 4 bits1The maximum number of is 4, that is0100, the maximum sum of the two is1000, it will not carry to the second 4 bits. Using the mask can get the correct result and clear the redundant data1
  4. Statistics per 16 bit binary1At this time, the direct shift addition does not use a mask. The reason is very simple. The result of every 8 bits in the previous step is saved in the last 4 bits and must be accurate. The direct addition will not overflow 8 bits, and the result must be saved in the rightmost 8 bits. We don’t care about the value of the left 8 bits in the 16 bits, Therefore, there is no need to use a mask, and it will not affect the subsequent calculation
  5. In this step, the sum of 32 bits is obtained and directly shifted and added, because the number of 1 of the left and right 16 bits must be saved in their respective right 8 bits, and the addition result will not overflow. It must still be in the rightmost 8 bits, and the 24 bits on the left have no effect
  6. The result must be in the last 6 bits, because there are only 32 at most1, only 6 bits are needed to save this value.

In this way, the final result is obtained by using the divide and conquer method through a fine binary operation1I have to sigh that this is really wonderful!

There are many subtle bit operation methods of integer numbers in Java, such as the following. Although I would like to introduce them, I will skip them because of space, because there are more important contents later

  • highestOneBit
  • lowestOneBit
  • numberOfLeadingZeros
  • numberOfTrailingZeros
  • reverse
  • rotateLeft
  • rotateRight

268 missing number

Missing number in question 268 of leetcode

Given a containing0, 1, 2, ..., ninnNumber of sequences, find out0 .. nThe number that does not appear in the sequence.

Example 1:

Input: [3,0,1]
Output: 2

This problem can use hash table or summation method, which is a good method, but since this article is about bit operation, let’s use the classical bit operation.

For the bit operation of numbers, bitwise XOR has a very important property, for numbersaandbyes:

a^a=0

a^0=a

a^b^a=b

By analogy, we can find that if bitwise XOR is performed for each element in an array, the elements with even frequency will become0, if the occurrence frequency of each element in the array is an even number of times, the result of bitwise XOR of the whole array is0, if only one element occurs an odd number of times, the result of bitwise XOR is this element.

Back to this question, how many times do all the numbers in the array appear1, the number that does not appear is0Obviously, we can’t directly bitwise XOR, so let’s add one after this array0..nThe array contains all the elements. When the two numbers are combined, the number of occurrences of the original number is2, and the number of times that the original number did not appear is1, you can use bitwise XOR.

However, there is no need to explicitly add an array here. Instead, XOR is performed inside the loop. The code is as follows:

public int missingNumber(int[] nums) {
    int xor = 0;
    for (int i = 0; i < nums.length; i++) {
        xor ^= i^nums[i];           //  Try to add new arrays by bit XOR virtual inside the loop
    }
    return xor^nums.length;         //  Make up the last digit n missed in the cycle
}

136 a number that appears only once

A number that appears only once in question 136 of leetcode

Given aNon emptyAn array of integers. Each element appears twice except that one element appears only once. Find the element that appears only once.

explain

Your algorithm should have linear time complexity. Can you do it without using extra space?

Example 1:

Input: [2,2,1]
Output: 1

According to the idea of bitwise XOR in the above question, you can write the result with your eyes closed:

public int singleNumber(int[] nums) {
    int num = 0;
    for (int i : nums) {
        num ^= i;
    }
    return num;
}

137 number II that appears only once

This is the number II that appears only once in question 137 of leetcode

Given aNon emptyInteger array, except that an element appears only once, every other element appears three times. Find the element that appears only once.

explain:

Your algorithm should have linear time complexity. Can you do it without using extra space?

Example 1:

Input: [2,2,3,2]
Output: 3

This question seems to be very similar to the one above, except that it originally appears2Once, now it has become3The original XOR operation can makea^a=0, if there is an operation symbol that makesa?a?a=0, then the problem will be solved. It seems that there is no such operator, so think of other methods.

First, let’s look at the characteristics of the XOR operator,1^1=00^1=11^0=10^0=0, think of addition,01+01=10Take the low order as0, so it’s actually adding left and right operators to remove carry (equivalent to modulo 2). If you want three1Add to get0, then add it in decimal, and then3Just take the mold.

In this way, each bit of each digital binary bit in the array is added, and finally3Take the module and get the result of each binary bit of the number that appears only once. The code of the preliminary idea is as follows:

public int singleNumber(int[] nums) {
    int bit;
    int res = 0;
    for (int i = 0; i < 32; i++) {
        bit = 0;
        For (int num: Num) {// calculate the number of 1s in bit I
            bit += (num>>i)&1;
        }
        res |= (bit%3)<<i;        //  The result is obtained by taking the modulus according to the number of 1
    }
    return res;
}

In fact, this is also a general solution. The number of times here is 3. If it is changed to 4 or 5 times, it can also be solved by this method. I have read many other people’s solutions, and there is a more exquisite solution. Refer to here.

In the above solution, we use onebitTo save the sum of one bit of all numbers, and each element isintType, but what is the maximum number of repetitions of this problem3Moreover, the final result we need is not the sum, but the modulus of sum to 3. Therefore, if we can take the modulus continuously in the calculation process, we can control the sum of each bitLess than 3, up to 2-bit binary numbers are OK, and 32-bit binary numbers are not requiredintTo save. Although there is no binary number with only 2 bits in Java, it can be represented by two 1-bit binary numbers, so the whole array uses twointThe numeric representation of type is OK. Another problem with the above solution is that the entire array is traversed 32 times, because each bit needs to be traversed, but if two are usedintTo represent, with appropriate bit operation, the number of traversals can be reduced to one!

First uselowerandhigherIt represents the low and high bits of this binary number. The change when encountering a binary number each time can be represented by the following figure (0 is obtained by modulo when encountering 3, so there can be no change)lower=1 higher=1This state can only be a transitional state in the middle).

num higher(old) lower(old) Higher (transition) Lower (transition) Mask (mask) higher(new) lower(new)
0 0 0 0 0 1 0 0
0 0 1 0 1 1 0 1
0 1 0 1 0 1 1 0
1 0 0 0 1 1 0 1
1 0 1 1 0 1 1 0
1 1 0 1 1 0 0 0

Do you feel a little confused and how to turn such a form into a formula?

First, let’s take a look at the transition from the old state to the transition state. It is completely a binary addition, and the low value is based onlowerandnumThe transition state that can be obtained islower=lower^num; The high-order value needs to get the low-order carry, that islower&numThen add the original high positionhigher^(lower&num), the high and low positions of the transition state can be easily calculated through these two parts.

Transition states can occurhigher=1 lower=1If it occurs 4 times, the problem can be solved directly. The final new state can be obtained without any additional operation, but what we require here is 3 times, that is, whenhigher=1 lower=1It is necessary to set the two bits to zero at the same time, which will remain unchanged in other states. At this time, we need to use the mask. First calculate the mask, and then the transition state through the maskcorrectInto the final state. The mask is determined according to the high and low bits of the transition state. If the number composed of the high and low bits of the transition state reaches the desired threshold (3 in this question), the mask becomes0, high and low positions are carried out at the same time&Operation set to zero; When you don’t reach it1, use&Operation is equivalent to maintaining the original value. You can see this problem whenhigher=1 lower=1Time maskmask=0, other timesmask=1, very familiar operation, isn’t thismask=~(higher&lower)Oh, my God! This problem has come to the bottom!

The last new state is directly bitwise and with the transition state and mask,higher=higher&maskandlower=lower&maskThe new value is reached.

After traversing the entire array, the number calculated three times appearshigherandlowerIt must be 0. There must be a number oncehigherIt must also be 0, andlowerThe low bit is the value of the binary bit of the number that appears once, so it is obtained finallylowerIs the required return result.

public int singleNumber(int[] nums) {
    int higher = 0, lower = 0, mask = 0;
    for (int num : nums) {
        higher = higher^(lower&num);
        lower  = num^lower;
        mask = ~(higher&lower);       //  Compute mask
        higher &=mask;
        lower &= mask;
    }
    return lower;
}

The above solution process is simpler. First, the transition state is obtained by adding, and then the final new state is calculated by mask. For any problem that occurskThe problem of finding numbers that only appear once in the array of times can be used. There are only two bits (one high and one low). Ifk=5, then 2 digits is not enough, and 3 digits are neededs1s2ands3, and the calculation of the mask becomesmask = ~(s3&~s2&s1)(when the transition state is101The mask is0)。

If the calculation of the above mask becomesmask=~(higher&~lower), this code can be put directly intoA number that appears only onceIn this problem.

In addition to using transition states, you can also use Karnaugh map to directly solve the problem of new and old state transformation. See this post for specific problem-solving methods.

260 number III that appears only once

This is the number III that appears only once in question 260 of leetcode

Given an array of integersnums, where exactly two elements appear only once and all other elements appear twice. Find the two elements that appear only once.

Example:

Input: [1,2,1,3,2,5]
Output: [3,5]
be careful:

  1. The order in which the results are output is not important. For the above example,[5, 3]That’s the right answer.
  2. Your algorithm should have linear time complexity. Can you just use constant space complexity?

The difference between this problem and the above problem is that the occurrence times of the elements to be solved above are only once, and the last element can be obtained. However, this problem needs to solve two elements that occur once. For example, these two elements areaandb, the result of a bitwise XOR traversal isa^b, it seems that we can’t draw any conclusion from this information.

Since we need to traverse twice to get two numbers, if we traverse both times, the information we want will be mixed together. The only way is to divide the array into two sub arrays, one containinga, the other containsbIn this way, we can get the desired result by traversing separatelyaandbYes.

To split this array, you need to distinguishaandb, becauseaandbIt must be different. There must be 1 bit in the 32 bits of binary representation. Find this bit, and then set this bit in the whole array as1The sum of is0Numbers are listed as two sub arrays (the same number will certainly be divided into the same sub array), and the results can be obtained by XOR respectively. To findaandbDifferent binary bits, obtained abovea^bIt can come in handy. The XOR result is1It must be the one with two different numbers. You can distinguish it by looking for any one. Here we directly1The lowest order of the. At the beginning of the article, you can get the lowest order1Operation of——num&(-num), can be used directly. The simplified code is as follows:

public int[] singleNumber(int[] nums) {
    if (nums == null || nums.length < 1) return null;
    int[] res = new int[2];
    int xor = 0;
    For (int num: Num) {// calculate a ^ B
        xor = xor^num;
    }
    int bits = xor & (-xor); //  Get lowest 1

    For (int num: nums) {// get the number a of occurrence times bit 1
        res[0] ^= (bits & num) == bits ? num : 0;
    }
    res[1] = res[0]^xor;     //  Get another number B from the previous number
    return res;
}

338 bit count

This is the bit count in question 338 of leetcode

Given a nonnegative integernum。 about0 ≤ i ≤ numEach number in the rangei, calculates the number in its binary number1And return them as an array.

Example 1:

Input: 2
Output: [0,1,1]

Example 2:

Input: 5
Output: [0,1,1,2,1,2]

Advanced:

  • The time complexity is given asO(n*sizeof(integer))The answer is very easy. But you can in linear timeO(n)Can you do it with a scan inside?
  • The space complexity of the algorithm is required to beO(n)

After reading this question and seeing the final advanced part, do you feel familiar – the problem needs to be solved in linear time, and the spatial complexity is O (n) – isn’t this dynamic programming! Think about the characteristics of the problem and find the optimal substructure.

For a binary numbern, if you want to get his number from left to rightnIn bit1You can get its number from left to right firstn-1In bit1Then according to the number ofnWhether the bit is1To decide whether to add1。 To get the number in the whole number1You need to know the number from left to right31Bit (from1Start count)1The number of and whether the lowest order is1。 It is not difficult to get the former, because if you move the whole number one bit to the right, it is a previously calculated number (dynamic programming starts from small to large), which becomes the optimal substructure and the recursive formula becomesres[i] = res[i>>1] + (i&1);, with this formula, the problem is solved.

For example, if you want to get10That is, binary1010of1You can find the three binary digits on the left first101of1The number of, plus the rightmost bit0That’s it.

public int[] countBits(int num) {
    int[] res = new int[num+1];      //  No initialization is required. The default value is 0
    for (int i = 0; i < res.length; i++) {
        res[i] = res[i>>1] + (i&1);
    }
    return res;
}

187 repeated DNA sequences

This is the DNA sequence repeated in question 187 of leetcode

All DNA consists of a series of nucleotides abbreviated as a, C, G and T, such as “acgaattccg”. When studying DNA, identifying repetitive sequences in DNA can sometimes be very helpful.

Write a function to find all 10 letter long sequences (substrings) that appear more than once in DNA molecules.

Example:

Input: S = “aaaacccccaaaaccccccaaaggttt”
Output: [“aaaaccccc”, “cccccccaaaa”]

Seeing so many obvious problems of bit operation in front of me, I feel a little hoodwinked when I jump to this problem. It seems that it has nothing to do with bit operation. But pay attention to the reading questions. There are two key qualifications——10 letter long sequenceandOnly “ACGT” has four characters, when you see these two conditions, you think of oneintThe number has 32 bits, so oneintNumbers can just represent such a 10 character sequence.

In implementation, we use

  • 00representativeA
  • 01representativeC
  • 10representativeG
  • 11representativeT

Such as sequenceAAAAACCCCCYou can use it00 00 00 00 00 01 01 01 01 01To show that this is only 20 bits from right to left, and the higher bits are0Omitted.

A string of DNA needs exactly 20 bits, and the high bits are directly removed with a mask. Each unique DNA string can use a uniqueintNumber representation. An array is used to represent the number of occurrences of each string. The maximum length of the array is1<<20

public List<String> findRepeatedDnaSequences(String s) {
    if (s == null || s.length() <= 10) return new ArrayList<>();
    char[] chars = s.toCharArray();              //  Convert to array to improve efficiency
    int[] freq = new int[1<<20];                 //  Frequency array
    int num = 0;                                 //  Int value during calculation
    int mask = (1<<20)-1;                        //  Mask, only the lowest 20 bits are reserved
    For (int i = 0; I < 10; I + +) {// initialize the first DNA string
        num <<= 2;
        if (chars[i] == 'C') {
            num |= 1;
        } else if (chars[i] == 'G') {
            num |= 2;
        } else if (chars[i] == 'T') {
            num |= 3;
        }
    }
    freq[num]++;
    List<Integer> repeated = new ArrayList<>();
    For (int i = 10; I < chars. Length; I + +) {// traverse all DNA strings with length of 10
        num <<= 2;                               //  Clear the top two bits, that is, remove the slipped string
        if (chars[i] == 'C') {
            num |= 1;
        } else if (chars[i] == 'G') {
            num |= 2;
        } else if (chars[i] == 'T') {
            num |= 3;
        }
        num &= mask;                             //  The mask reserves the lowest 20 bits
        freq[num]++;                             //  Statistical frequency
        if (freq[num] == 2) repeated.add(num);   //  Only when the number of occurrences is 2 can it be included to avoid repetition
    }

    List<String> res = new ArrayList<>(repeated.size());
    For (integer: repeated) {// convert int number into DNA string
        char[] seq = new char[10];
        for (int i = 9; i >= 0; i--) {
            switch (integer&3) {
                case 0:seq[i]='A';break;
                case 1:seq[i]='C';break;
                case 2:seq[i]='G';break;
                case 3:seq[i]='T';break;
            }
            integer >>=2;
        }
        res.add(new String(seq));
    }
    return res;
}

summary

This article mainly explains several basic methods and tricks of binary bit operation, and mentions the representation of floating-point numbers, hoping to increase the understanding of the underlying data of the computer.

Later, we talked about some classic topics above leetcode, such asDivide two numbersandA number that appears only once, they all use bit operation to solve practical problemsRepetitive DNA sequenceIt is a higher-level case of solving problems through bit operation.

I have written thousands of words, hoping to help you deepen your understanding of bit operation and skillfully apply it in work and study.

reference resources

A summary: how to use bit manipulation to solve problems easily and efficiently
Bit Manipulation 4% of LeetCode Problems
Bit Twiddling Hacks
Bitwise operators in Java
9.1 Floating Point
Binary format analysis of Java floating point number
How is a floating point number represented in Java?
Execution time 1ms, defeat 100%
Detailed popular thinking analysis, multi solution
Detailed explanation and generalization of the bitwise operation method for single numbers
Bitwise Hacks for Competitive Programming
Bit Tricks for Competitive Programming

For more information, please see myPersonal blog