203.移除链表元素
题意:删除链表中等于给定值 val 的所有节点。
示例 1: 输入:head = [1,2,6,3,4,5,6], val = 6 输出:[1,2,3,4,5]
示例 2: 输入:head = [], val = 1 输出:[]
示例 3: 输入:head = [7,7,7,7], val = 7 输出:[]
代码如下:
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode* dummy = new ListNode(-1);
dummy->next = head;
auto cur = dummy;
while (cur->next) {
if (cur->next->val == val) cur->next = cur->next->next;
else cur = cur->next;
}
return dummy->next;
}
};
707.设计链表
题意:
在链表类中实现这些功能:
- get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
- addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
- addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
- addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
- deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。
代码如下:
class MyLinkedList {
public:
struct Node {
int val;
Node* next;
Node(int _val): val(_val), next(NULL) {}
}*head;
MyLinkedList() {
head = NULL;
}
int get(int index) {
if (index < 0) return -1;
auto p = head;
for (int i = 0; i < index && p; i++) p = p->next;
if (!p) return -1;
return p->val;
}
void addAtHead(int val) {
auto cur = new Node(val);
cur->next = head;
head = cur;
}
void addAtTail(int val) {
if (!head) head = new Node(val);
else {
auto p = head;
while (p->next) p = p->next;
p->next = new Node(val);
}
}
void addAtIndex(int index, int val) {
if (index <= 0) addAtHead(val);
else {
int len = 0;
for (auto p = head; p; p = p->next) len++;
if (index == len) addAtTail(val);
else if (index < len) {
auto p = head;
for (int i = 0; i < index - 1; i++) p = p->next;
auto cur = new Node(val);
cur->next = p->next;
p->next = cur;
}
}
}
void deleteAtIndex(int index) {
int len = 0;
for (auto p = head; p; p = p->next) len++;
if (index >= 0 && index < len) {
if (!index) head = head->next;
else {
auto p = head;
for (int i = 0; i < index - 1; i++) p = p->next;
p->next = p->next->next;
}
}
}
};
206.反转链表
题意:反转一个单链表。
示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL
算法1
(链表操作,迭代) O(n)
翻转即将所有节点的next指针指向前驱节点。
由于是单链表,我们在迭代时不能直接找到前驱节点,所以我们需要一个额外的指针保存前驱节点。同时在改变当前节点的next指针前,不要忘记保存它的后继节点。
空间复杂度分析:遍历时只有3个额外变量,所以额外的空间复杂度是 O(1)
。
时间复杂度分析:只遍历一次链表,时间复杂度是 O(n)
。
代码如下:
/**
* 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 *prev = nullptr;
ListNode *cur = head;
while (cur)
{
ListNode *next = cur->next;
cur->next = prev;
prev = cur, cur = next;
}
return prev;
}
};
算法2
(链表操作,递归) O(n)
首先我们先考虑 reverseList
函数能做什么,它可以翻转一个链表,并返回新链表的头节点,也就是原链表的尾节点。
所以我们可以先递归处理 reverseList(head->next)
,这样我们可以将以head->next
为头节点的链表翻转,并得到原链表的尾节点tail,此时head->next
是新链表的尾节点,我们令它的next
指针指向head
,并将head->next
指向空即可将整个链表翻转,且新链表的头节点是tail
。
空间复杂度分析:总共递归 n
层,系统栈的空间复杂度是 O(n)
,所以总共需要额外 O(n)
的空间。
时间复杂度分析:链表中每个节点只被遍历一次,所以时间复杂度是 O(n)
。
C++ 代码:
/**
* 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) {
if (!head || !head->next) return head;
ListNode *tail = reverseList(head->next);
head->next->next = head;
head->next = nullptr;
return tail;
}
};
24. 两两交换链表中的节点
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
方法一:通过改变结点的指针,达到两两交换链表中的结点的目的
建立虚拟头结点,代码中的p
、a
、b
的初始化位置如上图所示。
代码如下:
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
auto dummy = new ListNode(-1);
dummy->next = head;
for (auto p = dummy; p->next && p->next->next;) {
auto a = p->next, b = a->next;
p->next = b;
a->next = b->next;
b->next = a;
p = a;
}
return dummy->next;
}
};
方法二:通过两两交换链表中的节点的值,达到两两交换链表中的节点的目的
代码如下:
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
if (!head || !head->next) return head;
ListNode* pre = head;
ListNode* cur = head->next;
while (1) {
int temp = pre->val;
pre->val = cur->val;
cur->val = temp;
if(cur->next && cur->next->next) pre = cur->next, cur = pre->next;
else break;
}
return head;
}
};
19. 删除链表的倒数第 N 个结点
力扣题目链接
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
示例 1:
输入:head = [1,2,3,4,5], n = 2 输出:[1,2,3,5]
示例 2:
输入:head = [1], n = 1 输出:[]
示例 3:
输入:head = [1,2], n = 1 输出:[1]
代码如下:
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
auto dummy = new ListNode(-1);
dummy->next = head;
int len = 0;
for (auto p = head; p; p = p->next) len++;
int x = len - n;
auto cur = dummy;
for (int i = 0; i < x; i++) {
cur = cur->next;
}
cur->next = cur->next->next;
return dummy->next;
}
};
160. 链表相交
力扣题目链接
题目描述
给定两个链表,请找它们的交汇点。
注意:
如果两个链表不相交,则返回null
;
在函数结束时,两个链表必须保持原来的结构;
链表中不存在环;
你的代码需要的时间复杂度是O(n),额外的空间复杂度是O(1);
样例
给定如下两个链表:
A: a1 → a2
↘
c1 → c2 → c3
↗
B: b1 → b2 → b3
则交汇点是 c1。
算法
(链表,指针扫描) O(n)
这题的思路很巧妙,我们先给出做法,再介绍原理。
算法步骤:
- 用两个指针分别从两个链表头部开始扫描,每次分别走一步;
- 如果指针走到
null
,则从另一个链表头部开始走; - 当两个指针相同时,
- 如果指针不是
null
,则指针位置就是相遇点; - 如果指针是
null
,则两个链表不相交;
此题我们画图讲解,一目了然:
-
两个链表不相交:
a,b 分别代表两个链表的长度,则两个指针分别走 a+b 步后都变成null
。 -
两个链表相交:
则两个指针分别走 a+b+c 步后在两链表交汇处相遇。
时间复杂度分析:每个指针走的长度不大于两个链表的总长度,所以时间复杂度是 O(n)。
C++ 代码
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode *p = headA, *q = headB;
while (p != q)
{
if (p) p = p->next;
else p = headB;
if (q) q = q->next;
else q = headA;
}
return p;
}
};
142. 环形链表 II
力扣题目链接
给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改链表。
解题思路:
(链表,快慢指针扫描) O(n)
本题的做法比较巧妙。
用两个指针 fast
,slow
同时从起点开始走,slow
每次走一步,fast
每次走两步。
如果过程中fast
走到null
,则说明不存在环。否则当slow
和fast
相遇后,让fast
返回起点,slow
待在原地不动,然后两个指针每次分别走一步,当两个指针再次相遇时,相遇点就是环的入口。
证明:如上图所示,a
是起点,b
是环的入口,c
是两个指针的第一次相遇点,ab
之间的距离为 x
,bc
之间的距离是y
。
则当slow
走到b
时,由于fast
比slow
多走一倍的路,所以fast
已经从b
开始在环上走了x
步到d
,可能多余1圈,距离b
还差y
步(这是因为第一次相遇点在b
之后y
步,我们让slow
退回到b
点,则fast
会退2y
步到d
点,也就是距离b
点还差y
步);所以fast
从b
点走x+y
步即可回到b
点,所以fast
从c
点开始走,走x
步即可恰好走到b
点,同时让slow
从头开始走,走x
步也恰好可以走到b
点。所以第二次相遇点就是b
点。
时间复杂度分析:slow
总共走了2x+y
步,fast
总共走了2x+2y+x
步,所以两个指针总共走了5x+3y
步。由于当第一次slow
走到b
点时,fast
最多追一圈即可追上slow
,所以y
小于环的长度,所以x+y
小于等于链表总长度。所以总时间复杂度是O(n)
。
代码如下:
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
if (!head || !head->next) return 0;
ListNode *slow = head, *fast = head;
while (fast) {
slow = slow->next;
fast = fast->next;
if (fast) fast = fast->next;
else return NULL;
if (fast == slow) {
slow = head;
while (fast != slow) {
fast = fast->next;
slow = slow->next;
}
return fast;
}
}
return NULL;
}
};