数据结构以学带练day4——链表两两交换、删除链表的倒数第N个节点 、相交链表、环形链表

151 阅读7分钟

题目

24.两两交换链表中的节点

image.png

虚拟头节点法

  • 当对整个链表进行操作时,就需要想想是不是得用到虚拟头节点,因为要对头节点进行(交换、删除等)操作,要知道它的前一个节点
  • 本题要注意链表奇数和偶数的结束循环条件,当链表长为偶数时,cur->next为空则结束;当链表长为奇数时,cur->next->next为空则结束。且必须先判断偶数情况,再判断奇数情况,否则会出现空指针的情况。即当cur->next已经为空了,但我们取cur->next->next则变成取 空->next 就会报错。

思路如下图:

7ffb48f4b3b6b343d69a12354fbec91.jpg

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        //虚拟头节点,下一个节点为head
        ListNode *dummyhead = new ListNode(0,head);
        //cur指针指向dummyhead
        ListNode *cur = dummyhead;
        //result指针也指向dummyhead
        ListNode *result = dummyhead;
        //用于存储的指针
        ListNode *temp1;
        ListNode *temp2;
        if(head==nullptr){return head;}
        //偶数:cur->next!=nullptr
        //奇数:cur->next->next!=nullptr
        while(cur->next!=nullptr && cur->next->next!=nullptr){
            //记录前一个断开的节点
            temp1 = cur->next;
            cur->next = cur->next->next;//1交换
            //记录后一个断开的节点
            temp2 = cur->next->next;
            cur->next->next = temp1;//2交换
            temp1->next = temp2;//3交换
            //cur+2
            cur = cur->next->next;
        }
        result = dummyhead->next;
        delete dummyhead;
        return result;
    }
};

19. 删除链表的倒数第 N 个结点

image.png

二刷整合自己想法和双指针法

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        //虚拟头,因为可能删除的就是头节点
        ListNode* myhead = new ListNode(0,head);
        ListNode* res = myhead;
        ListNode* fast = myhead;
        ListNode* slow = myhead;
        //思路1:fast遍历链表,得到长度len,之后进行删除操作
        #define _use_Think1 1
        #if _use_Think1
        {   
            int len = 0;
            while(fast->next!=nullptr){
                fast = fast->next;
                len++;
            }
            int jumpcount = len - n;
            while(jumpcount--){
                slow = slow->next;
            }
            if(slow->next->next!=nullptr)
                slow->next = slow->next->next;
            else
                slow->next = nullptr;
            res = myhead->next;
            delete myhead;
            return res;
        }
        #endif
        //思路2:先fast走n步,再fast和slow同时走,直到fast走到最后
        #define _use_Think2 0
        #if _use_Think2
        {
            while(n--){
                fast=fast->next;
            }
            while(fast->next!=nullptr){
                fast = fast->next;
                slow = slow->next;
            }
            if(slow->next->next!=nullptr)
                slow->next = slow->next->next;
            else
                slow->next = nullptr;
            res = myhead->next;
            delete myhead;
            return res;
        }
        #endif
    }
};

自己的想法

思路:使用虚拟头,用于对head操作。遍历整个链表(加虚拟头)长度,得到count,move=count-n-1得到要删除的节点的前一个的位置。之后进行删除,返回头节点。

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        int count = 0;
        ListNode *dummyhead = new ListNode(0,head);
        ListNode *cur = dummyhead;
        // //如果为空链表,返回这个空链表
        // if(dummyhead->next==nullptr){return head;}
        //遍历整个链表
        while(cur!=nullptr){
            cur = cur -> next;
            count++;      
        }
        cout<<count<<endl;
        //循环结束后cur指向null,count为链表长度+1,因为有虚拟头节点
        // //如果要删除的节点大于链表长度,返回整个链表
        // if(n>count){return head;}
        int move = count - n - 1;
        ListNode *cur2 = dummyhead;
        ListNode *result = dummyhead;
        while(move){
            cur2 = cur2 -> next;
            move--;
        }
        //循环结束,cur2指向要被删的节点的前一个
        //如果不是删除最后一个节点
        if(cur2->next!=nullptr && cur2->next->next!=nullptr){
            cur2->next = cur2->next->next;
        }
        //是删除最后一个节点
        else{
            cur2->next = nullptr;
        }
        result = dummyhead->next;
        delete dummyhead;
        return result;
    }
};

双指针方法

双指针指针经典应用

如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点(但一般是指向要删的前一个节点,所以fast一般走n+1步)就可以了。

