面试算法之链表(C++)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面试算法之链表(C++)相关的知识,希望对你有一定的参考价值。

参考文章:http://blog.csdn.net/anonymalias/article/details/11020477

链表定义

1 template <typename Type>
2 struct ListNode{
3     Type data;
4     ListNode *next;
5 };

 尾插法创建链表(无头结点)

/**
 * Create a list, without head node
 */
template <typename Type>
ListNode<Type> *CreatList(Type *data, int len)
{
    if(data == NULL || len <= 0)
        return NULL;

    ListNode<Type> *head, *last;

    head = new ListNode<Type>;
    last = head;

    for (int i = 0; i < len; ++i)
    {
        last->next = new ListNode<Type>;
        last->next->data = data[i];
        last = last->next;
    }

    last->next = NULL;

    last = head;
    head = head->next;

    delete last;

    return head;
}

1. 单链表逆序打印

方法1:逆置+打印(费时费力)

方法2:使用显式栈结构(循环)

方法3:使用隐式栈结构(递归)

方法2(循环):

 1 /**
 2  * reversely print the list
 3  * method 1: use the stack
 4  */
 5 template <typename Type>
 6 void ReversePrintList_1(const ListNode<Type> *head)
 7 {
 8     if(head == NULL)
 9         return;
10 
11     stack<Type> nodeStack;
12     while (head)
13     {
14         nodeStack.push(head->data);
15         head = head->next;
16     }
17 
18     while(!nodeStack.empty())
19     {
20         cout<<nodeStack.top()<<" ";
21         nodeStack.pop();
22     }
23     cout<<endl;
24 }

方法3(递归):

 1 /**
 2  * reversely print the list
 3  * method 2: recursively
 4  */
 5 template <typename Type>
 6 void ReversePrintList_2(const ListNode<Type> *head)
 7 {
 8     if(head == NULL)
 9         return;
10 
11     ReversePrintList_2(head->next);
12     cout<<head->data<<" ";
13 }

2. 单链表逆置

方法1:顺序扫描,用head指针从头到尾遍历原始链表,此时需要一个新链表,头结点为pre,逆置过程为

初始化:

  pre=NULL

循环:

  tmp = head->next;

  head->next = pre;

  pre = head;

  head = tmp;

代码:

 1 /**
 2  * reverse the list
 3  * method 1:sequential scanning 
 4  */
 5 template <typename Type>
 6 ListNode<Type> * ReverseList_1(ListNode<Type> *head)
 7 {
 8     if(head == NULL)
 9         return NULL;
10 
11     ListNode<Type> *pre = NULL;
12 
13     while (head)
14     {
15         ListNode<Type> *nextNode= head->next;
16         head->next = pre;
17         pre = head;
18         head = nextNode;
19     }
20 
21     return pre;
22 }

方法2:递归,把链表划分为两部分,一是当前结点,一是已经逆置的子链表。我们需要做的是得到已经逆置的链表的最后一个节点,并把当前节点添到该节点的后面。

head:子链表的首节点

post:已经逆置子链表的首节点

newHead:逆置链表的首节点(原始链表的尾节点)

 1 /**
 2  * reverse the list
 3  * method 2: recursion
 4  */
 5 template <typename Type>
 6 ListNode<Type> * ReverseList_2(ListNode<Type> *head)
 7 {
 8     if(head == NULL)
 9         return NULL;
10 
11     ListNode<Type> *newHead;
12     SubReverseList_2(head, newHead);
13 
14     return newHead;
15 }
16 
17 template <typename Type>
18 ListNode<Type> * SubReverseList_2(ListNode<Type> *head, ListNode<Type> *&newHead)
19 {
20     if (head->next == NULL)
21     {
22         newHead = head;
23         return head;
24     }
25 
26     ListNode<Type> *post = SubReverseList_2(head->next, newHead);
27     post->next = head;
28     head->next = NULL;
29 
30     return head;
31 }

3. 在O(1)时间删除链表中的node结点

方法:用下一个结点覆盖需要删除的结点

注意事项:

(1)首节点或者node结点为空;

