链表——基本算法

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).让两个指针同时往后遍历,并检测节点是否相同,若相同,则该节点就是交点。

如果交点在环外:分别求出两链表入环时的第一个节点即可。

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值