Leetcode交换24/删除19/相交/环形链表142

前言

Leetcode链表题组

一、24题(两两交换链表中的节点)

题目描述
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

class Solution {
    public ListNode swapPairs(ListNode head) {
        if(head==null) return head;
        // 虚拟头节点统一操作
        ListNode dummyhead = new ListNode(0);
        dummyhead.next = head;
        // 四个节点为一组
        ListNode pre,curleft,curright,tmp;
        pre = dummyhead;
        curleft = head;
        curright = head.next;
        // 判断当前处理的两个节点是否存在
        while(curleft!=null&&curright!=null){
            tmp = curright.next;
            pre.next = curright;
            curright.next = curleft;
            curleft.next = tmp;
            pre = curleft;
            curleft = tmp;
            if(curleft==null){
                break;
            }
            curright = tmp.next;
        }
        return dummyhead.next;
    }
}

算法解析

  • 每个当前操作的一组节点取名为[curleft,curright],除此以外还涉及curleft左边的节点left,curright右边的节点tmp。
  • 加入一个dummyhead以统一操作,每次操作涉及四个节点(详见易错点的第一点分析)。

易错点

  1. 首先要分析出来进行一次互换节点操作涉及四个节点(区别于反转链表的三个节点),假设操作的是curleft和curright的互换:
    pre->curleft->curright->tmp
    则一次完整的互换操作为:
pre.next = curright;
curright.next = currleft;
currleft = tmp;

所以对于一组当前current需要互换的节点[curleft,curright],要记录curleft左边的节点pre,以及curright右边的节点tmp。

  1. 还是老问题,对于Xxx.next,一定要判断Xxx有没有可能为null指针。
  2. 交换过后指针的移动:注意交换之后四个元素的顺序是pre->curright->curleft->tmp,所以pre移到curleft处(而不是curright),curleft移到tmp处,curright移到tmp处。

二、19题(删除链表的倒数第N个节点)

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

解法1

mine:时长击败100%用户😀

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode cur=head;
        int size = 0;
        while(cur.next!=null){
            size++;
            cur = cur.next;
        }
        size = size+1;
        //一共size个节点
        //删除倒数第n个,即删除正数第(size-x+1)个
        if(n==size){
            return head.next;
        }
        //删除第m个元素,要找到第m-1个元素
        int m = size-n+1;
        cur = head;
        int i = 1;
        while(i<m-1){
            cur=cur.next;
            i++;
        }
        cur.next = cur.next.next;
        return head;
    }
}

算法思路

  • 所谓倒数第n个,其实是正数第(size-n+1)个,所以找到第size-n个元素,执行cur.next = cue.next.next即可。
  • 遍历一次数组找到size,再从头找到第size-n个元素。

解法2

虽然但是,mine的时间复杂度也是O(2n)=O(n),这个是O(n),感觉也没太必要

public ListNode removeNthFromEnd(ListNode head, int n){
    ListNode dummyNode = new ListNode(0);
    dummyNode.next = head;

    ListNode fastIndex = dummyNode;
    ListNode slowIndex = dummyNode;

    // 只要快慢指针相差 n 个结点即可
    for (int i = 0; i <= n  ; i++){ 
        fastIndex = fastIndex.next;
    }

    while (fastIndex != null){
        fastIndex = fastIndex.next;
        slowIndex = slowIndex.next;
    }

    //此时 slowIndex 的位置就是待删除元素的前一个位置。
    //具体情况可自己画一个链表长度为 3 的图来模拟代码来理解
    slowIndex.next = slowIndex.next.next;
    return dummyNode.next;
}

算法思路:快慢指针,初始化让快指针比慢指针多走n步。然后遍历节点,当快指针走到末尾时,慢指针正好指向需要能执行删除操作元素的位置。(大意是这样,具体代码实现的+1-1还要再分析)

三、02.07(链表相交)

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

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode lastA=headA,lastB=headB;
        if(headA==null||headB==null) return null;
        //计算长度
        int sizeA=1,sizeB=1;
        while(lastA.next!=null){
            lastA = lastA.next;
            sizeA++;
        }
        while(lastB.next!=null){
            lastB = lastB.next;
            sizeB++;
        }
        if(lastA.val!=lastB.val) return null;
        lastA = headA;
        lastB = headB;
        //把指针移到链表长度相等的位置
        while(sizeA>sizeB){
            lastA=lastA.next;
            sizeA--;
        }
        while(sizeB>sizeA){
            lastB=lastB.next;
            sizeB--;
        }
        while(lastA!=lastB){
            lastA = lastA.next;
            lastB = lastB.next;
            if(lastA == null){
                break;
            }
        }
        return lastA;
    }
}

