Bit Operation Practice Manual

*Obviously learned a lot of knowledge consciously, but when I actually started to do the questions, there was still the dilemma of "a pen, a pair of hands, and a Leetcode for a night"? Have you ever had such an experience, the question type is not too difficult, and you can figure it out by reading the solution, but when you do it yourself, you look at the question face to face and have no way to start. This is caused by the lack of solid and insecure grasp of basic knowledge. Let's take bit operations as an example. I will explain the basics of bit operations to you clearly, and then give you practical experience with examples.

1. Bitwise AND operator

The bitwise AND operator is a binary bitwise operator, that is, it has two operands, denoted as x & y.

The bitwise AND operation will operate on each bit of the operand according to the following table. For each bit, there are only two cases of 0 or 1, so a total of 4 cases are combined.

https://article-images.zsxq.com/FuK9MkdBw13Y-L9Hmp176jUtqoKi

Through this table, we draw some conclusions:

1) Whether it is 0 or 1, as long as the bit is 1, or itself;

2) Whether it is 0 or 1, as long as the bit is 0, it becomes 0;

So for bitwise AND, as long as there is a 0 on this bit, the result of this bit will become 0.

int main() {

    int a = 0b1010;

    int b = 0b0110;

    printf("%d\n", (a & b) );

    return 0;

}
  1. (1) In the C language, 0b is used as a prefix to indicate that this is a binary number. Then the actual value of a is (1010).

  2. (2) Similarly, the actual value of b is (0110);

  3. (3) Here a & b is the & operation in the table for each digit of (1010) and (0110).

  4. So the final output is:

  5. Because the output is a decimal number, its binary representation is: (0010)_2.

  6. Note: The leading zeros here are optional. The author writes the leading zeros just for alignment and to make readers more aware of the bitwise AND operation.

2. Application of bitwise AND operator

1. Parity judgment

  1. We judge whether a number is odd or even, often by taking the modulus % to judge, as follows:
int main() {

    if(5 % 2 == 1) {

        printf("5是奇数\n");

    }

    if(6 % 2 == 0) {

        printf("6是偶数\n");

    }

    return 0;

}
  1. However, we can also write:
int main() {

    if(5 & 1) {

        printf("5是奇数\n");

    }

    if( (6 & 1) == 0 ) {

        printf("6是偶数\n");

    }

    return 0;

}

1. 
  1. This is the use of the characteristics of binary numbers of odd and even numbers, as shown in the following table:

https://article-images.zsxq.com/Fr3FEevcsw6AGwMedmlYozR8fBWD

  1. Therefore, we perform a bit-AND with 0b1 on any number, and if the result is zero, then the binary end bit of the number must be 0. According to the above table, it can be concluded that it is an even number; otherwise, it is an odd number.

  2. Note that since we haven't actually mentioned the if statement, we will briefly mention it here, and there will be a systematic explanation later:

  3. For the above statement, expr represents an expression, and the value of the expression is only zero or non-zero at the end. If the value is non-zero, the content in the body will be executed.

2. Take the last five digits

[Example 1] Given a number, find the last five digits of its binary representation and output it in decimal.

  1. The core of this problem is: we only need the last five digits, and we don’t need the remaining digits, so we can add the given digits to 0b11111, so that we can directly get the value of the last five digits.

  2. The code is implemented as follows:

int main() {

    int x;

    scanf("%d", &x);

    printf("%d\n", (x & 0b11111) );

    return 0;

}

So if the problem becomes the following form, how should the code be written?

[Example 2] If you want to get the last seven digits, the last nine digits, the last fourteen digits, and the last K digits, how should you achieve it?

3. Eliminate the last five digits

【Example 3】Given a 32-bit integer, it is required to eliminate its last five digits.

  1. Still according to the nature of bit and, eliminate the meaning of the last five bits, there are two layers:

  2. 1) The last five digits must all become zero;

  3. 2) The remaining bits remain unchanged;

  4. Then, according to the nature of bit operations, we need a number whose upper 27 bits are all 1, and the lower five bits are all 0, then this number is:

https://article-images.zsxq.com/Fgop5R_LxyZtPF70Vs-VdDN51j_x

  1. But if you want to write it like this, the code will not go crazy, and people will go crazy, so generally we convert it into hexadecimal, and every four binary digits can be converted into a hexadecimal number, so we get hexadecimal The number is 0xffffffe0.

  2. The code is implemented as follows:

int main() {

    int x;

    scanf("%d", &x);

    printf("%d\n", (x & 0xffffffe0) );

    return 0;

}

Tips: f stands for 4 1s; e stands for 3 1s, 1 0; 0 stands for 4 0s;

4. Eliminate the continuous 1 at the end

