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).
Given the total number of courses and a list of prerequisite pairs, is it possible for you to finish all courses?
Example
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
N
will be an integer in the range[1, 30]
.K
will be an integer in the range[1, 2^(N-1)]
.
思路
因为这周还是在讲习题课,没有讲新的内容,所以我就去找了道递归的题目练习一下。通常递归相关的题目代码量都不是非常大,难点再于递归函数的编写,个人总结了一下就是要弄清楚下一层递归的条件,以及最底层的返回。
对于这道题,以N = 4
为例,可以把前4层构造成一棵二叉树:
题目的意思很容易理解,就是让我们把最下层叶子结点编号,输出第K
个结点的数字:
所以递归函数的底层返回条件就很明显了,当搜索到叶子结点的时候把叶子结点的值返回。我们可以把查找目标叶子结点的过程描述为从根节点出发,到目标叶子结点的路径。
例如如果我们要找K = 4
的结点,那就是一条从根节点出发的左右右
路径,所以先把各个结点的路径写出来:
因为左结点值就是父节点的值,右结点的值是父结点的值取反,所以我们的函数只需要记录当前一个父结点的值就行,不需要把整棵树存下来。现在的问题就变成了我们什么时候要往左,什么时候要往右(做到这里的时候我发现了一种非递归的解法,下面我会讲到)。
如果目标结点在左子树,我们下一层的递归就是左子树,把右子树全部舍弃,如果目标结点在右字数就做相反的处理。判断目标结点在左子树还是右子树的条件如下:
初始化:
rank = 最底层叶子结点数(对于N = 4 的题目,rank = 8)
如果K <= rank / 2
,那么目标结点在左子树,根节点变为左结点
如果K > rank / 2
,那么目标结点在右子树,根节点变为右结点
每一次递归都让rank = rank /2
,重复上面过程,直到rank == 1
**注:**在实际写代码时,对于目标结点在右子树的情况,要做一步K -= rank / 2
的操作。因为rank
的值在不断减小,K
的值也必须相应地变化。
在N = 4
情况下寻找K =4
叶子结点的过程:
代码
class Solution {
public:
int kthGrammar(int N, int K)
{
int rank = 1;
for (int i = 0; i < N; i++)
rank *= 2;
return recursion(0, rank, K);
}
int recursion(int num,int rank, int index)
{
if (rank == 1) return num; //递归到底层
if (index <= rank / 2) return recursion(num, rank / 2, index); //递归左子树
else return recursion((num + 1) % 2, rank / 2, index - rank / 2); //递归右子树
}
};
非递归解法
在给N = 4
的二叉树写路径的时候,偶然间发现了这道题的非递归解法,如果把往左的操作编码为0
,往右的操作编码为1
,我们的二叉树就会变成这样:
叶子结点的编码其实就是K - 1
的二进制值!以前上离散数学的课学习二叉树时貌似有讲过二叉树的这个性质,但我离散数学掌握的马马虎虎,如果是学得好的大佬的话应该是很容易想出这种解法的。
那么问题就变得很简单了,把K -1
变成二进制后再由高到低遍历各个位,如果该位为1
就把当前值取反(初始为0
),最后就能得到正确答案。
代码
class Solution {
public:
int kthGrammar(int N, int K)
{
vector<int> binary;
int quotion = K - 1;
int num = 0;
//用辗转相除法求二进制值
while (quotion > 0)
{
binary.push_back(quotion % 2);
quotion /= 2;
}
//遍历二进制的值
for (int i = binary.size() - 1; i >= 0; i--)
if (binary[i] == 1) num = (num + 1) % 2;
return num;
}
};