思路:

  • 定义虚拟头节点
  • 快慢指针,快指针走n+1步(为什么是n+1呢,因为只有这样同时移动的时候slow才能指向删除节点的上一个节点),之后快慢指针同时走,直到快指针指向null,则此时慢指针指向要删除的节点前一个
  • 进行删除。
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        //定义双指针
        ListNode* fast;
        ListNode* slow;
        //定义虚拟头节点
        ListNode* dummy = new ListNode(0);
        dummy->next = head;
        fast = dummy;
        slow = dummy;
        for(int i=0;i<n+1;i++){
            //fast指针走n+1步
            fast = fast->next;
        }
        while(fast){
            //fast再走n步到末尾NULL
            fast = fast->next;
            //slow走n步到要删除节点的前一个节点
            slow = slow->next;
        }
        slow->next = slow->next->next;
        return dummy->next;
    }
};

面试题 02.07. 链表相交

image.png

思路:

求出A和B的两个链表长度,比较两个链表长度差值,然后把较长链表的开始指向和较短链表的开始指向对齐,之后进行移动,如果遇到指针指向的节点相同,则返回这个节点,否则返回NULL。

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode *curA1 = headA;//用于遍历
        ListNode *curA2 = headA;//用于查找相交

        ListNode *curB1 = headB;
        ListNode *curB2 = headB;

        int numA = 0;//记录长度
        int numB = 0;
        //遍历A和B长度
        while(curA1){
            curA1 = curA1 -> next;
            numA++;
        }
        while(curB1){
            curB1 = curB1 -> next;
            numB++;
        }
        //A比B长
        if(numA>numB){
            // 求长度差
            int gap = numA - numB;
            // 让curA和curB在同一起点上(末尾位置对齐)
            while (gap--) {
                curA2 = curA2->next;
            }
            while(curB2){
                if(curB2 == curA2){
                    return curB2;
                }
                    curA2 = curA2 -> next;
                    curB2 = curB2 -> next;
            }
        }
        //B比A长或相等长
        else{
            // 求长度差
            int gap = numB - numA;
            // 让curA和curB在同一起点上(末尾位置对齐)
            while (gap--) {
                curB2 = curB2->next;
            }
            while(curA2){
                if(curB2 == curA2){
                    return curB2;
                }
                    curA2 = curA2 -> next;
                    curB2 = curB2 -> next;  
            }
        }
        return NULL;
    }
};

142.环形链表 II

image.png

思路

判断有环

使用快慢指针法,分别定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。

  • fast指针一定先进入环中,如果fast指针和slow指针相遇的话,一定是在环中相遇。
  • fast是走两步,slow是走一步,在环内把slow看作静止,则fast是一个节点一个节点的靠近slow的,所以fast一定可以和slow重合。
  • fast和slow相遇一定有环,但不能说fast和slow一定在入口相遇,即fast==slow,此时的fast或slow位置不一定是环的入口位置。

找入口位置

假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入口节点节点数为 z。 如图所示:

image.png

那么相遇时: slow指针走过的节点数为: x + y, fast指针走过的节点数:x + y + n (y + z)n为fast指针在环内走了n圈才遇到slow指针, (y+z)为 一圈内节点的个数A。

因为fast指针是一步走两个节点,slow指针一步走一个节点, 所以 fast指针走过的节点数 = slow指针走过的节点数 * 2:

(x + y) * 2 = x + y + n (y + z)

---> x + y = n (y + z)

---> x = n (y + z) - y

---> x = (n - 1) (y + z) + z

n=1的时候,公式就化解为 x = z

这就意味着,从头结点出发一个指针(index2),从相遇节点 也出发一个指针(index1),这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点。即让index1和index2同时移动,每次移动一个节点, 那么他们相遇的地方就是 环形入口的节点。

n>1是fast指针在环形转n圈之后才遇到 slow指针。 这种情况和n为1的时候 效果是一样的,一样可以通过这个方法找到 环形的入口节点,只不过,index1 指针在环里 多转了(n-1)圈,然后再遇到index2,相遇点依然是环形的入口节点

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* fast = head;
        ListNode* slow = head;
        //Q:为什么while里面是判断fast!=NULL&&fast->next!=NULL?
        //A:while中执行fast=fast->next->next;如果不判断fast!=NULL&&fast->next!=NULL则可能因为fast->next==NULL,导致fast=fast->next->next报错。
        while(fast!=NULL && fast->next!=NULL){
            fast = fast->next->next;
            slow = slow->next;
            //说明有环,但不一定是入口
            //且当fast == slow时,此时的fast或slow的位置即是相遇的位置
            if(fast == slow){
                ListNode *index1 = fast;//相遇的位置
                ListNode *index2 = head;//开头的位置
                //没在入口相遇
                while(index1!=index2){
                    index1 = index1->next;
                    index2 = index2->next;
                }
                //循环结束则代表找到入口了,返回index1或index2都可
                return index1;
            }
            
        }
        return NULL;
    }
};

总结

今日学习时长:4h+40min