方法:
- 模拟。具体而言,该题目类似于大数加法。
- 位运算。我不怎么会位运算,所以在这里记录一下。
方法:
- 格雷码公式: g ( i ) = b ( i + 1 ) ⊕ b ( i ) g(i) = b(i + 1) \oplus b(i) g(i)=b(i+1)⊕b(i)> 这个公式的意思是:第
b
个格雷码g
的二进制表示的第i
位等于b
的二进制表示的第i+1
位和第i
位异或。
比如4位格雷码: 第5个格雷码g = 0111,它的计算过程如下所示: 已知5的二进制表示是0101,那么根据格雷码公式: g ( 3 ) = 0 , g ( 2 ) = b ( 3 ) ⊕ b ( 2 ) , g ( 1 ) = b ( 2 ) ⊕ b ( 1 ) , g ( 0 ) = b ( 1 ) ⊕ b ( 0 ) g(3) = 0,g(2) = b(3) \oplus b(2),g(1) = b(2) \oplus b(1),g(0) = b(1) \oplus b(0) g(3)=0,g(2)=b(3)⊕b(2),g(1)=b(2)⊕b(1),g(0)=b(1)⊕b(0)。 写成竖式的形式:
0 1 0 1
\;\;0 1 0 1
(最后一位省略,高位补零,相当于右移一位)按位异或,得到
0 1 1 1
- 对称法构造,详细说明见热评第一条
异或有以下三个性质:
- 任何数字和0异或,结果仍然是原来的数字,即 a ⊕ 0 = a a \oplus 0 = a a⊕0=a
- 任何数字和其自身做异或运算,结果是0。即 a ⊕ a = 0 a \oplus a = 0 a⊕a=0
- 异或运算符合交换律和结合律,即 a ⊕ b ⊕ a = b ⊕ a ⊕ a = b ⊕ ( a ⊕ a ) = b ⊕ 0 = b a \oplus b \oplus a = b \oplus a \oplus a = b \oplus (a \oplus a) = b \oplus 0 = b a⊕b⊕a=b⊕a⊕a=b⊕(a⊕a)=b⊕0=b
位或:|=
使用方式:a |= b
表达式含义:对a
和 b
执行按位或运算,并把结果分配给变量a
题目
136.只出现一次的数字
根据上述异或的性质,数组中全部元素异或的结果即为数组中只出现一次的数字。
方法:按照由高位到低位计算无符号32整数的各个二进制位,同时计算颠倒后的结果。
这个方法是我能做到的极限。
这道题目没什么好说的,就是位运算符的简单应用。
与运算的规则如下:
- 0 & 0 = 0
- 0 & 1 = 0
- 1 & 0 = 0
- 1 & 1 = 1
对上述规则进行总结,不难得到:
- 0 与 任何数(这里的任何数指的是0和1)结果都是0
- 只有 1 与 1 的结果才是1
对于这道题目,最直观的做法就是遍历数字范围,依次执行按位与运算,但是数字范围是整个
int
类型的正数范围,直接遍历会超时。注意到结果一定可以表示成 32 32 32位的整数,因此我们可以想方设法地计算结果的每一位。那么如何根据给定的数字范围判断第
i
位是 0 0 0还是 1 1 1?由与运算的规则,只要在数字范围内存在一个数字的第
i
位是 0 0 0,那么结果的第i
位是 0 0 0。那么问题就转换成如何判断在给定数字范围内是否存在一个数字的第i
位是 0 0 0?具体做法如下:
- 判断左端点和右端点的第
i
位是否为 0 0 0,若为0,则结果的第i
位是 0 0 0- 或判断左端点和右端点右移
i
位后是否相同。若不同,则结果第i
位是 0 0 0。第一个判断方法好理解,第二个判断方法为什么正确? 假设左端点
left
和右端点right
第第i
位是 1 1 1,且右移i
位后不相同,那么right - left
必然是一个偶数,这意味着在给定数字区间内第i
位变化偶数次,根据与运算规则,结果的第i
位必然是 0 0 0。
方法:位运算。
既然题目不允许允许使用+,-
等运算符,那么可以另辟蹊径,使用二进制运算符:与、或、非、异或以及同或等。此外,在涉及到加法时,进位是一定要考虑的。
两个数字均为int
类型,二进制表示为32
位。对于两个数字当前位x
和y
,设进位为t
:
- 若
x
和y
均为1
,结果的当前位取决于x ^ y ^ t
,进位一定是1
。注意,^
是异或运算符,异或又被称为不进位加法。 - 若
x
和y
其中有一个是1
,结果的当前位取决于1 ^ t
,进位t
不变。 - 若
x
和y
都是0
,则当前位是t
,进位一定是0
。
这个过程类似于CMOS基础中的加法器。
这是一道简单的模拟题目,涉及到的知识点是位运算。
如何取一个整数integer
的低
8
8
8位的二进制表示?
答:利用interger & 255
,即可得到整数integer
的低
8
8
8位的二进制表示。
如何取得一个字节数x
的最高
n
n
n位?
答:x >> (8 - n)
由于题目限制给定整数的大小不超过
255
255
255,因此所给整数的高24
位全为0
,没有必要和255
相与提取低8位。
通过上述分析,提出如下算法:
- 遍历数组,设当前遍历到第 i i i个元素
- 若
(data[i] >> 7 & 1) == 0
,说明是1字节字符 - 若
(data[i] >> 5) == 6
,说明是2字节字符,继续判断后面相邻数字是否满足i < data.size() && (data[i + 1] >> 6) == 2
- 若
(data[i] >> 4) == 14
,说明是3字节字符,继续判断后面相邻2个数字是否满足i + 2 < data.size() && (data[i + 1] >> 6) == 2 && (data[i + 2] >> 6) == 2
- 若
(data[i] >> 4) == 30
,说明是4字节字符,继续判断后面相邻3个数字是否满足i + 3 < data.size() && (data[i + 1] >> 6) == 2 && (data[i + 2] >> 6) == 2 && (data[i + 3] >> 6) == 2
- 若以上情况都不满足,直接返回
false
初看题目以为回溯可以解决,想了一会发现不知道怎么回溯,随后灵机一动,可以枚举小时和分钟来解决这个问题。完成之后看题解,发现枚举可以解决。废话不多说了,介绍枚举方法吧。
思路和算法
我的思路是这样的:
- 题目中给的信息只有当前亮着的
LED
的数量,并没有告诉我们顶部的LED
亮着的数目和底部LED
灯亮着的数目。因此,需要对顶部的LED
亮着的数目进行枚举。 - 在知道顶部的
LED
亮着的数目的情况下,哪些灯亮着还是未知的。因此需要枚举 [ 0 , 11 ] [0,11] [0,11]中的数字,计算其二进制表示中1
的个数,判断其是否是合法的。 - 分钟的计算同小时的计算。
根据上述思路,所设计的算法步骤如下:
- 枚举顶部
LED
亮着的数目 - 枚举
[
0
,
11
]
[0,11]
[0,11]中的数字,寻找所有合法的数字
hour
- 枚举
[
0
,
59
]
[0,59]
[0,59]中的数字,寻找所有合法的数字
minute
- 把
hour
和minute
转换为字符串,按照时间的表示把字符串拼接起来,最后加到答案中
- 负数在计算机中是以补码的形式存储的,在转换为
16
进制数字时不需要考虑把负数的二进制表示转换为补码。 - 进制转换方法:先转
2
进制,再转16
进制
思路1 : 双重循环暴力求解,超时。
思路2 : 统计数组nums
中所有数字的二进制表示中第i
位的0
的个数cnt_zero
和1
的个数cnt_one
,那么数组nums
中所有数字在第i
位的汉明距离可以表示为cnt_one * cnt_zero
。