一道找规律的题。
题目描述
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