Skip to content

Commit 9db3178

Browse files
committed
feat: leet code
1 parent fc749a3 commit 9db3178

3 files changed

Lines changed: 1684 additions & 35 deletions

File tree

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
---
2+
title: 1545. 找出第 N 个二进制字符串中的第 K 位
3+
date: "2026.03.04 00:46"
4+
tags:
5+
- Leetcode
6+
- answer
7+
- Math
8+
- String
9+
---
10+
11+
# 题目
12+
13+
给你两个正整数 n 和 k,二进制字符串 Sn 的形成规则如下:
14+
15+
S1 = "0"
16+
当 i > 1 时,Si = Si-1 + "1" + reverse(invert(Si-1))
17+
其中 + 表示串联操作,reverse(x) 返回反转 x 后得到的字符串,而 invert(x) 则会翻转 x 中的每一位(0 变为 1,而 1 变为 0)。
18+
19+
例如,符合上述描述的序列的前 4 个字符串依次是:
20+
21+
S1 = "0"
22+
S2 = "011"
23+
S3 = "0111001"
24+
S4 = "011100110110001"
25+
请你返回 Sn 的 第 k 位字符 ,题目数据保证 k 一定在 Sn 长度范围以内。
26+
27+
# 题解
28+
29+
首先我尝试了暴力解法,直接生成 Sn,但是发现当 n 比较大时,Sn 的长度会非常长,导致内存溢出。
30+
31+
```javascript
32+
/**
33+
* @param {number} n
34+
* @param {number} k
35+
* @return {character}
36+
*/
37+
var findKthBit = function (n, k) {
38+
var reverseR = function (input) {
39+
return input
40+
.split("") // 拆分成数组 ["0", "1", "1", "1", "0"]
41+
.map((char) => char ^ 1) // 翻转每一位: [1, 0, 0, 0, 1]
42+
.reverse() // 反转数组顺序: [1, 0, 0, 0, 1]
43+
.join(""); // 拼回字符串 "10001"
44+
};
45+
let S = "0";
46+
for (let i = 1; i < n; i++) {
47+
S = S + "1" + reverseR(S);
48+
}
49+
return S[k - 1];
50+
};
51+
```
52+
53+
然后我尝试了数学翻转。观察 $S_i = S_{i-1} + "1" + \text{reverse}(\text{invert}(S_{i-1}))$:
54+
55+
长度规律:$|S_n| = 2^n - 1$。
56+
57+
- 例如 $S_1$ 长度 $2^1-1=1$,中间位是第 $1$ 位。
58+
- $S_2$ 长度 $2^2-1=3$,中间位是第 $2$ 位。
59+
- $S_3$ 长度 $2^3-1=7$,中间位是第 $4$ 位。
60+
61+
三种位置分类讨论:
62+
63+
- 左半部分 ($k &lt; mid$):它完全就是 $S_{n-1}$ 的副本。所以直接去问“$S_{n-1}$ 的第 $k$ 位是什么”即可。
64+
- 正中间 ($k = mid$):根据逻辑公式,这一位永远是 "1"。
65+
- 右半部分 ($k &gt; mid$):这是最巧妙的地方。右边部分是 $S_{n-1}$ 取反再反转。
66+
67+
因为有 反转(Reverse),所以右半部分的第 1 个字符对应左半部分的最后一个,依次类推。
68+
69+
对应关系公式:$S_n[k] = \text{invert}(S_{n-1}[2^n - k])$。
70+
71+
比如在 $S_3$(长度 7)中找第 6 位,它对应 $S_2$ 的第 $2^3 - 6 = 2$ 位的结果再取反。
72+
73+
```javascript
74+
var findKthBit = function (n, k) {
75+
let flip = false; // 记录需要取反的次数
76+
while (n > 1) {
77+
let mid = 1 << (n - 1); // 2^(n-1)
78+
if (k === mid) {
79+
// 中间位固定为 1
80+
let res = 1;
81+
return (flip ? res ^ 1 : res).toString();
82+
} else if (k > mid) {
83+
// 如果在右侧,镜像到左侧,并增加一次取反
84+
k = 2 * mid - k;
85+
flip = !flip;
86+
}
87+
// 如果在左侧,直接继续看 n-1
88+
n--;
89+
}
90+
// 最终回到 S1,S1 是 "0"
91+
let res = 0;
92+
return (flip ? res ^ 1 : res).toString();
93+
};
94+
```
95+
96+
## mid 为什么是 $2^{n-1}$?
97+
98+
我们通过计算 $S_n$ 的总长度就能推导出中心点(mid)的位置。
99+
100+
**计算 $S_n$ 的总长度**:设 $L_n$ 为第 $n$ 个字符串 $S_n$ 的长度。根据题目规则:
101+
102+
- $S_1 = "0"$,所以 $L_1 = 1$
103+
- $S_n = S_{n-1} + "1" + \text{修改后的 } S_{n-1}$
104+
105+
那么长度关系式为: $$L_n = L_{n-1} (\text{左半部分}) + 1 (\text{中间位}) + L_{n-1} (\text{右半部分})$$ 即:$L_n = 2 \times L_{n-1} + 1$
106+
107+
我们可以列举一下:
108+
109+
- $L_1 = 1$
110+
- $L_2 = 2 \times 1 + 1 = 3$
111+
- $L_3 = 2 \times 3 + 1 = 7$
112+
- $L_4 = 2 \times 7 + 1 = 15$
113+
114+
规律:$L_n = 2^n - 1$
115+
116+
## 2. 为什么我们要这样找?
117+
118+
这就好比你在一个无限折叠的纸带上找特定的点:
119+
120+
- **原来的做法(模拟)**:先把一张白纸折 20 次,把它变成一个超级长的纸带,然后再从头开始数到第 $k$ 个点。
121+
- **现在的做法(回溯/迭代)**:看着这张已经“折好”的纸($S_n$),问:这个 $k$ 点是在折痕的左边还是右边?
122+
- 如果它在折痕右边,你就把它“翻”回左边(镜像转换),并记下它被翻过了一次(flip = !flip)。
123+
- 如果它在折痕左边,你就直接看左边。
124+
- 现在纸变小了一半(n--),你重复这个过程,直到你摸到了那道“折痕”(k === mid)或者纸缩小到不能再缩(n=1)。
125+
126+
## 3. 如果在右侧,且`S[k]=0`,是不是翻转过去说明`S[k]=1`
127+
128+
为什么“翻转过去”就是 1?
129+
130+
根据题目,$S_i$ 的右半部分是:$\text{reverse}(\text{invert}(S_{i-1}))$。 这里有两个动作:
131+
132+
- 取反 (invert):这一步让 $0 \to 1$,$1 \to 0$。
133+
- 反转 (reverse):这一步让位置左右颠倒。
134+
135+
所以,如果在右半部分看到了一个 0,顺着这两步推回去:
136+
137+
- 因为“取反”过:说明它在被取反之前是 1。
138+
- 因为“反转”过:说明它对应的位置在左半边的对称点。
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
title: Leetcode 题解汇总
3+
description: 这里收集了社区分享的所有 Leetcode 刷题笔记和题解。
4+
---
5+
6+
import { source } from "@/lib/source";
7+
8+
# Leetcode 题解
9+
10+
欢迎查阅 Leetcode 相关的分享内容。
11+
12+
<Cards>
13+
{source
14+
.getPages()
15+
.filter(
16+
(page) =>
17+
page.file.dirname === "CommunityShare/Leetcode" &&
18+
page.file.name !== "index",
19+
)
20+
.map((page) => (
21+
<Card
22+
key={page.url}
23+
title={page.data.title}
24+
href={page.url}
25+
description={page.data.description}
26+
/>
27+
))}
28+
</Cards>

0 commit comments

Comments
 (0)