代码随想录day4|24. 两两交换链表中的节点、19.删除链表的倒数第N个节点、面试题 02.07. 链表相交 、142.环形链表II、总结

代码随想录day4|24. 两两交换链表中的节点、19.删除链表的倒数第N个节点、面试题 02.07. 链表相交 、142.环形链表II、总结

  1. 两两交换链表中的节点
    24. 两两交换链表中的节点
    给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

在这里插入图片描述

示例 1:

输入:head = [1,2,3,4]
输出:[2,1,4,3]
示例 2:

输入:head = []
输出:[]
示例 3:

输入:head = [1]
输出:[1]

提示:

链表中节点的数目在范围 [0, 100] 内
0 <= Node.val <= 100
这个之前也做过 但是不太记得具体要注意什么了
目前思路:数组为空和数组为1和数组为奇数时候怎么交换
数组为空 返回空
数组为1 返回数组
数组为奇数:1.怎么知道:rightIndex->next->next是nullptr(不对),应该是cur->next->next==nullptr的时候不执行
2.怎么处理:不用交换了 直接退出循环 返回head

写到最后的时候出现了一个问题 dummyhead怎么指向新的head
并且左右指针换了之后 是右左指针 再往后移动下一次 就不能用rightIndex->next->next判断了

while(rightIndex->next->next){
            ListNode* temp=rightIndex->next;
            rightIndex->next=leftIndex;
            leftIndex->next=temp;
            delete temp;
            
            leftIndex=leftIndex->next;
            rightIndex=rightIndex->next;
        }

不对

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* dummyhead=new ListNode(0);
        
        while(head==nullptr||head->next==nullptr) return head;
        ListNode* leftIndex=head;
        ListNode* rightIndex=head->next;
        
        ListNode* temp=rightIndex->next;
            rightIndex->next=leftIndex;
            leftIndex->next=temp;
            delete temp;
        
        while(rightIndex->next->next){
            ListNode* temp=rightIndex->next;
            rightIndex->next=leftIndex;
            leftIndex->next=temp;
            delete temp;
            
            leftIndex=leftIndex->next;
            rightIndex=rightIndex->next;
        }
        return dummyhead
        

    }
};

错误的 所以参考之前写的 应该在循环里面每次重新设置左右指针
并且发现交换指针的时候也不对 应该是个先两个大跳 再反向
什么时候退出while1.奇数2.偶数走完了(这个刚才没考虑到)

 while(cur->next!=nullptr&&cur->next->next!=nullptr){
            ListNode* leftIndex=cur->next;
            ListNode* rightIndex=cur->next->next;
            //两个大跳+反向
           cur->next=rightIndex;
           leftIndex->next=rightIndex->next;
           rightIndex->next=leftIndex;

           cur=cur->next->next;
        }

中间要是&&

19.删除链表的倒数第N个节点
19.删除链表的倒数第N个节点
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

在这里插入图片描述

示例 1:

输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
示例 2:

输入:head = [1], n = 1
输出:[]
示例 3:

输入:head = [1,2], n = 1
输出:[1]

第一次尝试

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
       //思路:双指针
       //前面的比后面的先多走个n+1个点
       ListNode* dummyhead=new ListNode(0);
       dummyhead->next=head;
       ListNode* leftIndex=new ListNode();
       ListNode* rightIndex=new ListNode();
       leftIndex=dummyhead;
       rightIndex=dummyhead;
       
       for(int i=n;i>=0;i--){
        rightIndex=rightIndex->next;
       }
       while(rightIndex->next!=nullptr){
        rightIndex=rightIndex->next;
        leftIndex=leftIndex->next;
       }
       ListNode*temp=leftIndex->next;
       leftIndex->next=temp->next;
       delete leftIndex;

       return dummyhead->next;
       
    }
};

这个之前也做过,没写对,报错是数组越界,检查一下

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
       //思路:双指针
       //前面的比后面的先多走个n个点
       ListNode* dummyhead=new ListNode(0);
       dummyhead->next=head;
       ListNode* leftIndex=dummyhead;
       ListNode* rightIndex=dummyhead;

       for(int i=n;i>0;i--){
        rightIndex=rightIndex->next;
       }
       while(rightIndex->next!=nullptr){
        rightIndex=rightIndex->next;
        leftIndex=leftIndex->next;
       }
       ListNode*temp=leftIndex->next;
       leftIndex->next=temp->next;
       delete temp;

       return dummyhead->next;

    }
};

