一行代码解决的智力题.md 12.1 KB
Newer Older
L
labuladong 已提交
1 2
# 一行代码就能解决的算法题

3 4
<p align='center'>
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
L
labuladong 已提交
5
<a href="https://appktavsiei5995.pc.xiaoe-tech.com/index" target="_blank"><img class="my_header_icon" src="https://img.shields.io/static/v1?label=精品课程&message=查看&color=pink&style=flat"></a>
6 7 8 9
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%8E-@labuladong-000000.svg?style=flat-square&logo=Zhihu"></a>
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站-@labuladong-000000.svg?style=flat-square&logo=Bilibili"></a>
</p>

L
labuladong 已提交
10
![](https://labuladong.github.io/algo/images/souyisou1.png)
11

L
labuladong 已提交
12
**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO) 已更新到 V2.0;[第 13 期刷题打卡](https://mp.weixin.qq.com/s/eUG2OOzY3k_ZTz-CFvtv5Q) 最后一天报名!另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。**
13 14 15



L
labuladong 已提交
16
读完本文,你不仅学会了算法套路,还可以顺便解决如下题目:
17

L
labuladong 已提交
18 19 20 21 22
| LeetCode | 力扣 | 难度 |
| :----: | :----: | :----: |
| [292. Nim Game](https://leetcode.com/problems/nim-game/) | [292. Nim 游戏](https://leetcode.cn/problems/nim-game/) | 🟢
| [319. Bulb Switcher](https://leetcode.com/problems/bulb-switcher/) | [319. 灯泡开关](https://leetcode.cn/problems/bulb-switcher/) | 🟠
| [877. Stone Game](https://leetcode.com/problems/stone-game/) | [877. 石子游戏](https://leetcode.cn/problems/stone-game/) | 🟠
23 24 25

**-----------**

L
labuladong 已提交
26
下文是我在刷题过程中总结的三道有趣的「脑筋急转弯」题目,可以使用算法编程解决,但只要稍加思考,就能找到规律,直接想出答案。
L
labuladong 已提交
27 28 29

### 一、Nim 游戏

L
labuladong 已提交
30
力扣第 292 题「Nim 游戏」给了这样一个游戏规则:
L
labuladong 已提交
31

L
labuladong 已提交
32 33 34
你和你的朋友面前有一堆石子,你们轮流拿,一次至少拿一颗,最多拿三颗,谁拿走最后一颗石子谁获胜。

假设你们都很聪明,由你第一个开始拿,请你写一个算法,输入一个正整数 `n`,返回你是否能赢(true 或 false)。
L
labuladong 已提交
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51

比如现在有 4 颗石子,算法应该返回 false。因为无论你拿 1 颗 2 颗还是 3 颗,对方都能一次性拿完,拿走最后一颗石子,所以你一定会输。

首先,这道题肯定可以使用动态规划,因为显然原问题存在子问题,且子问题存在重复。但是因为你们都很聪明,涉及到你和对手的博弈,动态规划会比较复杂。

**我们解决这种问题的思路一般都是反着思考**

如果我能赢,那么最后轮到我取石子的时候必须要剩下 1~3 颗石子,这样我才能一把拿完。

如何营造这样的一个局面呢?显然,如果对手拿的时候只剩 4 颗石子,那么无论他怎么拿,总会剩下 1~3 颗石子,我就能赢。

如何逼迫对手面对 4 颗石子呢?要想办法,让我选择的时候还有 5~7 颗石子,这样的话我就有把握让对方不得不面对 4 颗石子。

如何营造 5~7 颗石子的局面呢?让对手面对 8 颗石子,无论他怎么拿,都会给我剩下 5~7 颗,我就能赢。

这样一直循环下去,我们发现只要踩到 4 的倍数,就落入了圈套,永远逃不出 4 的倍数,而且一定会输。所以这道题的解法非常简单:

L
labuladong 已提交
52 53
```java
boolean canWinNim(int n) {
L
labuladong 已提交
54 55 56 57 58 59 60 61
    // 如果上来就踩到 4 的倍数,那就认输吧
    // 否则,可以把对方控制在 4 的倍数,必胜
    return n % 4 != 0;
}
```

### 二、石头游戏

L
labuladong 已提交
62 63 64
力扣第 877 题「石子游戏」的规则是这样的:

你和你的朋友面前有一排石头堆,用一个数组 `piles` 表示,`piles[i]` 表示第 `i` 堆石子有多少个。你们轮流拿石头,一次拿一堆,但是只能拿走最左边或者最右边的石头堆。所有石头被拿完后,谁拥有的石头多,谁获胜。
L
labuladong 已提交
65

L
labuladong 已提交
66
**假设你们都很聪明**,由你第一个开始拿,请你写一个算法,输入一个数组 `piles`,返回你是否能赢(true 或 false)。
L
labuladong 已提交
67 68 69 70 71 72 73 74 75 76 77

注意,石头的堆的数量为偶数,所以你们两人拿走的堆数一定是相同的。石头的总数为奇数,也就是你们最后不可能拥有相同多的石头,一定有胜负之分。

举个例子,`piles=[2, 1, 9, 5]`,你先拿,可以拿 2 或者 5,你选择 2。

`piles=[1, 9, 5]`,轮到对手,可以拿 1 或 5,他选择 5。

`piles=[1, 9]` 轮到你拿,你拿 9。

最后,你的对手只能拿 1 了。

78
这样下来,你总共拥有 `2 + 9 = 11` 颗石头,对手有 `5 + 1 = 6` 颗石头,你是可以赢的,所以算法应该返回 true。
L
labuladong 已提交
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103

你看到了,并不是简单的挑数字大的选,为什么第一次选择 2 而不是 5 呢?因为 5 后面是 9,你要是贪图一时的利益,就把 9 这堆石头暴露给对手了,那你就要输了。

这也是强调双方都很聪明的原因,算法也是求最优决策过程下你是否能赢。

这道题又涉及到两人的博弈,也可以用动态规划算法暴力试,比较麻烦。但我们只要对规则深入思考,就会大惊失色:只要你足够聪明,你是必胜无疑的,因为你是先手。

```java
boolean stoneGame(int[] piles) {
    return true;
}
```

这是为什么呢,因为题目有两个条件很重要:一是石头总共有偶数堆,石头的总数是奇数。这两个看似增加游戏公平性的条件,反而使该游戏成为了一个割韭菜游戏。我们以 `piles=[2, 1, 9, 5]` 讲解,假设这四堆石头从左到右的索引分别是 1,2,3,4。

如果我们把这四堆石头按索引的奇偶分为两组,即第 1、3 堆和第 2、4 堆,那么这两组石头的数量一定不同,也就是说一堆多一堆少。因为石头的总数是奇数,不能被平分。

而作为第一个拿石头的人,你可以控制自己拿到所有偶数堆,或者所有的奇数堆。

你最开始可以选择第 1 堆或第 4 堆。如果你想要偶数堆,你就拿第 4 堆,这样留给对手的选择只有第 1、3 堆,他不管怎么拿,第 2 堆又会暴露出来,你就可以拿。同理,如果你想拿奇数堆,你就拿第 1 堆,留给对手的只有第 2、4 堆,他不管怎么拿,第 3 堆又给你暴露出来了。

也就是说,你可以在第一步就观察好,奇数堆的石头总数多,还是偶数堆的石头总数多,然后步步为营,就一切尽在掌控之中了。知道了这个漏洞,可以整一整不知情的同学了。

### 三、电灯开关问题

L
labuladong 已提交
104 105 106
力扣第 319 题「灯泡开关」的规则是这样的:

`n` 盏电灯,最开始时都是关着的。现在要进行 `n` 轮操作:
L
labuladong 已提交
107 108 109 110 111 112 113

第 1 轮操作是把每一盏电灯的开关按一下(全部打开)。

第 2 轮操作是把每两盏灯的开关按一下(就是按第 2,4,6... 盏灯的开关,它们被关闭)。

第 3 轮操作是把每三盏灯的开关按一下(就是按第 3,6,9... 盏灯的开关,有的被关闭,比如 3,有的被打开,比如 6)...

L
labuladong 已提交
114
如此往复,直到第 `n` 轮,即只按一下第 `n` 盏灯的开关。
L
labuladong 已提交
115

L
labuladong 已提交
116
现在给你输入一个正整数 `n` 代表电灯的个数,问你经过 `n` 轮操作后,这些电灯有多少盏是亮的?
L
labuladong 已提交
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131

我们当然可以用一个布尔数组表示这些灯的开关情况,然后模拟这些操作过程,最后去数一下就能出结果。但是这样显得没有灵性,最好的解法是这样的:

```java
int bulbSwitch(int n) {
    return (int)Math.sqrt(n);
}
```

什么?这个问题跟平方根有什么关系?其实这个解法挺精妙,如果没人告诉你解法,还真不好想明白。

首先,因为电灯一开始都是关闭的,所以某一盏灯最后如果是点亮的,必然要被按奇数次开关。

我们假设只有 6 盏灯,而且我们只看第 6 盏灯。需要进行 6 轮操作对吧,请问对于第 6 盏灯,会被按下几次开关呢?这不难得出,第 1 轮会被按,第 2 轮,第 3 轮,第 6 轮都会被按。

132
为什么第 1、2、3、6 轮会被按呢?因为 `6=1*6=2*3`。一般情况下,因子都是成对出现的,也就是说开关被按的次数一般是偶数次。但是有特殊情况,比如说总共有 16 盏灯,那么第 16 盏灯会被按几次? 
L
labuladong 已提交
133

L
labuladong 已提交
134
`16 = 1*16 = 2*8 = 4*4`
L
labuladong 已提交
135 136 137 138 139

其中因子 4 重复出现,所以第 16 盏灯会被按 5 次,奇数次。现在你应该理解这个问题为什么和平方根有关了吧?

不过,我们不是要算最后有几盏灯亮着吗,这样直接平方根一下是啥意思呢?稍微思考一下就能理解了。

140
就假设现在总共有 16 盏灯,我们求 16 的平方根,等于 4,这就说明最后会有 4 盏灯亮着,它们分别是第 `1*1=1` 盏、第 `2*2=4` 盏、第 `3*3=9` 盏和第 `4*4=16` 盏。
L
labuladong 已提交
141

L
labuladong 已提交
142
就算有的 `n` 平方根结果是小数,强转成 int 型,也相当于一个最大整数上界,比这个上界小的所有整数,平方后的索引都是最后亮着的灯的索引。所以说我们直接把平方根转成整数,就是这个问题的答案。
L
labuladong 已提交
143

L
labuladong 已提交
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159


<hr>
<details>
<summary><strong>引用本文的文章</strong></summary>

 - [丑数系列算法详解](https://labuladong.github.io/article/fname.html?fname=丑数)
 - [我的刷题心得](https://labuladong.github.io/article/fname.html?fname=算法心得)
 - [经典动态规划:博弈问题](https://labuladong.github.io/article/fname.html?fname=动态规划之博弈问题)

</details><hr>





160
**_____________**
L
labuladong 已提交
161

L
labuladong 已提交
162
**《labuladong 的算法小抄》已经出版,关注公众号查看详情;后台回复关键词「**进群**」可加入算法群;回复「**全家桶**」可下载配套 PDF 和刷题全家桶**
L
labuladong 已提交
163 164

![](https://labuladong.github.io/algo/images/souyisou2.png)
L
labuladong 已提交
165 166


167 168
======其他语言代码======

B
brucecat 已提交
169 170 171 172 173 174 175 176 177 178
[292.Nim游戏](https://leetcode-cn.com/problems/nim-game)

[877.石子游戏](https://leetcode-cn.com/problems/stone-game)

[319.灯泡开关](https://leetcode-cn.com/problems/bulb-switcher)



### python

179
[JodyZ0203](https://github.com/JodyZ0203)提供 292. Nim 游戏 Python3 解法代码:
180 181

```Python
182 183 184 185 186 187 188 189
class Solution:
    def canWinNim(self, n: int) -> bool:
        # 如果除于是0,说明是4的倍数,所以必输
        # 否则不是除于不等于0,说明不是4的倍数,说明必胜
        return n % 4 != 0  
```

[JodyZ0203](https://github.com/JodyZ0203)提供 877. 石子游戏 Python3 解法代码:
190 191

```Python
192 193 194 195 196 197 198
class Solution:
    def stoneGame(self, piles: List[int]) -> bool:
        # 双方都很聪明的前提下, 先手必胜无疑
        # 先手可以提前观察偶数堆还是基数的石头总数更多
        return True
```

B
brucecat 已提交
199 200 201 202 203 204 205 206 207 208 209 210 211 212

[JodyZ0203](https://github.com/JodyZ0203)提供 319. 灯泡开关 Python3 解法代码:

```Python
class Solution:
    def bulbSwitch(self, n: int) -> int:
        # 平方根电灯个数之后向下取整即可
        return floor(sqrt (n))       
```



### c++

213
[JodyZ0203](https://github.com/JodyZ0203)提供 877. 石子游戏 C++ 解法代码:
214

215 216 217 218 219 220 221 222 223 224 225
```cpp
class Solution {
public:
    bool stoneGame(vector<int>& piles) {
       // 双方都很聪明的前提下, 先手必胜无疑
       return true;
    }
};
```


226

227 228

[JodyZ0203](https://github.com/JodyZ0203)提供 319. 灯泡开关 C++ 解法代码:
229

230 231 232 233 234 235 236 237 238
```cpp
class Solution {
public:
    int bulbSwitch(int n) {
        // 平方根电灯个数之后向下取整即可
        return floor(sqrt (n));
    }
};
```
B
brucecat 已提交
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277



### javascript

[292.Nim游戏](https://leetcode-cn.com/problems/nim-game)

```js
/**
 * @param {number} n
 * @return {boolean}
 */
var canWinNim = function(n) {
    // 如果上来就踩到 4 的倍数,那就认输吧
    // 否则,可以把对方控制在 4 的倍数,必胜
    return n % 4 !== 0;
};
```

[877.石子游戏](https://leetcode-cn.com/problems/stone-game)

```js
var stoneGame = function(piles) {
    return true;
};
```

[319.灯泡开关](https://leetcode-cn.com/problems/bulb-switcher)

```js
/**
 * @param {number} n
 * @return {number}
 */
var bulbSwitch = function(n) {
    return Math.floor(Math.sqrt(n));
};
```