链表:两两交换链表结点 删除倒数第N个结点 链表相交 环形链表II

两两交换链表的结点

  • 题目:Leetcode24
  • 思路:不能只是交换结点的值,而是整个结点交换,需要改变结点指针域的指针指向。交换两个结点,实际上涉及到四个结点,除了交换的两个节点外,还涉及到它们的前一个和后一个结点。对于头结点,使用虚拟头结点可以避免对头结点的单独处理。
  • 时间复杂度:O(n)
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        //交换两个结点,实际上涉及到四个结点,包括这两个结点的前后结点
        //使用虚拟头结点,就不需要对头结点的交换做额外的判断
        ListNode* dummyhead = new ListNode(0);
        dummyhead->next = head;
        ListNode* cur = dummyhead;
        while(cur->next && cur->next->next) {
            //dummyhead(cur) -> 1(tmp) -> 2 -> 3
            ListNode* tmp = cur->next;    //记录临时结点
            cur->next = tmp->next;    //dummyhead(cur) -> 2
            tmp->next = cur->next->next;    //1(tmp) -> 3
            cur->next->next = tmp;    //2 -> 1(tmp)

            cur = cur->next->next;    //更新cur,准备下一轮交换
        }
        head = dummyhead->next;
        return head;
    }
};

删除链表的倒数第N个节点

  • 题目:Leetcode19
  • 思路:

快慢指针,让fast先移动n步,然后让fast和slow同时移动,fast和slow的步数差为n步,当fast指向链表末尾时,slow就指向倒数第n个结点。

依旧使用虚拟头结点,避免头结点的逻辑的特殊处理,由于使用了虚拟头结点,fast就变成了先走n+1步,因为只有这样同时移动的时候slow才能指向删除节点的上一个节点(方便做删除操作)

  • 时间复杂度:O(n)
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        //快慢指针、虚拟头结点
        //删除一个结点,必须利用到它的前驱结点,使用虚拟头结点可以避免对头结点的特殊判断
        ListNode* dummyhead = new ListNode(0);
        dummyhead->next = head;
        ListNode* fast = dummyhead;
        ListNode* slow = dummyhead;
        //令fast先走n+1步,目的是后续快慢指针同时移动结束后,慢指针正好指在待删除结点的前一个结点
        n++;
        //fast先移动n+1步
        while(n-- && fast != NULL) {
            fast = fast->next;
        }
        //快慢指针同时移动
        while(fast != NULL) {
            fast = fast->next;
            slow = slow->next;
        }
        //暂存指针,释放内存
        ListNode* tmp = slow->next;
        slow->next = tmp->next;
        delete tmp;
        tmp = nullptr;
        return dummyhead->next;
    }
};

链表相交

  • 题目:Leetcode 面试题 02.07.
  • 思路:使用双指针法。交点不是数值相等,而是指针相等。分别求出两个链表的长度,让长的链表的指针先走完它们的差值,使两个指针处于同一起跑线,也就是距离最后一个结点的长度相等,再同时向后遍历,当两个指针相等时,即为交点。
  • 时间复杂度:O(n + m)
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode* curA = headA;
        ListNode* curB = headB;
        int lenA = 0,lenB = 0;
        //分别遍历两个链表,求出各自长度
        while(curA) {
            lenA++;
            curA = curA->next;
        }
        while(curB) {
            lenB++;
            curB = curB->next;
        }
        //重置指针,等待重新遍历
        curA = headA;
        curB = headB;
        //使得链表A的长度一定大于B
        if(lenB > lenA) {
            swap(lenA, lenB);
            swap(curA, curB);
        }
        int gap = lenA - lenB;
        //先遍历链表A,让curA和curB在同一起点上(末尾位置对齐)
        while(gap--) {
            curA = curA->next;
        }
        //遍历curA 和 curB,遇到相同则直接返回
        while(curA) {
            if(curA == curB) {
                return curA;
            }
            curA = curA->next;
            curB = curB->next;
        }
        return NULL;
    }
};

环形链表II

  • 题目:Leetcode142
  • 思路:快慢指针相遇
    • 是否有环?
      • fast一次移动两个结点,slow一个节点,fast指针一定先进入环中,如果fast指针和slow指针相遇的话,一定是在环中相遇
    • 环的入口在哪?
      • 从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点
  • ​​​​​时间复杂度: O(n),快慢指针相遇前,指针走的次数小于链表长度,快慢指针相遇后,两个index指针走的次数也小于链表长度,总体为走的次数小于 2n
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        /*
        1.是否有环:有环,则快慢指针会在环内相遇
        2.环的入口:从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点
        3.slow进环第一圈就会和fast相遇
        */
        ListNode* fast = head;
        ListNode* slow = head;
        while(fast && fast->next) {
            fast = fast->next->next;
            slow = slow->next;
            //寻找快慢指针相遇点
            if(fast == slow) {
                ListNode* index1 = fast;
                ListNode* index2 = head;
                //寻找环的入口
                while(index1 != index2) {
                    index1 = index1->next;
                    index2 = index2->next;
                }
                return index1;
            }
        }
        return NULL;
    }
};

总结

  • 要多注意:循环终止条件、临时结点记录
  • 多练习:虚拟头结点使用,目前做题还是经常想不到
  • 多理解:环的入口

参考链接

代码随想录:

两两交换链表结点

删除倒数第N个结点

链表相交

环形链表II

你可能感兴趣的:(链表,数据结构,算法)