有两个地方写错了,一个是右面的点要比左面多走n个,一个是delete错了
参考一下之前写的

//ListNode *dummy=head;
        ListNode *dummy=new ListNode(0,head);//定义一个值为0,下一个节点指向head
        ListNode *h1=dummy;
        ListNode *h2=dummy;
        //ListNode *h1=head;
        //ListNode *h2=head;
       //思路对了,但是有点小瑕疵
       //双指针同时前进,先让右指针先走n步,然后左右指针同时前进
       //由于要删节点,所以保证左指针再要删的指针前面一个位置时停止前进
       while(n)
       {
            h2=h2->next;
            n--;
       }
       while(h2->next)//关键点:保证左指针再要删的指针前面一个位置时
       {
            h1=h1->next;
            h2=h2->next;
       }
       //ListNode* temp=h1->next;
       h1->next=h1->next->next;
       return dummy->next;//为了返回头指针,定义dummy指针

经验:可以利用while(),()内的值不为零的时候执行
并且可以不用delete方便一点书写

142.环形链表II

142.环形链表II
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

不允许修改 链表。

示例 1:

在这里插入图片描述

输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:

在这里插入图片描述

输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:

在这里插入图片描述

输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。

提示:

链表中节点的数目范围在范围 [0, 104] 内
-105 <= Node.val <= 105
pos 的值为 -1 或者链表中的一个有效索引

进阶:你是否可以使用 O(1) 空间解决此题?
之前做过 把看到过的节点都保存到unordered_map里
然后新看到的如果在里面找到了就是那个入口
(?为什么我之前用的是unordered_set 哦哦哦 查了文心一言 确实应该用unordered_set
用途:unordered_map 用于存储键值对,而 unordered_set 用于存储唯一元素。
数据类型:unordered_map 存储的是 pair<const Key, T> 类型的元素,而 unordered_set 存储的是单一的元素类型。
操作:两者有共同的成员函数,但由于存储的数据类型不同,一些操作(如通过键访问值)在 unordered_map 中可用,在 unordered_set 中则不可用。
内部实现:都基于哈希表,但具体实现细节可能因存储的数据类型而异。)

错误:对于初始化us已经忘了

unordered_set<ListNode*> seen;

并且unordered_set里有什么函数也忘了

  • insert
  • erase
  • find
  • count
  • size
  • empty
    unordered_map里有
  • ()
  • at(键)
  • operator
  • empty
  • size
  • insert
  • erase
  • find
  • count
  • clear
  • begin
  • end
auto result = myMap.insert(std::make_pair("apple", 100));

修改后的结果:

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

之前写的:

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
	unordered_set<ListNode*> seen;//和数组的区别就是成员变成了指针
        while(head!=nullptr)
        {
            if(seen.count(head))
            {
                return head;//返回该指针
            }
            seen.insert(head);
            head=head->next;

        }
        return nullptr;
        }
};

面试题 02.07. 链表相交

面试题 02.07. 链表相交
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。

图示两个链表在节点 c1 开始相交:

在这里插入图片描述

题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构 。

示例 1:

在这里插入图片描述

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at ‘8’
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
示例 2:

在这里插入图片描述

输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Intersected at ‘2’
解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。
在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。
示例 3:

在这里插入图片描述

输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。
由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
这两个链表不相交,因此返回 null 。

提示:

listA 中节点数目为 m
listB 中节点数目为 n
0 <= m, n <= 3 * 104
1 <= Node.val <= 105
0 <= skipA <= m
0 <= skipB <= n
如果 listA 和 listB 没有交点,intersectVal 为 0
如果 listA 和 listB 有交点,intersectVal == listA[skipA + 1] == listB[skipB + 1]

进阶:你能否设计一个时间复杂度 O(n) 、仅用 O(1) 内存的解决方案?

感觉熟悉
思路大概是两个人从两个链表起点走 然后走到终点再去对向链表的起点 然后走
如果四轮还没相遇 就没有交点 额好像不是这个
这个目前思路是走过的节点放进us里如果两个都到终点了没有重复就说明没交点

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode* left=headA;
        ListNode* right=headB;
        unordered_set<ListNode*> seen;
        while(left!=nullptr&&right!=nullptr){
            if(seen.count(left)&&left!=nullptr) return left;
            if(left!=nullptr){
                seen.insert(left);
                left=left->next;
            }
            if(seen.count(right)&&right!=nullptr) return right;
            if(right!=nullptr){
                seen.insert(right);
                right=right->next;
            }
        }
        return nullptr;
    }
};

