代码随想录Day4:链表(节点两两交换、删除倒数第n个节点、链表相交、环形链表)

一、理论(详见Day3)

  • 链表定义和结构:节点(指针域+数据域)通过指针串联在一起的线性结构
  • 链表类型:单链表(上图,只能沿一个方向走);双链表(有两个指针域,可以沿两个方向走);循环链表(收尾相连,上图单链表E的指针指向A即可)
  • 链表存储方式:与数组连续存储不同,链表的节点在内存中不是连续分布,具体分配机制取决于操作系统的内存管理方法,通过指针链接内存中的各个节点
  • 链表操作:删除(Java有内存回收机制,直接一步到位即可,无需回收,C++中需要手动释放要删除的节点);添加(分两步且前后顺序不能调,先F接到D,再C接到F)

二、实战

24两两交换链表中的节点

24. 两两交换链表中的节点 - 力扣(LeetCode)

整体思路:两两交换物理节点。

1.考虑加上一个虚拟节点,这样头结点的处理就和普通节点一样

2.厘清节点之间的交换步骤,顺序不重要,但是这种两两交换牵涉到4个节点,厘清步骤是为了防止遗漏,导致结果错误

package org.example.Node;

public class swapPairs24 {
    public ListNode swapPairs(ListNode head) {
        //如果只有一个节点或者为空直接输出
        if(head==null || head.next==null)return head;

        //虚拟节点,将头结点与普通节点一样
        ListNode dummy=new ListNode();
        dummy.next=head;

        //四个节点,cur和temp记录位置,pre和after做交换
        ListNode cur=dummy;
        ListNode pre;
        ListNode after;
        ListNode temp;

        //防止空指针,确保交换的pre和after不为空
        while(cur.next!=null && cur.next.next!=null)
        {

            pre=cur.next;
            after=pre.next;
            temp=after.next;
            //交换的三个步骤,因为都设置了节点,所以顺序不重要
            cur.next=after;
            after.next=pre;
            pre.next=temp;
            //注意这里是pre不是after,因为这时已经完成位置的交换
            cur=pre;
        }
        return dummy.next;
    }

    public static void main(String[] args) {
        //创建链表
        ListNode head = new ListNode(7);
        head.next = new ListNode(6);
        head.next.next = new ListNode(5);
        head.next.next.next = new ListNode(4);

        //打印原始链表
        System.out.println("Original List:");
        printList(head);

        swapPairs24 solution = new swapPairs24();
        head = solution.swapPairs(head);

        //打印移除后的链表
        System.out.println("After reverse:");
        printList(head);
    }

    //辅助函数:打印链表
    private static void printList(ListNode node) {
        while (node != null) {
            System.out.print(node.val + " ");
            node = node.next;
        }
        System.out.println();
    }
}

时间复杂度:O(n)    遍历整个链表一次
空间复杂度:O(1)    只使用了几个额外的指针变量

19删除链表的倒数第N个节点

19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)

思路:删除倒数第n个节点,即要找到倒数第n+1个节点进行操作,这样需要两个节点,初始值都在虚拟节点上,一个指针先走n+1距离,固定距离后两个指针一起移动直到先走的指针到达null。主要难点就在于n+1的距离以及整体的过程需要多模拟几遍。

fast和slow同时移动,直到fast指向末尾

package org.example.Node;

public class removeNthFromEnd19 {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        //如果只有一个节点或者为空直接输出
        if(head==null)return head;

        //虚拟节点,将头结点与普通节点一样
        ListNode dummy=new ListNode();
        dummy.next=head;

        //pre要删除的节点前面一个节点,after用于给pre定位
        ListNode pre=dummy;
        ListNode after=dummy;

        //最后after与要删除的节点距离n,所以after与pre距离n+1
        //after先走出距离差之后再一起移动
        while(n>=0)
        {
            after=after.next;
            n--;
        }
        //after到null的时候,pre正好到删除的前一个位置
        while(after!=null)
        {
            after=after.next;
            pre=pre.next;
        }
        //删除
        pre.next=pre.next.next;

