代码随想录拓展day4 143.重排链表;141. 环形链表;面试题 02.07. 链表相交
关于链表的一些应用,基本都用到了快慢指针的思路。对于单链表来说,确定边界,也就是遍历时的终止条件非常重要。
143.重排链表
143. 重排链表 - 力扣(Leetcode)
不用额外空间的方法还是比较难的,而且还涉及到了很多coding细节的问题,这里注意要用count的奇偶来判断下一个插入的节点。
思路
本篇将给出三种C++实现的方法
- 数组模拟
- 双向队列模拟
- 直接分割链表
方法一
把链表放进数组中,然后通过双指针法,一前一后,来遍历数组,构造链表。
代码如下:
class Solution {
public:
    void reorderList(ListNode* head) {
        vector<ListNode*> vec;
        ListNode* cur = head;
        if (cur == nullptr) return;
        while(cur != nullptr) {
            vec.push_back(cur);
            cur = cur->next;
        }
        cur = head;
        int i = 1; // 注意,head在最开始已经放进数组了,所以下标i从1开始
        int j = vec.size() - 1;  // i j为之前前后的双指针
        int count = 0; // 计数,偶数去后面,奇数取前面
        while (i <= j) {
            if (count % 2 == 0) {
                cur->next = vec[j];
                j--;
            } else {
                cur->next = vec[i];
                i++;
            }
            cur = cur->next;
            count++;
        }
        cur->next = nullptr; // 注意结尾
    }
};
方法二
把链表放进双向队列,然后通过双向队列一前一后弹出数据,来构造新的链表。这种方法比操作数组容易一些,不用双指针模拟一前一后了
class Solution {
public:
    void reorderList(ListNode* head) {
        deque<ListNode*> que;
        ListNode* cur = head;
        if (cur == nullptr) return;
        while(cur->next != nullptr) {
            que.push_back(cur->next); // 第一个放进去的是head->next
            cur = cur->next;
        }
        cur = head;
        int count = 0; // 计数,偶数去后面,奇数取前面
        ListNode* node;
        while(que.size()) {
            if (count % 2 == 0) {
                node = que.back();
                que.pop_back();
            } else {
                node = que.front();
                que.pop_front();
            }
            count++;
            cur->next = node;
            cur = cur->next;
        }
        cur->next = nullptr; // 注意结尾
    }
};
方法三
将链表分割成两个链表,然后把第二个链表反转,之后在通过两个链表拼接成新的链表。
如图:

这种方法,比较难,平均切割链表,看上去很简单,真正代码写的时候有很多细节,同时两个链表最后拼装整一个新的链表也有一些细节需要注意!
代码如下:
class Solution {
private:
    // 反转链表
    ListNode* reverseList(ListNode* head) {
        ListNode* temp; // 保存cur的下一个节点
        ListNode* cur = head;
        ListNode* pre = NULL;
        while(cur) {
            temp = cur->next;  // 保存一下 cur的下一个节点,因为接下来要改变cur->next
            cur->next = pre; // 翻转操作
            // 更新pre 和 cur指针
            pre = cur;
            cur = temp;
        }
        return pre;
    }
public:
    void reorderList(ListNode* head) {
        if (head == nullptr) return;
        // 使用快慢指针法,将链表分成长度均等的两个链表head1和head2
        // 如果总链表长度为奇数,则head1相对head2多一个节点
        ListNode* fast = head;
        ListNode* slow = head;
        while (fast && fast->next && fast->next->next) {
            fast = fast->next->next;
            slow = slow->next;
        }
        ListNode* head1 = head;
        ListNode* head2;
        head2 = slow->next;
        slow->next = nullptr;
        // 对head2进行翻转
        head2 = reverseList(head2);
        // 将head1和head2交替生成新的链表head
        ListNode* cur1 = head1;
        ListNode* cur2 = head2;
        ListNode* cur = head;
        cur1 = cur1->next;
        int count = 0; // 偶数取head2的元素,奇数取head1的元素
        while (cur1 && cur2) {
            if (count % 2 == 0) {
                cur->next = cur2;
                cur2 = cur2->next;
            } else {
                cur->next = cur1;
                cur1 = cur1->next;
            }
            count++;
            cur = cur->next;
        }
        if (cur2 != nullptr) { // 处理结尾
            cur->next = cur2;
        }
        if (cur1 != nullptr) {
            cur->next = cur1;
        }
    }
};
141. 环形链表
141. 环形链表 - 力扣(Leetcode)
快慢指针的一个应用。同时哈希表也可以解决这个问题,但是需要额外的空间。
思路
可以使用快慢指针法, 分别定义 fast 和 slow指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。
为什么fast 走两个节点,slow走一个节点,有环的话,一定会在环内相遇呢,而不是永远的错开呢?
首先第一点: fast指针一定先进入环中,如果fast 指针和slow指针相遇的话,一定是在环中相遇,这是毋庸置疑的。
那么来看一下,为什么fast指针和slow指针一定会相遇呢?
可以画一个环,然后让 fast指针在任意一个节点开始追赶slow指针。
会发现最终都是这种情况, 如下图:

fast和slow各自再走一步, fast和slow就相遇了
这是因为fast是走两步,slow是走一步,其实相对于slow来说,fast是一个节点一个节点的靠近slow的,所以fast一定可以和slow重合。
动画如下:

C++代码如下
class Solution {
public:
    bool hasCycle(ListNode *head) {
        ListNode* fast = head;
        ListNode* slow = head;
        while(fast != NULL && fast->next != NULL) {
            slow = slow->next;
            fast = fast->next->next;
            // 快慢指针相遇,说明有环
            if (slow == fast) return true;
        }
        return false;
    }
};
或者使用哈希表的方法,把所有遇到过的节点都加入表中,如果有节点重复出现了,因为是单链表,那一定是链表中有环了。
class Solution {
public:
    bool hasCycle(ListNode *head) {
        unordered_set<ListNode*> set;
        ListNode* cur = head;
        while(cur){
            if(set.find(cur) != set.end()){
                return true;
            } else {
                set.insert(cur);
            }
            cur = cur->next;
        }
        return false;
    }
};
面试题 02.07. 链表相交
面试题 02.07. 链表相交 - 力扣(Leetcode)
旧题目复习,依然是快慢指针的应用,同时用哈希表也可以解决。记住重点是单链表相交结尾必相同。



















