首页 > 编程语言 >代码随想录算法day4 - 链表2

代码随想录算法day4 - 链表2

时间:2024-08-30 23:47:12浏览次数:7  
标签:head ListNode nullptr day4 随想录 next 链表 节点

题目1 24. 两两交换链表中的节点

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

示例 1:

img

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

示例 2:

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

示例 3:

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

提示:

  • 链表中节点的数目在范围 [0, 100]
  • 0 <= Node.val <= 100

思路

无虚拟头

这道题中每次交换两个节点时要改变三个指针的指向,所以我们需要三个变量来记录这三个相邻节点。在第一次交换节点(在满足节点数量>=2)时需要注意只需要更改头节点和后一个节点就行了,第一次交换节点只需要两个节点,之后的交换才需要三个节点。

代码

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        if(head == nullptr)
        {
            return head;
        }
        ListNode* preNode = head;
        //如果节点有两个以上,则移动头指针到第二个节点
        if(head->next != nullptr)
        {
            head = head->next;
        }
        else
        {
            return head;
        }
        ListNode* ppreNode = nullptr;
        ListNode* curNode = preNode->next;
        for(int i = 0; curNode != nullptr; i++)
        {
            //i % 2 == 0判断表示隔一个节点后进行两两交换
            if(i % 2 == 0)
            {
                ListNode* tmpNode = curNode->next;
                curNode->next = preNode;
                preNode->next = tmpNode;
                if(ppreNode != nullptr)
                    ppreNode->next = curNode;
                curNode = tmpNode;
            }
            else
            {
                ppreNode = preNode;
                preNode = curNode;
                curNode = curNode->next;
            }
        }
        return head;
    }
};

有虚拟头

有虚拟头的情况比无虚拟头简单,因为不用额外考虑第一次只修改两次指针的情况,所有交换的情况都是三个指针进行操作。要注意的就是在实际项目中要在返回前释放掉虚拟头节点的内存,避免内存泄漏。

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        if(head == nullptr || head->next == nullptr)
        {
            return head;
        }
        ListNode* dumNode = new ListNode(-1, head);
        ListNode* ppreNode = dumNode,
                * preNode  = dumNode->next,
                * curNode  = preNode->next;
        while(curNode != nullptr)
        {
            ppreNode->next = curNode;
            ListNode* tmpNode = curNode->next;
            curNode->next = preNode;
            preNode->next = tmpNode;
            if(tmpNode != nullptr && tmpNode->next != nullptr)
            {
                ppreNode = preNode;
                preNode = tmpNode;
                curNode = tmpNode->next;
            }
            else
            {
                break;
            }
        }
        head = dumNode->next;
        delete dumNode;
        return head;
    }
};

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

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

示例 1:

img

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

示例 2:

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

示例 3:

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

提示:

  • 链表中结点的数目为 sz
  • 1 <= sz <= 30
  • 0 <= Node.val <= 100
  • 1 <= n <= sz

思路

暴力迭代法(两轮遍历)

暴力迭代法通过遍历两轮链表来删除倒数第n个节点,第一轮用来计算链表的节点总数,第二轮用于命中目标节点与前一个节点来进行删除操作。

额外指针数组(一轮遍历)

因为题目中给出了结点的数量范围,因此创建一个固定大小的listNode指针数组,用一轮遍历统计结点总数并将结点按序放入到数组中。之后通过计算能命中倒数第n个节点的位置,最终通过指针数组来删除指针。

代码

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        if(head == nullptr)
        {
            return head;
        }
        ListNode* curNode = new ListNode(0, head);
        ListNode* lst[32];
        lst[0] = curNode;
        int num = 0;
        for(; curNode != nullptr; num++)
        {
            curNode = curNode->next;
            lst[num + 1] = curNode;
        }
        //goal + n = num - > goal = num - n
        //num - n为要删除的节点位置,对其-1和+1为前和后结点
        lst[num - n - 1]->next = lst[num - n + 1];
        delete lst[num - n];
        head = lst[0]->next;
        delete lst[0];
        return head;
    }
};

双指针法