[Example 4] Given an integer, it is now required to convert the integer into binary, change the consecutive 1s at the end to 0, and output the changed number (just output in decimal).

  1. We know that the binary representation of this number must be:

https://article-images.zsxq.com/FjNYjX6GoiKRmv13CEGybKCt0Qe5

  1. If we add 1 to this binary number, we get:

https://article-images.zsxq.com/FjFgw6gfXY6yEU2lGniGHpOVdaL4

  1. We perform a bitwise AND operation on these two numbers to get:

https://article-images.zsxq.com/FivTjeFY443LOb1bqqNav9LkrIpD

  1. So, have you learned?

5. Judgment of the power of 2

【Example 5】Please use one sentence to judge whether a positive number is a power of 2.

  1. If a number is a power of 2, its binary representation must be of the form:

https://article-images.zsxq.com/FuSMvHw-X3ZrceocHXM8XJQvjkY2

  1. The decimal value of this number is 2 k times.

  2. Then we subtract one from it, that is, the binary representation of the k times of 2 minus one is as follows (refer to the borrowing of binary subtraction):

https://article-images.zsxq.com/Fp4a8QJapXNc0ry4bnQ3yEhYc5fY

  1. So the result of the AND of these two numbers is zero, so we know that if a number x is a power of 2, then x & (x-1) must be zero. In other cases this is not the case. So the answer to this question is:

3. Bitwise OR operator

The bitwise OR operator is a binary bitwise operator, that is, it has two operands, denoted as x | y. The bit-OR operation will operate on each bit of the operand according to the following table. For each bit, there are only two cases of 0 or 1, so a total of 4 cases are combined.

https://article-images.zsxq.com/FvpY3Ck4J9BVcaWPDjwehJJbclwg

Through this table, we draw some conclusions:

1) Whether it is 0 or 1, as long as the bit is 1, it becomes 1;

2) Only when both operands are 0, it becomes 0;

int main() {

    int a = 0b1010;

    int b = 0b0110;

    printf("%d\n", (a | b) );

    return 0;

}

(1) In the C language, 0b is used as a prefix to indicate that this is a binary number. Then the actual value of a is (1010);

(2) Similarly, the actual value of b is (0110);

(3) Here a | b is the | operation in the table for each digit of (1010) and (0110).

So the final output is:

Because the output is a decimal number, its binary representation is: (1110).

Fourth, the application of the bitwise OR operator

1. Set the mark bit

【Example 1】Given a number, judge the 5th bit of its binary low order, if it is 0, then set it as 1.

This problem, we can easily think of bit or. Let's analyze the meaning of the title. If the fifth bit is 1, no operation is required; if the fifth bit is 0, set it to 1. The implication is that no matter what the fifth bit is, we can directly set it to 1. The code is as follows:

int main() {

    int x;

    scanf("%d", &x);

    printf("%d\n", x | 0b10000);

    return 0;

}

2. Empty the mark bit

【Example 2】Given a number, judge the 5th bit of its binary low order, if it is 1, then set it to 0.

After we have learned bit and, it is easy to come to such an approach:

int main() {

    int x;

    scanf("%d", &x);

    printf("%d\n", x & 0b11111111111111111111111111101111);

    return 0;

}

The other bits cannot be changed, so the bit should be 1; the fifth bit must be set to zero, so the bit should be 0; there is a problem with writing this way, that is, the string of numbers is too long, it is not beautiful at all, and it is easy to make mistakes, of course we It can also be converted into hexadecimal, and the conversion process may also make mistakes.

And we can only set the fifth bit to 1 by using bit or, how to set it to 0?

We can use it with subtraction. Divided into the following two steps:

1) First, forcibly change the fifth position of the low bit to 1;

2) Then, forcibly remove the lower 5th digit;

Step (1) can use bit-OR operation, and step (2), we can directly use subtraction. The code is implemented as follows:

int main() {

    int x;

    int a = 0b10000;

    scanf("%d", &x);

    printf("%d\n", (x | a) - a );

    return 0;

}

Note: Direct subtraction is not acceptable, because we must first ensure that the bit is 1, otherwise rash subtraction will generate a borrow, which is inconsistent with the meaning of the question.

3. Low continuous zero to one

【Example 3】Given an integer x, change its lower consecutive 0s to 1s.

Assuming that the lower bits of this integer have k consecutive zeros, the binary representation is as follows:

https://article-images.zsxq.com/FiPsRdLga5cfh8-4OBQc7DIMfrKD

So, if we subtract one from it, the resulting binary number is:

https://article-images.zsxq.com/Fr1cTE2CXyRhlBgZUPFfJRwwDG56

We found that as long as these two numbers are ORed, we can get:

https://article-images.zsxq.com/Fi4GQ3jvcIupCmwF3tDYwl1_fjKC

