天天看点

80道常见数据结构面试题及其解法 计包含min函数的栈 整数序列是不是二元查找树的后序遍历结果

1.把二元查找树转变成排序的双向链表

题目:

输入一棵二元查找树,将该二元查找树转换成一个排序的双向链表。

要求不能创建任何新的结点,只调整指针的指向。

10

/ \

6 14

/ \ / \

4 8 12 16

转换成双向链表

4=6=8=10=12=14=16。

首先我们定义的二元查找树节点的数据结构如下:

struct BSTreeNode

{

int m_nValue; // value of node

BSTreeNode *m_pLeft; // left child of node

BSTreeNode *m_pRight; // right child of node

};

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108

// 1:构造二叉查找树;

// 2:中序遍历二叉查找树,因此结点按从小到大顺序访问,假设之前访问过的结点已经调整为一个双向链表,那么

//       只需要将当前结点连接至双向链表的最后一个结点即可,访问完后,双向链表也就调整完了

#include <iostream>

using

namespace

std;

struct

BSTreeNode

{

int

m_nValue; 

// value of node

BSTreeNode *m_pLeft; 

// left child of node

BSTreeNode *m_pRight; 

// right child of node

};

void

addBSTreeNode(BSTreeNode *&pCurrent,

int

value);

void

inOrderBSTree(BSTreeNode* pBSTree);

void

convertToDoubleList(BSTreeNode* pCurrent);

BSTreeNode *pHead=NULL;

//指向循环队列头结点

BSTreeNode *pIndex=NULL;

//指向前一个结点

int

main()

{

BSTreeNode *pRoot=NULL;

addBSTreeNode(pRoot,10);

addBSTreeNode(pRoot,6);

addBSTreeNode(pRoot,14);

addBSTreeNode(pRoot,4);

addBSTreeNode(pRoot,8);

addBSTreeNode(pRoot,12);

addBSTreeNode(pRoot,16);

inOrderBSTree(pRoot);

return

0;

}

void

addBSTreeNode(BSTreeNode *&pCurrent,

int

value)

//在这个函数中会要改变指针值,一定要记得使用引用传递

{

if

(pCurrent==NULL)

{

BSTreeNode* pBSTree=

new

BSTreeNode();

pBSTree->m_nValue=value;

pBSTree->m_pLeft=NULL;

pBSTree->m_pRight=NULL;

pCurrent=pBSTree;

}

else

if

(pCurrent->m_nValue<value)

{

addBSTreeNode(pCurrent->m_pRight,value);

}

else

if

(pCurrent->m_nValue>value)

{

addBSTreeNode(pCurrent->m_pLeft,value);

}

else

{

cout<<

"node repeated"

<<endl;

}

}

void

inOrderBSTree(BSTreeNode* pBSTree)

{

if

(NULL==pBSTree)

{

return

;

}

if

(NULL!=pBSTree->m_pLeft)

{

inOrderBSTree(pBSTree->m_pLeft);

}

//  if (NULL!=pBSTree)

//  {

//      cout<<pBSTree->m_nValue;

//  }

convertToDoubleList(pBSTree);

if

(NULL!=pBSTree->m_pRight)

{

inOrderBSTree(pBSTree->m_pRight);

}

}

void

convertToDoubleList(BSTreeNode* pCurrent)

{

pCurrent->m_pLeft=pIndex;

//使当前结点的左指针指向双向链表中最后一个结点

if

(NULL==pIndex)

//若最后一个元素不存在,此时双向链表尚未建立,因此将当前结点设为双向链表头结点

{

pHead=pCurrent;

}

else

//使双向链表中最后一个结点的右指针指向当前结点

{

pIndex->m_pRight=pCurrent;

}

pIndex=pCurrent;

//将当前结点设为双向链表中最后一个结点

cout<<pCurrent->m_nValue<<

" "

;

}

2:

计包含min函数的栈

要求