假设链表长度是n(n > 0),要删除的结点时倒数第y( 0 < y <= n )个,左指针的位置为lft,右指针的位置为rht,在链表前用额外的结点(dummyNode)指向链表头。让lft和rht的初始位置都为dummyNode,先让rht向后面的结点移动y次,此时lft和rht的正好相差y个结点。之后同时让lft和rht向后面结点移动,直到rht指向为空终止,此时lft对应的指针指向的正好是倒数第y个结点。因此如果想删除第n个结点就需要倒数第n+1个结点的位置,代码也因此能写出来。

代码

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummyNode = new ListNode(0, head);
        ListNode* lft = dummyNode,
                * rht = dummyNode;
        for(int i = 0; i <= n; i++)
        {
            rht = rht->next;
        }
        while(rht != nullptr)
        {
            lft = lft->next;
            rht = rht->next;
        }
        lft->next = lft->next->next;
        head = dummyNode->next;
        delete dummyNode;
        return head;
    }
};

题目3 07. 链表相交

给你两个单链表的头节点 headAheadB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null

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

img

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

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

示例 1:

img

输入: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:

img

输入: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:

img

输入: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
  • 如果 listAlistB 没有交点,intersectVal0
  • 如果 listAlistB 有交点,intersectVal == listA[skipA + 1] == listB[skipB + 1]

思路

这道题只要将两个链表的尾部对齐就好做了,首先判断两个链表的长短并在第一次遍历结束后通过最后一个结点判断两链表是否相交,若相交则用指针指向长链表的子链表结点,使得子链表长度和另一个短链表长度一致,最后一一比较就找到了相交的结点。

代码

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if(headA == nullptr || headB == nullptr)
        {
            return nullptr;
        }
        ListNode* aNode = headA,
                * bNode = headB;
        int lenA = 0, lenB = 0;
        while(aNode->next != nullptr)
        {
            aNode = aNode->next;
            lenA++;
        }
        while(bNode->next != nullptr)
        {
            bNode = bNode->next;
            lenB++;
        }
        if(aNode != bNode)
        {
            return nullptr;
        }
        int len;
#define FIND_UNODE(LLIST, SLIST, LLEN, SLEN, LEN)\
        LEN = LLEN - SLEN;                       \
        while(LEN--){                            \
            LLIST = LLIST->next;                 \
        }                                        \
        while(LLIST != SLIST){                   \
            LLIST= LLIST->next;                  \
            SLIST = SLIST->next;                 \
        }                  
        if(lenA > lenB)
        {
            FIND_UNODE(headA, headB, lenA, lenB, len);
        }
        else
        {
            FIND_UNODE(headB, headA, lenB, lenA, len);
        }
#undef FIND_UNODE
        return headA;
    }
};

题目4 142. 环形链表 II

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

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

不允许修改 链表。

示例 1:

img

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

示例 2:

img

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

示例 3:

img

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

提示:

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

进阶:你是否可以使用 O(1) 空间解决此题?

思路

双指针

这道题更像是数学计算题。。。利用双指针中快指针的一次多步和慢指针的一次一步来遍历整个链表,直到两个指针相遇,最后在通过新指针和快指针的进一步遍历及相遇来确定环的入口。

代码

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        if(head == nullptr)
        {
            return head;
        }
        ListNode* fast = head,
                * slow = head;
        while(fast->next != nullptr && fast->next->next != nullptr)
        {
            fast = fast->next->next;
            slow = slow->next;
            if(slow == fast)
            {
                ListNode* markNode = head;
                while(markNode != fast)
                {
                    markNode = markNode->next;
                    fast = fast->next;
                }
                return markNode;
            }
        }
        return nullptr;
    }
};

set集合筛选法

众所周知,在C++里有set模板类可以筛除重复的元素,我们用这个类保存链表里的所有结点,并对每一次插值前和插值后比较set对象的总数,若set对象总数没发生变化就说明已经进入环入口。使用这种方法要额外消耗内存,但在接近最坏的情况时(即链表非常长,环也很大的情况下,用这种方法能节省最后双指针法寻找遍历的时间)。

代码

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        if(head == nullptr)
        {
            return head;
        }
        set<ListNode*> roundSet;
        int preNum = INT_MAX;
        while(head != nullptr)
        {
            preNum = roundSet.size();
            roundSet.insert(head);
            if(preNum == roundSet.size())
                break;
            head = head->next;
        }
        return head;
    }
};

标签:head,ListNode,nullptr,day4,随想录,next,链表,节点
From: https://www.cnblogs.com/code4log/p/18389679