It is exactly what the title asks, so the code implementation is as follows:

int main() {

    int x;

    scanf("%d", &x);

    printf("%d\n", x | (x-1) );

    return 0;

}

(1) x | (x-1) is the "low continuous zero to one" requested by the title.

4. Low first zero to one

【Example 4】Given an integer x, change the first 0 in its low order to 1.

Remember to leave your answer in the comment area~

Five, XOR operator

The XOR operator is a binary bitwise operator, that is, it has two operands, denoted as x ^ y.

The XOR operation will operate on each bit of the operand according to the following table. For each bit, there are only two cases of 0 or 1, so a total of 4 cases are combined.

https://article-images.zsxq.com/FiBrFELi58vEcQmME5_CcnoTv5dW

Through this table, we draw some conclusions:

1) The XOR result of two identical decimal numbers must be zero.

2) The XOR result of any number and 0 must be itself.

3) XOR operation satisfies associative law and commutative law.

int main() {

    int a = 0b1010;

    int b = 0b0110;

    printf("%d\n", (a ^ b) );

    return 0;

}

(1) In the C language, 0b is used as a prefix to indicate that this is a binary number. Then the actual value of a is (1010).

(2) Similarly, the actual value of b is (0110);

(3) Then here a ^ b is the ^ operation in the table for each digit of (1010) and (0110).

So the final output is:

Because the output is a decimal number, its binary representation is: (1100).

Six, the application of the XOR operator

1. Invert the mark bit

【Example 1】Given a number, invert the 4th digit from the low digit, 0 becomes 1, 1 becomes 0.

This problem, we can easily think of XOR. Let's analyze the meaning of the title. If the fourth bit is 1, XOR it to 0b1000 to become 0; if the fourth bit is 0, let it XOR it to 0b1000 to become 1. are all XOR on 0b1000, the code is as

int main() {

    int x;

    scanf("%d", &x);

    printf("%d\n", x ^ 0b1000);

    return 0;

}

2. Variable exchange

【Example 2】Given two numbers a and b, use XOR operation to exchange their values.

This is an old interview question, and the code is given directly:

int main() {

    int a, b;

    while (scanf("%d %d", &a, &b) != EOF) {

        a = a ^ b;

        b = a ^ b;

        a = a ^ b;

        printf("%d %d\n", a, b);

    }

    return 0;

}

Let's directly look at the two sentences (1) and (2), which is equivalent to b equals a ^ b ^ b. According to several properties of XOR, we know that the value of b at this time has become the original value of a up.

And look at the sentence (3) again, which is equivalent to a equal to a ^ b ^ a, or according to several properties of XOR, at this time, the value of a has become the original value of b.

Thus, the exchange of variables a and b is realized.

3. A number that appears an odd number of times

【Example 3】Input n numbers, only one of which appears an odd number of times, and all other numbers appear an even number of times. Find the number that occurs an odd number of times.

According to the nature of XOR, the result of XOR of two identical numbers is zero. That is to say, all the XOR numbers that appear an even number of times are zero, then XOR all the n numbers, and the obtained number must be a number that appears an odd number of times.

int main() {

    int n, x, i, ans;

    scanf("%d", &n);

    ans = 0;

    for(i = 0; i < n; ++i) {

        scanf("%d", &x);

        ans = (ans ^ x);

    }

    printf("%d\n", ans);

    return 0;

}

4. Lost numbers

【Example 4】Given an n-1 number, representing n-1 of 1 to n, find the missing number.

Remember to leave your answer in the comment area~

5. Simple encryption

Based on the two characteristics that the XOR of two numbers is zero , and that any number and zero is itself , XOR can also be used for simple encryption. After XORing a fixed number on the plaintext into ciphertext, you can convert the ciphertext into plaintext by continuing to XOR this number.

Seven, bitwise negation operator

The negation operator is a unary bitwise operator, that is, has only one operand, denoted ~x. The negation operation will operate on each bit of the operand according to the following table, and there are only two cases of 0 or 1 for each bit.

https://article-images.zsxq.com/FqGHMjJbeJzIbbRMwyx6vVUtpGbP

int main() {

    int a = 0b1;

    printf("%d\n", ~a );

    return 0;

}

Here ~a represents the inversion of the binary number 1, which should be 0 intuitively. But the actual output is:

-2

Why is this? That's because this is a 32-bit integer, and the actual inversion operation is as follows:
00000000 00000000 00000000 00000001

11111111 11111111 11111111 11111110

The binary representation of a 32-bit integer, leading zeros are also involved in negation. For a signed 32-bit integer, we need to use the highest bit to represent the sign bit, that is, the highest bit is 0, it represents a positive number; the highest bit is 1, it represents a negative number;

At this time we need to introduce the concept of complement code.

1. The concept of complement code

