剑指offer——链表相关问题总结
2015-07-26 19:07
513 查看
首先统一链表的数据结构为:
题目一:从尾到头打印链表:输入一个链表,从尾到头打印链表每个节点的值。
分析:
难点在于链表只有指向后继的指针,没有指向前驱的指针。
转换思路,结合栈后进先出的特点,可以遍历链表,依次将数据元素存入栈中,然后再依次出栈,即为从尾到头的顺序。
题目二:链表中倒数第k个结点:输入一个链表,输出该链表中倒数第k个结点。
分析:
(1)根据上题的启发,其实这个题也可以借助栈来完成。先从头到尾依次将结点存入栈,然后取出从栈顶开始的第k个结点即可。
(2)另一种方法是使用先后指针来完成,一个指针先从头开始向前走k-1步,然后另一个指针从头开始走,当第一个指针指向最后一个 结点时,后一个指针指向倒数第k个结点。
边界条件:要记得考虑k大于链表长度的情况和k=0的情况都返回空。
方法一:
方法二:
题目三:反转链表(链表逆序):输入一个链表,反转链表后,输出链表的所有元素。
题目四:合并两个排序的链表:输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
题目五:两个链表的第一个公共结点,输入两个链表,找出它们的第一个公共结点。
分析:两个链表都是单向链表,如果他们有公共的结点,那么这两个链表从某一结点开始,他们的next都指向同一个结点,之后所有的点都重合,不可能再出现分叉。所以它们的拓扑形看起来像一个Y形,而不可能是X形。
方法一:首先遍历两个链表得到它们的长度,就能知道哪个链表长,以及长的链表比短的链表多几个结点。在第二次遍历的时候,在较长的链表上先走相差的步数,接着同时在两个链表上遍历,找到的第一个相同的结点就是它们的公共结点。
时间复杂度O(m+n)。不需要辅助栈。
方法二:分别将两个链表存入两个辅助栈中,然后比较两个栈顶的结点是否相同,如果相同,则把栈顶弹出,接着比较下一个栈顶,直到找到最后一个相同的结点。
时间复杂度O(m+n),空间复杂度O(m+n)。
方法一:
方法二:
题目六:链表中环的入口结点:一个链表中包含环,请找出该链表的环的入口结点。
分析:有两个可以面试的问题:一个题是判断一个链表中,是否有环。第二个是环的入口结点。
经典方法就是使用快慢指针。快的一次走两步,慢的一次走一步,如果指针重合,说明链表有环。
在此基础上,可以想到,快的比慢的刚好多走了一个环的长度,而且速度是慢的二倍,说明快的总共走的是两个环的长度,慢的总共走了一个环的长度。
所以保持慢指针现在的位置,让快指针再次从头走起,每次走一步,当这次两个指针重合的时候,它们刚好都在环的入口结点上。
判断是否有环的代码:
找环的入口结点代码
转载请注明出处:http://blog.csdn.net/xingyanxiao/article/details/47068509
struct ListNode { int val; struct ListNode *next; ListNode(int x) :val(x), next(NULL) {} };
题目一:从尾到头打印链表:输入一个链表,从尾到头打印链表每个节点的值。
分析:
难点在于链表只有指向后继的指针,没有指向前驱的指针。
转换思路,结合栈后进先出的特点,可以遍历链表,依次将数据元素存入栈中,然后再依次出栈,即为从尾到头的顺序。
vector<int> printListFromTailToHead(struct ListNode* head) { ListNode *p=head; stack<int> temp; while(p) { temp.push(p->val); p=p->next; } vector<int>result; while(!temp.empty()) { result.push_back(temp.top()); temp.pop(); } return result; }
题目二:链表中倒数第k个结点:输入一个链表,输出该链表中倒数第k个结点。
分析:
(1)根据上题的启发,其实这个题也可以借助栈来完成。先从头到尾依次将结点存入栈,然后取出从栈顶开始的第k个结点即可。
(2)另一种方法是使用先后指针来完成,一个指针先从头开始向前走k-1步,然后另一个指针从头开始走,当第一个指针指向最后一个 结点时,后一个指针指向倒数第k个结点。
边界条件:要记得考虑k大于链表长度的情况和k=0的情况都返回空。
方法一:
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) { ListNode *p=pListHead; stack<ListNode*> temp; int len=0; while(p) { ++len; temp.push(p); p=p->next; } if(len<k||k==0) return NULL; while(k!=1) { temp.pop(); --k; } return temp.top(); }
方法二:
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) { if(pListHead==NULL||k==0) return NULL; ListNode *pAhead=pListHead; ListNode *pBehind=pListHead; for(int i=0;i<k-1;i++) { if(pAhead->next!=NULL) pAhead=pAhead->next; else return NULL; } while(pAhead->next!=NULL) { pAhead=pAhead->next; pBehind=pBehind->next; } return pBehind; }
题目三:反转链表(链表逆序):输入一个链表,反转链表后,输出链表的所有元素。
ListNode* ReverseList(ListNode* pHead) { if(!pHead) return pHead; ListNode *reverse=NULL; ListNode *pre=NULL; ListNode *next=NULL; ListNode *curr=pHead; while(curr) { next=curr->next; if(!next) reverse=curr; curr->next=pre; pre=curr; curr=next; } return reverse; }
题目四:合并两个排序的链表:输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
ListNode* Merge(ListNode* pHead1, ListNode* pHead2) { ListNode *result=new ListNode(0); ListNode *r=result; while(pHead1&&pHead2) { if(pHead1->val<=pHead2->val) { r->next=pHead1; r=r->next; pHead1=pHead1->next; } else { r->next=pHead2; r=r->next; pHead2=pHead2->next; } } if(pHead1) r->next=pHead1; if(pHead2) r->next=pHead2; return result->next; }
题目五:两个链表的第一个公共结点,输入两个链表,找出它们的第一个公共结点。
分析:两个链表都是单向链表,如果他们有公共的结点,那么这两个链表从某一结点开始,他们的next都指向同一个结点,之后所有的点都重合,不可能再出现分叉。所以它们的拓扑形看起来像一个Y形,而不可能是X形。
方法一:首先遍历两个链表得到它们的长度,就能知道哪个链表长,以及长的链表比短的链表多几个结点。在第二次遍历的时候,在较长的链表上先走相差的步数,接着同时在两个链表上遍历,找到的第一个相同的结点就是它们的公共结点。
时间复杂度O(m+n)。不需要辅助栈。
方法二:分别将两个链表存入两个辅助栈中,然后比较两个栈顶的结点是否相同,如果相同,则把栈顶弹出,接着比较下一个栈顶,直到找到最后一个相同的结点。
时间复杂度O(m+n),空间复杂度O(m+n)。
方法一:
ListNode* FindFirstCommonNode( ListNode *pHead1, ListNode *pHead2) { int len1=0,len2=0; ListNode *p=pHead1,*q=pHead2; while(p) { len1++; p=p->next; } while(q) { len2++; q=q->next; } if(len1==0||len2==0) return NULL; p=pHead1;q=pHead2; while(len1>len2) { p=p->next; len1--; } while(len1<len2) { q=q->next; len2--; } while(p!=q) { p=p->next; q=q->next; } return p; }
方法二:
ListNode* FindFirstCommonNode( ListNode *pHead1, ListNode *pHead2) { ListNode *p=pHead1; ListNode *q=pHead2; stack<ListNode *> temp1; stack<ListNode *> temp2; while(p) { temp1.push(p); p=p->next; } while(q) { temp2.push(q); q=q->next; } ListNode *result=NULL; while(!temp1.empty()&&!temp2.empty()&&temp1.top()==temp2.top()) { result=temp1.top(); temp1.pop(); temp2.pop(); } return result; }
题目六:链表中环的入口结点:一个链表中包含环,请找出该链表的环的入口结点。
分析:有两个可以面试的问题:一个题是判断一个链表中,是否有环。第二个是环的入口结点。
经典方法就是使用快慢指针。快的一次走两步,慢的一次走一步,如果指针重合,说明链表有环。
在此基础上,可以想到,快的比慢的刚好多走了一个环的长度,而且速度是慢的二倍,说明快的总共走的是两个环的长度,慢的总共走了一个环的长度。
所以保持慢指针现在的位置,让快指针再次从头走起,每次走一步,当这次两个指针重合的时候,它们刚好都在环的入口结点上。
判断是否有环的代码:
bool HasLoop(ListNode* pHead) { ListNode *slow=pHead,*fast=pHead; while(fast&&fast->next) { slow=slow->next; fast=fast->next->next; if(slow==fast) return true; } return false; }
找环的入口结点代码
ListNode* EntryNodeOfLoop(ListNode* pHead) { ListNode *slow=pHead,*fast=pHead; while(fast&&fast->next) { slow=slow->next; fast=fast->next->next; if(slow==fast) { fast=pHead; while(fast!=slow) { fast=fast->next; slow=slow->next; } return slow; } } return NULL; }
转载请注明出处:http://blog.csdn.net/xingyanxiao/article/details/47068509
相关文章推荐
- JS判断SharePoint页面编辑状态
- 高端黑链SEO—恶意JS脚本注入访问伪随机域名
- 利用 Html 元标记控制搜索引擎蜘蛛
- ExtJs4 layout 布局
- javascrip中parentNode和offsetParent之间的区别
- Web前端离线缓存应用
- JQuery判断元素是否存在
- 关于jsp页面跳转及参数传递的问题
- WordPress 前端投稿/编辑插件 DJD Site Post(支持游客和已注册用户)
- xml 和 json 的区别
- 【HTML】设置NotePad++不打开上次关闭的文件
- JQuery选择器中的一些注意事项
- [Effective Java]第十章 并发
- Using native JSON
- 使用MathJax在HTML中显示LaTeX
- 设置中用到的简单好用的PreferenceActivity
- C# ArrayBuffer[转]
- HTML position元素
- 使用JQuery Deferred对象的then() 解决多个AJAX操作顺序依赖的问题
- js数组转换成json数组(包含extjs的checkbox勾选项获取办法)