算法设计与分析(六)

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

  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 = 4为例,可以把前4层构造成一棵二叉树:
1

题目的意思很容易理解,就是让我们把最下层叶子结点编号,输出第K个结点的数字:
2
所以递归函数的底层返回条件就很明显了,当搜索到叶子结点的时候把叶子结点的值返回。我们可以把查找目标叶子结点的过程描述为从根节点出发,到目标叶子结点的路径。
例如如果我们要找K = 4的结点,那就是一条从根节点出发的左右右路径,所以先把各个结点的路径写出来:
3
因为左结点值就是父节点的值,右结点的值是父结点的值取反,所以我们的函数只需要记录当前一个父结点的值就行,不需要把整棵树存下来。现在的问题就变成了我们什么时候要往左,什么时候要往右(做到这里的时候我发现了一种非递归的解法,下面我会讲到)。

如果目标结点在左子树,我们下一层的递归就是左子树,把右子树全部舍弃,如果目标结点在右字数就做相反的处理。判断目标结点在左子树还是右子树的条件如下:

初始化:
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叶子结点的过程:
2
4
5
6

代码

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,我们的二叉树就会变成这样:
7
叶子结点的编码其实就是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;
    }
};

猜你喜欢

转载自blog.csdn.net/Maple_Lai/article/details/83040892
今日推荐