目录
一、移除链表元素
203. 移除链表元素 - 力扣(LeetCode)https://leetcode.cn/problems/remove-linked-list-elements/description/
给你一个链表的头节点
head
和一个整数val
,请你删除链表中所有满足Node.val == 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
输出:[]
思路:
设置一个虚拟头结点移除节点操作。最后 return 头结点的时候,应为
return h->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* removeElements(ListNode* head, int val) {
ListNode* h = new ListNode(0); // 头
h->next = head;
ListNode* cur = h;
while(cur->next != NULL)
{
if(cur->next->val == val)
{
// 删除后面的一个节点
ListNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
}
else
{
cur = cur->next;
}
}
return h->next;
}
};
二、设计链表
707. 设计链表 - 力扣(LeetCode)https://leetcode.cn/problems/design-linked-list/description/
选择使用单链表或者双链表,设计并实现自己的链表。
单链表中的节点应该具备两个属性:
val
和next
。val
是当前节点的值,next
是指向下一个节点的指针/引用。如果是双向链表,则还需要属性
prev
以指示链表中的上一个节点。假设链表中的所有节点下标从 0 开始。
实现 MyLinkedList
类:
MyLinkedList()
初始化MyLinkedList
对象。int get(int index)
获取链表中下标为index
的节点的值。如果下标无效,则返回-1
。void addAtHead(int val)
将一个值为val
的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。void addAtTail(int val)
将一个值为val
的节点追加到链表中作为链表的最后一个元素。void addAtIndex(int index, int val)
将一个值为val
的节点插入到链表中下标为index
的节点之前。如果index
等于链表的长度,那么该节点会被追加到链表的末尾。如果index
比长度更大,该节点将 不会插入 到链表中。void deleteAtIndex(int index)
如果下标有效,则删除链表中下标为index
的节点。
示例:
输入
["MyLinkedList", "addAtHead", "addAtTail", "addAtIndex", "get", "deleteAtIndex", "get"]
[[], [1], [3], [1, 2], [1], [1], [1]]
输出
[null, null, null, null, 2, null, 3]
解释
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addAtHead(1);
myLinkedList.addAtTail(3);
myLinkedList.addAtIndex(1, 2); // 链表变为 1->2->3
myLinkedList.get(1); // 返回 2
myLinkedList.deleteAtIndex(1); // 现在,链表变为 1->3
myLinkedList.get(1); // 返回 3
class MyLinkedList {
public:
// 定义链表节点结构体
struct LinkedNode {
int val;
LinkedNode* next;
LinkedNode(int val): val(val), next(nullptr){}
};
// 初始化链表
MyLinkedList() {
_dummyHead = new LinkedNode(0); // 虚拟头结点
_size = 0;
}
// 获取到第index个节点数值,如果index是非法数值直接返回-1, 注意index是从0开始的,第0个节点就是头结点
int get(int index) {
if (index > (_size - 1) || index < 0) {
return -1;
}
LinkedNode* cur = _dummyHead->next;
while(index--){ // 如果--index 就会陷入死循环
cur = cur->next;
}
return cur->val;
}
// 在链表最前面插入一个节点,插入完成后,新插入的节点为链表的新的头结点
void addAtHead(int val) {
LinkedNode* newNode = new LinkedNode(val);
newNode->next = _dummyHead->next;
_dummyHead->next = newNode;
_size++;
}
// 在链表最后面添加一个节点
void addAtTail(int val) {
LinkedNode* newNode = new LinkedNode(val);
LinkedNode* cur = _dummyHead;
while(cur->next != nullptr){
cur = cur->next;
}
cur->next = newNode;
_size++;
}
// 在第index个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
// 如果index 等于链表的长度,则说明是新插入的节点为链表的尾结点
// 如果index大于链表的长度,则返回空
// 如果index小于0,则在头部插入节点
void addAtIndex(int index, int val) {
if(index > _size) return;
if(index < 0) index = 0;
LinkedNode* newNode = new LinkedNode(val);
LinkedNode* cur = _dummyHead;
while(index--) {
cur = cur->next;
}
newNode->next = cur->next;
cur->next = newNode;
_size++;
}
// 删除第index个节点,如果index 大于等于链表的长度,直接return,注意index是从0开始的
void deleteAtIndex(int index) {
if (index >= _size || index < 0) {
return;
}
LinkedNode* cur = _dummyHead;
while(index--) {
cur = cur ->next;
}
LinkedNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
//delete命令指示释放了tmp指针原本所指的那部分内存,
//被delete后的指针tmp的值(地址)并非就是NULL,而是随机值。也就是被delete后,
//如果不再加上一句tmp=nullptr,tmp会成为乱指的野指针
//如果之后的程序不小心使用了tmp,会指向难以预想的内存空间
tmp=nullptr;
_size--;
}
// 打印链表
void printLinkedList() {
LinkedNode* cur = _dummyHead;
while (cur->next != nullptr) {
cout << cur->next->val << " ";
cur = cur->next;
}
cout << endl;
}
private:
int _size;
LinkedNode* _dummyHead;
};
三、反转链表
206. 反转链表 - 力扣(LeetCode)https://leetcode.cn/problems/reverse-linked-list/description/
给你单链表的头节点
head
,请你反转链表,并返回反转后的链表。
示例 1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
示例 2:
输入:head = [1,2]
输出:[2,1]
示例 3:
输入:head = []
输出:[]
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* reverseList(ListNode* head) {
ListNode* pre = nullptr;
ListNode* cur = head;
ListNode* tmp;
while(cur != nullptr)
{
tmp = cur->next;
cur->next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
};
首先定义一个cur指针,指向头结点,再定义一个pre指针,初始化为null。然后开始反转,首先要把 cur->next 节点用tmp指针保存。接下来,就是循环走如下代码逻辑了,继续移动pre和cur指针。最后,cur 指针已经指向了null,循环结束,链表也反转完毕了。 此时 pre指针就指向了新的头结点。
2. 递归法
/**
* 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* reverse(ListNode* pre, ListNode* cur)
{
if(cur == nullptr) {
return pre;
}
ListNode* tmp = cur->next;
cur->next = pre;
return reverse(cur, tmp);
}
ListNode* reverseList(ListNode* head) {
// 递归法
return reverse(nullptr, head);
}
};
四、两两交换链表中的节点
24. 两两交换链表中的节点 - 力扣(LeetCode)https://leetcode.cn/problems/swap-nodes-in-pairs/description/
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:
输入:head = [1,2,3,4]
输出:[2,1,4,3]
示例 2:
输入:head = []
输出:[]
示例 3:
输入:head = [1]
输出:[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* swapPairs(ListNode* head) {
if(head == nullptr) return nullptr;
if(head->next == nullptr) return head;
ListNode* h = new ListNode(0);
h->next = head;
ListNode* cur = h;
while(cur->next != nullptr && cur->next->next != nullptr)
{
ListNode* nxt = cur->next;
// 交换
cur->next = nxt->next;
nxt->next = nxt->next->next;
cur->next->next = nxt;
cur = nxt;
}
ListNode* result = h->next; // 删除头节点
delete h;
return result;
}
};
五、删除链表的倒数第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]
思路:
(1)定义 fast 指针 和 slow 指针,初始值为 虚拟头结点。
(2)fast 首先走 n + 1 步 ,为什么是 n+1 呢,因为 只有这样同时移动的 时候 slow 才能 指向删除节点的 上一个节点(方便做 删除操作)。
(3)fast 和 slow 同时移动,直到 fast 指向末尾。
(4)删除 slow 指向的下一个节点。
/**
* 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) {
if(head->next == nullptr && n == 1) return nullptr;
if(head->next == nullptr && n == 0) return head;
// 快满指针
ListNode* h = new ListNode(0);
h->next = head;
ListNode* left = h;
ListNode* right = h;
while(n--)
{
right = right->next;
}
while(right->next != nullptr)
{
right = right->next;
left = left->next;
}
if(right->next == nullptr)
{
ListNode* tmp = left->next;
left->next = left->next->next;
delete tmp;
}
return h->next;
}
};
六、面试题 02.07.链表相交
给你两个单链表的头节点
headA
和headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回null
。
示例 1:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
示例 2:
输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。
由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
这两个链表不相交,因此返回 null 。
思路:
设 curA 指向 链表 A 的头结点,curB 指向 链表 B 的头结点。求出两个链表的长度,并求出两个链表长度的 差值,然后让 curA 移动到,和 curB 末尾对齐的 位置。此时就可以比较 curA 和 curB 是否相同,如果 不相同,同时向后移动 curA 和 curB,如果遇到 curA == curB,则找到交点。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
// 只能先对齐
int lenA = 0;
int lenB = 0;
ListNode* curA = headA;
ListNode* curB = headB;
int relenA = 0; // A、B 对应得下标值点
int relenB = 0;
// 计算长度
while(curA != NULL)
{
curA = curA->next;
lenA++;
}
while(curB != NULL)
{
curB = curB->next;
lenB++;
}
int len = lenA - lenB;
if(len > 0)
{
// A 长
relenA += len;
while(len--)
{
headA = headA->next;
}
}
else if(len < 0)
{
// B 长
len = abs(len);
relenB += len;
while(len--)
{
headB = headB->next;
}
}
// 判断
while(headA != NULL && headB != NULL)
{
if(headA == headB)
{
return headA;
}
headA = headA->next;
headB = headB->next;
relenA++;
relenB++;
}
return NULL;
}
};
七、环形链表II
142. 环形链表 II - 力扣(LeetCode)https://leetcode.cn/problems/linked-list-cycle-ii/description/
给定一个链表的头节点
head
,返回链表开始入环的第一个节点。 如果链表无环,则返回null
。如果链表中有某个节点,可以通过连续跟踪
next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果pos
是-1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。不允许修改 链表。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。
思路:
可以使用快慢指针法,分别定义 fast 和 slow 指针,从头结点出发,fast指 针 每次移动 两个节点,slow 指针 每次移动 一个节点,如果 fast 和 slow指针在途中 相遇 ,说明这个 链表有环。
fast 指针一定先进入环中,如果 fast 指针 和 slow 指针相遇的话,一定是 在环中相遇,这是毋庸置疑 的。
因为 fast 是走两步,slow 是走一步,其实相对于 slow 来说,fast 是一个节点一个节点的 靠近 slow 的,所以 fast 一定 可以 和 slow 重合。
假设从 头结点到环形入口节点 的节点数为 x。 环形入口节点到 fast 指针 与 slow 指针 相遇节点 节点数为 y。 从 相遇节点 再到环形入口 节点节点数为 z。如下图:
那么相遇时: slow 指针 走过的节点数为:
x + y
, fast 指针走过的节点数:x + y + n (y + z)
,n 为 fast 指针在环内走了 n 圈 才遇到 slow 指针, (y+z)为 一圈内节点的个数 A。因为 fast 指针 是一步走两个节点,slow 指针 一步走一个节点, 所以 fast 指针 走过的节点数 = slow 指针走过的节点数 * 2:
(x + y) * 2 = x + y + n (y + z)
。所以x = n (y + z) - y
。整理公式得:x = (n - 1) (y + z) + z
,这里 n 一定是大于 等于 1 的,因为 fast 指针 至少要多 走一圈才能 相遇 slow 指针。当 n 为 1 的时,公式就化解为
x = z
,这表示 fast 指针 在环形里 转了一圈 之后,就 遇到了 slow 指针 了。所以,从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
// 快慢指针
ListNode* slow = head;
ListNode* fast = head;
while(fast != NULL && fast->next != NULL)
{
slow = slow->next;
fast = fast->next->next;
// 环内相遇节点
if(slow == fast)
{
ListNode* index1 = head;
ListNode* index2 = slow;
while(index1 != index2)
{
index1 = index1->next;
index2 = index2->next;
}
return index1;
}
}
return NULL;
}
};