定义栈的数据结构,要求添加一个min 函数,能够得到栈的最小元素。 

要求函数min、push 以及pop 的时间复杂度都是O(1)。

解法

使用一个辅助栈来保存最小元素,这个解法简单不失优雅。设该辅助栈名字为minimum stack,其栈顶元素为当前栈中的最小元素。这意味着
  • 要获取当前栈中最小元素,只需要返回minimum stack的栈顶元素即可。
  • 每次执行push操作,检查push的元素是否小于或等于minimum stack栈顶元素。如果是,则也push该元素到minimum stack中。
  • 当执行pop操作的时候,检查pop的元素是否与当前最小值相等。如果相同,则需要将改元素从minimum stack中pop出去。
struct minStack{
    stack<int> s;
    stack<int> minS;

    void push(int i){
        if (s.empty() || minS.empty()){
            s.push(i);
            minS.push(i);
        }else{
            if (minS.top() >= i){
                minS.push(i);
            }
            s.push(i);
        }
    }

    void pop(){
        if (s.empty() || minS.empty()){
            return;
        }
        if (s.top() > minS.top()){
            s.pop();
        }else{
            s.pop();
            minS.pop();
        }
    }

    int min(){
        if (minS.empty())
            return -1;
        else 
            return minS.top();
    }
};
           

3:题目:输入一个整形数组,数组里有正数也有负数。数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。求所有子数组的和的最大值。要求时间复杂度为O(n)。

一种从前往后遍历的方法如下:

[cpp]  view plain  copy

  1. // 需要保存起始点与结束点下标的时候,从前往后遍历也是可以的  
  2. int MaxSum(int *a , int n)  
  3. {  
  4.     int tempstart = 0 , sum=0 , max = -1000;  
  5.     int i , start , end;  
  6.     start = end = 0;  
  7.     for(i = 0 ; i < n ; ++i)  
  8.     {  
  9.         if(sum < 0)  
  10.         {  
  11.             sum = a[i];  
  12.             tempstart = i;  
  13.         }  
  14.         else  
  15.             sum += a[i];  
  16.         if(sum > max)  
  17.         {  
  18.             max = sum;  
  19.             start = tempstart;  
  20.             end = i;  
  21.         }  
  22.     }  
  23.     return max;  
  24. }  

4 面试题目:

        输入一个整数和一棵二元树。从树的根结点开始往下访问一直到叶结点所经过的所有结点形成一条路径。打印出和与输入整数相等的所有路径。

        例如输入整数 22 ,如下图二元树:

                                            10

                                           /     \

                                          5     12

                                        /    \   

                                       4     7 

   则打印出两条路径:10, 12和10, 5, 7。