In the computer, the binary code is expressed in the form of complement code, and the complement code is defined as follows:

The complement of a positive number is itself, and the sign bit is 0; the complement of a negative number is the inversion of the binary bit of the positive number plus one, and the sign bit is one;

2. Complement code example

According to the definition of complement code, the calculation of -2's complement code requires two steps:

1) Perform bitwise inversion on the binary of 2, as follows:
00000000 00000000 00000000 00000010

11111111 11111111 11111111 11111101

2) Then add 1, as follows:

11111111 11111111 11111111 11111101

  • 00000000 00000000 00000000 00000001

11111111 11111111 11111111 11111110

The result is exactly the result of ~1 we mentioned at the beginning.

3. The true meaning of complement code

The true meaning of the complement code is actually reflected in the word "complement". In mathematics, the addition of two numbers that are opposite to each other is equal to 0, while in the computer, the addition of two numbers that are opposite to each other is equal to 2 n times.

In other words, two numbers that are opposite to each other complement each other, making 2 n times.

For 32-bit integers, n = 32; for 64-bit integers, n = 64. So the complement code can also be expressed as follows:

https://article-images.zsxq.com/FtYp9xvEhlduj1YtCnEf0vB1UBwf

So, for the int type, there are:

https://article-images.zsxq.com/Fv6q2Vd9LSbDZwSCdHhlaf6xKqP3

Right now:

https://article-images.zsxq.com/Fh68gYeZdzO0W4Dmw6pwnO02h09n

So, we start counting...

2^32 = 1 00000000 00000000 00000000 00000000

2^32 - 1 = 11111111 11111111 11111111 11111111

2^32 - 2 = 11111111 11111111 11111111 11111110

Take a closer look at the binary representation of -2.

Eight, the application of the bitwise negation operator

1, the negation of 0

【Example 1】What is the negation result of 0?

First, invert the original code to get:
00000000 00000000 00000000 00000000

11111111 11111111 11111111 11111111

We just finished discussing this question, and the answer is 2^32 - 1. But when you actually output it, you will find that its value is -1.

why is that?

The reason is because there are two types of int in C language, namely unsigned int and signed int. The int we discussed before is the abbreviation of signed int.

1) signed integer

For the signed integer type signed int, the highest bit represents the sign bit, so only 31 bits can represent the value, and the range of values ​​that can be represented is:

https://article-images.zsxq.com/Fi0lx3D7DTuL6VbpSvjuj4KSYb_q

Therefore, for signed integers, the output uses %d, as follows:

int main() {

    printf("%d\n", ~0 );

    return 0;

}

The result is:

-1

2) Unsigned integer

For the unsigned integer type unsigned int, since the sign bit is not required, there are a total of 32 bits to represent the value, and the value range is:

https://article-images.zsxq.com/Fvrxl2rj6EjwUVfXT6rgAmcrhLlu

For unsigned integers, the output uses %u, as follows:

int main() {

    printf("%u\n", ~0 );

    return 0;

}

The result is:

4294967295

That is 2^32 - 1.

2. Opposite number

【Example 2】Given a positive number x of int type, find the opposite number of x (note: negative sign cannot be used).

Here, we can directly use the definition of the complement. For a positive number x, the complement of its opposite number is the binary inversion of x plus one. That is: ~x + 1 .

int main() {

    int x = 18;

    printf("%d\n", ~x + 1 );

    return 0;

}

The result of the operation is as follows:

-18

3. Instead of subtraction

【Example 3】Given two positive numbers x and y of int type, realize x - y (note: the minus sign cannot be used).

This problem is relatively simple. If the opposite number above has been understood, then x - y can actually be expressed as x + (-y), and -y can be expressed as ~y + 1, so the subtraction x - y can be Use x + ~y + 1 instead.

  • The code is implemented as follows:
int main() {

    int a = 8;

    int b = 17;

    printf("%d\n", a + ~b + 1 );

    return 0;

}

The result of the operation is:

-9

4. Instead of addition

【Example 4】Given two positive numbers x and y of type int, realize x + y (note: the plus sign cannot be used).

We can change x + y to x - (-y), and -y can be replaced by ~y + 1;

So x + y becomes x - ~y - 1 , adding without a plus sign.

int main() {

    int x = 18;

    int y = 7;

    printf("%d\n", x - ~y - 1 );

    return 0;

}

The result of the operation is:

25

Nine, left shift operator

1. Left-shifted binary form

The left shift operator is a binary bitwise operator, that is, it has two operands, expressed as x << y. where x and y are both integers.

x << y is pronounced as: "shift x to the left by y bits", the bits here are of course binary bits, so what it means is: first represent x in binary, then shift left by y bits, and at the end Add y zeros.

For example: For the binary number (10111), the result of shifting y bits to the left is:

