- 记录LeetCode题目笔记,汇总LeetCode解答记录
Overview
- LeetCode-191. Number of 1 Bits (位1的个数)
- LeetCode-338. Counting Bits(比特位计数)
- LeetCode-409. Longest Palindrome (最长回文串)
- LeetCode-223. Rectangle Area(矩形面积)
- LeetCode-476. Number Complement(数字的补数)
191. Number of 1 Bits(位1的个数)
Description
本问题中,计数了一个无符号整数的位,结果称为 pop count
,或 汉明权重。
Approach 1:除K取余法
Analysis
- 除K取余法,利用
num%2
和num/2
不断取出数字的二进制数值。 - 需要注意的是,在如Java语言中,不能使用
while(num>0)
进行判断。因为Java编译器使用二进制补码记法来表示有符号整数。例如输入11111111111111111111111111111101
, 会被作为-3
处理。 - 因此,在Java中应该避免取模和除法操作,使用
(num&1) == 1
代替num%2
;使用无符号右移num >>> 1
代替num/2
。 - 在C++中,若使用无符号类型
uint32_t
作为输入类型,可以直接使用while(num>0)
作为循环判断。
Java 中,
>>>
为无符号右移,>>
为有符号右移。
- 时间复杂度:
O(1)
。运行时间依赖于数字n
的位数。本题中是一个32位数字,因此时间复杂度位为O(1)
。 - 空间复杂度:
O(1)
,没有使用额外的空间。
Solution
- C++
1 | class Solution { |
- Java
1 | public class Solution { |
或者采用如下实现
1 | public int hammingWeight(int n) { |
Approach 2:位操作小技巧 - n&(n-1)
Analysis
对前面的算法进行优化。
不再检查数字的每一个位,而是不断把数字最后一个 1
反转,并把计数加1。当数字变成 0
的时候偶,我们就知道它没有 1
的位了,此时返回计数。
这里关键的想法是对于任意数字 n
,将 n
和 n - 1
做与运算,不断循环,最后一定会把 1
的位变成 0
。
为什么?考虑 n
和 n - 1
的二进制表示。
在二进制表示中,数字 n
中最低位的 1
总是对应 n - 1
中的 0
。因此,将 n
和 n - 1
与运算总是能把 n
中最低位的 1
变成 0
,并保持其他位不变。
使用这个小技巧,代码变得非常简单。
- 时间复杂度:
O(1)
。运行时间依赖于数字n
的位数。本题中是一个32位数字,因此时间复杂度位为O(1)
。 - 空间复杂度:
O(1)
,没有使用额外的空间。
Method
- Java
1 | public class Solution { |
Approach 3:字符串长度+正则匹配
Analysic
JS中,
toString(radix)
方法可以将数字转换为基于radix
的进制数(若radix
缺省,则默认转换为 10 进制数)。
- 首先,使用
toString(2)
方法,将数字转换为 2 进制字符串 - 然后,使用正则匹配,滤除字符串中的
0
,得到新的字符串 - 最后,新字符串的长度即数字中
1
的个数
Solution
- JS
1 | /** |
Approach 4:使用内置函数
Analysis
Java中,函数 Integer.bitCount() 可以返回数字中2进制格式下
1
的个数。The
java.lang.Integer.bitCount()
method returns the number of one-bits in the two’s complement binary representation of the specified int value i. This is sometimes referred to as thepopulation count
.类似的,C++内置的
__builtin_popcount()
函数也可以返回数字中2进制格式下1
的个数。
Method
- C++
1 | class Solution { |
- Java
1 | public class Solution { |
338. Counting Bits(比特位计数)
Description
Approach 1-Pop count
Analysis
本问题可以看做 LeetCode- 191. Number of 1 Bits 的后续。
191. Number of 1 Bits
问题中,计数了一个无符号整数的位,结果称为 pop count
,或 汉明权重。
现在,我们先默认这个概念。假设我们有函数 int popcount(int x)
,可以返回一个给定非负整数的位计数。我们只需要在 [0, num]
范围内循环并将结果存到一个列表中。
- 时间复杂度:
O(nk)
。对于每个整数x
,我们需要O(k)
次操作,其中k
是x
的位数。 - 空间复杂度:
O(n)
。
Solution
- Java
1 | class Solution { |
- C++
1 | class Solution { |
Approach 2-动态规划+最高有效位
Analysis
利用已有的计数结果来生成新的计数结果。
假设有一个整数
1 | x = (1001011101)_2 = (605)_{10} |
我们已经计算了从 0
到 x - 1
的全部结果。
我们知道,x
与 我们计算过的一个数只有一位之差
1 | x' = (1011101)_2 = (93)_{10} |
它们只在最高有效位上不同。
让我们以二进制形式检查 [0, 3]
的范围
1 | (0) = (0)_2 |
可以看出, 2
和 3
的二进制形式可以通过给 0
和 1
的二进制形式在前面加上 1
来得到。因此,它们的 pop count
只相差 1。
类似的,我们可以使用 [0, 3]
作为蓝本来得到 [4, 7]
,使用 [0, 7]
作为蓝本来得到 [8, 15]
,即根据区间 [0, b)
的结果去产生区间 [b, 2b)
的结果,其中 b
为
1 | b = 2^m > x (m=0,1,2...) |
总之,对于 pop count P(x)
,我们有以下的状态转移函数
1 | P(x + b) = P(x) + 1, b = 2^m > x (m=0,1,2...) |
- 时间复杂度:
O(n)
。对每个整数x
,只需要常数时间。 - 空间复杂度:
O(n)
。需要O(n)
的空间来存储结果。
Solution
- Java
1 | class Solution { |
Approach 3-动态规划+最低有效位
Analysis
只要 x'
小于 x
,且它们的 pop count
之间存在函数关系,就可以写出状态转移函数。
遵循上一方法的相同原则,我们还可以通过最低有效位来获得状态转移函数。
观察 x
和 x' = x / 2
的关系
1 | x=(1001011101)_2=(605)_{10} |
可以发现 x'
与 x
只有一位不同,这是因为 x'
可以看做 x
移除最低有效位的结果。
这样,我们就有了下面的状态转移函数
1 | P(x) = P(x/2) + (x mod 2) |
例如
1 | P(0) = 0; //00 边界条件 |
Solution
- Java
1 | class Solution { |
409. Longest Palindrome(最长回文串)
Description
Approach 1-Map计数
Analysis
使用 Map
数据结构统计每个字符串出现的次数。遍历 Map
字典,回文串长度增加 2*(map[i]/2)
。同时,对出现次数对2取模,若为1,表示回文串中有出现单次的字符,设置标志位 hasSingle = true
,最后记得对出现次数加1。
- 时间复杂度:
O(n)
,n
为字符串的长度,至少遍历每个字符一次。 - 空间复杂度:
O(1)
,需要开辟额外空间来计数,字母最多为26个。
Solution
- C++
1 | class Solution { |
- Java
1 | class Solution { |
223. Rectangle Area(矩形面积)
Description
Approach 1-面积拆分求解
Analysis
- 根据上图,可以确定计算的面积值为两个矩形面积的和再减去重合部分的面积,即
resultArea = recArea1 + recArea2 - repetitionArea
- 重合部分定点坐标的确定
- Bottom Left Point Coordinate:
(max(A,E), max(B,F))
- Top Right Point Coordinate:
(min(C,G), min(D,H))
- Bottom Left Point Coordinate:
- 易忽略点
Due to the total bits of “int” is 32, we prefer to use “w2<=w1” to “(int) w2-w1” to judge the return value. The data of ”(int) w2-w1” may overflow the range of integer.(For example, w2>0 and w1<0)
Slove
- C++
1 | class Solution { |
在计算过程上进行优化,有如下代码实现
1 | class Solution { |
- JS
1 | var computeArea = function(A, B, C, D, E, F, G, H) { |
- Java
1 | public class Solution { |
476. Number Complement (数字的补数)
Description
给定一个正整数,输出它的补数。补数是对该数的二进制表示取反。
注意
1. 给定的整数保证在32位带符号整数的范围内。
2. 你可以假定二进制数不包含前导零位。
示例 1
输入: 5
输出: 2
解释: 5的二进制表示为101(没有前导零位),其补数为010。所以你需要输出2。
示例 2
输入: 1
输出: 0
解释: 1的二进制表示为1(没有前导零位),其补数为0。所以你需要输出0。
Approach 1-寻找最高位为1的位置
Analysis
分析题目可知,只需对每个位的二进制数值(没有前导零位时)进行翻转即可。
但数值实际存储中,是包含前导零位的。如果直接对 num 进行取反操作,会把符号位取反,并且把最高位为 1 之前的所有位数都取反。
1 | 不含前导零位时,5 = 101,取反操作得到010,即十进制数值2 |
因此,对每位翻转(或取反,或和1进行异或)的起始位置是从最高位的1开始的,前面的 0 是不能被翻转的。可以考虑从高位往低位遍历,当遇到第1个1后,记录最高位为1的位置,之后再进行翻转操作(异或实现)。
Solution
- C++
1 | class Solution { |
Approach 2-创建每位都是1的二进制数值
Analysis
在不含前导零位时,把 num
和同位数的且每位都是1的二进制数进行与操作,即可得到结果。
因此,问题转化为求解一个和 num
位数一样且每位都是1的二进制数值 mask
,最后进行异或操作(mask ^ num
)即可。
例如,5 = 101
,因此 mask = 111
,进行异或操作 mask ^ num = 111 ^ 101 = 010
。
得到每位都是1的二进制,如 111
,可以通过1000
减去 1 得到。
Solution
- Java
1 | class Solution { |
Approach 3-转换为字符串+正则匹配
Analysis
此处给出JS实现的特解
- 先将 num 转化为二进制字符串
- 借助正则表达式查找,将字符串中的 0 替换成1
- 再将该字符串值转化成整数
int
- 最后将该值和
num
进行异或操作
Solution
- JS
1 | /** |