1.理论基础
链表节点的定义:
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) {}
};
根据卡哥提示,由于力扣中已经给出如上节点定义,所以平时刷题时确实可能会忽略这部分。下面引用卡哥关于此话题的原文(感谢卡哥):
“有同学说了,我不定义构造函数行不行,答案是可以的,C++默认生成一个构造函数。
但是这个构造函数不会初始化任何成员变量,下面我来举两个例子:
通过自己定义构造函数初始化节点:
ListNode* head = new ListNode(5);
使用默认构造函数初始化节点:
ListNode* head = new ListNode();
head->val = 5;
所以如果不定义构造函数使用默认构造函数的话,在初始化的时候就不能直接给变量赋值!”
2 力扣203.移除链表元素
题目描述:
给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
实际头结点:
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
//剪枝,由Case:7、7、7、7而来,融合了head->null链
while (head && head->val == val) {
ListNode* tmp = head;
head = head->next;
delete tmp;
}
if (!head) return head;
ListNode* p = head;
while (p->next) {
if (p->next->val != val) p = p->next;
else {
ListNode* tmp = p->next;
p->next = p->next->next;
delete tmp;
}
}
return head;
}
};
虚拟头结点:
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode* dummyhead = new ListNode(0);
dummyhead->next = head;
ListNode* p = dummyhead;
while (p->next) {
if (p->next->val != val)p = p->next;
else {
ListNode* tmp = p->next;
p->next = p->next->next;
delete tmp;
}
}
head = dummyhead->next;
delete dummyhead;
return head;
}
};
3 力扣707.设计链表
题目描述:
class MyLinkedList {
public:
struct LinkedNode {
int val;
LinkedNode* next;
LinkedNode(int val):val(val), next(nullptr){}
};
MyLinkedList() {
_dummyHead = new LinkedNode(0);
_size = 0;
}
int get(int index) {
if (index > (_size - 1) || index < 0) {
return -1;
}
LinkedNode* cur = _dummyHead->next;
while(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++;
}
void addAtIndex(int index, int val) {
if (index > _size) {
return;
}
LinkedNode* newNode = new LinkedNode(val);
LinkedNode* cur = _dummyHead;
while(index--) {
cur = cur->next;
}
newNode->next = cur->next;
cur->next = newNode;
_size++;
}
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;
_size--;
}
private:
int _size;
LinkedNode* _dummyHead;
};
4 力扣206.反转链表
题目描述:
三指针
尾节点指向nullptr不可省略
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (!head || !head->next) return head;
ListNode* p = head;
if (!head->next->next) {
head = head->next;
head->next = p;
p->next = nullptr;
return head;
}
ListNode* q = p->next;
ListNode* r = q->next;
p->next = nullptr;
while (r) {
q->next = p;
p = q;
q = r;
r = r->next;
}
q->next = p;
head = q;
return head;
}
};
正向递归:
class Solution {
public:
ListNode* reverse(ListNode* pre,ListNode* cur){
if(cur == NULL) return pre;
ListNode* temp = cur->next;
cur->next = pre;
// 可以和双指针法的代码进行对比,如下递归的写法,其实就是做了这两步
// pre = cur;
// cur = temp;
return reverse(cur,temp);
}
ListNode* reverseList(ListNode* head) {
// 和双指针法初始化是一样的逻辑
// ListNode* cur = head;
// ListNode* pre = NULL;
return reverse(NULL, head);
}
};
反向递归:
class Solution {
public:
ListNode* reverseList(ListNode* head) {
// 边缘条件判断
if(head == NULL) return NULL;
if (head->next == NULL) return head;
// 递归调用,翻转第二个节点开始往后的链表
ListNode *last = reverseList(head->next);
// 翻转头节点与第二个节点的指向
head->next->next = head;
// 此时的 head 节点为尾节点,next 需要指向 NULL
head->next = NULL;
return last;
}
};
5 力扣19.删除链表的倒数第N个节点
题目描述:
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
双指针:
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyhead = new ListNode(0);
dummyhead->next = head;
ListNode* p = dummyhead;
ListNode* q = head;
while (n--)q = q->next;
if (!q) {
ListNode* tmp = head;
head = head->next;
delete tmp;
return head;
}
while (q) {
p = p->next;
q = q->next;
}
ListNode* tmp = p->next;
p->next = p->next->next;
delete tmp;
return head;
}
};
6 力扣142.环形链表II
题目描述:
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
class Solution {
public:
ListNode* detectCycle(ListNode* head) {
ListNode* p = head;
ListNode* s = head;
ListNode* f = head;
if (!head || !head->next) return NULL;
do {
s = s->next;
f = f->next->next;
} while (s != f && f && f->next);
if (!f || !f->next) return NULL;
while (s != p) {
s = s->next;
p = p->next;
}
return p;
}
};
本题比较有趣的是采用步长为2的快指针f和步长为1的慢指针s会相遇在环中一个节点,这个节点到环的入口的节点数等于head到环的入口的节点数。然后再从相遇点派出一个速度为1 的指针(s即可),同时,从head也派出一个p,它们就会在环的入口相遇。
7 力扣24. 两两交换链表中的节点
题目描述:
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
四指针:
s作为上一对尾,p和q作为交换对,r作为下一对头
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* p = head;
if (!head || !head->next) return head;
ListNode* q = head->next;
ListNode* r = head->next->next;
q->next = p;
p->next = r;
head = q;
while (r && r->next) {
ListNode* s = p;
p = p->next;
q = r->next;
r = r->next->next;
q->next = p;
p->next = r;
s->next = q;
}
return head;
}
};
8 力扣面试题 02.07. 链表相交
题目描述:
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
图示两个链表在节点 c1 开始相交:
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
class Solution {
public:
ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) {
ListNode* p = headA;
ListNode* q = headB;
int cntA = 0, cntB = 0;
while (p) {
p = p->next;
cntA++;
}
while (q) {
q = q->next;
cntB++;
}
delete p, q;
ListNode* a = headA;
ListNode* b = headB;
int d = cntA > cntB ? (cntA - cntB) : (cntB - cntA);
if (cntA > cntB) while (d--) a = a->next;
else while (d--) b = b->next;
while (a != b) {
a = a->next;
b = b->next;
}
return a;
}
};