代码随想录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)
有否环形,以及环的入口

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值