https://article-images.zsxq.com/FvTqe04ZHua0EeVeUMDCtMNXzo7n

2. Execution result of left shift

The execution result of x << y is equivalent to:

https://article-images.zsxq.com/Fk_rdCU_5WjxTh_orJIHhEnrhKuY

The following code:

int main() {

    int x = 3;

    int y = 5;

    printf("%d\n", x << y);

    return 0;

}

The output is:

Exactly what this left shift operator actually means:

https://article-images.zsxq.com/FhtmddBPBXDrRnnKt9ntJl1rZJmd

The most commonly used is when x = 1, 1 << y represents 2^y, which is the power of 2.

3. Execution result of negative left shift

The so-called left shift of negative numbers means that in x << y, when x is a negative number, the code is as follows:

int main() {

    printf("%d\n", -1 << 1);

    return 0;

}

Its output is as follows:

We found that the same is satisfied, this can be explained by complement, the complement of -1 is:

https://article-images.zsxq.com/FhggBS-a-eEd_69rLowsn9EhEwrw

After shifting one bit to the left, the highest 1 is gone, and the lower bit is filled with 0 to get:

https://article-images.zsxq.com/Fs8_QTeR0VEbB_61OllhxoRrPCj5

And this happens to be the complement of -2. Similarly, continue to shift left by 1 bit to get:

https://article-images.zsxq.com/FghDxL2eqaV_gY91WKkqtWjZeWXD

This is -4's complement, and so on, so the result of a left shift of a negative integer is also satisfied.

It can be understood that - (x << y) and (-x) << y are equivalent.

4. What is the situation of shifting negative digits to the left

Just now we discussed the case of x < 0, so what happens when we try y < 0?

Does it also satisfy the following equation?

https://article-images.zsxq.com/FpFGvQNEhi_LQJq9OXWCaJhd4O89

If it is still satisfied, then the left shift of two integers may produce a decimal.

Look at an example:

int main() {

    printf("%d\n", 32 << -1);

    printf("%d\n", 32 << -2);

    printf("%d\n", 32 << -3);

    printf("%d\n", 32 << -4);

    printf("%d\n", 32 << -5);

    printf("%d\n", 32 << -6);

    printf("%d\n", 32 << -7);

    return 0;

}

Although it can run normally, the result does not seem to be what we expected, and a warning will be reported as follows:

[Warning] left shift count is negative [-Wshift-count-negative]

In fact, the editor tells us not to use negative numbers when shifting to the left as much as possible, but its execution result cannot be regarded as an error. At least it is correct in the example, and the result will not appear as a decimal, but rounded.

Shifting negative digits to the left actually has the same effect as shifting the corresponding positive digits to the right.

5. What happens when overflow occurs when shifting to the left

We know that the numbers of type int are all 32 bits, and the highest bit represents the sign bit, so assuming that the highest bit is 1, and the second highest bit is 0, after the left shift, the sign bit will become 0, what problems will arise?

For example, the binary representation of -2^31 + 1 is: the highest and lowest bits are 1, and the rest are zero.

int main() {

    int x = 0b10000000000000000000000000000001;

    printf("%d\n", x);

    return 0;

}

The output is:

So, after shifting it to the left by one bit, what is the result?

int main() {

    int x = 0b10000000000000000000000000000001;

    printf("%d\n", x << 1);

    return 0;

}

Let's make a blind guess, the highest bit 1 is removed, and the lowest bit is filled with 0, the result should be 0b10.

The actual output is indeed:

But if you follow the previous rules, the answer should be:

https://article-images.zsxq.com/FmKoYtppTNqzNexloHYWWhCUIcxn

This is back to the problem of complement code. In fact, in the computer, the int integer is actually a ring, which will come back after overflow, and the length of the ring is exactly 2^32, so -2^32 + 2 = 2 , this is a bit like the concept of congruence, these two numbers are congruent modulo 2^32.

Ten, the application of the left shift operator

1. Modulo conversion into bit operation

For a number y modulo x modulo a power of 2, we can convert to bitwise AND 2^y-1.

That is, mathematically:

https://article-images.zsxq.com/FpUvtfkgp_qUoKYkLHViqWqgeTgD

In the computer, it can be represented by one line of code: x & ((1 << y) - 1).

2. Generate tag code

We can use the left shift operator to realize the marking code, that is, 1 << k is used as the marking code of the k-th marking position, so that operations such as 0, setting 1, and inverting the marking position can be realized in one sentence.

1) Mark position 1

[Example 1] For the number x, we want to set the kth bit of its binary bit (starting from 0, from low to high) to 1.

The operation of setting to 1 reminds us of a bitwise OR operation.

Its characteristics are: bit or 1, the result is 1; bit or 0, the result remains unchanged.