思路:对于如图所示的二叉树,其实就是一个递归的过程,上例可以有三条路径10 5 4 ,10 5 7,10 12,递归调用者三次路径即可,如果满足就打印出来,不可以就再重复递归 代码如下:

  1. #include <iostream>  
  2. #include <vector>  
  3. using namespace std;  
  4. struct BinaryTreeNode // a node in the binary tree  
  5. {  
  6.     int m_nValue; // value of node  
  7.     BinaryTreeNode *m_pLeft; // left child of node  
  8.     BinaryTreeNode *m_pRight; // right child of node  
  9. };  
  10. vector<BinaryTreeNode*> pathVec;  
  11. void creatTree(BinaryTreeNode *&root, int *a, int i, int len)  
  12. {  
  13.     if(i >= len)  
  14.         return;  
  15.     root = new BinaryTreeNode;  
  16.     root->m_nValue = a[i];  
  17.     root->m_pLeft = NULL;  
  18.     root->m_pRight = NULL;  
  19.     creatTree(root->m_pLeft, a, 2*i + 1, len);  
  20.     creatTree(root->m_pRight, a, 2*i + 2, len);  
  21. }  
  22. void preorder(BinaryTreeNode* &root)  
  23. {  
  24.     if(!root)  
  25.         return;  
  26.     cout << root->m_nValue << " ";  
  27.     preorder(root->m_pLeft);  
  28.     preorder(root->m_pRight);      
  29. }  
  30. void printPath(vector<BinaryTreeNode*>& pathVec)  
  31. {  
  32.     for(vector<BinaryTreeNode*>::iterator it = pathVec.begin(); it != pathVec.end(); it++)  
  33.         cout << (*it)->m_nValue << " ";  
  34.     cout << endl;  
  35. }  
  36. void pathTree(BinaryTreeNode* &root, int val)  
  37. {  
  38. //输出二叉树中路径等于val的所有路径  
  39.     static int sum = 0;  
  40.     sum += root->m_nValue;  
  41.     pathVec.push_back(root);  
  42.     if(sum == val && root->m_pLeft == NULL && root->m_pRight == NULL)  
  43.         printPath(pathVec);  
  44.     if(root->m_pLeft)  
  45.         pathTree(root->m_pLeft, val);  
  46.     if(root->m_pRight)  
  47.         pathTree(root->m_pRight, val);  
  48.     sum -= root->m_nValue;  
  49.     pathVec.pop_back();  
  50. }  
  51. int main()  
  52. {  
  53.     BinaryTreeNode *root = 0;  
  54.     int a[] = {10, 5, 12, 7, 8};  
  55.     int len = sizeof a / sizeof a[0];  
  56.     creatTree(root, a, 0, len);  
  57. //  preorder(root);  
  58.     pathTree(root, 22);  

需要注意的是出栈与入栈的处理,友情是这一步   sum -= root->m_nValue; 

5:题目是:输入n 个整数,输出其中最小的k 个。例如输入1,2,3,4,5,6,7 和8 这8 个数字,则最小的4 个数字为1,2,3 和4。

问题分析:先用冒泡排序法将数列排序,然后输出其中最小的k个数,注意输入输出格式问题(空格问题)

代码:

[cpp]  view plain  copy

  1. #include <iostream>  
  2. using namespace std;  
  3. void sort(int a[],int n,int k)  
  4. {  
  5.     int i=0,j=0;  
  6.     int temp;  
  7.     if(k>n)  
  8.     {  
  9.         cout<<"error"<<"\n";  
  10.     }  
  11.     else  
  12.     {  
  13.         for(i=0;i<n-1;i++)  
  14.             for(j=0;j<n-i-1;j++)  
  15.             {  
  16.                 if(a[j]>a[j+1])  
  17.                 {  
  18.                     temp=a[j];  
  19.                     a[j]=a[j+1];  
  20.                     a[j+1]=temp;  
  21.                 }  
  22.             }  
  23.             for(i=0;i<k-1;i++)  
  24.             {  
  25.                 cout<<a[i]<<" ";  
  26.             }  
  27.             cout<<a[k-1]<<"\n";  
  28.     }  
  29. }  
  30. int main()  
  31. {  
  32.     int n;//输入整数个数  
  33.     int k;//输出最小的k个数  
  34.     cin>>n>>k;  
  35.     int a[100];  
  36.     int i=0;  
  37.     for(i=0;i<n;i++)  
  38.         cin>>a[i];  
  39.     sort(a,n,k);  
  40.     return 0;  
  41. }  

6.判断单链表是否有环

方法:使用p、q两个指针,p每次向前走一步,q每次向前走两步,若在某个时候p == q,则存在环。

#include <stdio.h>
 2 #include <stdlib.h>
 3 
 4 #define LEN 8
 5 typedef struct node* node_t;
 6 
 7 struct node{  
 8     char val;  
 9     struct node *next;  
10 };  
11 
12 //method 1
13 int has_loop(struct node *head);
14 //method 2
15 int has_loop2(node_t head);
16 
17 int main()
18 {
19     node_t* arr = (node_t*)malloc(sizeof(struct node)*LEN);
20     arr[0] = (node_t)malloc(sizeof(struct node));
21     int i;
22     for(i = 1; i < LEN; i++)
23     {
24         arr[i] = (node_t)malloc(sizeof(struct node));
25         arr[i - 1]->next = arr[i];
26     }
27     arr[LEN - 1]->next = NULL;
28 
29     //you can add a loop here to test
30     //arr[6]->next = arr[0];
31     if (has_loop(arr[0]))
32         printf("method1: has loop.\n");
33     else
34         printf("method1: has no loop.\n");
35 
36     if (has_loop2(arr[0]))
37         printf("method2: has loop.\n");
38     else
39         printf("method2: has no loop.\n");
40 
41     return 0;
42 }
43 
44 //if two pointer are equal, but they don't have the same steps, then has a loop
45 int has_loop(node_t head)  
46 {  
47     node_t cur1 = head;  
48     int pos1 = 0;  
49     while(cur1){  
50         node_t cur2 = head;  
51         int pos2 = 0;  
52         pos1 ++;  
53         while(cur2){  
54             pos2 ++;  
55             if(cur2 == cur1){  
56                 if(pos1 == pos2)  
57                     break;  
58                 else  
59                     return 1;
60             }  
61             cur2 = cur2->next;  
62         }  
63         cur1 = cur1->next;  
64     }  
65     return 0;  
66 } 
67 
68 //using step1 and step2 here 
69 //if exists a loop, then the pointer which use step2 will catch up with the pointer which uses step1
70 int has_loop2(node_t head)
71 {
72     node_t p = head;
73     node_t q = head;
74     while (p != NULL && q != NULL)
75     {
76         /*
77         p = p->next;
78         if (q->next != NULL)
79             q = q->next->next;
80         if (p == q)
81             return 1;
82             */
83         //correct it on 17/11/2012
84         p = p->next;
85         q = q->next;
86         if (q != NULL)
87             q = q->next;
88         if (p != NULL && p == q)
89             return 1;
90     }
91     return 0;
92 }      
80道常见数据结构面试题及其解法 计包含min函数的栈 整数序列是不是二元查找树的后序遍历结果

第7题 微软亚院之编程判断俩个链表是否相交

一个比较经典的问题,判断两个链表是否相交,如果相交找出他们的交点。

80道常见数据结构面试题及其解法 计包含min函数的栈 整数序列是不是二元查找树的后序遍历结果

思路:

1、碰到这个问题,第一印象是采用hash来判断,将两个链表的节点进行hash,然后判断出节点,这种想法当然是可以的。

2、当然采用暴力的方法也是可以的,遍历两个链表,在遍历的过程中进行比较,看节点是否相同。

3、第三种思路是比较奇特的,在编程之美上看到的。先遍历第一个链表到他的尾部,然后将尾部的next指针指向第二个链表(尾部指针的next本来指向的是null)。这样两个链表就合成了一个链表,判断原来的两个链表是否相交也就转变成了判断新的链表是否有环的问题了:即判断单链表是否有环?

这样进行转换后就可以从链表头部进行判断了,其实并不用。通过简单的了解我们就很容易知道,如果新链表是有环的,那么原来第二个链表的头部一定在环上。因此我们就可以从第二个链表的头部进行遍历的,从而减少了时间复杂度(减少的时间复杂度是第一个链表的长度)。

下图是一个简单的演示:

80道常见数据结构面试题及其解法 计包含min函数的栈 整数序列是不是二元查找树的后序遍历结果

这种方法可以判断两个链表是否相交,但不太容易找出他们的交点。

4、仔细研究两个链表,如果他们相交的话,那么他们最后的一个节点一定是相同的,否则是不相交的。因此判断两个链表是否相交就很简单了,分别遍历到两个链表的尾部,然后判断他们是否相同,如果相同,则相交;否则不相交。示意图如下:

80道常见数据结构面试题及其解法 计包含min函数的栈 整数序列是不是二元查找树的后序遍历结果

判断出两个链表相交后就是判断他们的交点了。假设第一个链表长度为len1,第二个问len2,然后找出长度较长的,让长度较长的链表指针向后移动|len1 - len2| (len1-len2的绝对值),然后在开始遍历两个链表,判断节点是否相同即可。

下面给出一个简单的实现:

typedef struct node_t

{

int data;//data

struct node_t *next; //next

}node;

node* find_node(node *head1, node *head2)

{

if(NULL == head1 || NULL == head2)

{

return NULL;//如果有为空的链表,肯定是不相交的

}

node *p1, *p2;

p1 = head1;

p2 = head2;

int len1 = 0;

int len2 =0;

int diff = 0;

while(NULL != p1->next)

{

p1 = p1->next;

len1++;

}

while(NULL != p2->next)

{

p2 = p2->next;

len2++;

}

if(p1 != p2) //如果最后一个节点不相同,返回NULL

{

return NULL;

}

diff = abs(len1 - len2);

if(len1 > len2)

{

p1 = head1;

p2 = head2;

}

else

{

p1 = head2;

p2 = head1;

}

for(int i=0; i<diff; i++)

{

p1 = p1->next;

}

while(p1 != p2)

{

p1 = p1->next;

p2 = p2->next;

}

return p1;

}

通过上面的操作就可以找到两个链表的交点了。

8  逆转单向链表 

点击(此处)折叠或打开

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. //定义链表的节点。
  5. struct Link {
  6.     Link* next;
  7.     int value;
  8. };
  9. //递归方式:逆转单向链表
  10. Link* reverse1(Link *head) 
  11. {
  12.     if(head == NULL ) return NULL;
  13.     if(head->next == NULL) return head;
  14.     Link* tail = NULL;
  15.     Link* temp = head->next;
  16.     tail = reverse1(head->next);
  17.     temp->next = head;
  18.     head->next = NULL;
  19.     return tail;
  20. }
  21. //非递归方式:逆转单向链表 
  22. void reverse(Link **head) 
  23. {
  24.     if(!head || !(*head)) return;
  25.     Link *pre, *cur, *nex;
  26.     pre = NULL, cur=*head;
  27.     while(cur) {
  28.         nex = cur -> next;
  29.         cur->next = pre;
  30.         pre = cur;
  31.         cur = nex;
  32.     }
  33.     *head = pre;
  34. }
  35. //输出整个链表
  36. void printLink(Link *head)
  37. {
  38.     Link *curr = head;
  39.     while(curr) {
  40.         printf("%d ", curr->value);
  41.         curr = curr->next;
  42.     }
  43.     printf("\n");
  44. }
  45. int main(void)
  46. {
  47.     Link *head, *p[10], *newhead;
  48.     int num = 10;
  49.     for(int i=0; i<num; i++) {
  50.         p[i] = new Link();
  51.         p[i]->next = NULL;
  52.         p[i]->value = i+1;
  53.         if (i==0) head = p[i];
  54.         else p[i-1]->next = p[i];
  55.     }
  56.     printLink(head);
  57.     reverse(&head);
  58.     printLink(head);
  59.     for(int i=0; i<num; i++) delete p[i];
  60.     system("pause");
  61. }
9:

整数序列是不是二元查找树的后序遍历结果

题目:输入一个整数数组,判断该数组是不是某二元查找树的后序遍历的结果。如果是返回true,否则返回false。

例如输入5、7、6、9、11、10、8,由于这一整数序列是如下树的后序遍历结果:

         8

       /  \

      6    10

    / \    / \

   5   7   9  11

因此返回true。

如果输入7、4、6、5,没有哪棵树的后序遍历的结果是这个序列,因此返回false。

树的题目一般都是递归的思路,因为是因为是排序树,而且是后序,所以思路是序列最后一个必是根节点,从前往后遍历,比根节点小的都是其左子树,并且位于序列的左半部分,比其大的为其右子树,应该位于其右半部分。左右子树按上述思路分别进行判断。

代码如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

#include<iostream>

using

namespace

std;

void

test(

const

int

data[],

int

start,

int

node,

bool

&flag){

if

(flag&&start<node){       

// 判断条件,注意start<node

int

left=start;

while

(data[node]>data[left]){  

//取得其左子树

left++;                      

}

for

(

int

j=left;j<node;j++){      

//判断是否为其右子树

if

(data[j]<data[node]) {flag=

false

;

return

;}

//若

}

test(data,0,left-1,flag);          

//递归

test(data,left,node-1,flag);

}

}

int

main(

void

){

bool

flag=

true

;

int

a[]={5,7,6,9,11,10,8};

test(a,0,6,flag);

if

(flag) cout<<

"yes"

<<endl;

else

cout<<

"no"

<<endl;

int

b[]={7,4,6,5};

test(b,0,3,flag);

if

(flag) cout<<

"yes"

<<endl;

else

cout<<

"no"

<<endl;

system

(

"pause"

);

return

0;

}

二元查找树的定义为:

(1)若左子树不空,则左子树上所有节点的值均小于其根节点的值。

(2)若右子树不空,则右子树上所有节点的值均小于其跟节点的值

(3)其左右子树也均为二叉查找树。

那么先给定一个数字序列5、7、6、9、11、10、8,判断这个序列是否是二元查找树的后根遍历。可以回想一下后序遍历的性质,先访问左子树,然后访问右子树,最后访问根节点。结合以上这些性质,就可以理解到,应该从给定序列的最后一个元素开始,最后一个元素设定为根节点,循环遍历一次数组,找左右子树的分界点,上面这个例子就是6,9之间。这样可以判断,5、6、7,这三个数为以8为根节点的左子树上的节点,9、11、10为以8为根节点的右子树的根节点。如果在对以上分出来的两个数组做同样的操作,那么就可以解决这个问题。综上所述,可以应用递归方法,递归的关键是找到结束的条件。下面给出代码,并进行两组测试。

[cpp]  view plain  copy

  1. #include<stdio.h>  
  2. #include<assert.h>  
  3. int verify_is_post(int *s,int length)  
  4. {  
  5.     assert(s!=NULL);  
  6.     assert(length>0);  
  7.     int i,j;  
  8.     int root=s[length-1];  
  9.     for(i=0;i<length-1;i++)  
  10.     {  
  11.         if(s[i]>root)  
  12.             break;  
  13.     }  
  14.     for(j=i;j<length-1;j++)  
  15.     {  
  16.         if(s[j]<root)  
  17.             return 0;  
  18.     }  
  19.     int left=1;  
  20.     if(i>0)  
  21.     {  
  22.         left=verify_is_post(s,i);//分界点左侧数组  
  23.     }  
  24.     int right=1;  
  25.     if(i<length-1)  
  26.     {  
  27.         right=verify_is_post(s+i,length-i-1);//分界点右侧数组  
  28.     }  
  29.     return left&&right;  
  30. }  
  31. void main()  
  32. {  
  33.     int s1[]={5,7,6,9,11,10,8};  
  34.     int s2[]={7,4,6,5,8};  
  35.     int length1=sizeof(s1)/sizeof(int);  
  36.     int result1=verify_is_post(s1,length1);  
  37.     if(result1)  
  38.         printf("yes,it is the post-order traversal.\n");  
  39.     else  
  40.         printf("no,it is not the post-order traversal.\n");  
  41.     int length2=sizeof(s2)/sizeof(int);  
  42.     int result2=verify_is_post(s2,length2);  
  43.     if(result2)  
  44.         printf("yes,it is the post-order traversal.\n");  
  45.     else  
  46.         printf("no,it is not the post-order traversal.\n");  
  47. }