(2)链表中只含一个结点(node结点和首节点为同一结点)

(3)node结点为尾节点

(4)其他结点,直接覆盖删除即可

 1 /**
 2  * delete a node from list
 3  */
 4 template <typename Type>
 5 ListNode<Type> * DeleteNode(ListNode<Type> *head, ListNode<Type> *node)
 6 {
 7     if(head == NULL || node == NULL)
 8         return head;
 9 
10     //only have one node
11     if (node == head && node->next == NULL)
12     {
13         delete head;
14         return NULL;
15     }
16 
17     //node counts > 1, and delete the tail node
18     if (node->next == NULL)
19     {
20         ListNode<Type> *pre = head;
21 
22         while (pre->next != node)
23             pre = pre->next;
24 
25         delete node;
26         pre->next = NULL;
27 
28         return head;
29     }
30 
31     //other node
32     ListNode<Type> *delNode = node->next;
33     node->data = delNode->data;
34     node->next = delNode->next;
35 
36     delete delNode;
37 
38     return head;
39 }

4. 链表中倒数第k个结点

方法1:(扫描2次)顺序扫描链表,统计链表长度n,再顺序扫描一次,第n-k个结点即为倒数第k个结点;

方法2:(扫描1次+O(n)辅助空间)顺序扫描链表,将结点保存在stack中,stack中的值依次弹出,第k个弹出的即为原始链表中倒数第k个结点;

方法3:类似方法2,使用递归实现;

方法4:(扫描1次+O(1)辅助空间)两个指针ahead,after;初始都指向首节点;after向后移动k-1个结点;ahead和after同时向后移动,当after指向尾节点是,ahead即为倒数第k个结点;

 1 /**
 2  * return the last k node from list, 1 =< k <= list length
 3  */
 4 template <typename Type>
 5 const ListNode<Type> * LastKNode(const ListNode<Type> *head, int k)
 6 {
 7     if(head == NULL || k < 1)
 8         return NULL;
 9 
10     const ListNode<Type> *ahead, *after;
11     after = ahead = head;
12 
13     for (int i = 0; i < k - 1; ++i)
14     {
15         //the list length less than k
16         if(ahead->next == NULL)
17             return NULL;
18 
19         ahead = ahead->next;
20     }
21 
22     while (ahead->next != NULL)
23     {
24         ahead = ahead->next;
25         after = after->next;
26     }
27 
28     return after;
29 }

5. 合并两个有序链表

方法:新建一个首节点,然后遍历2个有序链表,比较大小,放在新链表后边

 1 /**
 2  * merge two sorted list
 3  */
 4 template <typename Type>
 5 ListNode<Type> * MergeTwoSortedList(ListNode<Type> *H1, ListNode<Type> *H2)
 6 {
 7     if (H1 == NULL)
 8         return H2;
 9     if (H2 == NULL)
10         return H1;
11 
12     ListNode<Type> *head, *last;
13 
14     head = new ListNode<Type>;
15     last = head;
16 
17     while (H1 != NULL && H2 != NULL)
18     {
19         if (H1->data <= H2->data)
20         {
21             last->next = H1;
22             last = H1;
23             H1 = H1->next;
24         } 
25         else
26         {
27             last->next = H2;
28             last = H2;
29             H2 = H2->next;
30         }
31     }
32 
33     if (H1 != NULL)
34         last->next = H1;
35     else if (H2 != NULL)
36         last->next = H2;
37 
38     H1 = head->next;
39     delete head;
40 
41     return H1;
42 }

6. 求两个单链表的第一个公共结点

方法1:使用2个stack作为辅助空间,分别保存2个链表的遍历顺序,然后弹出,直到弹出的结点内容不同为止,上一个结点就是所求。

