算法训练day3 | php | 24. 两两交换链表中的节点 , 19.删除链表的倒数第N个节点 , 160. 链表相交 , 142.环形链表II

一、力扣题24. 两两交换链表中的节点

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

示例 1:

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

示例 2:

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

示例 3:

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

提示:

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

        本题主要要对节点交换的顺序了解清楚,这一点很容易搞混。

        如图,在交换1,2两个节点后,1 节点要指向 3 节点,而为了统一步骤,就设置了虚拟头结点,将虚拟头结点指向 2 节点, 这样每次交换节点就都是三个步骤,不需要专门为头两个节点设置特别的步骤。

        如图,每交换两个节点就需要用到三个步骤,把步骤搞清楚就可以了。需要注意的是,在交换节点的循环里,循环条件需要先验证交换的两个节点都不为空。

        我之前步子跨太大,想直接把 1 节点指向 4 节点,这样就省了步骤 1 ,也不需要用到虚拟头结点了, 但是这样会造成空指针异常,如果这条链表只有两个节点,也就是没有 3,4节点时,节点1 ->next 为null, 那么1 ->next->next 为 null ->next,就会报错了。

        我也想过把循环条件增加为判断 <交换的两个节点和它的下一个节点> 共三个节点不为空才能进去,但是这样就有了个问题,如果一个链表只有三个节点,那这个循环就进不去,也就无法交换前两个节点了,故舍弃这个想法。

        

方法:

function swapPairs($head) {
        $cur = $head;
        $behind = $cur->next;
        if($cur == null || $behind == null) {
            return $cur;
        }
        $head = $head->next;
        $prev = new ListNode();
        while($behind != null && $cur != null) {
            $prev->next = $behind;
            $temp = $behind->next;
            $behind->next = $cur;
            $cur->next = $temp;
            $prev = $cur;
            $cur = $temp;
            $behind = $cur->next;
        }
        return $head;
    }

二、力扣题 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]

提示:

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

   方法一:暴力解

        我自己写的时候用的比较简单的方法,先遍历一遍链表,获取到链表的节点个数,然后再重新遍历一遍删除链表中的目标节点。为了能删除头结点,也用了虚拟头结点。

function removeNthFromEnd($head, $n) {
        $dummyHead = new ListNode();
        $dummyHead->next = $head;
        $cur = $head;
        $i = 0;
        while($cur->next) {
            $cur = $cur->next;
            $i++;
        }
        $j = 0;
        $cur = $dummyHead;
        while($j <= $i - $n) {
            $cur = $cur->next;
            $j++;
        }
        $cur->next = $cur->next->next;
        return $dummyHead->next;
    }

 方法二:双指针

        去看了讲解,发现双指针还能这么玩,自己对双指针的应用和了解还是不够。

        先定义两个快慢指针fast 和 slow,都放在虚拟头结点,fast 先走 n 步,然后fast 和 slow 再同时移动,当 fast 走到最后一个节点时,slow 刚好停在要删除的目标节点的上一个节点,然后就可以删除了。

        (原讲解是 fast 先走的是 n+1 步,之后当fast 指向null时,slow 刚好在目标节点的上一个节点,结果都一样,只是对fast 的判断条件不同。)

function removeNthFromEnd($head, $n) {
        $dummyHead = new ListNode();
        $dummyHead->next = $head;
        $fast = $dummyHead;
        $slow = $dummyHead;
        while($n > 0) {
            $fast = $fast->next;
            $n--;
        }
        while($fast->next) {
            $fast = $fast->next;
            $slow = $slow->next;
        }
        $slow->next = $slow->next->next;
        return $dummyHead->next;
    }

三、力扣题160. 相交链表 (面试题)

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。

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

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

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

方法一:暴力解

        一如既往的循环遍历,把其中一个链表的遍历作为外循环,另一个作为内循环,然后遍历找到相交点。时间复杂度应该是O(n * m),  这个真的好耗时,用了三千多ms的时间。

        需要注意的是,如果不同节点 headA 和 headB 的值相同,那么  headA == headB 也会成立,判断条件需要是 headA === headB 才能判断出来是不是同一个节点。

function getIntersectionNode($headA, $headB) {
        $b = $headB;
        while($headA) {
            $headB = $b;
            while($headB) {
                if($headA === $headB) {
                    return $headA;
                }
                $headB = $headB->next;
            }
            $headA = $headA->next;
        }
        return null;
    }

方法二:对齐后再一同移动

        去看了讲解发现了更精妙的解法, 感觉以后要多从数学的角度来解题目。

        先把两个链表各自遍历一遍,获取两个链表的节点个数,然后相减获得差值 n ,再把 fast 指向其中较长的链表的头结点,fast 先移动 n 步,然后再和另一条链表的头结点同时移动,遍历判断出相交点。 时间复杂度为O(n + m + z ), 比上一个方法省时很多。

function getIntersectionNode($headA, $headB) {
        $a = 0;
        $temp1 = $headA;
        $temp2 = $headB;
        while($temp1) {
            $temp1 = $temp1->next;
            $a++;
        }
        $b = 0;
        while($temp2) {
            $temp2 = $temp2->next;
            $b++;
        }

        $num = abs($a - $b);
        if($a > $b) {
            while($num > 0) {
            $headA = $headA->next;
            $num--;
            }
        } else {
            while($num > 0) {
            $headB = $headB->next;
            $num--;
            }
        }

        while($headA) {
            if($headA === $headB) {
                return $headA;
            }
            $headA = $headA->next;
            $headB = $headB->next;
        }
        return null;
    }

四、力扣题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 或者链表中的一个有效索引

方法一:暴力解

        依旧是遍历为主,将链表中的每个节点的next与这个节点之前的所有节点对比判断。

        设置两个循环,外循环遍历一个个节点,遍历到这个节点时,会记录这个节点在链表里是第几个,然后在内循环里设置的循环次数就是这个节点在链表里的位置,如果这个节点排第七个,就会循环六次,逐一判断这个节点的next是否指向前面六个节点。

·        时间复杂度为 O( n*(1+n)*n/2  ),耗时两千多毫秒。

function detectCycle($head) {
        $cur = $behind = $temp = $head;
        $i = 0;
        while($behind) {
            $j = $i;
            $temp = $cur;
            while($j > 0) {
                if($behind->next == $temp) {
                    return $temp;
                }
                $temp = $temp->next;
                $j--;
            }
            $behind = $behind->next; 
            $i++;
        }
        return null;
    }

方法二:分析出规律

        看的讲解的分析过程有点复杂,这里直接贴链接

        代码随想录

        设置两个快慢指针,fast和slow同时走,fast 每次走两步,slow每次走一步,fast每次指向的节点位置都是偶数,slow则是偶奇数都有,又因为是环形链路,所以这两个指针一定会相遇。

        而通过以上链接里的分析可得出,如果一个指针one 在链路的头结点,一个指针two在快慢指针的相交节点上,两个指针同时移动,每次移动一步,最后相遇的节点,一定是链路开始入环的第一个节点。

function detectCycle($head) {
        $fast = $head;
        $slow = $head;
        while($fast) {
            $slow = $slow->next;
            $fast = $fast->next->next;
            if($slow == $fast) {
                while($head != $slow) {
                    $head = $head->next;
                    $slow = $slow->next;
                }
                return $head;
            }
        }
        return null;
    }

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值