剑指offer面试题5——链表之从尾到头打印链表
2017-10-21 19:04
811 查看
题目描述:输入一个链表,从尾到头打印每个结点的值。
【方法一】:迭代输出
分析:首先,printListFromTailToHead()函数的返回值是 vector <int>,因此,肯定要再定义一个vector <int>类型。所以,需要定义一个vector <int> 的变量,作为返回值。此处程序利用了递归。要打印第一个结点的数值,就要先打印第二个。如果要打印第二个,就要打印第三个。。。依次类推,就实现了逆序输出。
【方法二】、采用头插法,将链表反序
代码如下:
分析:使用了vector 中的 insert()函数,即插入函数。要要打印的链表,从头结点开始,不断地插入到新定义的变量 m_nvalue 的最前面,然后将其返回即可。对于vector 中的 insert()函数,用法如下(使用一个小程序进行说明)
【方法三】、利用栈
还有一个思路,将结点压入栈中,也就是说,把数据域和指针域都压入栈中,而不是单纯地只压入栈。然后,打印出来的(或者return )是结点的数据域。这也是《剑指offer》上给出的例子,为了适应程序,我做了修改。
代码分析:本程序中,还是定义了一个 m_nvalue,因为,函数的返回类型 vector <int>.。如果是void,那么,直接这样写。
《剑指offer》一书中,Page 49 对此题进行了详细描述。结合本书,我再次做了如下整理:
链表应该是面试时被提及的最频繁的数据结构。链表的结构很简单,它由指针把若干个结点链接成链状结构。链表的创建、插入结点、删除结点等操作都只需要20行左右的代码就可以搞定,这些代码量适合做面试题目。像哈希表、有向图等复杂的数据结构,实现它们的一个操作需要的代码量都比较大,很难在几十分钟的面试中完成。
另外,链表是一种动态的数据结构,其操作需要对指针进行操作,因此,应聘者需要有较好的编程功底才能写出完整的操作链表的代码。而且,链表这种数据结构很灵活,面试官可以利用链表来设计具有挑战性的面试题。如此几种原因 ,很多面试官都特别青睐链表相关的题目。
我们说,链表是一种动态数据结构,因为在创建链表时,无须知道链表的长度。当插入一个结点的时候,我们只需要为新结点分配内存,然后调整指针的指向来确保新结点被链接到链表中。我们经常说,线性表的链式存储不需要连续的存储空间,是因为链表在内存分配时,不是在创建链表时一次性完成的,而是每添加一个结点分配一个内存。由于没有闲置的内存,链表的空间效率比数组高很多.
如果单链表的定义如下:
那么,往链表的末尾中添加一个结点的 C++ 代码如下:
解释一下上面的代码:
(1)函数的第一个参数 pHead 是一个指向指针的指针。当我们往一个空链表中插入一个结点时,新插入的结点就是链表的头指针。由于此时会改动头指针,因此必须把 pHead 参数设置成指向指针的指针,否则,出了这个函数,pHead 仍然是一个空指针。
(2)上述代码的步骤如下:先定义一个新的结点。如果此时头结点为空,那么,直接把新生成的结点赋值给头结点就可以了。如果头结点不为空,我们自然要找到该链表的表尾,把生成的结点插入。所以,我新定义一个结点 pNode,指向头结点,如果此时头结点的指针域不为空,那么,就让 pNode 指向下一个结点,直到找到表尾,退出 while() 循环,把新结点插入即可。
接下来,写一个删除含有某个值的结点的程序。
解释一下上面的代码:
(1)我们知道,链表在内存中占的内存,不是一次性分配的,因此,我们要从头结点开始,遍历链表,直到找到第 i 个结点。
(2)程序挺简单,不再赘述了。
拓展【以后复习的时候使用】
(1)链表很受面试官的青睐。此题还可以利用链表的反转、栈循环等来实现,以后再给出代码吧。做个笔记再次,防止自己忘记。
(2)在面试的时候,只写出函数是不够的,还需要自己进行输入输出定义。要多加练习。
【方法一】:迭代输出
/** * struct ListNode { * int val; * struct ListNode *next; * ListNode(int x) : * val(x), next(NULL) { * } * }; */ 以上内容是题目给的结点定义,以及初始结点的初始化 class Solution { public: vector <int> m_value; vector<int> printListFromTailToHead(ListNode* head) { if(head != NULL) { if(head ->next != NULL) { m_value = printListFromTailToHead(head ->next); } m_value.push_back(head->val); // return m_value; } return m_value; } };
分析:首先,printListFromTailToHead()函数的返回值是 vector <int>,因此,肯定要再定义一个vector <int>类型。所以,需要定义一个vector <int> 的变量,作为返回值。此处程序利用了递归。要打印第一个结点的数值,就要先打印第二个。如果要打印第二个,就要打印第三个。。。依次类推,就实现了逆序输出。
【方法二】、采用头插法,将链表反序
代码如下:
class Solution { public: vector<int> printListFromTailToHead(ListNode* head) { vector <int> m_nvalue; if(head != NULL) { m_nvalue.insert(m_nvalue.begin(),head->val); while(head->next != NULL) { m_nvalue.insert(m_nvalue.begin(),head->next->val); head = head->next; } } return m_nvalue; } };
分析:使用了vector 中的 insert()函数,即插入函数。要要打印的链表,从头结点开始,不断地插入到新定义的变量 m_nvalue 的最前面,然后将其返回即可。对于vector 中的 insert()函数,用法如下(使用一个小程序进行说明)
#include<vector> #include<iostream> using namespace std; int main() { vector<int> v(3); v[0]=2; v[1]=7; v[2]=9; v.insert(v.begin(),8);//在最前面插入新元素。 v.insert(v.begin()+2,1);//在迭代器中第二个元素前插入新元素 v.insert(v.end(),3);//在向量末尾追加新元素。 vector<int>::iterator it; for(it=v.begin(); it!=v.end();it++) { cout<<*it<<" "; } cout<<endl; }程序输出为: 8 2 1 7 9 3
【方法三】、利用栈
class Solution { public: vector<int> printListFromTailToHead(ListNode* head) { vector <int> m_nvalue; stack<int> s; while(head != NULL) { s.push(head->val); head = head ->next; } while(!s.empty()) { m_nvalue.push_back(s.top()); s.pop(); } return m_nvalue; } };程序分析:将链表的结点,依次压入栈。利用栈的先进后出,实现链表的反转。这样写,是有几个问题的。首先,不能改变头结点吧,因此,得重新定义一个结点比较好,把头结点赋值给这个新定义的结点。
还有一个思路,将结点压入栈中,也就是说,把数据域和指针域都压入栈中,而不是单纯地只压入栈。然后,打印出来的(或者return )是结点的数据域。这也是《剑指offer》上给出的例子,为了适应程序,我做了修改。
class Solution { public: vector<int> printListFromTailToHead(ListNode* head) { vector <int> m_nvalue; stack<ListNode*> sNode; ListNode* pNode = head; while(pNode != NULL) { sNode.push(pNode); pNode = pNode ->next; } while(!sNode.empty()) { m_nvalue.push_back(sNode.top()->val); sNode.pop(); } return m_nvalue; } };
代码分析:本程序中,还是定义了一个 m_nvalue,因为,函数的返回类型 vector <int>.。如果是void,那么,直接这样写。
void printListFromTailToHead(ListNode* head) { vector <int> m_nvalue; stack<ListNode*> sNode; ListNode* pNode = head; while (pNode != NULL) { sNode.push(pNode); pNode = pNode->next; } while (!sNode.empty()) { pNode = sNode.top(); printf("%d\t",pNode->val); sNode.pop(); } }
《剑指offer》一书中,Page 49 对此题进行了详细描述。结合本书,我再次做了如下整理:
链表应该是面试时被提及的最频繁的数据结构。链表的结构很简单,它由指针把若干个结点链接成链状结构。链表的创建、插入结点、删除结点等操作都只需要20行左右的代码就可以搞定,这些代码量适合做面试题目。像哈希表、有向图等复杂的数据结构,实现它们的一个操作需要的代码量都比较大,很难在几十分钟的面试中完成。
另外,链表是一种动态的数据结构,其操作需要对指针进行操作,因此,应聘者需要有较好的编程功底才能写出完整的操作链表的代码。而且,链表这种数据结构很灵活,面试官可以利用链表来设计具有挑战性的面试题。如此几种原因 ,很多面试官都特别青睐链表相关的题目。
我们说,链表是一种动态数据结构,因为在创建链表时,无须知道链表的长度。当插入一个结点的时候,我们只需要为新结点分配内存,然后调整指针的指向来确保新结点被链接到链表中。我们经常说,线性表的链式存储不需要连续的存储空间,是因为链表在内存分配时,不是在创建链表时一次性完成的,而是每添加一个结点分配一个内存。由于没有闲置的内存,链表的空间效率比数组高很多.
如果单链表的定义如下:
struct ListNode { int m_nValue; // 这里的 m_ 指的是类成员变量,n 指无符号整型,p 指指针类型,另外,c 表示 char 类型,b 表示 bool ListNode* m_pNext; // 如果是 g_ 表示全局变量, };
那么,往链表的末尾中添加一个结点的 C++ 代码如下:
void AddToTail(ListNode ** pHead, int value) { ListNode* pNew = new ListNode(); pNew->m_nValue = value; pNew->m_nValue = NULL; if (*pHead = NULL) { *pHead = pNew; } else { ListNode* pNode = *pHead; while (pNode->m_pNext != NULL) { pNode = pNode->m_pNext; } pNode->m_pNext = pNew; } }
解释一下上面的代码:
(1)函数的第一个参数 pHead 是一个指向指针的指针。当我们往一个空链表中插入一个结点时,新插入的结点就是链表的头指针。由于此时会改动头指针,因此必须把 pHead 参数设置成指向指针的指针,否则,出了这个函数,pHead 仍然是一个空指针。
(2)上述代码的步骤如下:先定义一个新的结点。如果此时头结点为空,那么,直接把新生成的结点赋值给头结点就可以了。如果头结点不为空,我们自然要找到该链表的表尾,把生成的结点插入。所以,我新定义一个结点 pNode,指向头结点,如果此时头结点的指针域不为空,那么,就让 pNode 指向下一个结点,直到找到表尾,退出 while() 循环,把新结点插入即可。
接下来,写一个删除含有某个值的结点的程序。
void RemoveNode(ListNode ** pHead, int value) { if (pHead == NULL || *pHead == NULL) return; ListNode *pToBeDeleted = NULL; if ((*pHead)->m_nValue == value) { pToBeDeleted = *pHead; *pHead = (*pHead)->m_pNext; } else { ListNode* pNode = *pHead; while (pNode->m_pNext != NULL && pNode->m_pNext->m_nValue != value) { pNode = pNode->m_pNext; } if (pNode->m_pNext != NULL && pNode->m_pNext->m_nValue == value) { pToBeDeleted = pNode->m_pNext; pNode->m_pNext = pNode->m_pNext->m_pNext; } } if (pToBeDeleted != NULL) { delete pToBeDeleted; pToBeDeleted = NULL; } }
解释一下上面的代码:
(1)我们知道,链表在内存中占的内存,不是一次性分配的,因此,我们要从头结点开始,遍历链表,直到找到第 i 个结点。
(2)程序挺简单,不再赘述了。
拓展【以后复习的时候使用】
(1)链表很受面试官的青睐。此题还可以利用链表的反转、栈循环等来实现,以后再给出代码吧。做个笔记再次,防止自己忘记。
(2)在面试的时候,只写出函数是不够的,还需要自己进行输入输出定义。要多加练习。
相关文章推荐
- 剑指offer-面试题5.从尾到头打印链表
- 剑指Offer系列-面试题5:从尾到头打印链表
- 剑指offer--面试题5:从尾到头打印链表--Java实现
- 《剑指Offer》面试题五之从尾到头打印链表
- 剑指offer 面试题5 从尾到头打印链表(栈实现)
- 剑指offer面试题6-从尾到头打印链表-java
- 《剑指Offer》面试题:从尾到头打印链表
- 剑指Offer(第二版)面试题6:从尾到头打印链表
- 剑指Offer面试题5(Java版):从尾到头打印链表
- 剑指Offer面试题5(Java版):从尾到头打印链表
- 剑指Offer: 面试题5 从尾到头打印链表
- (剑指Offer)面试题5:从尾到头打印链表
- 剑指offer——面试题5:从尾到头打印链表
- 剑指Offer学习之面试题5 : 从尾到头打印链表
- 剑指Offer面试题6:从尾到头打印链表
- 剑指offer面试题[5]-从尾到头打印链表
- 剑指offer面试题5:从尾到头打印链表
- 剑指Offer面试题5[从尾到头打印链表]
- 《剑指Offer》面试题5:从尾到头打印链表
- 《剑指offer》面试题5—从尾到头打印链表