首页 > 其他分享 >[代码随想录]Day04-链表part02

[代码随想录]Day04-链表part02

时间:2023-07-29 21:45:36浏览次数:51  
标签:slow ListNode part02 随想录 Next 链表 节点 指针

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

思路:

6

首先给他加一个虚拟头结点,然后思考反转的逻辑,这是每两个为一组,比如1,2是一组、3,4是一组,如果说1,2一组2,3一组就变成了链表的逆转了。

那么指针的逻辑是:

  1. 两个指针一个r指向要交换的二元组的第一个节点
  2. 一个l指向前一个节点
  3. 二元组的第二个节点直接通过r.Next来获取t

之后切换的逻辑是:

  1. l.Next指向t
  2. r.Next指向t.Next
  3. t.Next指向r

然后再把这个链表"拉直",就发现交换成功了,当然要记得再迭代新的位置:

  1. l = r
  2. r = r.Next

代码:

双指针迭代

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func swapPairs(head *ListNode) *ListNode {
    //  0 1 2 开始
    //  0->2,1->2.Next,2->1
    res := new(ListNode)
    res.Next = head // 添加虚拟头节点
    l, r := res, res.Next 
    for r != nil {
        t := r.Next
        if t == nil {  // 如果就剩下一个节点直接退出
           break
        }
        l.Next = t
        r.Next = t.Next
        t.Next = r
        l = r
        r = r.Next
    }
    return res.Next
}

参考:

代码随想录

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

思路:

最简单的方式就是遍历一遍得到链表长度,然后再根据链表的长度进行操作即可。

但是题目要求一次遍历完成删除操作,那就有文章可以做了——我们可以先派遣一个先锋,让他先走n步,这样当先锋到达结尾时,后边的节点就是要删除的节点,我们整理一下:

我们需要有删除的节点的前一个节点位置的控制权

我们准备一个pre、一个cur以及tmp = cur.Next,首先让cur先走n步,然后开始一步一步遍历,if tmp== nil的时候,cur在最后一个节点的位置,pre在要删除的节点的前一个位置,此时我们只需要让pre.Next = pre.Next.Next即可完成删除操作。

迭代:直接pre和cur都下一步就可以了,即pre = pre.Next以及cur = cur.Next

代码:

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func removeNthFromEnd(head *ListNode, n int) *ListNode {
    res := new(ListNode)
    res.Next = head // 虚拟头
    pre, cur := res, res
    for i := 1; i <= n; i++ { // 前锋先走n步
        cur = cur.Next
    }
    for cur != nil {
        tmp := cur.Next
        if tmp == nil { // 此时cur在最后一个节点处,pre在要删除的元素前一个节点
            pre.Next = pre.Next.Next
        }
        pre = pre.Next
        cur = cur.Next
    }
    return res.Next
}

参考:

代码随想录

题目:面试题 02.07. 链表相交

思路:

必须要注意的一点是,这里的相交是结构上的问题,而不是数值上的问题;是真的在链表结构上有相交的部分(指向同一个地址)。

1615224578-MRfzKN-Picture6.png

那实际上数学上的一个问题,A + B 和 B + A 同时开始走,如果有公共部分就会在最后走到一起。

当然也可以用哈希的方式来解决。

代码1:

双指针数学方法解决

func getIntersectionNode(headA, headB *ListNode) *ListNode {
    nodeA, nodeB := headA, headB
    for nodeA != nodeB { // 当nodeA和nodeB都是nil的时候就会停止(如果没有公共部分)
        if nodeA == nil {
            nodeA = headB  // 跑完A跑B
        }else {
            nodeA = nodeA.Next
        }
        if nodeB == nil {
            nodeB = headA // 跑完B跑A
        }else {
            nodeB =nodeB.Next
        }
    }
    return nodeA // 或者nodeB
}

代码2:

哈希,空间复杂度比较高

func getIntersectionNode(headA, headB *ListNode) *ListNode {
    maps := make(map[*ListNode]bool, 0)
    for headA != nil {
        maps[headA] = true
        headA = headA.Next
    }
    for headB != nil {
        if _, ok := maps[headB]; ok {
            return headB
        }
        headB = headB.Next
    }
    return nil
}

参考:

代码随想录

题目:142. 环形链表 II

思路:

还是两种方法一个哈希一个双指针,这次是一个快指针一个慢指针。

142.环形链表II(求入口).gif

假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入口节点节点数为 z。 如图所示:

20220925103433.png

那么相遇时: 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同时移动,每次移动一个节点, 那么他们相遇的地方就是 环形入口的节点。

代码:

双指针

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func detectCycle(head *ListNode) *ListNode {
    slow, fast := head, head
    for fast != nil {
        slow = slow.Next // 慢指针每次走一步
        if fast.Next == nil {
            return nil
        }
        fast = fast.Next.Next // 快指针每次走两步
        if fast == slow { // 当快指针 = 慢指针时,满指针距离节点距离 = 头部距离节点距离
            p := head
            for p != slow {
                p = p.Next
                slow = slow.Next
            }
            return p // 找到入口
        }
    }
    return nil
}

参考:

代码随想录

Krahets大神

总结:

