本节对应代码随想录中:代码随想录,讲解视频:链表遍历学清楚! | LeetCode:19.删除链表倒数第N个节点_哔哩哔哩_bilibili
题目链接:19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)
给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
这道题目还挺简单的,题意也很明确。需要注意的是最好使用虚拟头结点,否则示例2这种就需要单独处理会麻烦一点。比较容易想到的就是既然要删倒数第 n 个节点,那我们首先遍历以下链表看看总共有多少个节点,然后再根据总节点数量 size 和 n 的大小遍历到待删节点的前一个节点执行删除操作即可。
这里需要注意的就是遍历的边界条件是到了待删节点的前一个节点开始执行删除操作。当遍历到待删节点本身的时候是无法进行删除的,因为我们无法获得待删节点的前一个节点是什么。
class Solution {public:ListNode* removeNthFromEnd(ListNode* head, int n) {ListNode* dummyHead = new ListNode(0);dummyHead->next = head;ListNode* tmp = head;int size = 0; // 记录链表长度// 计算链表长度while (tmp != nullptr) {tmp = tmp->next;size++;}tmp = dummyHead;// 找到待删除节点的前一个节点for (int i = 0; i < size - n; i++) {tmp = tmp->next;}ListNode* delNode = tmp->next;tmp->next = tmp->next->next;delete delNode;return dummyHead->next;}
};
可以优化的点:
ListNode* dummyHead = new ListNode(0);dummyHead->next = head;
这两句其实可以用 ListNode* dummyHead = new ListNode(0, head);
这一句话来实现。因为 ListNode 的构造函数中有这样一句 ListNode(int x, ListNode *next) : val(x), next(next) {}
可以直接指定节点的 next,不过写成两句更加直观一点。LeetCode 上题目描述中进阶中说“你能尝试使用一趟扫描实现吗?”。一开始没想出来,看了题目的提示:Maintain two pointers and update one with a delay of n steps.才明白。
例如0 1 2 3 4 5,我们可以用一个指针 cur 来遍历,用另一个指针 pre 慢 cur 指针 n+1 步遍历。比如 n=2时,当 cur=3时,pre=0(虚拟头结点),然后两个指针一起移动,当 cur 遍历到5后面的 nullptr 时,pre 刚好遍历到待删节点4的前一个节点3的位置。
如果是慢 n 步遍历,那么 pre 会遍历到待删节点4的位置,此时是无法执行删除节点4的操作的,因此是慢 n+1步。
class Solution {public:ListNode* removeNthFromEnd(ListNode* head, int n) {ListNode* dummyNode = new ListNode(0);dummyNode->next = head;ListNode* cur = dummyNode;ListNode* pre = dummyNode;// cur先向前移动n+1步for (int i = 0; i < n + 1; i++) {cur = cur->next;}// 然后cur和pre一起向前移动while (cur != nullptr) {cur = cur->next;pre = pre->next;}ListNode* delNode = pre->next;pre->next = pre->next->next;delete delNode;return dummyNode->next;}
};
但是写完之后又有点疑惑,这还算是一趟扫描吗,因为其中有 for 循环和 while 循环。仔细琢磨后发现确实算是一趟扫描。
直观解法的解法中,在第一趟扫描中,需要计算链表的长度,以便确定需要删除节点的前一个节点。而在第二趟扫描中,才真正开始删除节点。因此,这个解法需要进行两趟扫描。而双指针解法使用了两个指针来记录待删除节点的前一个节点,而不需要事先计算链表的长度。
举个极端点的例子,在1000个节点中,我想删除倒数第1个节点即节点1000,在第一个解法中,需要先遍历1000个节点得知链表长度为1000,然后再遍历一趟到节点999的位置执行删除操作。而在第二个解法中,只需要 cur 指针先向前走2步,然后 pre 指针和 cur 指针再开始一起走,最后 cur 指针走到节点1000的下一个即 nullptr 时,pre 刚好走到节点999的位置。在这个例子中,解法一走了1001+999=1999步,而解法二只走了1001+2=1003步!
为什么是1001而不是1000?因为从1000走到下一步 nullptr 还需要一步
如果你还是有点不太理解,看看下面的代码,它没有使用 for 循环而是使用了 size 进行计数,其实和上面的解法是一个意思。
class Solution {public:ListNode* removeNthFromEnd(ListNode* head, int n) {ListNode* dummyHead = new ListNode(0);dummyHead->next = head;ListNode* cur = dummyHead;ListNode* pre = dummyHead;int size = 0;while (cur != nullptr) {// size > n说明慢指针可以开始移动了if (size > n)// 循环结束后pre指向待删除节点的前一个节点pre = pre->next;elsesize++;cur = cur->next;}ListNode* delNode = pre->next;pre->next = pre->next->next;delete delNode;return dummyHead->next;}
};
上一篇: 冰库买卖合同范本大全(精选20篇)
下一篇: 盘锦企业培训合同范本40篇