《手撕力扣链表题》反转链表、删除链表的倒数第 N 个结点

简介: 《手撕力扣链表题》反转链表、删除链表的倒数第 N 个结点

一、反转链表

b902d8dc64cf40fc8e14333d7aab0bee.png

原题链接反转链表 

🌻迭代

📝其实要反转链表不需要再定义一个新的链表来实现反转,只需要改变原链表next的指向就可以了。从头结点开始,顺次让每个链表结点都指向它的前一个结点就好,头结点的前一个就是空结点,原来最后一个结点不再指向空结点,而改为指向倒数第二个结点。


🔔但要注意的是:改变指向必须从链表的头结点开始,原链表的每一个结点的指向都要改变(要不然会形成死循环的

// 反转链表,迭代法
class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode cur = head;
        ListNode pre = null; // 定义cur的前一个结点
        while (cur != null) {
            // 因为接下来cur.next的指向会变所有,所以先存一份
            ListNode tmp = cur.next;
            cur.next = pre; // 对该结点的指向进行反转,当前结点指向前一个结点
            pre = cur; // 对当前结点的前一个结点进行更新
            cur = tmp; // 对当前结点进行更新
        }
        return pre; // 因为当前cur结点已经为空,所以返回他的前一个结点
    }
}

🌻递归

其实递归对链表的反转思路和上面的迭代法是一样的,只不过是把对链表结点的更新用函数的的递归来完成了

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode cur = head;
        ListNode pre = null; // 定义cur的前一个结点
        return reverse(pre, cur);
       // 因为当前cur结点已经为空,所以返回他的前一个结点
    }
    public ListNode reverse(ListNode pre, ListNode cur) { // 进行递归操作的那个方法
        if(cur == null) return pre; // 递归的出口
        ListNode tmp = cur.next; // 保存cur的下一个结点
        cur.next = pre;// 反转操作
        return reverse(cur, tmp);
        // 上面这一步做的就是我们前面迭代法中对pre和cur的更新
        // pre = cur;   cur = tmp = cur.next;
    }
}

二、删除链表的倒数第N个结点

c04d4a84dc6743eba4f92fe9ff49f7a9.png

原题链接删除链表的倒数第N个结点

🌻小白做法:

思路:先找到链表的结点数目,以此来得到要删除的结点的坐标,再通过坐标找到要删除链表的前一个结点,以便来进行删除。


注意考虑特殊情况(当要删除的结点是头结点)

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        if (head.next == null) { // 当链表中只有一个元素时候,要删除的第n个结点只能是该链表中这个唯一的结点
            return null;
        }
        ListNode tmp =  head;
        int size = 0;
        while (tmp != null) { // 遍历得到链表结点数目
            tmp = tmp.next;
            size++;
        }
        int index = size - n; // 得到要删除的结点的坐标
        if (index == 0) {   // 如果要删除的是头结点,无法找到它的前一个结点,所以要进行特判
            head = head.next; 
            return head;
        }
        ListNode cur = head;
        for (int i = 0; i < index - 1; ++i) { // 通过遍历让cur结点指向要删除结点的前一个结点
            cur = cur.next;
        }
        cur.next = cur.next.next;
        return head;
    }
}

🌻设置虚拟头结点的做法

思路和小白做法一样,但因为设置了虚拟头结点,所以不用考虑删除头结点这种特殊情况

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummyHead = new ListNode(0, head); // 定义一个虚拟头结点,并将它指向头结点,这样删除头结点时就不用再专门考虑了
        ListNode tmp =  head;
        int size = 0;
        while (tmp != null) { // 遍历得到链表结点数目
            tmp = tmp.next;
            size++;
        }
        int index = size - n; // 得到要删除的结点的坐标
        ListNode cur = dummyHead;
        for (int i = 0; i < index; ++i) { // 通过遍历让cur结点指向要删除结点的前一个结点
            cur = cur.next;
        }
        cur.next = cur.next.next; 
        return dummyHead.next; 
        // 注意此时虽然head和dummyHead.next都表示对头结点的引用,但他们俩在内存中存放的位置不同,
        // 当dummyHead.next更改了他的指向时,对head并没有什么影响。所以不能return head;
    }
}

