LeetCode【学习计划】:【算法】
190. 颠倒二进制位
LeetCode: 190. 颠倒二进制位
简 单 \color{#00AF9B}{简单} 简单
颠倒给定的 32 位无符号整数的二进制位。
提示:
- 请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。
- 在 Java 中,编译器使用二进制补码记法来表示有符号整数。因此,在 示例 2 中,输入表示有符号整数
-3
,输出表示有符号整数-1073741825
。
示例 1:
输入:n = 00000010100101000001111010011100
输出:964176192 (00111001011110000010100101000000)
解释:输入的二进制串 00000010100101000001111010011100 表示无符号整数 43261596,
因此返回 964176192,其二进制表示形式为 00111001011110000010100101000000。
示例 2:
输入:n = 11111111111111111111111111111101
输出:3221225471 (10111111111111111111111111111111)
解释:输入的二进制串 11111111111111111111111111111101 表示无符号整数 4294967293,
因此返回 3221225471 其二进制表示形式为 10111111111111111111111111111111 。
提示:
- 输入是一个长度为
32
的二进制字符串
进阶: 如果多次调用这个函数,你将如何优化你的算法?
方法1:逐位颠倒
我们在循环i
中,设i
的初值为31,每次循环i
自减直至0。每次循环枚举n
的最低位:n & 1
;然后将它左移i
位与结果变量ans
作或操作:ans |= (n & 1) << i
,这样这一位就被存放到了结果中。
最后,n
向右移位,以便下一次循环中最低位的枚举。
需要注意的是,在某些语言(如Java、JavaScript)中没有无符号整型,因此对n
的右移操作要使用逻辑右移。
#include <cstdint>
class Solution
{
public:
uint32_t reverseBits(uint32_t n)
{
uint32_t ans = 0;
for (int i = 31; i >=0 && n != 0; i--)
{
ans |= (n & 1) << i;
n >>= 1;
}
return ans;
}
};
复杂度分析
-
时间复杂度: O ( k ) O(k) O(k)。其中,
k
为C++中int
类型的二进制位数减1,int
型的长度由编译器决定。 -
空间复杂度: O ( 1 ) O(1) O(1)
参考结果
Accepted
600/600 cases passed (4 ms)
Your runtime beats 54.64 % of cpp submissions
Your memory usage beats 70.26 % of cpp submissions (5.8 MB)
方法2:分治
题目中为翻转二进制位,其实和翻转一个字符串有一点类似。若要翻转一个字符串,我们可以将其分成左右两个部分,对每个部分进行递归的翻转,最后再将左半部分拼在右半部分的后面就完成了翻转。
由于题目给出n
的类型为无符号32位整型uint32_t
,它的长度确定为32位,且次方法步骤较少,那么我们就可以把递归的过程给直接写明,而不用写作函数递归调用的方式。
- 递归的最低层将每个相邻的二进制位进行交换。
- 交换每2个相邻的二进制位
- 交换每4个相邻的二进制位
- 交换每8个相邻的二进制位
- 交换每16个相邻的二进制位
最后,32位的二进制位便被交换完毕。
以最低层为例,观察一下具体操作:
-
设用于移位的数:
M1 = 0x55555555; // 01010101010101010101010101010101
-
先右移一位,取出的就是奇数位上的数,并且已经右移过了:
(n >> 1) & M1
-
取出偶数位上的数,并左移:
(n & M1) << 1
-
将它们进行或操作便能完成交换:
n = (n >> 1) & M1 | (n & M1) << 1;
#include <cstdint>
class Solution
{
private:
const uint32_t M1 = 0x55555555; // 01010101010101010101010101010101
const uint32_t M2 = 0x33333333; // 00110011001100110011001100110011
const uint32_t M4 = 0x0f0f0f0f; // 00001111000011110000111100001111
const uint32_t M8 = 0x00ff00ff; // 00000000111111110000000011111111
public:
uint32_t reverseBits(uint32_t n)
{
n = (n >> 1) & M1 | (n & M1) << 1;
n = (n >> 2) & M2 | (n & M2) << 2;
n = (n >> 4) & M4 | (n & M4) << 4;
n = (n >> 8) & M8 | (n & M8) << 8;
n = n >> 16 | n << 16;
return n;
}
};
复杂度分析
-
时间复杂度: O ( 1 ) O(1) O(1)
-
空间复杂度: O ( 1 ) O(1) O(1)
参考结果
Accepted
600/600 cases passed (0 ms)
Your runtime beats 100 % of cpp submissions
Your memory usage beats 31.52 % of cpp submissions (5.9 MB)
136. 只出现一次的数字
LeetCode: 136. 只出现一次的数字
简 单 \color{#00AF9B}{简单} 简单
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
输入: [2,2,1]
输出: 1
示例 2:
输入: [4,1,2,1,2]
输出: 4
方法1:哈希表
这道题用哈希表可以解决,但是太简单了,也没什么意思。
方法2:异或
这一方法我们需要用到异或 ⊕ \oplus ⊕运算的知识。
异或的概念是:若两者对应的二进制位不同,则为1
;相同则为0
。
例:
01010 ⊕ 10101 = = 11111 11111 ⊕ 00000 = = 00000 10 ⊕ 11 = = 10 10 ⊕ 00 = = 01 01010 \oplus 10101 == 11111 \\ 11111 \oplus 00000 == 00000 \\ 10 \oplus 11 == 10 \\ 10 \oplus 00 == 01 01010⊕10101==1111111111⊕00000==0000010⊕11==1010⊕00==01
如果是同一个数进行异或操作,那么每一位都是相同的,则结果必然是0
。如果是多个“同一个数”进行异或操作,因为每“同一个数”的异或结果为0
,而0==0
,因此异或结果也是0
。这就表明,只要每个数出现的次数为偶数,则所有数进行异或操作的结果都是0:
a n s = ( a 1 ⊕ a 1 ) ⊕ ( a 2 ⊕ a 2 ) ⊕ . . . ⊕ ( a n ⊕ a n ) = 0 ⊕ 0 ⊕ . . . ⊕ 0 = 0 \begin{aligned} ans = & (a_1 \oplus a_1) \oplus (a_2 \oplus a_2) \oplus ... \oplus (a_n \oplus a_n) \\ & = 0 \oplus 0 \oplus ... \oplus 0 \\ & = 0 \end{aligned} ans=(a1⊕a1)⊕(a2⊕a2)⊕...⊕(an⊕an)=0⊕0⊕...⊕0=0
这样一来,只有中间有一个多出来的数,那么结果就是那个数。因为某个数的二进制位中,0
和0
的异或结果还是0
,1
和0
的异或结果还是1
,所以某个数和0
的异或结果还是那个数。这样一来,我们就能找到那个单独的数了:
a n s = ( a 1 ⊕ a 1 ) ⊕ ( a 2 ⊕ a 2 ) ⊕ . . . ⊕ ( a n − 1 ⊕ a n − 1 ) ⊕ a n = 0 ⊕ 0 ⊕ . . . ⊕ 0 ⊕ a n = a n \begin{aligned} ans = & (a_1 \oplus a_1) \oplus (a_2 \oplus a_2) \oplus ... \oplus (a_{n-1} \oplus a_{n-1}) \oplus a_n \\ & = 0 \oplus 0 \oplus ... \oplus 0 \oplus a_n \\ & = a_n \end{aligned} ans=(a1⊕a1)⊕(a2⊕a2)⊕...⊕(an−1⊕an−1)⊕an=0⊕0⊕...⊕0⊕an=an
#include <vector>
using namespace std;
class Solution
{
public:
int singleNumber(vector<int> &nums)
{
int ans = 0;
for (int i = 0; i < nums.size(); i++)
{
ans ^= nums[i];
}
return ans;
}
};
复杂度分析
-
时间复杂度: O ( n ) O(n) O(n)。
n
为数组的长度,数组中每一个数都要遍历一次。 -
空间复杂度: O ( 1 ) O(1) O(1)
参考结果
Accepted
61/61 cases passed (16 ms)
Your runtime beats 72.1 % of cpp submissions
Your memory usage beats 92.58 % of cpp submissions (16.4 MB)