Recursive interview question Summary

Source: Internet
Author: User
Document directory
  • 1) tower Problems
  • 2) Calculate the depth of a binary tree
  • 3) Determine whether the binary tree is balanced
  • 4) Arrangement Algorithm
  • 5) combination algorithms
I. Preface

During job interviews, I like to ask algorithm questions most, although I think some clever algorithm questions are only related to the quantity of questions. However, in order to find a job, you have to strengthen algorithms, especially basic algorithms. This is an embodiment of a person's basic skills. Introduction to algorithms is a good teaching material, but it seems that it takes time to publish a large part of the topic. Now, I have read it a little more. I am sorry that many of the derivation processes have not been carefully read, in the future, you need to make up for it. What best embodies the essence of an algorithm is non-recursion. I have always thought of recursion as a half-solution. In order to deepen my understanding, I decided to write down some of my ideas, it is convenient to better clarify your own ideas, and I also ask all the leaders to correct them.

Ii. Recursive Algorithms

The content of this section is from "Linux C one-stop programming". The author is Miss Song Jinsong. To be honest, this is the best technical book about Linux C Programming in China, which is strongly recommended!

A simple example of recursion is to calculate integer factorial, n! = N * (n-1 )!, 0! = 1. You can write the following recursive program:

int factorial(int n){if (n == 0)return 1;else {int recurse = factorial(n-1);int result = n * recurse;return result;}}

factorialThis function is a recursive function that calls itself.Directly or indirectly calling a function is called a recursive function.HerefactorialYou can call yourself directly. Sometimes function a calls function B and function B calls function A, that is, function a indirectly calls itself. This is also a recursive function. If you are confused,You can setfactorial(n-1)This step is considered to be calling another function.-- Another function with the same function name and code is called to jump to its Code for execution, and then returnfactorial(n-1)The next step of this call is to continue.

To prove the correctness of the recursive algorithm, we can follow up to view the execution result step by step. I remember that when I first learned recursive algorithms, I was always confused by the second Monk. At that time, I always wanted to follow the recursion step by step to see the execution results. It is easy to implement fewer recursive layers. However, if there are more than one layer, the header will be large and you do not know which layer of recursion you followed. For example, if factorial (3) is followed by factorial, but factorial (100) is troublesome.

In fact, not every function needs to follow up to see the execution result. For example, when we call the printf function in our function, we didn't get into it to see how it was printed, because weBelieveIt can complete printing. We have the following code when writing the factorial function:

...int recurse = factorial(n-1);int result = n * recurse;...

If we believe that factorial is correct, passing the parameter n-1 will return (n-1 )!, Then result = N * (n-1 )! = N !, This is the result of factorial (n.

Of course, this is a bit strange: We haven't finished writing.factorialWhy should I trust this function?factorial(n-1)Is it true? Leap
Of faith itself is leap, isn't it?If you believe that the recursive function you are writing is correct, call it, and write this recursive function on the basis of this, it will be correct, so you can believe it is correct.

It seems a bit mysterious. We strictly prove it in mathematics.factorialFunction correctness. Just now,factorial(n)The correctness depends onfactorial(n-1)As long as the latter is correct, the result of the latter is superior.nObviously there is no doubt about returning this step, so our function implementation is correct. Therefore, we need to provefactorial(n)Is to provefactorial(n-1)Is correct. Similarly, it must be provedfactorial(n-1)Is to provefactorial(n-2)And so on. Finally, we need to provefactorial(1)Is to provefactorial(0). Whilefactorial(0)The correctness does not depend on other function calls. It is a small branch in the program.return
1;
This 1 is written according to the factorial definition. It must be correct. Thereforefactorial(1)Is true, sofactorial(2)It's also true. So on, finallyfactorial(n)It is also correct. In fact, this is mathematical
Use mathematical induction to prove that the base case is correct and the recursive relationship is correct.Remember to write base when writing recursive functions.
Case
Otherwise, the entire function is incorrect even if the recurrence relationship is correct. IffactorialThe function misses the base.
Case, it will lead to an infinite loop.


Iii. recursive algorithm questions

From a simple example of factorial in the previous section, we can understand the essence of recursive algorithms:To understand the function, and believe that the function you are writing is correct, call it on this basis, then it is correct.The following describes how to understand recursion from several common algorithm questions. This is my understanding. You are welcome to propose a better method.

1) tower Problems

