[LeetCode 779.] K-th Symbol in Grammar

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: 0

Input: N = 2, K = 1
Output: 0

Input: N = 2, K = 2
Output: 1

Input: N = 4, K = 5
Output: 1

Explanation:
row 1: 0
row 2: 01
row 3: 0110
row 4: 01101001

Note:

  1. N will be an integer in the range [1, 30].
  2. 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
posted @ 2021-04-24 17:25  与MPI做斗争  阅读(37)  评论(0编辑  收藏  举报