额 我这个运行之后结果有对有错 不知道为什么
文心一言给出的两种方法挺简约的
carl给的感觉太长了不想看()

文心一言
您的代码中有几个逻辑上的问题,导致它不能正确地找到两个链表的相交点。首先,您的代码试图在遍历过程中同时检查 left 和 right 是否在 seen 集合中,但这种检查方式是不必要的,并且可能导致逻辑上的混淆。其次,您的代码在遍历完一个链表后没有继续遍历另一个链表直到结束,这是找到相交节点的关键步骤。
当两个链表相交时,它们从相交点开始到链表末尾的部分是完全相同的。如果我们将两个链表连接起来(即当遍历完一个链表时,从另一个链表的头部开始继续遍历),那么相交点就会在新的“长链表”中出现两次。这就是我们可以利用哈希集合来找到相交点的基本原理。
以下是修改后的代码,可以正确地找到两个链表的相交点:

#include <unordered_set>  
  
struct ListNode {  
    int val;  
    ListNode *next;  
    ListNode(int x) : val(x), next(nullptr) {}  
};  
  
class Solution {  
public:  
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {  
        if (!headA || !headB) return nullptr;  
  
        std::unordered_set<ListNode*> seen;  
        ListNode* current = headA;  
          
        // 遍历链表A,并将所有节点加入哈希集合  
        while (current) {  
            seen.insert(current);  
            current = current->next;  
        }  
  
        // 遍历链表B,查找是否有节点在哈希集合中  
        current = headB;  
        while (current) {  
            if (seen.count(current)) {  
                return current; // 找到相交点  
            }  
            current = current->next;  
        }  
  
        return nullptr; // 没有相交点  
    }  
};

但是,如果您想利用双指针法(而不是哈希集合)来找到相交点,可以这样做:

class Solution {  
public:  
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {  
        if (!headA || !headB) return nullptr;  
  
        ListNode* pA = headA, *pB = headB;  
  
        // 遍历两个链表,直到它们相等或都为nullptr  
        while (pA != pB) {  
            pA = pA ? pA->next : headB; // 如果pA为空,则从headB开始  
            pB = pB ? pB->next : headA; // 如果pB为空,则从headA开始  
        }  
  
        return pA; // 当它们相等时,它们要么都是nullptr(没有相交点),要么指向相交点  
    }  
};

这种方法利用了两个链表在相交点之后会完全相同的特性,以及如果它们不相交,那么当我们将一个链表遍历到末尾时,从头开始遍历另一个链表,两个指针最终会在相交点(如果存在)相遇,或者同时到达末尾(如果不存在相交点)。

啊啊啊 soga刚才觉得熟悉 但是没深想
总结一下他说的 就是
如果存在相交点:最终会在相交点相遇
如果不存在相交点:同时到达末尾
(?)为啥
!!! 因为假设1路径一共长度为a
2路径一共长度为b
交点之后的路径长度为c

A从1路径出发,B从2路径出发
当两者同时走了a+b-c长度的时候(中间经历了一次换路径)一定会相遇
啊啊啊好聪明((〃>皿<))

耶 并且看出来我那个为啥不对 这里应该是|| 我把他取名为双指针哈希表结合法()

while(left!=nullptr||right!=nullptr)

并且我对这里刚才有点小误解
pA = pA ? pA->next : headB; // 如果pA为空,则从headB开始 这里为什么pa为空的时候pa不等于pa呢