做链表题最好是有一个虚拟头指针辅助,作甚么操作都会变得方便一点,翻转不需要。

要对链表进行操作:

  1. 首先就是想好在一次变更中,按照什么顺序进行Next的变更才能得到正确的结果,一般可以通过画图来辅助理清思路。
  2. 然后根据上述过程找到对他操作会对哪些节点产生影响,保证在遍历过程中持有对这些节点的控制权(也就是有位置指针或者能通过.Next来获得)
  3. 最后就是进行迭代时怎么获得正确的位置,以及最终的结果要返回的是哪个位置。

举个例子,24. 两两交换链表中的节点

0(虚拟头)-> 1 -> 2 -> 3 -> 4要交换的是12和34

  1. 交换节点时的第一次操作:
    1. 0的Next指向2
    2. 1的Next要改为2的Next
    3. 2的Next要改为1
  2. 那么通过上述我们知道,我们至少需要有1前面节点的控制权(因为1和2可以通过.Next获取),当然实际过程中最好是有两个节点的控制权比较清晰,即0和1的位置
  3. 当一次操作完成后现在的顺序是0-> 2-> 1-> 3-> 4,我们要对3、4进行操作,对比一下我们要把在节点0(虚拟头)位置的指针改成1所在位置,在节点1位置的指针改成3所在位置。

标签:slow,ListNode,part02,随想录,Next,链表,节点,指针
From: https://www.cnblogs.com/wtcsky/p/17590599.html

相关文章

  • 力扣---141. 环形链表
    给你一个链表的头节点 head ,判断链表中是否有环。如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从0开始)。注意:pos 不作为参数进行传递 。仅仅是为了标......
  • 双链表的插入与删除
    双链表(DoublyLinkedList)是一种链表数据结构,它与单链表相比,在每个节点上都有两个指针,一个指向前一个节点,一个指向后一个节点。这使得在双链表中的插入和删除操作更加灵活。1.双链表插入:在双链表中插入一个节点,需要先找到插入位置的前一个节点,然后通过更新指针的方式将新节点插入......
  • [代码随想录]Day03-链表part01
    题目:203.移除链表元素思路:做链表首先最好有个虚拟头指针,这样做删除添加操作的时候可以统一操作否则还得特判。那删除元素的过程,从虚拟头指针开始要做以下几步:如果当前p的next不为空,那么就可以进行判断如果p的next的值是val(↑p的next不为空↑),那么就把p的next修改为p的下......
  • 代码随想录算法训练营第三天| LeetCode 203.移除链表元素(同时也对整个单链表进行增删
    203.移除链表元素      题目链接/文章讲解/视频讲解::https://programmercarl.com/0203.%E7%A7%BB%E9%99%A4%E9%93%BE%E8%A1%A8%E5%85%83%E7%B4%A0.html    卡哥题目建议:本题最关键是要理解虚拟头结点的使用技巧,这个对链表题目很重要。   做题思路:   ......
  • 数据结构之带头节点的单链表增删改查操作实现
     单链表的定义什么是单链表   单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。   单链表的各个数据元素在物理上可以是离散存放的,每个结点除了存放数据元素外,还要存储指向下一个节点的指针。而顺序表是连续存放的,每个结点中只......
  • 链表/栈/队列/KMP
    链表用数组模拟,不同于结构体加指针调用new关键字开上万级别的节点非常慢,基本会超时单链表来构造邻接表用于存图与树基本结构:head表示头结点的下标e[i]表示节点i的值ne[i]表示节点i的下一个节点的下标idx存储当前已经用到了哪个节点,表示新节点基本操作:......
  • 代码随想录算法训练营第四十天| 300.最长递增子序列 674. 最长连续递增序列 718.
    300.最长递增子序列要求:可以删减任意个节点,最后保存最大的递增长度难点:410489如何保证全局的视角,看到很前面的节点是否大于当前的节点,而不是仅仅记录状态思路:dp[n],当子序列的末尾为N时,它的最大子序列长度也就意味着,N在它的子序列中是最大的,遍历这个N之前的所有序......
  • 代码随想录算法训练营第二天| LeetCode 977.有序数组的平方 ,209.长度最小的子数组 ,59.
    977.有序数组的平方     题目链接:https://leetcode.cn/problems/squares-of-a-sorted-array/    文章讲解:https://programmercarl.com/0977.%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E7%9A%84%E5%B9%B3%E6%96%B9.html    视频讲解: https://www.bili......
  • 单链表查找与删除
    单链表是一种常见的数据结构,它由一系列的节点组成,每个节点包含一个数据元素和一个指向下一个节点的指针。在单链表中,查找和删除节点是常见的操作。1.单链表查找:要查找单链表中的一个节点,需要从链表的头节点开始,沿着指针依次遍历每个节点,直到找到目标节点或者到达链表的末尾(即指针......
  • [代码随想录]Day02-数组part02
    题目:977.有序数组的平方思路:一开始的思路是从中间向两边扩:找到第一个大于等于0的位置r;判断nums[r]是否大于等于0,如果不是赋值为len(nums)表示不在范围内了。l的位置在r的左侧因此l=r-1只要l>=0或者r<len(nums)满足一个就可以继续;遍历时要保证数组不能越界说实话维护......