【代码随想录算法训练Day4】LeetCode 24.两两交换链表中的节点、LeetCode 19.删除链表的倒数第N个节点 、LeetCode 142.环形链表II

Day4 链表

LeetCode 24.两两交换链表中的节点【链表/递归】

本题还是有一定难度的,思考了很长时间没有翻明白指针的迭代方法,还需要好好学习和思考。

解法1:迭代

朴素解法,思路就是让cur指向2,然后再令2指向1,1再指向3,在此过程中由于指针的改变必须按照这个顺序进行,就会导致后面的节点丢失,为了防止出现这种情况,我们就要提前存储后面两个节点。有理有据,行云流水,令人拍案。

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* dummyHead=new ListNode(0);
        dummyHead->next=head;
        ListNode* cur=dummyHead;
        while(cur->next!=nullptr && cur->next->next!=nullptr){
            ListNode* tmp=cur->next;
            ListNode* tmp1=cur->next->next->next;

            cur->next=cur->next->next;
            cur->next->next=tmp;
            cur->next->next->next=tmp1;

            cur=cur->next->next;
        }
        return dummyHead->next;
    }
};

解法2:递归

链表用递归到底是哪些神人想出来的,简洁的代码却内藏玄机,搞明白思路之后更是佩服的五体投地orz。

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        if(head==nullptr || head->next==nullptr) return head;
        ListNode* cur=head->next;
        ListNode* tmp=head->next->next;
        cur->next=head;
        head->next=swapPairs(tmp);
        return cur;
    }
};

LeetCode 19.删除链表的倒数第N个节点【双指针/递归】

感受我的痛苦☹,这道题的想法很精妙,但是细节也很多,有很多需要特殊考虑的地方,否则就会出现空指针异常。

解法1:双指针

首先这个找到倒数第n个节点的方法就很精妙:用快慢两个指针,快指针先走n步,然后快慢指针一起走,当快指针走到末尾时,慢指针恰好走到倒数第n的节点。因为要删除这个节点,就要找到这个节点前一个节点,所以在快指针走的时候,要少走一步,当快指针的next为空时就要停止。
但这也带来了问题,因为我们要多向前看一步,就必须保证快指针不为空,所以当链表为空,或者链表仅有一个元素时,都要提前判断,返回空指针。另外,如果要删除的是第一个元素节点,也要特殊判断,因为这会导致快指针提前走到末尾,导致空指针异常,所以这个也要提前判断,返回head->next即可。

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        if(head==nullptr || head->next==nullptr) return nullptr;
        ListNode* cur=head;
        ListNode* tmp=head;
        while(n--) tmp=tmp->next;
        if(tmp==nullptr) return head->next;
        while(tmp->next!=nullptr){
            tmp=tmp->next;
            cur=cur->next;
        }
        cur->next=cur->next->next;
        return head;
    }
};

解法2:递归

递归回溯,好好看,好好学。感觉开启了新大陆。

class Solution {
public:
    int cur=0;
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        if(head==nullptr) return NULL;
        head->next=removeNthFromEnd(head->next,n);
        cur++;
        if(n==cur) return head->next;
        return head;
    }
};

面试题02.07 LeetCode 160.相交链表【链表】

这道题还是很有研究的价值的,两个链表的交点并不是数值相等就是交点,需要指针相等才行。

解法1:统一起跑线

如果链表A和B有交点,那么交点之后的链表重合,所以未重合部分的长度的差值就等于两个链表长度的差值。我们让两指针按差值一前一后前进,一定会在交点指针重合,此时我们就找到交点了。

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode* curA=headA;
        ListNode* curB=headB;
        int lenA=0,lenB=0;
        while(curA!=NULL){
            lenA++;
            curA=curA->next;
        }
        while(curB!=NULL){
            lenB++;
            curB=curB->next;
        }
        curA=headA;
        curB=headB;
        if(lenB>lenA){
            swap(lenA,lenB);
            swap(curA,curB);
        }
        int gap=lenA-lenB;
        while(gap--) curA=curA->next;
        while(curA!=NULL){
            if(curA==curB) return curA;
            curA=curA->next;
            curB=curB->next;
        }
        return NULL;
    }
};

解法2:走你曾走过的路

与前一种方法思路类似,既然两个链表的长度存在差值,那我们就让两个指针同时从各自的头结点出发,A如果走到链表末尾,就从B链表的开头继续走,同理B走到末尾也从A的开头继续走,当长链表走到短链表头的时候,长度差就消除了,我们继续走,一定会走到指针重合的相交点。

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if(!headA || !headB) return NULL;
        ListNode* curA=headA;
        ListNode* curB=headB;
        while(curA!=curB){
            curA=curA?curA->next:headB;
            curB=curB?curB->next:headA;
        }
        return curA;
    }
};

LeetCode 142.环形链表II【哈希表/双指针(数学题)】

又是一道非常经典的题目,当前进的路开始转圈,我们怎样才能找到离开循环的出口呢?

解法1:快慢指针

快指针一次走两步,慢指针一次走一步,两个指针一定会在环里相遇。然后就是一道数学问题。
来源:代码随想录,卡神牛!
假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入口节点节点数为 z。 如图所示:
请添加图片描述
那么相遇时: 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): x + y = n (y + z)
因为要找环形的入口,那么要求的是x,因为x表示 头结点到 环形入口节点的的距离。
所以要求x ,将x单独放在左面:x = n (y + z) - y ,
再从n(y+z)中提出一个 (y+z)来,整理公式之后为如下公式:x = (n - 1) (y + z) + z 注意这里n一定是大于等于1的,因为 fast指针至少要多走一圈才能相遇slow指针。
这个公式说明什么呢?
先拿n为1的情况来举例,意味着fast指针在环形里转了一圈之后,就遇到了 slow指针了。
当 n为1的时候,公式就化解为 x = z,
这就意味着,从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点。
也就是在相遇节点处,定义一个指针index1,在头结点处定一个指针index2。
让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;
        while(fast && fast->next){
            fast=fast->next->next;
            slow=slow->next;
            if(slow==fast){
                ListNode* index1=fast;
                ListNode* index2=head;
                while(index1!=index2){
                    index1=index1->next;
                    index2=index2->next;
                }
                return index1;
            }
        }
        return nullptr;
    }
};

解法2:哈希表

直观的思路就是把链表上的节点存到哈希表中记录下来,如果我们遇到了先前存过的节点,那就是遇到了环.

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        unordered_set<ListNode*> visited;
        while(head){
            if(visited.count(head))
                return head;
            visited.insert(head);
            head=head->next;
        }
        return nullptr;
    }
};

难度上来了,需要好好研究了。

  • 8
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值