4 - 链表

继续学算法~

合并链表

合并两个有序链表

21. 合并两个有序链表 - 力扣(LeetCode)

可以准备一个假的头节点作为合成后链表的头节点,然后像归并排序那样合并有序链表,如果其中一个链表合并完/空了,直接把另一个链表的剩余节点归并。

参考代码如下:

ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
    ListNode* head = new ListNode(-1);
    ListNode* cur = head;
    ListNode* cur1 = list1;
    ListNode* cur2 = list2;

    while (cur1 && cur2)
    {
        if (cur1->val < cur2->val)
        {
            cur->next = cur1;
            cur1 = cur1->next;
        }
        else
        {
            cur->next = cur2;
            cur2 = cur2->next;
        }
        cur = cur->next;
    }

    if (cur1)
        cur->next = cur1;
    else if (cur2)
        cur->next = cur2;

    return head->next;
}

合并k个有序链表

23. 合并 K 个升序链表 - 力扣(LeetCode)

和之前合并两个有序链表类似,但这次链表数量最多高达10^4,如果用暴力循环必定超时。因此可以使用优先队列,存储k个链表的头节点,然后将优先队列队头取出来(别忘了将它的下一个元素入队),加入假头节点链表即可。

算法时间复杂度为,其中k是链表条数,N是链表节点总数。

代码如下:

ListNode* mergeKLists(vector<ListNode*>& lists) {
    auto cmp = [](ListNode* v1, ListNode* v2){
        return v1->val > v2->val;
    };
    priority_queue<ListNode*, vector<ListNode*>, decltype(cmp)> q;

    // 初始化优先队列
    for (ListNode* node : lists)
        if (node)
            q.push(node);

    // 开始合并链表
    ListNode* dummy = new ListNode(-1);
    ListNode* cur = dummy;
    while (!q.empty())
    {
        ListNode* node = q.top();
        q.pop();
        if (node->next)
            q.push(node->next);

        cur->next = node;
        cur = cur->next;
    }

    return dummy->next;
}

链表倒数第k个节点

单链表的倒数第k个节点

19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)

删除链表的倒数第k个节点,也就是删除链表正数第n-k+1个节点。可以用双指针的方式通过一次遍历得到链表的第X个节点:首先让前驱指针向前走k步,然后让前驱和后继指针同时往前走,直到前驱为空,这样后继指针就指向链表的倒数第k个节点了。

参考代码如下:

ListNode* removeNthFromEnd(ListNode* head, int n) {
    ListNode* dummy = new ListNode(-1);
    dummy->next = head;

    // 找到倒数第n+1个节点
    ListNode* pre = dummy;
    ListNode* ne = dummy;
    while (n--)
        ne = ne->next;
    while (ne->next)
    {
        ne = ne->next;
        pre = pre->next;
    }

    // 删除倒数第n个节点
    pre->next = pre->next->next;
    return dummy->next;
}

单链表的中点

876. 链表的中间结点 - 力扣(LeetCode)

使用快慢指针,快指针每次走两下,慢指针每次走一下,这样当快指针走完后(碰到空节点),慢指针指向的就是中点了。

参考代码如下:

ListNode* middleNode(ListNode* head) {
    ListNode* fast = head, *slow = head;
    while (fast && fast->next)
    {
        fast = fast->next->next;
        slow = slow->next;
    }
    return slow;
}

判断链表是否包含环

141. 环形链表 - 力扣(LeetCode)

可以用快慢指针解决,如果链表存在环,必然有快慢指针重合的情况(快指针超了慢指针一圈)。参考代码如下:

bool hasCycle(ListNode *head) {
    ListNode *fast = head, *slow = head;
    while (fast && fast->next)
    {
        fast = fast->next->next;
        slow = slow->next;

        if (fast == slow)
            return true;
    }
    return false;
}

142. 环形链表 II - 力扣(LeetCode)

这是上面问题的进阶,需要找到环形链表的起点。

当快慢指针相遇时,快指针比慢指针多走了k步,而这个k必定是环长度的倍数。假设相遇点距离起点m步,慢指针走到相遇点用k步,快指针走到相遇点用2k步。那么从头节点到环形链表起点,从相遇点到环形链表起点都需要k-m步。

因此把其中一个指针放到起点,然后让他们同时走1步,直到二者相遇,相遇的地方就是环形链表的起点。

参考代码如下:

ListNode *detectCycle(ListNode *head) {
    ListNode *fast = head, *slow = head;

    bool isCycle = false;
    while (fast && fast->next)
    {
        fast = fast->next->next;
        slow = slow->next;

        if (fast == slow)
        {
            isCycle = true;
            break;
        }

    }

    if (isCycle)
    {
        fast = head;
        while (fast != slow)
        {
            fast = fast->next;
            slow = slow->next;
        }
        return fast;
    }
    else
        return nullptr;
}

链表相交

两链表是否相交

160. 相交链表 - 力扣(LeetCode)

如图,A链表遍历完马上遍历B,B链表遍历完马上遍历A,直到它们同时到达相交节点,而这个节点可能有内容(相交了),也有可能是nullptr(不相交)。

参考代码如下:

ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
    ListNode *pa = headA;
    ListNode *pb = headB;

    while (pa != pb)
    {
        pa = pa == nullptr ? headB : pa->next;
        pb = pb == nullptr ? headA : pb->next;
    }

    return pa;
}