The indexing tower problem is a common problem. That is to say, there are n plates of varying sizes on a single tower a, which are arranged from bottom to top in ascending order. If you want to move all N dishes to another Tower C, you can use a tower B for transfer, but you must meet the requirement that large dishes cannot be placed on small dishes at any time.

The basic idea is divided into three steps, first to the above N-1 plate through C to B, then the bottom of the plate to C, then talk about B above the N-1 plate through A to C. Total time complexity F (n) = 2f (n-1) + 1, so f (n) = 2 ^ n-1.

void hano(char a, char b, char c, int n) {    if (n > 0) {        hano(a, c, b, n-1);        move(a, c);        hano(b, a, c, n-1);    }}void move(char a, char b){    cout << a << "->" << b << endl;}

 

2) determine the depth of a binary tree. Here the depth refers to the maximum height of a binary tree from the root node to the leaf node. For example, if there is only one node, the depth is 1. If there are N layers, the height is N.

Int depth (struct node * root) {If (root = NULL) return 0; else {int ldepth = depth (root-> left ); // get the depth of the Left subtree int rdepth = depth (root-> right); // get the depth of the right subtree return ldepth> rdepth? Ldepth + 1: rdepth + 1; // the depth of a binary tree is obtained when the value is greater than limit + 1 }}
So how can we understand the depth function in terms of functions? We can know that the purpose of defining this function is to find the binary tree depth. That is to say, if we have completed the depth function, depth (Root) will be able to correctly return the depth of the binary tree with the root node as the root node. Therefore, in our code, depth (root-> left) returns the depth of the Left subtree, while depth (root-> right) returns the depth of the right subtree. Although we haven't finished writing the depth function at this time, we believe that the depth function can complete the function correctly.Therefore, we get the ldepth and rdepth, and then return the depth of the Binary Tree compared to the shard plus 1 through comparison. If it is hard to understand, we can imagine that the depth (root-> left) function called in depth is another function with the same name to complete the same function.

Note the base case. Here, when root = NULL, the depth is 0, and the function returns 0.

3) Determining whether a binary tree balances a balanced binary tree means that the depth difference between left and right Subtrees of any of its nodes is not greater than 1. You can use recursive algorithms to determine whether a binary tree is balanced.

Bool is_balanced (binarytreenode * proot) {If (proot = NULL) // basic information. If it is null, return true; int left = depth (proot-> m_pleft ); int right = depth (proot-> m_pright); int diff = left-right; // calculate the difference between left and right subtree depth if (diff> 1 | diff <-1) // If the depth difference is greater than 1, false return false; return is_balanced (proot-> m_pleft) & is_balanced (proot-> m_pright); // recursively judges left and right Subtrees, note that &, that is, the left and right Subtrees must be balanced}
The function defines that the binary tree proot is a balanced binary tree, that is, the depth difference between left and right Subtrees of all its nodes is not greater than 1. First, determine whether the root node meets the conditions. If the root node does not meet the conditions, false is returned directly. If yes, you need to determine whether the left and right subtree are both balanced binary trees. If yes, true is returned; otherwise, false is returned.The above Code does not provide high performance and will traverse nodes repeatedly. An improved algorithm is to traverse the nodes of the tree in descending order, before traversing the current node, we have already traversed its left and right sub-trees. We only need to record the depth of the node during the traversal, so that we can traverse and determine whether the node is balanced. The Code is as follows:
Bool is_balanced_2 (binarytreenode * proot, int * pdepth) {If (proot = NULL) {* pdepth = 0; return true;} int left, right; if (is_balanced_2 (proot-> m_pleft, & left) // left subtree balance & is_balanced_2 (proot-> m_pright, & right )) // right subtree balance {int diff = left-right; if (diff <= 1 & diff> =-1) {* pdepth = 1 + (left> right? Left: right); Return true;} return false ;}
This function defines whether the binary tree with the root of proot is a balanced binary tree, and stores the depth of the tree in the value pointed to by pdepth.Basically, if the tree is null, the depth is 0, and true is returned. Otherwise, only when the left and right subtree are balanced, the depth exists in the left and right variables, respectively, to determine whether the depth difference between the left and right subtree is not greater than 1. If yes, true is returned, note that you also need to set the depth value of the tree.

The called function is defined as follows:

bool IsBalanced(BinaryTreeNode* pRoot){    int depth = 0;    return is_balanced_2(pRoot, &depth);}
4) The arrangement algorithm is also a model of recursion. I remember the first time I saw the code layer by layer, and the headers were all big. Now the function is actually better understood. First look at the Code:
Void perm (int A [], int K, int N) {// K is the starting position, n is the array size if (k = N-1) {output (, n); // output} else {for (INT I = K; I <n; I ++) {swap (A, I, K ); // exchange perm (A, K + 1, n); // The next swap (A, I, K); // restore the original sequence }}}

First, it is clear that the perm (A, k, n) function is used to output all arrays of array a starting from position K. The length of the array is N. In this way, when calling a program, the call format is perm (A, 0, n), that is, all the arrays starting from position 0, that is, all the arrays of the array.
The basic condition is k = N-1, which has reached the last element, an arrangement has been completed, directly output. Otherwise, each element starting from position K is exchanged with the value of position K (including switching between itself and itself), and then arranged for the next time. After sorting, remember to restore the original sequence. Assuming the size of array a is n = 3, the program calls perm (A, 0, 3) as follows: for the first time, swap 0, 0, and execute perm (A, 1, 3 ), after the operation, the value 0 is switched again, and the array is restored to the initial value. The second round of switching (note that the array is the initial value at this time), and execute perm (A, 1, 3), after the execution of the next round of switching, the array is restored to the initial value. Perform perm (A, 1, 3) for the third round of switching to 2, 0, and then switch to 2, 0, and restore the array to the initial value. That is to say, in terms of function, first determine the 0th locations, and then call perm (A, 1, 3) to output the sorting starting from 1, so that all the sorting can be output. The possible values of The 0th locations are a [0], a [1], and a [2]. This ensures the possible values of The 0th locations through exchange, remember to restore the initial value after each switch. For example, if array A = {1, 2, 3}, the program runs the following output: 1 2 3, 1 3 2, 2 1 3, 2 3 1, 3 2 1, 3 1 2. That is, the first output is the arrangement of the first value in the order of 1, followed by the arrangement of 2 and 3 as the first value. 5) The combination algorithm can also be implemented using recursion, but its principle is similar to that of the 0-1 backpack. You can select either or not. Note that you cannot select the number of duplicates. The complete code is as follows:

# Include <iostream> using namespace STD; # define N 3 // The array size is 3int select [N] = {0}; // select an array, used to store which numbers of the array are selected. /* Number in the output array */void output (int A [], int N) {for (INT I = 0; I <n; I ++) {If (select [I]) cout <A [I] <"" ;}cout <Endl ;} /* array a selects K count from position I */void combination (int A [], int I, int K) {if (I> N) return; // if the position is out of the range of the array, return the result directly. Otherwise, the segment error is returned. If (k = 0) {// when the selection is complete, output the selected number output (, n);} else {select [I] = 1; combination (A, I + 1, k-1); // number I is selected, select the number of K-1 from the subsequent I + 1 select [I] = 0; combination (A, I + 1, k); // The number of I is not selected, then, from the subsequent position I + 1, select K numbers}/* to combine the main function, including selecting 1 to n numbers */void combination_helper (int A [], int N) {for (int K = 1; k <= N; k ++) {combination (A, 0, k) ;}} int main () {int A [n] = {1, 2, 3}; combination_helper (A, n); Return 0 ;}

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.