1.反转链表
给出3个指针,一个cur,用于遍历链表中的每个节点,一个prev,用于保存cur指向的节点的上一个节点地址,还有一个after,用于保存cur指向的节点的下一个节点地址,链表操作遵循先连后断,即:
struct ListNode* reverseList(struct ListNode* head){
if(NULL == head||head->next ==NULL){
return head;
}
struct ListNode*prev=NULL;
struct ListNode*cur=head;
struct ListNode*after=NULL;
while(cur){
after=cur->next;//先用after保存cur之后节点的地址
cur->next=prev;//修改cur->next的指向,指向它的前一个节点
prev=cur;//给prev重新新赋值
cur=after;//给cur重新赋值
}
return prev;
}
2.给定一个头结点为 head
的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
用快慢指针的方法解决,一个快指针fast,一个慢指针slow,开始时,都让他们指向链表的头节点,然后fast每次向后遍历两个节点,而slow每次往后遍历一个节点,循环这两步操作,循环结束条件是:当单链表有偶数个节点时,fast先遇到空指针NULLL;当单链表有奇数个节点时,fast->next先遇到空指针NULL。
typedef struct ListNode Node;
struct ListNode* middleNode(struct ListNode* head){
Node* fast=head;
Node* slow=head;
while( fast && fast->next){//当fast走到最后时,偶数个节点时,fast先为空,奇数个节点时,fast->next为空
fast=fast->next->next;//先让快指针走两步
slow=slow->next;//再让慢指针走一步
}
return slow;
}
3.输入一个链表,输出该链表中倒数第k个结点。
(1).先定义两个节点指针,一个front,一个back,开始时都让他们指向链表的头节点;(2).先让front走k步(有可能front还没走完k步就已经到达链表的末尾,说明倒数第k个节点肯定没有,就没必要再执行下一步,直接返回NULL) ;(3).front和back同时往后走,当front到达链表末尾时,back恰好在倒数第k个节点的位置。
struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
if(NULL == pListHead || k == 0){
return NULL;
}
struct ListNode* cur=pListHead;
struct ListNode* pre=pListHead;
while(k--){
if(NULL == cur){
return NULL;
}
else{
cur=cur->next;
}
}
while(cur){
pre=pre->next;
cur=cur->next;
}
return pre;
}
4.将两个升序链表合并为一个新的 升序 链表并返回。
(1).定义一个带头结点空链表,再定义一个尾指针tail用来指向新链表的尾节点,便于后续尾插;另外再定义两个指针,一个cur1,用于遍历链表list1,一个cur2,用于遍历链表list2;(2).当cur1和cur2每遍历一个节点时,比较两个节点的值,让值小的那个节点尾插到新链表,并让指针往后走一步,另一个指针不动,循环操作,直到其中一个链表被遍历完;(3).循环结束后,看哪一个链表不为空,就把尾指针tail指向不为空的那一部分;(4).返回新链表头节点的指针域的值。
typedef struct ListNode Node;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
if(NULL == list1&&NULL == list2){
return NULL;
}
Node head;
Node* tail=&head;
Node* cur1=list1;
Node* cur2=list2;
while(cur1&&cur2){
if(cur1->val <= cur2->val){
tail->next=cur1;
tail=cur1;
cur1=cur1->next;
}
else{
tail->next=cur2;
tail=cur2;
cur2=cur2->next;
}
}
if(cur1 == NULL){
tail->next=cur2;
}
else{
tail->next=cur1;
}
return head.next;
}
5.现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。
(1).先定义d两个带头结点的单链表,plist1,plist2,再定义两个尾指针,tail1,tail2,分别指向两个新链表的尾节点,便于后续尾插;(2).定义一个指针cur,用于遍历原链表,当遍历原链表每一个节点时,将其值与给定值x比较,若cur->val <= x,将cur往pllist1中尾插,若cur->val > x,将cur往plist2中尾插,直至原链表遍历完;(3).合并,让plist1的最后一个节点的next指向plist2,且让plist2的最后一个节点的next指向NULL。
class Partition {
public:
ListNode* partition(ListNode* pHead, int x) {
if(NULL == pHead||NULL == pHead->next){
return pHead;
}
ListNode* cur=pHead;
ListNode plist1(0);//存放小于等于x的节点
ListNode* tail1=&plist1;//tail1指向plist1的尾节点
ListNode plist2(0);//存放大于x的节点
ListNode* tail2=&plist2;//tail2指向plist2的尾节点
while(cur){
if(cur->val < x){
tail1->next=cur;
tail1=cur;
}
else{
tail2->next=cur;
tail2=cur;
}
cur=cur->next;
}
tail1->next=plist2.next;
tail2->next=NULL;
return plist1.next;
}
6.判断单链表是不是回文结构
(1).用快慢指针的思想找到原链表的中间节点;(2).反转后半段链表;(3).判断前半段与后半段是否相等;(4).再将后半段逆置,恢复原链表。
bool chkPalindrome(ListNode* A) {
// write code here
if(NULL == A){
return false;
}
ListNode* fast=A;
ListNode* slow=A;
ListNode* head=A;
while(fast && fast->next){//先找中间节点
fast=fast->next->next;
slow=slow->next;
}
ListNode* cur=slow;//反转后半段链表
ListNode* after=NULL;
ListNode* pre=NULL;
while(cur){
after=cur->next;
cur->next=pre;
pre=cur;
cur=after;
}
while( head&&pre ){//判断前半段与后半段是否相等
if(head->val == pre->val){
head=head->next;
pre=pre->next;
}
else{
break;
}
}
if(pre == NULL){
return true;
}
else{
return false;
}
}
7.两个链表(不带环)相交
两个链表相交的可能性:
检测两个链表(不带环)是否相交:(1).找到两个链表的最后一个节点;(2).检测两个链表的最后一个节点是否相同。
求两个相交链表(不带环)的节点:(1).分别求出两个链表的节点个数;(2).定义两个指针,分别指向两个链表的头节点;(3).让指向较长链表的指针先遍历两链表节点个数差值个节点;(4).让两个指针同时往后遍历,并检测节点是否相同,若相同,则该节点就是交点。
typedef struct ListNode ListNode;
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
ListNode* curA=headA;
ListNode* curB=headB;
int countA=0;//记录A链表的个数
int countB=0;//记录B链表的个数
//遍历两个链表,判断尾节点的地址是否相等
while(curA){
curA=curA->next;
countA++;
}
while(curB){
curB=curB->next;
countB++;
}
int k =0 ;
ListNode* tailA=curA;
ListNode* tailB=curB;
curA=headA;//重置
curB=headB;
if(tailA == tailB){//相交
if(countA>=countB){
k=countA-countB;
while(k--) {//先让A走k步
curA=curA->next;
}
while(curA && curB){
if(curA == curB){
return curA;
}
else{
curA=curA->next;
curB=curB->next;
}
}
}
else{
k=countB-countA;
while(k--) {//先让B走k步
curB=curB->next;
}
while(curA && curB){
if(curA == curB){
return curB;
}
else{
curA=curA->next;
curB=curB->next;
}
}
}
}
return NULL;//不相交
}
8.给定一个链表,判断链表中是否有环。
思路:快慢指针,即慢指针一次走一步,快指针一次走两步,两个指针从链表其实位置开始运行,如果链表带环则一定会在环中相遇,否则快指针率先走到链表的末尾。
typedef struct ListNode Node;
bool hasCycle(struct ListNode *head) {
Node* fast=head;
Node* slow=head;
while(fast && fast->next){
fast=fast->next->next;
slow=slow->next;
if(fast == slow){
return true;
}
}
return false;
}
扩展问题:
>1.为什么快指针每次走两步,慢指针走一步可以?
假设链表带环,两个指针最后都会进入环,快指针先进环,慢指针后进环。当慢指针刚进环时,可
能就和快指针相遇了,最差情况下两个指针之间的距离刚好就是环的长度。此时,两个指针每移动
一次,之间的距离就缩小一步,不会出现每次刚好是套圈的情况,因此:在满指针走到一圈之前,
快指针肯定是可以追上慢指针的,即相遇。
>2.快指针一次走3步,走4步,...n步行吗?
假设:快指针每次走3步,满指针每次走一步,此时快指针肯定先进环,慢指针后来才进环。假设慢指针进环时候,快指针的位置如图所示:
此时按照上述方法来绕环移动,每次快指针走3步,慢指针走1步,是永远不会相遇的,快指针刚好将慢指针套圈了,因此不行。
只有快指针走2步,慢指针走一步才可以,因为换的最小长度是1,即使套圈了两个也在相同的位置。
9.给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 NULL
(1). 用快慢指针的方法判断链表是否带环;(2).让一个指针从链表起始位置开始遍历链表,同时让一个指针从判环时相遇点的位置开始绕环运行,两个指针都是每次均走一步,最终肯定会在入口点的位置相遇。
typedef struct ListNode Node;
struct ListNode *detectCycle(struct ListNode *head) {
Node* fast=head;
Node* slow=head;
Node* cur=head;
while(fast&&fast->next){
fast=fast->next->next;
slow=slow->next;
if(fast == slow){
Node* M=fast;
while(M != cur){//一个从头开始,一个从相遇点开始,每次两个指针都走一步,
M=M->next;//判断是否相等,相等则为入口点
cur=cur->next;
}
break;
}
}
//没有环
if(fast == NULL||NULL == fast->next){//满足一个条件即可,所以是逻辑或
return NULL;
}
return cur;
}
说明:
H为链表的起始点,E为环入口点,M为判环时候相遇点。设:环的长度为R, H到E的距离为L, E到M的距离为X,则: M到E的距离为R - X。
在判环时,快慢指针相遇时所走的路径长度:fast:L+X+nR,slow:L + X
注意:
1.当慢指针进入环时,快指针可能已经在环中绕了n圈了, n至少为1。因为:快指针先进环走到M的位置,最后又在M的位置与慢指针相遇。
2.慢指针进环之后,快指针肯定会在慢指针走一圈之内追上慢指针。因为:慢指针进环后,快慢指针之间的距离最多就是环的长度,而两个指针在移动时,每次它们之间的距离都缩减一步,因此在慢指针移动一圈之前,快指针肯定是可以追上慢指针。
而快指针速度是满指针的两倍,因此有如下关系是:
2*(L+X)=L+X+ nR
L+X= nR
L= nR- X (n为1,2.3...... n的大小取决于环的大小,环越小n越大)
极端情况下,假设n= 1,此时:L= R-X
即:一个指针从链表起始位置运行,一个指针从相遇点位置绕环,每次都走一步,两个指针最终会在入口点的位置相遇
扩展:
>1.两个链表不带环相交有三种情况,参考算法7。
>2.一个链表带环,一个不带环,则这两个链表不会相交。
>3.两个都带环的链表相交有两种情况:
在此种情况下,求交点的方法:
如果交点在环外:1.先找到入环时的第一个节点,以此作为两链表的尾节点;2.(1).分别求出两个链表的节点个数;(2).定义两个指针,分别指向两个链表的头节点;(3).让指向较长链表的指针先遍历两链表节点个数差值个节点;(4).让两个指针同时往后遍历,并检测节点是否相同,若相同,则该节点就是交点。
如果交点在环外:分别求出两链表入环时的第一个节点即可。