面试算法之【链表(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)
时间: 2024-10-13 14:42:12

面试算法之【链表(C++)】的相关文章

面试大总结之一:Java搞定面试中的链表题目

链表是面试中常考的,本文参考了其它一些文章,加上小编的自己总结,基本每个算法都测试并优化过. 算法大全(1)单链表 中还有一些链表题目,将来也会整理进来. * REFS: * http://blog.csdn.net/fightforyourdream/article/details/16353519 * http://blog.csdn.net/luckyxiaoqiang/article/details/7393134 轻松搞定面试中的链表题目 * http://www.cnblogs.co

图解堆算法、链表、栈与队列(Mark)

原文地址: 图解堆算法.链表.栈与队列(多图预警) 堆(heap),是一类特殊的数据结构的统称.它通常被看作一棵树的数组对象.在队列中,调度程序反复提取队列中的第一个作业并运行,因为实际情况中某些时间较短的任务却可能需要等待很长时间才能开始执行,或者某些不短小.但很重要的作业,同样应当拥有优先权.而堆就是为了解决此类问题而设计的数据结构.--

BAT面试算法精品课直通BAT面试算法精品课购买优惠码-牛客网

BAT面试算法精品课,直通BAT面试算法精品课. 大家可以先看试看的内容,视频短小精悍,切中解题的要害.既快速掌握核心知识又能节约复习时间. 反正我是不喜欢拖拖拉拉,长篇大论的视频课程. 况且知识面涵盖的比较广,适合找工作复习用. 第一次购买课程,觉得价格也能接受. 如果你也对这门课感兴趣,可以使用以下优惠码,还能优惠10元 优惠码:Axn54dY 或直接打开链接 http://www.nowcoder.com/courses/1?coupon=Axn54dY

算法学习 - 链表的游标实现~ C++

链表的游标实现,就是用另外一种方法来访问链表,模拟游标. 在我学习的理解中,就是创建一个节点数组,模拟内存的排列,然后从其中来申请内存和释放内存.但是实际的内存没有被释放~ 下面直接贴代码了: // // main.cpp // CursorList // // Created by Alps on 14-7-27. // Copyright (c) 2014年 chen. All rights reserved. // #include <iostream> #define CursorSp

Java数据结构和算法之链表

三.链表 链结点 在链表中,每个数据项都被包含在'点"中,一个点是某个类的对象,这个类可认叫做LINK.因为一个链表中有许多类似的链结点,所以有必要用一个不同于链表的类来表达链结点.每个LINK对象中都包含一个对下一个点引用的字段(通常叫做next)但是本身的对象中有一个字段指向对第一个链结点的引用. 单链表 用一组地址任意的存储单元存放线性表中的数据元素. 以元素(数据元素的映象)  + 指针(指示后继元素存储位置)  = 结点(表示数据元素 或 数据元素的映象) 以"结点的序列&q

面试算法:利用堆栈计算逆向波兰表达式

更详细的讲解和代码调试演示过程,请参看视频 如何进入google,算法面试技能全面提升指南 给定一个四则运算表达式的字符串,如果该表达式满足逆向波兰表达式,那么该字符串要满足以下条件: 1: 该表达式含有一个数字字符或一串数字字符. 2:它拥有给定格式,如"A, B, .",其中A,B是逆向波兰表达式,句号.表示的是四种运算符"+,-,*,/"其中之一. 例如字符串"3,4,*,1,2,+,+"就满足逆向波兰表达式,该表达式的值为:3 * 4 +

面试算法实习生

昨天刚开始是笔试,完事后两面技术面,最后hr面.第一次面试算法实习生,在此做下笔记,记录面试问题与自己的不足. 笔试: 回来看牛客网,居然发现大部分都有,但可惜我还没刷. 回忆下知识点从网上搜出这套题好多都有(2,5,6,7,8,9,21,42,46,48,49,53,55) 一.选择题 1. 某超市研究销售纪录数据后发现,买啤酒的人很大概率也会购买尿布,这种属于数据挖掘的哪类问题?(A) A. 关联规则发现 B. 聚类 C. 分类 D. 自然语言处理 2. 以下两种描述分别对应哪两种对分类算法

算法:链表

通过链表的一些题目,了解链表的基本操作实现,掌握递归算法的基本思路,掌握扎实的编程习惯. 一.单链表基本操作 1.1.单链表节点定义 struct ListNode { int value; ListNode *pNext; }; 1.2.在尾部插入节点 void AddToTail(ListNode *pHead, int value) { /* * 尾部插入 * 添加空的头结点,简化代码 */ ListNode *node = new ListNode(); node->value = va

C++算法之链表排序的代码

下面的资料是关于C++算法之链表排序的代码. { return; while(curr){ prev = curr; curr = curr->next; insert_for_sort_operation(ppNode, prev); } return; } b)对于待插入的节点,选择合适的位置插入即可 { return; } while(cur){ if(pNode->data < cur->data) break; prev = cur; cur = cur->next

面试算法爱好者书籍/OJ推荐

面试算法爱好者书籍/OJ推荐 这个书单也基本适用于准备面试. 一.教科书 基本上一般的算法课本介绍的范围都不会超出算法导论和算法引论的范围.读完这两本书.其它的算法课本大致翻翻也就知道是什么货色了. 1. 算法导论 这本书的内容用三个字来总结,就是伟光正.基本上囊括了经常使用算法的方方面面,证明巨细无遗,十分适合刚開始学习的人阅读. 可是这本书的一大问题就是.证明尽管具体.却从未解释算法背后的理由.仅仅告诉你怎样去做.却从未探讨过为何如此去做. 2.算法引论(Introduction to Al