Skip to content

Commit 879a81a

Browse files
committed
231
1 parent 6925dfa commit 879a81a

File tree

3 files changed

+369
-1
lines changed

3 files changed

+369
-1
lines changed

SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,4 +210,5 @@
210210
* [228. Summary Ranges](leetcode-228-Summary-Ranges.md)
211211
* [229. Majority Element II](leetcode-229-Majority-ElementII.md)
212212
* [230. Kth Smallest Element in a BST](leetcode-230-Kth-Smallest-Element-in-a-BST.md)
213+
* [231*. Power of Two](leetcode-231-Power-of-Two.md)
213214
* [更多](more.md)

leetcode-201-300.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,6 @@
5858

5959
<a href="leetcode-229-Majority-ElementII.html">229. Majority Element II</a>
6060

61-
<a href="leetcode-230-Kth-Smallest-Element-in-a-BST.html">230. Kth Smallest Element in a BST</a>
61+
<a href="leetcode-230-Kth-Smallest-Element-in-a-BST.html">230. Kth Smallest Element in a BST</a>
62+
63+
<a href="leetcode-231-Power-of-Two.html">231. Power of Two</a>

leetcode-231-Power-of-Two.md

Lines changed: 365 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,365 @@
1+
# 题目描述(简单难度)
2+
3+
![](https://windliang.oss-cn-beijing.aliyuncs.com/231.jpg)
4+
5+
判断一个数是不是 `2` 的幂次。
6+
7+
# 思路分析
8+
9+
题目比较简单,有很多解法,看了其他人的解法,各种秀操作,哈哈。解法一和解法二是我开始想到的,后边的解法是其他人的也总结到这里。
10+
11+
# 解法一
12+
13+
介绍一种暴力的方法,判断 `1` 和当前数是否相等,再判断 `2` 和当前数是否相等,再判断 `4` 和当前数是否相等...直到所枚举的数超过了当前数,那么就返回 `false`。至于枚举的数 `1 2 4 8 16...`,可以通过移位得到。
14+
15+
```java
16+
public boolean isPowerOfTwo(int n) {
17+
int power = 1;
18+
while (power <= n) {
19+
if (power == n) {
20+
return true;
21+
}
22+
power = power << 1;
23+
// if (power == Integer.MIN_VALUE)
24+
if (power == -2147483648) {
25+
break;
26+
}
27+
}
28+
return false;
29+
}
30+
```
31+
32+
当然有一点需要注意,需要了解一些补码的知识,参考 [趣谈补码](https://zhuanlan.zhihu.com/p/67227136)
33+
34+
对于 `int` 类型,最大的 `2` 的幂次是 2 的 30 次方,即`1073741824`,二进制形式是 `0100000...00`
35+
36+
最大的负数是 `-2147483648`,二进制形式是 `1000000...00`
37+
38+
可以发现前者左移一位刚好变成了后者,这也是代码中判断是否是 `-2147483648` 的原因,不然的话会造成死循环的。
39+
40+
# 解法二
41+
42+
我们把数字放眼到二进制形式,列举一下 `2` 的幂次。
43+
44+
```java
45+
1 1
46+
2 10
47+
4 100
48+
8 1000
49+
16 10000
50+
...
51+
```
52+
53+
可以发现发现都是 `100...` 的形式,如果做过 [201 题](https://leetcode.wang/leetcode-201-Bitwise-AND-of-Numbers-Range.html),对 `Integer.highestOneBit` 方法可能还记得。可以实现保留最高位的 `1` ,然后将其它位全部置为 `0`。即,把 `0 0 0 1 X X X X` 变成 `0 0 0 1 0 0 0 0`
54+
55+
如果我们对给定的数 `n` 调用这个方法,如果 `n``2` 的幂次,那么它还是它本身。如果是其他数,由于其它位被置 `0` 了,所以它一定不等于它本身了。
56+
57+
```java
58+
public boolean isPowerOfTwo(int n) {
59+
if (n == 0 || n == -2147483648) {
60+
return false;
61+
}
62+
return Integer.highestOneBit(n) == n;
63+
}
64+
```
65+
66+
`0``-2147483648` 不符合上边的规则,单独考虑,我们可以更干脆一些,小于等于 `0` 的数直接不考虑。
67+
68+
```java
69+
public boolean isPowerOfTwo(int n) {
70+
if (n <= 0) {
71+
return false;
72+
}
73+
return Integer.highestOneBit(n) == n;
74+
}
75+
```
76+
77+
[201 题](https://leetcode.wang/leetcode-201-Bitwise-AND-of-Numbers-Range.html#解法三) 的解法三对 `highestOneBit` 的源码进行了分析,下边把之前的解析贴过来。
78+
79+
我们调用了库函数 `Integer.highestOneBit`,我们去看一下它的实现。
80+
81+
```java
82+
/**
83+
* Returns an {@code int} value with at most a single one-bit, in the
84+
* position of the highest-order ("leftmost") one-bit in the specified
85+
* {@code int} value. Returns zero if the specified value has no
86+
* one-bits in its two's complement binary representation, that is, if it
87+
* is equal to zero.
88+
*
89+
* @param i the value whose highest one bit is to be computed
90+
* @return an {@code int} value with a single one-bit, in the position
91+
* of the highest-order one-bit in the specified value, or zero if
92+
* the specified value is itself equal to zero.
93+
* @since 1.5
94+
*/
95+
public static int highestOneBit(int i) {
96+
// HD, Figure 3-1
97+
i |= (i >> 1);
98+
i |= (i >> 2);
99+
i |= (i >> 4);
100+
i |= (i >> 8);
101+
i |= (i >> 16);
102+
return i - (i >>> 1);
103+
}
104+
```
105+
106+
它做了什么事情呢?我们从 `return` 入手。
107+
108+
对于 `0 0 0 1 X X X X` ,最终会变成 `0 0 0 1 1 1 1 1`,记做 `i` 。把 `i` 再右移一位变成 `0 0 0 0 1 1 1 1`,然后两数做差。
109+
110+
```java
111+
i 0 0 0 1 1 1 1 1
112+
i >>> 1 0 0 0 0 1 1 1 1
113+
0 0 0 1 0 0 0 0
114+
```
115+
116+
就得到了这个函数最后返回的结果了。
117+
118+
`0 0 0 1 X X X X` 变成 `0 0 0 1 1 1 1 1`,可以通过复制实现。
119+
120+
第一步,将首位的 `1` 赋值给它的旁边。
121+
122+
```java
123+
i |= (i >> 1);
124+
0 0 0 1 X X X X -> 0 0 0 1 1 X X X
125+
126+
现在首位有两个 1 了,所以就将这两个 1 看做一个整体,继续把 1 赋值给它的旁边。
127+
i |= (i >> 2);
128+
0 0 0 1 1 X X X -> 0 0 0 1 1 1 1 X
129+
130+
现在首位有 41 了,所以就将这 41 看做一个整体,继续把 1 赋值给它的旁边。
131+
i |= (i >> 4);
132+
0 0 0 1 1 1 1 X -> 0 0 0 1 1 1 1 1
133+
134+
其实到这里已经结束了,但函数中是考虑最坏的情况,类似于这种 1000000...00, 首位是 1, 有 310
135+
```
136+
137+
我们可以把上边的源码直接放过来。
138+
139+
```java
140+
public boolean isPowerOfTwo(int n) {
141+
if (n <= 0) {
142+
return false;
143+
}
144+
int i = n;
145+
i |= (i >> 1);
146+
i |= (i >> 2);
147+
i |= (i >> 4);
148+
i |= (i >> 8);
149+
i |= (i >> 16);
150+
i = i - (i >>> 1);
151+
return i == n;
152+
}
153+
```
154+
155+
上边的解法是我开始想到的,下边的解法全部来自 [这里](https://leetcode.com/problems/power-of-two/discuss/63966/4-different-ways-to-solve-Iterative-Recursive-Bit-operation-Math),分享一下。
156+
157+
# 解法三
158+
159+
一直进行除以 `2`,直到不是 `2` 的倍数。
160+
161+
```java
162+
public boolean isPowerOfTwo(int n) {
163+
if (n == 0) return false;
164+
while (n % 2 == 0) {
165+
n /= 2;
166+
}
167+
return n == 1;
168+
}
169+
```
170+
171+
也可以改写成递归的形式。
172+
173+
```java
174+
public boolean isPowerOfTwo(int n) {
175+
return n > 0 && (n == 1 || (n%2 == 0 && isPowerOfTwo(n/2)));
176+
}
177+
```
178+
179+
# 解法四
180+
181+
做过 [191 题](https://leetcode.wang/leetcode-191-Number-of-1-Bits.html) 的话,对一个 `trick` 应该有印象。
182+
183+
有一个方法,可以把最右边的 `1` 置为 `0`,举个具体的例子。
184+
185+
比如十进制的 `10`,二进制形式是 `1010`,然后我们只需要把它和 `9` 进行按位与操作,也就是 `10 & 9 = (1010) & (1001) = 1000`,也就是把 `1010` 最右边的 `1` 置为 `0`
186+
187+
规律就是对于任意一个数 `n`,然后 `n & (n-1)` 的结果就是把 `n` 的最右边的 `1` 置为 `0`
188+
189+
也比较好理解,当我们对一个数减 `1` 的话,比如原来的数是 `...1010000`,然后减一就会向前借位,直到遇到最右边的第一个 `1`,变成 `...1001111`,然后我们把它和原数按位与,就会把从原数最右边 `1` 开始的位置全部置零了 `...10000000`
190+
191+
有了这个知识,我们看一下解法二列举的 `2` 的幂次,只有一个 `1`,如果通过 `n&(n-1)`,那么就会变成 `0` 了。
192+
193+
同样的 `0``-2147483648` 不符合上边的规则,需要单独考虑。又因为所有负数一定不是 `2` 的幂次,所以代码可以写成下边的样子。
194+
195+
```java
196+
public boolean isPowerOfTwo(int n) {
197+
if (n <= 0) {
198+
return false;
199+
}
200+
return (n & (n - 1)) == 0;
201+
}
202+
```
203+
204+
# 解法五
205+
206+
`java` 中还有一个方法 `Integer.bitCount(n)`,返回 `n` 的二进制形式的 `1` 的个数。
207+
208+
```java
209+
public boolean isPowerOfTwo(int n) {
210+
if (n <= 0) {
211+
return false;
212+
}
213+
return Integer.bitCount(n) == 1;
214+
}
215+
```
216+
217+
同样的,我们学习一下 `bitCount` 的源码。
218+
219+
```java
220+
/**
221+
* Returns the number of one-bits in the two's complement binary
222+
* representation of the specified {@code int} value. This function is
223+
* sometimes referred to as the <i>population count</i>.
224+
*
225+
* @param i the value whose bits are to be counted
226+
* @return the number of one-bits in the two's complement binary
227+
* representation of the specified {@code int} value.
228+
* @since 1.5
229+
*/
230+
public static int bitCount(int i) {
231+
// HD, Figure 5-2
232+
i = i - ((i >>> 1) & 0x55555555);
233+
i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
234+
i = (i + (i >>> 4)) & 0x0f0f0f0f;
235+
i = i + (i >>> 8);
236+
i = i + (i >>> 16);
237+
return i & 0x3f;
238+
}
239+
```
240+
241+
[191 题](https://leetcode.wang/leetcode-191-Number-of-1-Bits.html) 题目就是求二进制 `1` 的个数,其中解法三介绍的其实就是上边的解法,我把解释贴过来。
242+
243+
有点类似于 [190 题](https://leetcode.wang/leetcode-190-Reverse-Bits.html) 的解法二,通过整体的位操作解决问题,参考 [这里](https://leetcode.com/problems/number-of-1-bits/discuss/55120/Short-code-of-C%2B%2B-O(m)-by-time-m-is-the-count-of-1's-and-another-several-method-of-O(1)-time) ,也是比较 `trick` 的,不容易想到,但还是很有意思的。
244+
245+
本质思想就是用本身的比特位去记录对应位数的比特位 `1` 的个数,举个具体的例子吧。为了简洁,求一下 `8` 比特的数字中 `1` 的个数。
246+
247+
```java
248+
统计数代表对应括号内 1 的个数
249+
1 1 0 1 0 0 1 1
250+
首先把它看做 8 组,统计每组 1 的个数
251+
原数字:(1) (1) (0) (1) (0) (0) (1) (1)
252+
统计数:(1) (1) (0) (1) (0) (0) (1) (1)
253+
每个数字本身,就天然的代表了当前组 1 的个数。
254+
255+
接下来看做 4 组,相邻两组进行合并,统计数其实就是上边相邻组统计数相加即可。
256+
原数字:(1 1) (0 1) (0 0) (1 1)
257+
统计数:(1 0) (0 1) (0 0) (1 0)
258+
十进制: 2 1 0 2
259+
260+
接下来看做 2 组,相邻两组进行合并,统计数变成上边相邻组统计数的和。
261+
原数字:(1 1 0 1) (0 0 1 1)
262+
统计数:(0 0 1 1) (0 0 1 0)
263+
十进制: 3 2
264+
265+
接下来看做 1 组,相邻两组进行合并,统计数变成上边相邻组统计数的和。
266+
原数字:(1 1 0 1 0 0 1 1)
267+
统计数:(0 0 0 0 0 1 0 1)
268+
十进制: 5
269+
```
270+
271+
看一下 「统计数」的变化,也就是统计的 `1` 的个数。
272+
273+
看下二进制形式的变化,两两相加。
274+
275+
![](https://windliang.oss-cn-beijing.aliyuncs.com/191_2.jpg)
276+
277+
看下十进制形式的变化,两两相加。
278+
279+
![](https://windliang.oss-cn-beijing.aliyuncs.com/191_3.jpg)
280+
281+
最后我们就的得到了 `1` 的个数是 `5`
282+
283+
所以问题的关键就是怎么实现每次合并相邻统计数,我们可以通过位操作实现,举个例子。
284+
285+
比如上边 `4` 组到 `2` 组中的前两组合成一组的变化。要把 `(1 0) (0 1)` 两组相加,变成 `(0 0 1 1)` 。其实我们只需要把 `1001``0011` 相与得到低两位,然后把 `1001` 右移两位再和 `0011` 相与得到高两位,最后将两数相加即可。也就是`(1001) & (0011) + (1001) >>> 2 & (0011)= 0011`
286+
287+
扩展到任意情况,两组合并成一组,如果合并前每组的个数是 `n`,合并前的数字是 `x`,那么合并后的数字就是 `x & (000...111...) + x >>> n & (000...111...) `,其中 `0``1` 的个数是 `n`
288+
289+
```java
290+
public int hammingWeight(int n) {
291+
n = (n & 0x55555555) + ((n >>> 1) & 0x55555555); // 32 组向 16 组合并,合并前每组 1 个数
292+
n = (n & 0x33333333) + ((n >>> 2) & 0x33333333); // 16 组向 8 组合并,合并前每组 2 个数
293+
n = (n & 0x0f0f0f0f) + ((n >>> 4) & 0x0f0f0f0f); // 8 组向 4 组合并,合并前每组 4 个数
294+
n = (n & 0x00ff00ff)+ ((n >>> 8) & 0x00ff00ff); // 4 组向 2 组合并,合并前每组 8 个数
295+
n = (n & 0x0000ffff) + ((n >>> 16) & 0x0000ffff); // 2 组向 1 组合并,合并前每组 16 个数
296+
return n;
297+
}
298+
```
299+
300+
写成 `16` 进制可能不好理解,我们拿16 组向 8 组合并举例,合并前每组 2 个数。也就是上边我们推导的,我们要把 `(1 0) (0 1)` 两组合并,需要和 `0011` 按位与,写成 `16` 进制就是 `3`,因为合并完是 `8` 组,所以就是 `8``3`,即 `0x33333333`
301+
302+
再回到 `java` 的源码。
303+
304+
```java
305+
public static int bitCount(int i) {
306+
// HD, Figure 5-2
307+
i = i - ((i >>> 1) & 0x55555555);
308+
i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
309+
i = (i + (i >>> 4)) & 0x0f0f0f0f;
310+
i = i + (i >>> 8);
311+
i = i + (i >>> 16);
312+
return i & 0x3f;
313+
}
314+
```
315+
316+
第一步写法不一样,也很好理解。结合下边的图看,
317+
318+
![](https://windliang.oss-cn-beijing.aliyuncs.com/191_2.jpg)
319+
320+
第一步要做的就是把 `11 -> 10``01 - > 01``00 -> 00``10 -> 01`
321+
322+
其实就是原来的数减去高位的数,`11 - 1 = 10`, `01 - 0 = 01``00 - 0 = 00``10 - 1 = 01`
323+
324+
第二步一致。
325+
326+
第三步,没有与完相加,而是先相加再相与,之所以可以这么做,是因为四位的话最多就是 `4``1`,也就是 `0100``0100` 合并,结果最多也就是 `4` 位,所以只需要最后和 `0f` 相与,将高四位置零即可。
327+
328+
第四步和第五步没有相与,是因为最多 `32``1`,用二进制表示就是 `100000`,所以只需要低 `6` 位的结果,高 `8` 位是什么已经不重要了,也就不需要通过相与置零了,最后只需要和 `0x3f(111111)` 相与取得我们的结果即可。
329+
330+
# 解法六
331+
332+
前边提到对于 `int` 类型,最大的 `2` 的幂次是 2 的 30 次方,即`1073741824`。对于 `n` 分两种情况讨论。
333+
334+
* 如果 `n``2` 的幂次,那么 $$n=2^k$$
335+
336+
$$2^{30}=2^k*2^{30-k}$$,所以 $$2^{30} \% 2^k==0$$
337+
338+
* 如果 `n` 不是 `2` 的幂次,那么 $$n = j * 2^k$$,其中 `j` 是一个奇数,因为 `n` 一直进行除以 `2` 可以得到 `k`,直到不能被 `2` 整除,此时一定是奇数,也就是公式中的 `j`
339+
340+
$$2^{30} \% (j * 2^k) = 2^{30-k} \% j!=0$$
341+
342+
综上所述,通过是否能被 `2``30` 次方,即`1073741824` 整除,即可解决我们的问题。
343+
344+
```java
345+
public boolean isPowerOfTwo(int n) {
346+
if (n <= 0) {
347+
return false;
348+
}
349+
return 1073741824 % n == 0;
350+
}
351+
```
352+
353+
# 解法七
354+
355+
超级暴力打表法,因为 `int` 范围内 `2` 的幂次也只有 `32` 个数。
356+
357+
```java
358+
public boolean isPowerOfTwo(int n) {
359+
return new HashSet<>(Arrays.asList(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 4194304, 8388608,16777216, 33554432, 67108864, 134217728, 268435456, 536870912, 1073741824)).contains(n);
360+
}
361+
```
362+
363+
#
364+
365+
这道题的话使用了很多二进制的技巧,因为题目本身比较简单,所以就是各种大神秀操作了,哈哈,大概是解法最多的一道题了。

0 commit comments

Comments
 (0)