🌻快慢指针

刚才我们看到了,因为题目要求删除的是倒数第n个结点,所以上面的方法都是先要求出链表的长度,再通过长度求出要删除结点的下标,那我们能不能再不求出链表长度的情况下成功删除该结点呢?


💖我们其实想象一下我们的链表其实是分为两个部分的:倒数第n个结点前面是一部分,倒数第n个结点后面又是一部分。而双指针法正是利用了这个特性来做的

439378cecb44496caea7c4492541dd98.gif

📝你看,如果我们fast和slow这两个结点引用变量一开始都分别指向我们的虚拟头结点,如果fast先移n个结点,那么是不是此时fast到链表结尾的距离就是我们从虚拟头结点到倒数第n个结点的距离了。


📝那么我们就可以先让fast先移动n步,然后fast和slow再同时开始移动,直到fast指向了链表的结尾,此时slow不就指向了我们要删除的那个结点了吗?


🔔 但要注意的是我们删除结点一般都是要找到该结点的前一个结点,所以我们的fast先要移动n+1步,以便之后让我们的slow指向要删除结点的前一个结点。

 public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummyHead = new ListNode(0, head); // 定义一个虚拟头结点,并将它指向头结点,这样删除头结点时就不用再专门考虑了
        ListNode fast = dummyHead, slow = dummyHead; // 定义两个快慢指针,分别指向该虚拟头节点
        for(int i = 0; i < n + 1; ++i) { // fast先移动n+1步
            fast = fast.next;
        }
        while (fast != null) { 
            slow = slow.next;
            fast = fast.next;
        }
        // 此时slow指向要删除的结点的前一个结点
        slow.next = slow.next.next;
        return dummyHead.next; // 返回头结点
    }

小伙伴们,都看到这里了,是不是已经迫不及待的想刷题了,一起冲呀!!!

655cf2f5ccc042e5bf416034aa771745.jpg

相关文章
|
2月前
【力扣】-- 移除链表元素
【力扣】-- 移除链表元素
37 1
|
2月前
|
算法
【❤️算法笔记❤️】-每日一刷-19、删除链表的倒数第 N个结点
【❤️算法笔记❤️】-每日一刷-19、删除链表的倒数第 N个结点
69 1
|
2月前
LeetCode第二十四题(两两交换链表中的节点)
这篇文章介绍了LeetCode第24题的解法,即如何通过使用三个指针(preNode, curNode, curNextNode)来两两交换链表中的节点,并提供了详细的代码实现。
22 0
LeetCode第二十四题(两两交换链表中的节点)
|
2月前
(剑指offer)18、删除链表的节点—22、链表中倒数第K个节点—25、合并两个排序的链表—52、两个链表的第一个公共节点(2021.12.07)
(剑指offer)18、删除链表的节点—22、链表中倒数第K个节点—25、合并两个排序的链表—52、两个链表的第一个公共节点(2021.12.07)
52 0
|
2月前
|
索引
力扣(LeetCode)数据结构练习题(3)------链表
力扣(LeetCode)数据结构练习题(3)------链表
90 0
|
7月前
【移除链表元素】LeetCode第203题讲解
【移除链表元素】LeetCode第203题讲解
|
6月前
|
存储 SQL 算法
LeetCode力扣第114题:多种算法实现 将二叉树展开为链表
LeetCode力扣第114题:多种算法实现 将二叉树展开为链表
|
6月前
|
存储 SQL 算法
LeetCode 题目 86:分隔链表
LeetCode 题目 86:分隔链表
|
6月前
|
存储 算法 Java
【经典算法】Leetcode 141. 环形链表(Java/C/Python3实现含注释说明,Easy)
【经典算法】Leetcode 141. 环形链表(Java/C/Python3实现含注释说明,Easy)
63 2
|
7月前
<数据结构>五道LeetCode链表题分析.环形链表,反转链表,合并链表,找中间节点.
<数据结构>五道LeetCode链表题分析.环形链表,反转链表,合并链表,找中间节点
57 1