相关文章

  • list容器---深入探索STL中的双向链表
    目录一、引言二、list容器原理三、list容器的常用操作  1.创建list容器  2.添加元素  3.删除元素  4.访问元素  5.遍历list容器四、list容器的优缺点五、实际应用场景六、总结        本文将详细介绍C++STL中的list容器,包括其原理、常用......
  • 代码随想录算法训练营,8月30日 | 203.移除链表元素, 707.设计链表, 206.反转链表
    链表理论基础1.单链表:数据域和指针域(指向下一个结点的位置)组成,头结点,结尾为空指针;双链表:多了一个指向前一个结点的指针;循环链表:结尾指向头结点。2.链表在内存中的存储不是顺序的,跟数组不同,要找一个数据只能通过前一个数据来找,所有这就导致链表的查询比数组麻烦,但是插入删除数据......
  • 「代码随想录算法训练营」第四十九天 | 图论 part7
    目录最小生成树的解题prim算法举例说明(来自代码随想录)题目:53.寻宝Kruskal算法举例说明(来自代码随想录)题目:53.寻宝最小生成树的解题最小生成树类型的题目主要用于解决所有节点的最小连通子图的问题,即:以最小的成本(边的权值)将图中所有节点链接到一起。最小生成树可以使用prim算......
  • 顺序表和链表知识点
    1顺序表顺序表是指用一段物理地址连续的空间去存储数据的线性结构。顺序表有两种:静态顺序表,动态顺序表。1.1静态顺序表结构体定义typedefintElemDataSL;typedefstructSequeList{ ElemDataSLarr[100]; intsize;}SL;静态顺序表在创建结构体的时候就已经把......
  • 分享丨【题单】链表、二叉树与一般树(前后指针/快慢指针/DFS/BFS/直径/LCA)
    作者:灵茶山艾府链接:https://leetcode.cn/circle/discuss/K0n2gO/一、链表注:由于周赛中的链表题可以转成数组处理,难度比直接处理链表低,故不标明难度分。带着问题去做下面的题目:在什么情况下,要用到哨兵节点(dummynode)?在什么情况下,循环条件要写while(node!=null)?什么情况......
  • 代码随想录day45 || 115 不同子序列, 583 两个字符串删除操作, 72 编辑距离
    115不同子序列funcnumDistinct(sstring,tstring)int{ //动态规划,思考一下判断连续和不连续的区别,如果相等都是左上角+1,如果不等,连续情况就是直接等于左上角,不连续情况直接归零 //dp[i][j]表示s[i]中存在t[j]结尾的的个数 //递推公式,不要求连续字串,所以,如果s[i......
  • 代码随想录算法day3 - 链表1
    题目1203.移除链表元素给你一个链表的头节点head和一个整数val,请你删除链表中所有满足Node.val==val的节点,并返回新的头节点。示例1:输入:head=[1,2,6,3,4,5,6],val=6输出:[1,2,3,4,5]示例2:输入:head=[],val=1输出:[]示例3:输入:head=[7,7,7,7],v......
  • 数据结构-单链表-详解-1
    数据结构-单链表-详解-11.前言2.结点3.打印3.1关于断言3.2下一结点的找法物理结构逻辑结构1.前言在数据结构-顺序表-详解中,我详细介绍了顺序表的实现,而顺序表也有一些缺点:中间,头部插入时,需整体挪动数据,效率低下。空间不够时扩容,有一定的消耗,也可能有一定空间浪费......
  • 数据结构:双向链表
    目录结构体创建链表插入链表头插法尾插法 遍历打印删除更新查找销毁结构体typedefintDataType;typedefstructnode{structnode*pPre;DataTypedata;structnode*pNext;}LinkNode;创建链表LinkNode*CreateDouList(){LinkNode......
  • 代码随想录算法训练营,29日 | 704. 二分查找,27. 移除元素,977.有序数组的平方,209.长度最
    数组基础文档讲解︰代码随想录(programmercarl.com)1.连续空间、相同类型元素2.元素只能覆盖3.二维数组的地址连续吗(C++连续,Java不连续)704.二分查找题目链接:704.二分查找文档讲解︰代码随想录(programmercarl.com)视频讲解︰二分查找日期:2024-08-29思路:第一反应是想到二分查......