Therefore, our requirement for the mark code is: the kth bit is 1, and the other bits are 0, which is exactly (1 << k), then the statement that sets the kth position as 1 can be written as: x | (1 << k).

2) mark position 0

[Example 2] For the number x, we want to set the kth bit of its binary digit (starting from 0, from low to high) to 0.

The zero-set operation reminds us of a bit-AND operation.

Its characteristics are: the bit and the upper 0, the result is 0; the bit and the upper 1, the result remains unchanged.

Therefore, our requirement for the mark code is: the kth bit is 0, and the other bits are 1, what we need is (~(1 << k)), then the statement that the kth position is 0 can be written as: x & ( ~(1 << k)).

3) The flag bit is reversed

[Example 3] For the number x, we want to invert the kth bit of its binary digit (starting from 0, from low to high).

The negation operation is associated with the XOR operation.

Its characteristics are: XOR 1, the result is negated; XOR 0, the result remains unchanged.

So our requirement for the marking code is: the kth bit is 1, and the other bits are 0, and its value is (1 << k). Then the statement to invert the kth bit can be written as: x ^ (1 << k) .

3. Generate a mask

Similarly, we can use a left shift to generate a mask to perform some operation on the last k bits of a number.

The binary representation of (1 << k) is: 1 plus k 0s, then the binary representation of (1 << k) - 1 represents k 1s.

Change the k bits at the end to 1, which can be written as: x | ((1 << k) - 1).

Change the k at the end to 0, which can be written as: x & ~((1 << k) - 1).

Invert the k bits at the end, which can be written as: x ^ ((1 << k) - 1).

Eleven, right shift operator

1. Right-shifted binary form

The right shift operator is a binary bitwise operator, that is, it has two operands, expressed as x >> y. where x and y are both integers.

x >> y is pronounced as: "shift x to the right by y bits", and the bits here are of course binary bits, so what it means is: first express x in binary, and for positive numbers, shift right by y bits; For a negative number, the high bits are filled with 1 after the right shift of y bits.

For example: For the decimal number 87, its binary value is (1010111), and the result of shifting left by y bits is:

https://article-images.zsxq.com/FvInA6fe5nYR4n5vi7lWXfKy8S9F

2. The execution result of right shift

The execution result of x >> y is equivalent to:

https://article-images.zsxq.com/FkeAdR01NS5ApAkj5EjHYz-zUNjc

This symbol stands for rounding off. The following code:

int main() {

    int x = 0b1010111;

    int y = 3;

    printf("%d\n", x >> y);

    return 0;

}

The output is:

Exactly what this right shift operator actually means:

https://article-images.zsxq.com/Funy2VkX9ZHf-H9VFUc3_NALCVY9

Since the division may not be divisible, there is a step of removing the integer.

3. Execution result of negative right shift

The so-called right shift of negative numbers means that in x >> y, when x is a negative number, the code is as follows:

int main() {

    printf("%d\n", -1 >> 1);

    return 0;

}

Its output is as follows:

We found that it also satisfies the following formula

https://article-images.zsxq.com/FhOdztoufxwrir271BznnGYfmtgE

This can be explained in two's complement, the complement of -1 is:

https://article-images.zsxq.com/Fj-tKcTGYyQW68iOIqXEpvtPyWpe

After shifting one bit to the right, since it is a negative number, 1 is added to the high bit to get:

https://article-images.zsxq.com/Fj-tKcTGYyQW68iOIqXEpvtPyWpe

It can be understood that - (x >> y) and (-x) >> y are equivalent.

Then let's do a simple question to consolidate it

[Example 1] It is required not to run the code, and the naked eye can see how much the code outputs.



int main() {

    int x = (1 << 31) | (1 << 30) | 1;

    int y = (1 << 31) | (1 << 30) | (1 << 29);

    printf("%d\n", (x >> 1) / y);

    return 0;

}

4. What is the situation of shifting negative digits to the right

Just now we discussed the case of x < 0, so what happens when we try y < 0? Does it also satisfy the following properties?

https://article-images.zsxq.com/FkIn8OfkxIBaXIrLHlWwvfSXfEcT

If it is still satisfied, then the left shift of two integers may produce a decimal.

Look at an example:

int main() {

    printf("%d\n", 1 >> -1);

    printf("%d\n", 1 >> -2);

    printf("%d\n", 1 >> -3);

    printf("%d\n", 1 >> -4);

    printf("%d\n", 1 >> -5);

    printf("%d\n", 1 >> -6);

    printf("%d\n", 1 >> -7);

    return 0;

}

Although it can run normally, the result does not seem to be what we expected, and a warning will be reported as follows:

[Warning] right shift count is negative [-Wshift-count-negative]