题目分析:得从空间内存角度理解,即两个链表从某个节点开始共用链表了,而不是两个链表某一段值相等。所以如果这个节点在A链表中是lastA,在B链表中是lastB,则有:lastA = lastB。所以关键是比较节点的地址,而不是节点的值。

算法思路:某一段是公用的,必然长度相等,所以首先要把两个链表变成同一长度。然后逐个节点进行地址比较即可。

易错点
①一开始思考错了,想成是找到两个节点:lastA.next=lastB.next,这样会很容易出错,因为有长度为1这个特殊情况,导致lastA.next == lastB.next == null。其实是找到两个地址相同的节点。
②为什么会这么想呢,因为题目的那个图是图1这样的,如果是图2这样我就可能不会错了/(ㄒoㄒ)/~~。
图1图1.
图2
图2

四、142(环形链表)

题目描述
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
我的评价是...很有创意的一道题..抽象的仿佛回到了高中
大佬解法

public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
            if (slow == fast) {// 有环
                ListNode index1 = fast;
                ListNode index2 = head;
                // 两个指针,从头结点和相遇结点,各走一步,直到相遇,相遇点即为环入口
                while (index1 != index2) {
                    index1 = index1.next;
                    index2 = index2.next;
                }
                return index1;
            }
        }
        return null;
    }
}

算法分析:

  1. 快慢指针思路。让快指针以2的速度前进,慢指针以1的速度前进,如果他们最终相遇,则表示有环。
  2. 如果有环,假设环点距离head为x,环点距离相遇点位y,相遇点距离环点为z(顺时针)。则必然有公式:
①2(x+y) = x+y+n(y+z);//其中,n>=1,因为只有在快指针在绕了多圈过程中才可能被慢指针追上
变形:
②x = (n-1)(y+z)+z;//该公式表明,当一个指针从head出发,一个指针从相遇点顺时针运动,
相遇时的位置就是环点。【怎么看出来的呢?答:举一个极端情况n=1,则x=z】
  1. 公式1的解释:相遇时,慢指针走了x+y,快指针走了x+y+n(y+z),又快指针的速度是慢指针的两倍,所以存在公式①。问题来了,为什么慢指针没有走完一圈就被快指针追上了呢?不是应该是x+y+k(y+z),k>=0吗?答案如下
    ①理论解释:关于找环的入口,为什么慢指针不会出现走好几圈才被快指针追上。可以先假设当慢指针第一次到环入口处的时候,和快指针的距离为m,此时快指针已经在环里面走了,而慢指针接下来也会在环里面走。再假设环长为s,所以快指针和慢指针的距离是(s-m),(注:都是按顺时针看距离),而快指针每次都会比慢指针多走一步,相当于每次都以一步的距离再缩进距离,所以当慢指针走(s-m)步的时候,快指针就能把距离缩为0了,也就是两点相遇了。而s-m是肯定小于s的,也就是小于一圈,所以慢指针肯定在没有走完一圈的时候就会被快指针追上。(来自b友)
    画图解释

2024/8/25新总结:①小学奥数追赶问题不多解释 ②为什么慢指针只走一圈就会被追上:详见上述分析 ③环点寻找:首先求得相遇点,经过推导知道:head距离环入口等于相遇点距离环入口+n圈环的长度。所以此时,如果让两个速度均为1的指针,1个从head出发,1个从相遇点出发,它们的相遇处一定就是环入口。

20240825错误写法

while(slow!=null&&fast.next!=null){
            slow = slow.next;
            fast = fast.next.next;
            if(slow==fast){
                ListNode index1=fast;
                ListNode index2=head;
                while(index1!=index2){
                    index1=index1.next;
                    index2=index2.next;
                }
                return index1;
            }
        }

错误原因:①因为会有[1,2]这种情况,此时head==fast。所以内部循环体应该是while(index1!=index2){…}return index1;②大循环体判断条件应该是while(slow!=null&&fast!=null&&fast.next!=null),且fast!=null要在fast.next!=null前面。

总结

24题思考了一会儿。19题思路比较简单,就是有点绕,可能是因为植树问题从小到大我都很差的原因,每次都要靠画图来分析(;′⌒`)。相交这道题想到思路,理解对题意就比较容易。142难。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值