LeetCode 779. K-th Symbol in Grammar
一道找规律的题。
题目描述
On the first row, we write a 0. Now in every subsequent row, we look at the previous row and replace each occurrence of 0 with 01, and each occurrence of 1 with 10.
Given row N and index K, return the K-th indexed symbol in row N. (The values of K are 1-indexed.) (1 indexed).
Examples:
Input: N = 1, K = 1
Output: 0Input: N = 2, K = 1
Output: 0Input: N = 2, K = 2
Output: 1Input: N = 4, K = 5
Output: 1Explanation:
row 1: 0
row 2: 01
row 3: 0110
row 4: 01101001
Note:
- N will be an integer in the range [1, 30].
- K will be an integer in the range [1, 2^(N-1)].
解题思路
显然是要找规律,四行可能还看不清楚,多写几行就会发现:每一行的前半部分都和前一行的数相同,每一行的后半部分都是前半部分按位取反。
这里我们最直观的办法就是按照规律来生成数据,然后查找对应位就行了,反正 N 最大也才 30 嘛。结果就是打脸,超时了。
然后我们更进一步,不生成数据,只研究所需数据的生成路径,可以发现:
- 如果该数字位于当前行的后半部分,那么只需要知道前半部分对应位置的数,取反即可;
- 如果该数字位于当前行的前半部分,那么可以砍掉后半部分,继续操作(相当于跳入上一行);
- 如果已经到达位置0,结束任务。
最后我们得到的就是从位置0到达所求位置的路径,只需要知道取反操作的次数,就能够知道数字到底是0还是1。
这个算法更快,一方面在于减少了内存读写、消除了大量冗余求值,另一方面是砍半操作压缩了求值路径,使得我们可以用最短的路径到达所需位置。
参考解法
/*
* @lc app=leetcode id=779 lang=cpp
*
* [779] K-th Symbol in Grammar
*/
// @lc code=start
class Solution {
public:
/*
int kthGrammar(int N, int K) {
int sz = 1 << (N - 1);
uint64_t *bits = new uint64_t[(sz+63)/64];
#define SET0(bits, i)
do{ bits[(i)>>6] &= ~(1UL << ((i)&63)); }while(0)
#define SET1(bits, i)
do{ bits[(i)>>6] |= (1UL << ((i)&63)); }while(0)
#define GET(bits, i)
((bits[(i)>>6] >> ((i)&63)) & 1)
SET0(bits, 0);
int len = 1;
while(len < K) {
for (int j=0; j<len; j++) {
if (GET(bits, j)) SET0(bits, len+j);
else SET1(bits, len+j);
}
len <<= 1;
} // 规律,以2的幂次为单位,后半部分是前半部分按位取反
// 每一行都是下一行的前半部分
for (int i=0; i<len; i++) printf("%d", GET(bits,i));
return GET(bits, K-1);
} // TLE, case 30 434991989
*/
int kthGrammar(int N, int K) {
int res = 0;
int i = K-1;
int width = 1;
while (width <= i) {
width <<= 1;
}
while (i) {
if (i >= width / 2) {
i = i - width / 2;
res = 1 - res;
} // 后半部分的值是前半部分对应位置值取反
width /= 2; // 折半降行
}
return res;
} // AC
};
// @lc code=end