In fact, the editor tells us to try not to use negative numbers when shifting to the right, but its execution result cannot be regarded as an error, at least it is correct in the example.

Shifting negative digits to the right actually has the same effect as shifting the corresponding positive digits to the left.

12. Application of right shift operator

1. Remove the low k bits

【Example 2】Given a number x, remove its lower k bits and output it.

This problem can be done directly by shifting to the right, as follows: x >> k.

2. Take the low bit consecutively 1

[Example 3] Obtain a number x with consecutive low-order 1s and output them.

For a number x, it is assumed that there are k consecutive 1s in the lower bits. as follows:

https://article-images.zsxq.com/Fsl9kFfLcoD6dd_P2bwxA3RjTP0q

Then we add 1 to it, and we get:

https://article-images.zsxq.com/Fs_N8KlpKEMMQH1lwFA5g0uVdVuU

At this time, the result of XORing these two numbers is:

https://article-images.zsxq.com/FiBtxZ6AnUcJQFN2r97bOPbR-Wwc

At this time, by shifting one bit to the right, we get k consecutive 1 values, which is exactly what we are looking for.

So you can use the following statement to find: (x ^ (x + 1)) >> 1.

3. Take the value of the kth bit

[Example 4] Obtain the value of the kth (0 <= k <= 30) bit of a number x and output it.

For binary numbers, the value of the kth bit must be 0 or 1.

As for the numbers from 1 to k-1 digits, it is meaningless to us, we can use right shift to remove, and then use the bit AND operator to obtain whether the last bit of binary is 0 or 1, as follows: (x >> k) & 1.

So next, what happens when we try y < 0? Does it also satisfy the following properties?

[External link image transfer...(img-3wbr0PBV-1690102819771)]

If it is still satisfied, then the left shift of two integers may produce a decimal.

Look at an example:

int main() {

    printf("%d\n", 1 >> -1);

    printf("%d\n", 1 >> -2);

    printf("%d\n", 1 >> -3);

    printf("%d\n", 1 >> -4);

    printf("%d\n", 1 >> -5);

    printf("%d\n", 1 >> -6);

    printf("%d\n", 1 >> -7);

    return 0;

}

Although it can run normally, the result does not seem to be what we expected, and a warning will be reported as follows:

[Warning] right shift count is negative [-Wshift-count-negative]

In fact, the editor tells us to try not to use negative numbers when shifting to the right, but its execution result cannot be regarded as an error, at least it is correct in the example.

Shifting negative digits to the right actually has the same effect as shifting the corresponding positive digits to the left.

12. Application of right shift operator

1. Remove the low k bits

【Example 2】Given a number x, remove its lower k bits and output it.

This problem can be done directly by shifting to the right, as follows: x >> k.

2. Take the low bit consecutively 1

[Example 3] Obtain a number x with consecutive low-order 1s and output them.

For a number x, it is assumed that there are k consecutive 1s in the lower bits. as follows:

[External link image transfer...(img-d1hKcnX3-1690102819771)]

Then we add 1 to it, and we get:

[External link image transfer...(img-5VGxiKFY-1690102819772)]

At this time, the result of XORing these two numbers is:

[External link image transfer...(img-4z96Jhpo-1690102819772)]

At this time, by shifting one bit to the right, we get k consecutive 1 values, which is exactly what we are looking for.

So you can use the following statement to find: (x ^ (x + 1)) >> 1.

3. Take the value of the kth bit

[Example 4] Obtain the value of the kth (0 <= k <= 30) bit of a number x and output it.

For binary numbers, the value of the kth bit must be 0 or 1.

As for the numbers from 1 to k-1 digits, it is meaningless to us, we can use right shift to remove, and then use the bit AND operator to obtain whether the last bit of binary is 0 or 1, as follows: (x >> k) & 1.

Thirteen leecode related questions

191. The number of bit 1

Problem-solving ideas:

  1. Initialize the counter variable count to 0.
  2. Define a loop variable i and initialize it to 0.
  3. Enter the loop to determine whether i is less than 32 (because unsigned integers have 32 bits).
  4. Inside the loop, bit operations are first used to set the ith bit of the number n to 1, and the result is compared with n. If the result is equal to n, it means that the i-th bit is 1, and the counter count is increased by 1.
  5. The loop variable i is incremented by 1, and the next loop continues.
  6. After the loop ends, return the final count value count.
class Solution {
public:
    int hammingWeight(uint32_t n) {
        int count=0;
        uint32_t i=0;
        while(i<32)
        {
            if((n | (1<<i)) == n)count++;

            i++;
        }
        return count;
    }
};

201. Bitwise AND of ranges of numbers

