第四天|链表| 24. 两两交换链表中的节点 ,19.删除链表的倒数第N个节点 ,160.链表相交 ,142.环形链表II

目录

24. 两两交换链表中的节点

方法1_fff:

方法2:

方法3:递归法

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

160.链表相交

142.环形链表II

1.判断链表是否有环

2.如果有环,如何找到这个环的入口


24. 两两交换链表中的节点

方法1_fff:

思路如图所示。

在运行时出现的问题:空指针异常

做了改进,详见代码。

class Solution {
        public ListNode swapPairs(ListNode head) {
//            方法1_fff:
//            ListNode temp= cur.next;  while (cur != null && temp != null)出现问题,报错NullPointerException
//            因为当 cur 为 null 时,仍然尝试访问 cur.next。
//            进行了更改后正常运行
            if (head == null || head.next == null) {
                return head;
            }
            ListNode dummy = new ListNode(-1, head);
            ListNode prev = dummy;
            ListNode cur = head;
            ListNode temp;
            while (cur != null && cur.next != null) {
                temp= cur.next;

                cur.next = temp.next;
                temp.next = prev.next;
                prev.next = temp;

                prev = cur;
                cur = cur.next;
            }
            return dummy.next;
        }
    }

注意:

最后return dummy.next;而不是return head;

虽然 head 本身没有被直接修改,但链表的连接关系已经改变,head 所在的位置已经不再是链表的第一个节点了。因此,需要返回 dummy.next 以确保返回的是新的头节点。

方法2:

思路如图所示。

    class Solution {
        public ListNode swapPairs(ListNode head) {
//            方法2:
            ListNode dummy = new ListNode(0, head);
            ListNode cur = dummy;
            while (cur.next != null && cur.next.next != null) {
                ListNode node1 = cur.next;
                ListNode node2 = cur.next.next;
                cur.next = node2; // 步骤 1
                node1.next = node2.next;// 步骤 3
                node2.next = node1;// 步骤 2
                cur = cur.next.next;
            }
            return dummy.next;
        }
    }

方法3:递归法

递归法有点抽象,举个例子更好理解。

假设链表为 1 -> 2 -> 3 -> 4,调用 swapPairs 的过程如下:

  1. swapPairs(1)
    • next = 2
    • 递归调用 swapPairs(3)
      • next = 4
      • 递归调用 swapPairs(null),返回 null
      • 交换 34,返回 4 -> 3
    • 交换 12,返回 2 -> 1 -> 4 -> 3

最终,整个链表两两交换后的结果是 2 -> 1 -> 4 -> 3

// 递归版本
class Solution {
    public ListNode swapPairs(ListNode head) {
        // base case 退出提交
        if(head == null || head.next == null) return head;
        // 获取当前节点的下一个节点
        ListNode next = head.next;
        // 进行递归
        ListNode newNode = swapPairs(next.next);
        // 这里进行交换
        next.next = head;
        head.next = newNode;

        return next;
    }
} 

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

19的实现用了2种方法

方法1是自己想的,略复杂。

方法1思路:首先获取链表长度,又已知需要删除倒数第几个数,因此可以得出需要删除正数第几个数。得到需要删除结点的前一个结点,然后进行删除即可。

方法2思路:看了题解,觉得使用快慢指针的方法好机智,学到了!

双指针的经典应用,如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了。

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        方法1_fff:
//        if (head == null){
//            return head;
//        }
//        int size = 0;
//        ListNode dummy = new ListNode(-1, head);
//        ListNode cur = dummy;
      获取链表长度size
//        while (cur.next != null){
//            size++;
//            cur = cur.next;
//        }
//        cur = dummy;
        获取是正数第len个为需要被删除的结点的prev
//        int len = size - n;
//        while (len > 0){
//            len--;
//            cur = cur.next;
//        }
        删除
//        if (cur.next.next == null){
//            cur.next = null;
//        }else {
//            cur.next = cur.next.next;
//        }
//        return  dummy.next;


//        方法2:快慢指针
        ListNode dummy = new ListNode(-1, head);
        ListNode fast = dummy;
        ListNode slow = dummy;
        while (n-- > 0 && fast != null) {
            fast = fast.next;
        }
        while (fast.next != null){
            fast = fast.next;
            slow = slow.next;
        }
        slow.next = slow.next.next;
        return dummy.next;
    }
}

160.链表相交

这道题自己没有思路,做不出来,看了题解,觉得好聪明哈哈哈哈。

方法1思路:

        首先求出两个链表的长度,并求出两个链表长度的差值,然后让curA移动到,和curB 末尾对齐的位置,如图:

比较curA和curB是否相同,如果不相同,同时向后移动curA和curB,如果遇到curA == curB,则找到交点。否则循环退出返回空指针。

public class Solution {
        public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
//            方法1:先行移动长链表实现同步移动
            int sizeA = 0;
            int sizeB = 0;
            ListNode curA = new ListNode(-1, headA);
            ListNode curB = new ListNode(-1, headB);
            while (curA.next != null) {
                sizeA++;
                curA = curA.next;
            }
            while (curB.next != null) {
                sizeB++;
                curB = curB.next;
            }
            ListNode currA = new ListNode(-1, headA);
            ListNode currB = new ListNode(-1, headB);
            if (sizeA > sizeB) {
                int n = sizeA - sizeB;
                while (n-- > 0) {
                    currA = currA.next;
                }
            } else {
                int n = sizeB - sizeA;
                while (n-- > 0) {
                    currB = currB.next;
                }
            }
            while (currA.next != null){
                if (currA.next == currB.next){
                    return currA.next;
                }
                currA = currA.next;
                currB = currB.next;
            }
            return null;
        }
    }

题解中还给了第二种方法:合并链表实现同步移动

这个有点抽象,我看不懂,先把代码贴上来。

等之后万一会了再来更新。

(版本二) 合并链表实现同步移动
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
		// p1 指向 A 链表头结点,p2 指向 B 链表头结点
		ListNode p1 = headA, p2 = headB;
		while (p1 != p2) {
			// p1 走一步,如果走到 A 链表末尾,转到 B 链表
			if (p1 == null) p1 = headB;
			else            p1 = p1.next;
			// p2 走一步,如果走到 B 链表末尾,转到 A 链表
			if (p2 == null) p2 = headA;
			else            p2 = p2.next;
		}
		return p1;
    }
}

142.环形链表II

这道题重点学习思路,想通了写代码就很容易了。

这个题目是主要思路:

  • 判断链表是否有环
  • 如果有环,如何找到这个环的入口

1.判断链表是否有环

可以使用快慢指针法,分别定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点slow指针每次移动一个节点,即fast相对于slow是每次移动一个结点,因此不会错过,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。

2.如果有环,如何找到这个环的入口

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

那么 n如果大于1是什么情况呢,就是fast指针在环形转n圈之后才遇到 slow指针。

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

    public class Solution {
        public ListNode detectCycle(ListNode head) {
            if (head == null) {
                return head;
            }
            ListNode fast = head;
            ListNode slow = head;

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

            }
            return null;
        }
    }

第四天的总算是结束了,直冲Day6!

Day5休息日没有题目。耶耶耶

其实我是每天都在补前一天的5555

Day5当缓冲吧

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值