[LeetCode] LeetCode中链表题总结

写在前面

本篇博客主要讲解LeetCode中链表相关题型的解法,以及对它的思考和理解,链表类型题是互联网公司面试中最喜欢手撕代码的题型,因为这类题用C/C++解题时会用到指针,而指针又可以进一步考察内存管理,然后进一步引出操作系统,可谓以点带面。

160. 相交链表

如下面的两个链表:

在这里插入图片描述
在节点 c1 开始相交。

解题思路: 此题属于链表中比较简单的题,解题思路应该是要在如何让两个指针同时指向同一个节点上做文章,常见的解题思路是,将长链表指针移动长短链表长度差值的位置,然后一起移动,即可同时指向同一节点,然后看了群友们的解法,发现采取的是另一个解法,但是仔细分析,本质上与这个解法是相同的,群友的解法是,将pa指针指向A,pb指针指向B,当pa访问到A末尾时切到B链表的头节点上访问B,同理pb切到链表A上,在切换完后的访问的过程中,会同时指向交点。请结合下图理解。虽然pa和pb没能在第一个红线处相遇,但是一定在第二个红线处相遇,因为他们走过相同的长度,只要有交点,即可相遇。但是我们细想,本质上依然是想pb多走一个差值的长度,只不过上前面解法中真的让pb先走,而本解法中只是多走。请看两种解法代码。
在这里插入图片描述

// 先将长链表指针移动至两链表长度差值的位置,然后长短链表一起移动
// 当两链表指针指向同一个节点时,这个节点即为交点
// Time: O(n),space: O(1)
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if (!headA || !headB) return NULL; 
        int len1 = 0, len2 = 0;
        ListNode *cur1 = headA, *cur2 = headB;
        while (cur1) {
            ++len1;
            cur1 = cur1->next;
        }
        while (cur2) {
            ++len2;
            cur2 = cur2->next;
        }
        cur1 = headA;
        cur2 = headB;
        int i = 0;
        if (len1 > len2) {
            while (i < len1 - len2) {
                cur1 = cur1->next;
                ++i;
            }
        } else {
            while (i < len2 - len1) {
                cur2 = cur2->next;
                ++i;
            }
        }
        while (cur1 && cur2 && cur1 != cur2) {
            cur1 = cur1->next;
            cur2 = cur2->next;
        }
        return cur1 != cur2 ? NULL : cur1;
    }
};
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode *cur1 = headA, *cur2 = headB;
        while (cur1 != cur2) {
            cur1 = (cur1 ? cur1->next : headB);
            cur2 = (cur2 ? cur2->next : headA);
        }
        return cur1;
    }
};

23. 合并K个排序链表

合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。

解题思路: 一般解链表的题,我有一种感觉,即是,链表相关的题,一般情况下指针指来指去都能解的出来,但是要想解的顺利需要想一个模型非常自洽的解法,拿此题来说,如果用暴力解法,维护k个指针,每个都讲k个指针所指的值进行比较取最小的一个拼接至结果链表中,然后再移动他的指针,这种方法沿用的是两个链表合并的做法,取最小值的时间复杂度为O(k),总时间复杂度为O(n*k),不算最优的解法。最好的解法是采用分治,每趟两两合并,一趟合并即可将合并链表数减半,至此此题可解。注意点是,由于链表个数可能为奇数,那么从两头向中间合并时,最中间的那个链表没被合并,因此在将长度减半时需(len+1),这样下次最中间的链表能参与合并。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        if (lists.empty()) return NULL;
        if (lists.size() == 1) return lists[0];
        int len = lists.size();
        while (len > 1) {
            int i = 0, j = len - 1;
            while (i < j) {
                lists[i] = mergeTwoLists(lists[i], lists[j]);
                ++i;
                --j;
            }
            len = (len + 1) / 2;
        }
        return lists[0];
    }
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        if (!l1) return l2;
        if (!l2) return l1;
        ListNode *dummy = new ListNode(-1), *cur = dummy;
        while (l1 && l2) {
            if (l1->val < l2->val) {
                cur->next = l1;
                l1 = l1->next;
            } else {
                cur->next = l2;
                l2 = l2->next;
            }
            cur = cur->next;
        }
        if (l1) cur->next = l1;
        else cur->next = l2;
        ListNode *res = dummy->next;
        delete dummy;
        return res;
    }
};

其他

  1. 判断链表是否成环?用fast和slow指针,fast指针每次移动两步,slow指针每次移动一步,当fast指针和slow指针能再次相遇则链表中存在环,还可以进一步判断环的起点位置,当fast指针遇上slow指针时,fast多走的步数即为环的大小LoopLen,那么当fast指针和slow移回head指针后,让fast指针先移动LoopLen步,然后与slow与fast一起移动(每次移动一步),当再次相遇时,相遇点即为环的起点。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值