Problem-solving ideas:

  1. Initialize the counter variable count to 0.
  2. Enter the loop to judge whether left is less than right.
  3. Inside the loop, both left and right are shifted to the right (equivalent to dividing by 2), and the counter count is incremented at the same time.
  4. After the loop ends, return the result of left shifting left by count bits.
class Solution {
public:
    int rangeBitwiseAnd(int left, int right) {
        int count=0;
        while(left<right)
        {
            left>>=1;
            right>>=1;
            ++count;
        }
        return left<<count;
    }
};

The sword refers to Offer 56 - II. The number of occurrences of numbers in the array II

Problem-solving ideas:

  1. Initialize an array count with a length of 32, which is used to count the number of occurrences of each digit.
  2. Initialize the result variable result to 0.
  3. Entering the first loop, the loop variable i controls the i-th bit of the current statistics.
  4. Inside the first loop, enter the second loop, the loop variable num traverses each element in the array nums.
  5. Inside the second loop, the i-th bit of num is taken out and added to count[i] by right-shifting i bits and bitwise AND operation, that is, counting the number of occurrences of the number in the current bit.
  6. After the loop ends, enter the third loop, and the loop variable i controls the i-th bit of the current statistics.
  7. In the third loop, by taking mod 3 from count[i], the value of the number that appears only once in the current bit is obtained, and after shifting it to the left by i bits, the result variable result is updated by bitwise OR operation.
  8. After the loop ends, the final result variable result is returned.

class Solution {
public:
    int singleNumber(std::vector<int>& nums) {
        int count[32] = {0}; // 用于统计每一位上的出现次数
        int result = 0;

        for (int i = 0; i < 32; i++) {
            for (int num : nums) {
                // 统计当前位上数字出现的次数
                count[i] += (num >> i) & 1;
            }

            // 对统计结果取余,得到只出现一次的数字在当前位上的值
            result |= (count[i] % 3) << i;
        }

        return result;
    }
};

982. Bitwise AND Zero Triplets

Problem-solving ideas:

  1. Initialize the result variable ans to 0.
  2. Initialize the array count with a length of 2^16 (ie 65536), which is used to count the number of occurrences of the bitwise AND result of each number.
  3. Entering the first loop, the loop variable x traverses each element in the array nums.
  4. Inside the first loop, enter the second loop, the loop variable y traverses each element in the array nums.
  5. Inside the second loop, use the bitwise AND operator to use the bitwise AND result of x and y as an index, and increase the value of the corresponding position of count by 1 to count the number of occurrences of each bitwise AND result.
  6. After the second loop ends, enter the third loop, the loop variable x traverses each element in the array nums.
  7. Inside the third loop, enter the fourth loop, the loop variable i traverses all integers from 0 to 2^16-1.
  8. Inside the fourth loop, judge whether the condition of (x & i) == 0 is met, and if so, add the value of count[i] to the result variable ans.
  9. After the fourth cycle ends, the final result variable ans is returned.
class Solution {
public:
    int countTriplets(vector<int>& nums) {
        int ans=0;
        int count[1<<16]={0};
        for(int x:nums)
            for(int y:nums)
                count[x&y]++;
        
        for(int x:nums)
            for(int i=0;i<1<<16;++i)
                if((x&i)==0)ans+=count[i];
        return ans;
    }
};

1835. XOR Sum of All Pairwise AND Results

Problem-solving ideas:

  1. We need to XOR all the elements in the array arr1 to get a variable a as an intermediate result. This is because the XOR operation satisfies the associative and commutative laws, that is, the result is the same regardless of the order of the elements.

  2. We need to iterate over each element in the array arr2, and perform a bitwise AND operation on them, and save the result back to the array arr2. The purpose of this is to perform a bitwise AND operation on each element in arr2 and a, so as to obtain the bitwise AND result of each (i, j) number pair.

  3. We traverse the array arr2 again, perform XOR operation on each element in it, and get the final XOR and ans.

  4. We only need to perform two loops to complete the calculation, which are the process of traversing arr1 and traversing arr2, so the time complexity is O(N+M), where N is the length of arr1 and M is the length of arr2.

  5. The reason for writing this way is to take advantage of the nature of the XOR operation, while reducing the use of extra space, and directly operate on the original array arr2. This can improve code efficiency and save memory space.

class Solution {
public:
    int getXORSum(vector<int>& arr1, vector<int>& arr2) {
        //(a&b)^(a&c)=a&(b^c)
        int ans=0,a=0;
        for(int i=0;i<arr1.size();++i){a^=arr1[i];}
        for(int i=0;i<arr2.size();++i){arr2[i]&=a;}
        for(int i=0;i<arr2.size();++i){ans^=arr2[i];}
        return ans;
    }
};

postscript

The most important thing for brushing the questions is the foundation! Base! Base!

Guess you like

Origin blog.csdn.net/m0_67759533/article/details/131881432