代码随想录算法训练营第四天| 24.两两交换、19.删除链表的倒数第N个结点、面试题02.07.链表相交

代码随想录算法训练营第四天| 24.两两交换、19.删除链表的倒数第N个结点、面试题02.07.链表相交

24.两两交换

题目链接:https://leetcode.cn/problems/remove-nth-node-from-end-of-list/description/

一开始土木就理解错了,我以为的两两交换,是1与2换,1再与3换…(感觉有点像冒泡。通过看卡哥的视频讲解,其实应该是1和2换,3和4换,5和6换,这样两个为一组进行交换。具体代码如下:

ListNode* swapPairs(ListNode* head) {
    ListNode*dummyhead =new ListNode();
    dummyhead->next = head;
    ListNode*cur=dummyhead;
    while(cur->next!=NULL&&cur->next->next!=NULL)
    {
        ListNode*temp = cur->next;
        ListNode*temp1 = cur->next->next->next;
        cur->next = cur->next->next;
        cur->next->next = temp;
        temp->next = temp1;
        cur=cur->next->next;
    }
    ListNode*result=dummyhead->next;
    delete dummyhead;
    return result;
    }

这题需要注意的点有很多:

首先,要使用虚拟头结点(必会)的方法,可以大大简化逻辑过程。

其次,和反转链表的解题思想类似,我们需要设置temp指针来指向被切断后无法定位的结点。由于在一次循环中,存在两次的切断定位操作,所以需要使用两个临时指针,即temp、temp1指针。

最后,对于循环条件的判断:要分奇偶来讨论,经过画图发现,当偶数个时,cur->nextNULL时循环停止;当奇数个时,cur->next->nextNULL时循环停止。所以循环条件为cur->next!=NULL&&cur->next->next!=NULL。

注意!!cur->next!=NULL&&cur->next->next!=NULL的顺序不可变!如果改变顺序,会造成空指针异常因为cur->next可能是空指针,这个时候cur->next->next就是对空指针取next,必然是错的

最后的最后,注意cur=cur->next->next,而不是cur->next->next=cur,联想p=p->next

19.删除链表的倒数第N个结点

题目链接:https://leetcode.cn/problems/remove-nth-node-from-end-of-list/description/

1.直接法(出错)

一眼看到这道题的时候,其实我是有思路的:先遍历一遍得到链表的长度length,然后用length-n得到倒数第N个结点的前一个结点…但是转念一想,这个解法又没啥技术含量,肯定不是最优解。在代码中需要遍历两次,边界条件比较容易出错。代码如下(有问题,不能运行,但边界条件正确):

ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode*p=head;
        int count=1;
        while(p->next!=NULL)
        {
        p=p->next;//p为尾结点
        count++;
        }
        int count1=1;
        ListNode*q=head;
        for(;count1<count-n;q=q->next)
        {
            count1++;
        } 
        ListNode*k=q->next;
        q->next=q->next->next;
        delete k;
        return head;

(上面的代码没有用到虚拟头结点,却没有分类讨论,肯定是错的…)

2.双指针法

准备工作:

while(n–)的语法特点

  1. 对于while(n),当n为0时,while循环跳出;而对于while(n–),当n为0时,while循环同样跳出,但是有两点不同:一是while(n–)是先看原始的n是否为0,而不是n–之后的结果。二是当n为0时,while(n–)循环跳出,但是n–仍然继续执行。(即原本n为0,经过while(n–)之后,n为-1)
  2. while(n–)在逻辑上表现为:将循环体执行n次
正式解题:

代码如下:

 ListNode* removeNthFromEnd(ListNode* head, int n) {
            ListNode*dummyhead =new ListNode;
            dummyhead->next=head;
            ListNode*fast=dummyhead;
            ListNode*slow=dummyhead;
            n++;
            while(n--&&fast!=nullptr)
                fast=fast->next;
            while(fast!=nullptr)
            {
                fast=fast->next;
                slow=slow->next;
            }
            ListNode*k=slow->next;
            slow->next=slow->next->next;
            delete k;
            return dummyhead->next;
        }

养成一个好习惯:但凡涉及到链表的操作问题,我们首先都要想到虚拟头结点

这道题的解题技巧非常之妙:设置快慢两个指针。之前讲过的快慢指针是指在范围上或者在速度上,两个指针不一样;这里的快慢指针是指,快指针先走几步,然后快慢指针再一起走相同点都是快指针要走的更多,慢指针要走得更少

总体思路:先让fast指针跑n+1步,然后fast和slow指针一起跑,等fast指针为null之后,slow指针还有n+1步没跑,也就是说,slow指针此时指向的是倒数第n+1个结点,可由此删除倒数第n个结点。

面试题02.07.链表相交

题目链接:https://leetcode.cn/problems/intersection-of-two-linked-lists-lcci/

1.暴力解法

用两个while循环解决问题,代码如下:

ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
            ListNode*dummy1=new ListNode();
            ListNode*dummy2=new ListNode();
            dummy1->next=headA;
            dummy2->next=headB;
            ListNode*A=dummy1;
            bool flag=false;
            while(A!=NULL)
            {
            ListNode*B=dummy2;
                while(B!=NULL)
                {
                    if(A->next==B->next)
                    {
                    flag=true;
                    goto mark;
                    }
                    else
                    B=B->next;
                }
                A=A->next;
            }
            mark:
            if(flag)
            return A->next;
            else
            return NULL;
        }

暴力解法难点:对于A和B,分别需要设置一个虚拟头结点

补充的知识:

  1. 判断两个指针p1和p2是否相等,直接上 if(p1==p2)即可(上文的代码里就是if(A->next==B->next)来判断指针所指的地址是否相等)
  2. 在C++中,想跳出多个循环不能用break,而是用goto,具体方法见上述代码。

2.尾对齐法

我自己写的代码有太多地方需要改进的了,我直接放卡哥的代码吧(跟我不一样的地方我会注释出来):

ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode* curA = headA;
        ListNode* curB = headB;
        int lenA = 0, lenB = 0;
        while (curA != NULL) { // 求链表A的长度
            lenA++;
            curA = curA->next;
        }
        while (curB != NULL) { // 求链表B的长度
            lenB++;
            curB = curB->next;
        }
        curA = headA;
        curB = headB;
        // 让curA为最长链表的头,lenA为其长度
        if (lenB > lenA) {
            swap (lenA, lenB);
            swap (curA, curB);
        }
        // 求长度差
        int gap = lenA - lenB;
        // 让curA和curB在同一起点上(末尾位置对齐)
        while (gap--) {
            curA = curA->next;
        }
        // 遍历curA 和 curB,遇到相同则直接返回
        while (curA != NULL) {
            if (curA == curB) {
                return curA;
            }
            curA = curA->next;
            curB = curB->next;
        }
        return NULL;
    }

总体思路:让来链表A和B尾对齐,然后调整长链指针的位置,使得两个指针从相同位置向后遍历。

其实思路不难,难点其实就是一些具体操作的细节:

1.

curA = headA;
curB = headB;

​ 将原本尾部的指针重新回到原点(我原本的方法是再设一对指针,其实完全没必要)

2.

while (gap–)
curA = curA->next;

​ 用了一个while(n–),非常巧妙,这样gap的值直接决定了curA的移动次数(我原本的方法是用for循环,费力不讨好)

3. swap()函数的使用,确实省心不少,让代码变得非常简洁

  • 6
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值