        return dummy.next;
    }

    public static void main(String[] args) {
        //创建链表
        ListNode head = new ListNode(7);
        head.next = new ListNode(6);
        head.next.next = new ListNode(5);
        head.next.next.next = new ListNode(4);

        //打印原始链表
        System.out.println("Original List:");
        printList(head);

        removeNthFromEnd19 solution = new removeNthFromEnd19();
        int val=2;
        head = solution.removeNthFromEnd(head,val);

        //打印移除后的链表
        System.out.println("After reverse:");
        printList(head);
    }

    //辅助函数:打印链表
    private static void printList(ListNode node) {
        while (node != null) {
            System.out.print(node.val + " ");
            node = node.next;
        }
        System.out.println();
    }
}

160链表相交

面试题 02.07. 链表相交 - 力扣(LeetCode)

思路:求两个链表交点节点的指针。求出两个链表长度后,将两个链表末尾对齐,比较每个节点是否相同,不同则同步下一位,相同则找到交点。

注意:这里的相同是指针相同而非数值相同

package org.example.Node;

public class getIntersectionNode0207 {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode curA=headA;
        ListNode curB=headB;

        int lenA=0,lenB=0;
        while (curA != null) { // 求链表A的长度
            lenA++;
            curA = curA.next;
        }
        while (curB != null) { // 求链表B的长度
            lenB++;
            curB = curB.next;
        }

        //恢复curA,curB的值
        curA=headA;
        curB=headB;

        //统一为A长,B短
        if (lenB > lenA) {
            //1. swap (lenA, lenB);
            int tmpLen = lenA;
            lenA = lenB;
            lenB = tmpLen;
            //2. swap (curA, curB);
            ListNode tmpNode = curA;
            curA = curB;
            curB = tmpNode;
        }

        int delta=lenA-lenB;
        while(delta>0)
        {
            curA=curA.next;
            delta--;
        }
        // 遍历curA 和 curB,遇到相同则直接返回
        while (curA != null) {
            if (curA == curB) {
                return curA;
            }
            curA = curA.next;
            curB = curB.next;
        }

        //没有找到交点
        return null;
    }
}
  • 时间复杂度:O(n + m)
  • 空间复杂度:O(1)

142环形链表

142. 环形链表 II - 力扣(LeetCode)

1.判断链表是否有环

双指针:快慢指针相遇,快指针每次走两个节点,慢节点一次走一个节点,在环中的时候快指针相对于慢指针的速度是2-1=1,以一次一个节点的速度去靠近慢指针,因此在有环的情况下不会错过

2.环的入口

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

那么相遇时: 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,x = n (y + z) - y,整理公式之后为如下公式:x = (n - 1) (y + z) + z 注意这里n一定是大于等于1的,因为 fast指针至少要多走一圈才能相遇slow指针。

当 n为1的时候,公式就化解为 x = z,这就意味着,从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点

当n大于1的时候,就是fast指针在环形转n圈之后才遇到 slow指针。其实这种情况和n为1的时候 效果是一样的,一样可以通过这个方法找到 环形的入口节点,只不过,index1 指针在环里 多转了(n-1)圈,然后再遇到index2,相遇点依然是环形的入口节点。

为什么判断是slow都默认一圈呢,是因为如上图,每一段都是一圈的展开,我们能发现在slow转一圈的时候,快指针无论在前一圈的哪个节点,两倍距离都能追上。

package org.example.Node;

public class detectCycle142 {
    public ListNode detectCycle(ListNode head) {
        ListNode fast=head;
        ListNode slow=head;

        //因为快指针一次跳两个,所以要检查两个位置
        while(fast!=null && fast.next!=null)
        {
            fast=fast.next.next;
            slow=slow.next;
            if(fast==slow)
            //快慢指针相遇,说明有环
            {
                //x==z
                ListNode index1=fast;
                ListNode index2=head;
                while(index2!=index1)
                {
                    index2=index2.next;
                    index1=index1.next;
                }
                return index1;
            }
        }
        return null;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值