给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
示例:
给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.
说明:
给定的 n 保证是有效的。
【思路】双指针
/**
* 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) {
auto dummy = new ListNode(-1); // 创建虚拟头结点
dummy->next = head;
auto fast = dummy, slow = dummy;
while(n--)
fast = fast->next; // 快指针走n步
while(fast->next){
fast = fast->next;
slow = slow->next;
}
// 当快指针走到最后一个节点时,慢指针在倒数第n+1的节点
slow->next = slow->next->next;
return dummy->next;
}
};
/*先扫描一遍链表,求出链表长度,走到倒数第k+1的位置,即走n-k-1步,删掉下一个节点*/
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int k) {
auto dummy = new ListNode(-1);
dummy->next = head; // 创建虚拟头结点
int n = 0; // 记录链表的长度
for(auto p = dummy; p; p = p->next) n++;
auto p = dummy;
for(int i = 0; i < n - k - 1; i++) p = p->next; // 走到倒数第k+1个点
p->next = p->next->next;
return dummy->next;
}
};
// 时间复杂度均为O(n)
反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
进阶:
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?
【思路】
方法一:迭代
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode *pre = NULL, *cur = head;
while(cur){
auto next = cur->next;
cur->next = pre;
pre = cur;
cur = next;
}
return pre;
}
};
方法二:递归
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if(!head || !head->next) return head;
auto tail = reverseList(head->next); // tail:5
head->next->next = head; // 2->1
head->next = NULL; // 1->NULL
return tail;
}
};
在 O ( n l o g n ) O(n log n) O(nlogn) 时间复杂度和常数级空间复杂度下,对链表进行排序。
示例 :
输入: 4->2->1->3
输出: 1->2->3->4
输入: -1->5->3->4->0
输出: -1->0->3->4->5
【思路】只能用自底向上的非递归迭代式归并排序
求链表长度
第一次,将整个区间分成连续的若干段,每段长度是2: [ a 0 , a 1 ] , [ a 2 , a 3 ] , [ a 2 , a 3 ] , … , [ a n − 1 , a n − 1 ] [a_0,a_1],[a_2,a_3],[a_2,a_3],…,[a_{n−1},a_{n−1}] [a0,a1],[a2,a3],[a2,a3],…,[an−1,an−1], 然后将每一段内排好序,小数在前,大数在后;
第二次,将整个区间分成连续的若干段,每段长度是4: [ a 0 , … , a 3 ] , [ a 4 , … , a 7 ] , … , [ a n − 4 , … , a n − 1 ] [a_0,…,a_3],[a_4,…,a_7],…,[a_{n−4},…,a_{n−1}] [a0,…,a3],[a4,…,a7],…,[an−4,…,an−1],然后将每一段内排好序,这次排序可以利用之前的结果,相当于将左右两个有序的半区间合并,可以通过一次线性扫描来完成;
依此类推,直到每段小区间的长度大于等于 n n n 为止;
另外,当 n n n 不是2的整次幂时,每次迭代只有最后一个区间会比较特殊,长度会小一些,遍历到指针为空时需要提前结束。
时间复杂度分析:整个链表总共遍历 l o g n logn logn 次,每次遍历的复杂度是 O ( n ) O(n) O(n),所以总时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn)。
空间复杂度分析:整个算法没有递归,迭代时只会使用常数个额外变量,所以额外空间复杂度是 O ( 1 ) O(1) O(1)。
/**
* 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* sortList(ListNode* head) {
int n = 0;
for(auto p = head; p; p = p->next) n++; // 求链表长度
auto dummy = new ListNode(-1); // 创建虚拟头结点
dummy->next = head;
for(int i = 1; i < n; i *= 2){ // 从1开始枚举划分链表长度
auto cur = dummy; // 从第一个结点开始每次从前往后扫描每一个长度为2i的段
for(int j = 1; j + i <= n; j += 2*i){ // 每次枚举所有相应两个长度为i的区间
auto p = cur->next, q = p; // 找到当前两个区间的头结点
for(int k = 0; k < i; k++) q = q->next; // q为下一段区间的头结点
// 二路归并
int l = 0, r = 0;
while(l < i && r < i && p && q){ // 最后一段可能不全,要保证q不为空
if(p->val <= q->val) cur = cur->next = p, p = p->next, l++;
else cur = cur->next = q, q = q->next, r++;
}
// 将剩余链表接上
while(l < i && p) cur = cur->next = p, p = p->next, l++;
while(r < i && q) cur = cur->next = q, q = q->next, r++;
cur->next = q; // 下一段开始的位置
}
}
return dummy->next;
}
};