实际上,在 C++ 中,pA = pA ? pA->next : headB; 这行代码并不是在检查 pA 是否“为空”,而是在使用条件运算符(也称为三元运算符)来根据 pA 的值(即 pA 是否为 nullptr)来决定 pA 应该被赋予什么新值。
这里的 pA ? pA->next : headB 表达式是这样工作的:
如果 pA 不是 nullptr(即 pA 是真的,或者说 pA 指向了一个有效的内存地址),那么表达式的结果是 pA->next,即 pA 的下一个节点。
如果 pA 是 nullptr(即 pA 是假的,因为它不指向任何有效的内存地址),那么表达式的结果是 headB。
然后,这个结果(无论是 pA->next 还是 headB)被赋值给 pA。
所以,当说“如果 pA 为空,则从 headB 开始”时,我们实际上是在说:“如果 pA 是 nullptr,则让 pA 指向 headB,这样我们就可以从 headB 开始遍历链表 B”。
关于“为什么 pa 为空的时候 pa 不等于 pa”的疑问,这里有一个误解。pA(或任何指针)在 C++ 中是一个变量,它存储了一个内存地址。当 pA 为空时,它存储的是 nullptr(或在某些旧代码中可能是 0 或 NULL,但现在推荐使用 nullptr)。这里的“等于”或“不等于”是指针与其所存储的值(即内存地址)之间的比较,而不是指针与其自身(作为变量)之间的比较。
在条件表达式 pA ? pA->next : headB 中,我们并没有在比较 pA 与其自身;我们只是在根据 pA 是否为 nullptr 来选择两个不同的值之一。

其实是没看着那个等号()

总结

carl的总结 其实已经有点点小忘了具体的题是咋做的了()
总结
链表总结篇
#链表的理论基础
在这篇文章关于链表,你该了解这些! (opens new window)中,介绍了如下几点:

链表的种类主要为:单链表,双链表,循环链表
链表的存储方式:链表的节点在内存中是分散存储的,通过指针连在一起。
链表是如何进行增删改查的。
数组和链表在不同场景下的性能分析。
可以说把链表基础的知识都概括了,但又不像教科书那样的繁琐。

#链表经典题目
#虚拟头结点
在链表:听说用虚拟头节点会方便很多? (opens new window)中,我们讲解了链表操作中一个非常重要的技巧:虚拟头节点。

链表的一大问题就是操作当前节点必须要找前一个节点才能操作。这就造成了,头结点的尴尬,因为头结点没有前一个节点了。

每次对应头结点的情况都要单独处理,所以使用虚拟头结点的技巧,就可以解决这个问题。

在链表:听说用虚拟头节点会方便很多? (opens new window)中,我给出了用虚拟头结点和没用虚拟头结点的代码,大家对比一下就会发现,使用虚拟头结点的好处。

#链表的基本操作
在链表:一道题目考察了常见的五个操作! (opens new window)中,我们通过设计链表把链表常见的五个操作练习了一遍。

这是练习链表基础操作的非常好的一道题目,考察了:

获取链表第index个节点的数值
在链表的最前面插入一个节点
在链表的最后面插入一个节点
在链表第index个节点前面插入一个节点
删除链表的第index个节点的数值
可以说把这道题目做了,链表基本操作就OK了,再也不用担心链表增删改查整不明白了。

这里我依然使用了虚拟头结点的技巧,大家复习的时候,可以去看一下代码。

#反转链表
在链表:听说过两天反转链表又写不出来了? (opens new window)中,讲解了如何反转链表。

因为反转链表的代码相对简单,有的同学可能直接背下来了,但一写还是容易出问题。

反转链表是面试中高频题目,很考察面试者对链表操作的熟练程度。

我在文章 (opens new window)中,给出了两种反转的方式,迭代法和递归法。

建议大家先学透迭代法,然后再看递归法,因为递归法比较绕,如果迭代还写不明白,递归基本也写不明白了。

可以先通过迭代法,彻底弄清楚链表反转的过程!

#删除倒数第N个节点
在链表:删除链表倒数第N个节点,怎么删? (opens new window)中我们结合虚拟头结点 和 双指针法来移除链表倒数第N个节点。

#链表相交
链表:链表相交 (opens new window)使用双指针来找到两个链表的交点(引用完全相同,即:内存地址完全相同的交点)

#环形链表
在链表:环找到了,那入口呢? (opens new window)中,讲解了在链表如何找环,以及如何找环的入口位置。

这道题目可以说是链表的比较难的题目了。 但代码却十分简洁,主要在于一些数学证明。
总结
在这里插入图片描述
这个图是 代码随想录知识星球 (opens new window)成员:海螺人 (opens new window),所画,总结的非常好,分享给大家。

考察链表的操作其实就是考察指针的操作,是面试中的常见类型。

链表篇中开头介绍链表理论知识 (opens new window),然后分别通过经典题目介绍了如下知识点:

关于链表,你该了解这些!(opens new window)
虚拟头结点的技巧(opens new window)
链表的增删改查(opens new window)
反转一个链表(opens new window)
删除倒数第N个节点(opens new window)
链表相交(opens new window)
有否环形,以及环的入口

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值