方法2:计算两个链表的长度len1和len2,分别用两个指针h1和h2指向两个链表的头,然后较长链表(假设链表1最长)的h1向后移动len2-len1个结点,然后同时向后移动h1和h2,直到h1=h2为止。

 1 /**
 2  * Find the first common node
 3  */
 4 template <typename Type>
 5 ListNode<Type> * Find1stCommonNode(ListNode<Type> *h1, ListNode<Type> *h2)
 6 {
 7     if(h1 == NULL || h2 == NULL)
 8         return NULL;
 9 
10     int len1, len2;
11 
12     len1 = GetListLength(h1);
13     len2 = GetListLength(h2);
14 
15     if (len1 > len2)
16     {
17         for (int i = 0;i < len1 - len2; ++i)
18             h1 = h1->next;
19     }
20     else
21     {
22         for (int i = 0;i < len2 - len1; ++i)
23             h2 = h2->next;
24     }
25 
26     while (h1 && h1 != h2)
27     {
28         h1 = h1->next;
29         h2 = h2->next;
30     }
31 
32     return h1;
33 }
34 
35 template <typename Type>
36 int GetListLength(const ListNode<Type> *head)
37 {
38     int num = 0;
39 
40     while (head)
41     {
42         ++num;
43         head = head->next;
44     }
45 
46     return num;
47 }

7.判断两个链表是否为Y型

方法:只需要判断最后一个结点是否相同即可。

 1 /**
 2  * judge two list crossing or not
 3  */
 4 template <typename Type>
 5 bool IsCrossing(ListNode<Type> *h1, ListNode<Type> *h2)
 6 {
 7     if(h1 == NULL || h2 == NULL)
 8         return false;
 9 
10     while(h1->next != NULL)
11         h1 = h1->next;
12     while(h2->next != NULL)
13         h2 = h2->next;
14 
15     if(h1 == h2)
16         return true;
17     return false;
18 }

8. 判断单链表是否存在环

判断单链表是否存在环的思想就是判断遍历的结点是否已经遍历过。

方法1:通过辅助空间来保存已经遍历过的结点,在每遍历一个结点时判断该结点是否已经在空间中,如果在就说明有环,否则把该结点写入辅助空间,直到找到环或访问链表结束。可以通过hashmap来保存访问的结点,查找效率是O(1)。但是需要O(n)的辅助空间。

方法2:通过两个指针,分别从链表的头结点出发,一个每次向后移动1步,另一个移动两步,两个指针移动速度不一样,如果存在环,那么两个指针一定会在环里相遇。

 1 /**
 2  * judge the list has circle or not
 3  */
 4 template <typename Type>
 5 bool HasCircle(ListNode<Type> *head)
 6 {
 7     if(head == NULL)
 8         return false;
 9 
10     ListNode<Type> *fast, *slow;
11     fast = slow = head;
12 
13     while (fast && fast->next != NULL)
14     {
15         fast = fast->next->next;
16         slow = slow->next;
17 
18         if(fast == slow)
19             return true;
20     }
21 
22     return false;
23 }

9. 求链表的中间结点

如果链表的长度为偶数,返回中间两个结点的任意一个,若为奇数,则返回中间结点。

与求解倒数第k个结点的方法类似,可以通过两个指针来完成,不同的是,这里无法计算两个指针的距离,因此需要其中一个指针是快指针,另一个指针是慢指针。

 1 /**
 2  * get the middle node of list 
 3  */
 4 template <typename Type>
 5 const ListNode<Type> * ListMidNode(const ListNode<Type> *head)
 6 {
 7     if(head == NULL)
 8         return NULL;
 9 
10     const ListNode<Type> *fast, *slow;
11     fast = slow = head;
12 
13     while(fast && fast->next != NULL)
14     {
15         fast = fast->next->next;
16         slow = slow->next;
17     }
18 
19     return slow;
20 }

如果要求长度为偶数时,返回中间两个结点的第一个,将第13行改为如下条件:

while(fast && fast->next != NULL && fast->next->next != NULL)

 

以上是关于面试算法之链表(C++)的主要内容,如果未能解决你的问题,请参考以下文章

数据结构与算法面试之链表问题集锦(下)

数据结构与算法之链表

算法导论之链表

面试题之链表插入排序

字节跳动+百度+阿里巴巴高频面试题之链表专题

字节跳动+百度+阿里巴巴高频面试题之链表专题