今天来看一道不怎么难的题,给大家放松一下。放松的同时也希望和大家一起回顾一下 “链表” Linked List 的一些基本知识和使用方法。
链表是面试里常考察的题型之一,他和 array 最大的不同之处在于他 更好的延展性,比数组,甚至是动态数组对于不元素大小的头尾增删效率更加,因为不需要 对内存空间长度的重新分配。与之带来的缺点就是从全局角度来说的 “长度不可见”,“位置不可见”,所有的长度和位置都依赖于我们按照链表的方向进行遍历,使得一般 Array 的解题思路用在链表上一般都会 TLE
今天这道题目虽然用遍历方法很好解决,也不会 TLE,但是我们可以以这道简单题目为跳板,更好的学习和了解一些在链表中可以使用的 shortcut,那就让我们开始吧!
虽然说创造一个新的 List head,并且不断 copy 原有的元素并删除指定元素可以更加轻松的完成本道题目的书写(不用 handle 因为删除 linked list 所导致的 segmentation fault 问题),但是额外的空间是非常不 efficient的,各位码友在面试的时候能 inplace 修改做出来就不要用 额外空间,会很减分的
因为是 singly linked list,所以我们不知道他有多长,也不知道他的末尾在哪里。大家应该很容易想到一种暴力解法:
这种方法是可以通过测试的,但是显然是一种很笨的方法。如果是面试大厂的同学们需要做的更好,才能在面试中脱颖而出~~
因为 single linked list 决定了我们只能从这一个方向遍历,并且也只能遍历全部才能知道具体的长度和位置,那我们可以怎么样在刚刚的基础上进行优化呢?
聪明的小伙伴可能已经发现:无论是什么情况,我们想要的那个点和 list 末尾节点中间都差 n
因为我们的题目是要删除 从后往前数的 第 n 个点,那换个思路,他和末尾点的偏移量就是 n。再平移回起点,只需要我们在起始点地方就已经做好起始点的偏移量,等到起始点的偏移量先到达末尾之后,我们当时的起始点不就是我们想要的那个元素吗?
如果使用双指针,一个点指向起始点后 n+1 个,另一个指向原点,那当其中一个点到达终点以后,另一个点 K 就会在 需要被删除的目标点前一个,K->next = K->next->next 就可以删除掉目标点了。这种方法巧妙的利用了我们所需要的距离差恒定的思想,将一个绝对位置问题改变成了相对位置,这样我们就不再需要第一遍的遍历了。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummy = new ListNode();
dummy->next = head;
ListNode* fast = dummy;
ListNode* slow = dummy;
// we want to iterate to make n gap, so that when we reach, we delete the next one
for(int i = 0; i <= n; i++){
fast = fast -> next;
}
//iterate through until fast reach the end, then we find the target at the slow
while(fast != nullptr){
fast = fast->next;
slow = slow->next;
}
ListNode* toFree = slow->next;
slow->next = toFree->next;
delete toFree;
return dummy->next;
}
};