题目
我们构建了一个包含 n
行( 索引从 1
开始)的表。首先在第一行我们写上一个
0
。接下来的每一行,将前一行中的0
替换为01
,1
替换为10
。
- 例如,对于
n = 3
,第1
行是0
,第2
行是01
,第3行是0110
。
给定行数 n
和序数 k
,返回第 n
行中第 k
个字符。( k
从索引 1
开始)
示例 1:
输入: n = 1, k = 1
输出: 0
解释: 第一行:0
示例 2:
输入: n = 2, k = 1
输出: 0
解释:第一行: 0第二行: 01
示例 3:
输入: n = 2, k = 2
输出: 1
解释:
第一行: 0
第二行: 01
提示:
- \(1 <= n <= 30\)
- \(1 <= k <= 2^{n - 1}\)
标签
位运算, 递归, 数学
题解
数学
一个简单的想法其实是按照题目模拟,但 \(2^{30}\) 的数据范围肯定是 TLE 的,如果没有,那就是数据太弱了。
但实际上我们仔细观察并不需要完全模拟,只需找到第 k 个数字的具体值就好了,怎么找呢?简单画个四层的图,我们先找规律:
看到这个图,相信不难想到一定,从上向下模拟,如果我们每次都知道向左还是向右,那么就不需要完全模拟了,每层只需 \(O(1)\) 就能进入下一层,而我们完全可以根据 \(k\) 的值得到具体进入下一层是向左还是向右。这里我最先想到的是一个假设,向左还是向右可以看 \(k\) 的二进制位,这是很自然的想法,因为每次进入下一层,结点数量就会加倍,也就是左移一位,对于 \(k\) 的索引我们不按题目中说的以 1 为起点,而是用惯用的以 0 为起点。
- 给定一个数 \(k\) ,在第 \(n\) 层,我们有 \(2^{n - 1}\) 个结点
- 第一层的左右子树分别有 \(2^{n - 2}\) 个叶子结点,如果 \(k < 2^{n - 2}\) 那么我们就应该向左进入第二层,否则就应该向右进入第二层。
- 同理在第二层,左右子树分别有 \(2^{n - 3}\) 个叶子结点,如果 \(k < 2^{n - 3}\) 那么我们就应该向左进入第三层,否则就应该向右进入第三层。
- ...
- 以此类推
- 事实上我们有个简单的办法判断向左还是向右,即检测第一层检测 \(k\) 的第 \(n - 1\) 位,若是 \(0\) 则进入左子树,否则进入右子树,而第二层检测第 \(n - 2\) 位,以此类推,这个只要大家将 \(k\) 写成二进制形式就不难看出原因
有了以上推断,代码其实就很简单了
更进一步,写代码的判断时,我们可能会发现向左值不变,向右值改变,换而言之,\(k\) 的二进制中的 0 不影响结果,而每个 1 会使结果改变一次,所以实际上统计 \(k\) 的二进制中的 1 即可,常规方法还是 \(O(n)\) 的,而使用分治可以优化到 \(O(\log n)\), 这里就不赘述了,大家可以自行研究
1 | # Code language: Python |
1 | // Code language: Java |
1 | // Code language: C++ |
1 | /* Code language: JavaScript */ |
1 | /* Code language: Go */ |
- 时间复杂度: \(O(n)\)
- 空间复杂度: \(O(1)\)
如果对你有帮助的话,请给我点个赞吧~
欢迎前往 我的博客 或 Algorithm - Github 查看更多题解