前言:链表易混淆点:
1.node与node->val不要混淆,node表示整个节点,但是很多时候会错误理解为val
2.node->next只能指向一个地方,改变后之前的指针就没了
3.cur = head,然后操作cur来操作整个链表的原理不清楚:
cur = head这个操作相当于用一个指针变量指向head,但是这里的指向和next指针不同,这里是指一般的指针变量的指向的含义,也就是让cur指向与 head相同的内存位置,我们对cur的操作是可以改变head的
4.有时候会产生一个误解,比如代码中标注位置(二叉树模拟链表):
class Solution {
public:
TreeNode* dummy = new TreeNode(0);
//===============================
TreeNode* cur = dummy->right;
void traverse(TreeNode* root){
if(!root) return;
cur = new TreeNode(root->val);
cur = cur->right;
//================================
traverse(root->left);
traverse(root->right);
}
void flatten(TreeNode* root) {
if(!root)return;
traverse(root);
root->left = nullptr;
root->right = dummy->right;
}
};
我会以为cur先成为dummy的right节点,然后cur被修改为new值,再然后将cur向后移动
错误点:
1.我们不能只考虑next(此处为right),还应该考虑谁指向了当前指针,new值没有指针指向它
2.
一、链表基础操作(重要)
单链表有两种构造方式,一种是使用虚拟头节点,一种是不是虚拟头节点;
使用虚拟头节点可以使得对头节点操作和后面一致,因此后面都使用虚拟头节点创建;
1.1 创建链表节点
struct LinkedNode{
int val;
LinkedNode* next;
LinkedNode(int val): val(val),next(nullptr){}
};
这样创建节点,可以在定义的时候初始化(输入val),更加方便
ListNode* head = new ListNode(5);
1.2 创建链表
MyLinkedList() {
_dummyhead = new LinkedNode(0);
_size = 0;
}
1.3 获取指定下标的元素值
后面会发现有些用的是cur = _dummyhead,而不是本题的cur = _dummyhead->next;这其实无所谓,只要搞清楚我们需要把指针移到哪里就行了,cur = _dummyhead会多移位一次而已;
int get(int index) {
LinkedNode* cur = _dummyhead->next;//要创建一个cur指针来指向要操作的位置,不知道直接操作头指针
//
if(index >= _size || index < 0) return -1;//最后一个节点是size-1
while(index--){
cur = cur->next;//cur指向向后移
}
return cur->val;
}
1.4 头部添加节点(头节点之前添加)
添加节点的时候,指针要指向指定位置之前的节点,所有这里直接使用了虚拟头节点;
void addAtHead(int val) {
LinkedNode* newnode = new LinkedNode(val);
newnode->next = _dummyhead->next;
_dummyhead->next = newnode;
_size++;
}
1.5 在链表尾部插入元素
主要考虑要移动多少次能够移动到最后一个节点;
void addAtTail(int val) {
LinkedNode* newnode = new LinkedNode(val);
LinkedNode* cur = _dummyhead;
while(cur->next != nullptr){
cur = cur->next;
}
cur->next = newnode;
_size++;
}
1.6 在指定节点前面添加节点
因为是在前面添加,所以要操作的指针要指向目标节点的前面一个节点;
void addAtIndex(int index, int val) {
LinkedNode* newnode = new LinkedNode(val);
LinkedNode* cur = _dummyhead;
if(index > _size) return;
if(index < 0 ) index = 0;
while(index--){
cur = cur->next;
}
newnode->next = cur->next;
cur->next = newnode;//
_size++;
}
1.7 删除指定节点
要删除一个节点,也需要把要操作的指针指向目标节点之前;
void deleteAtIndex(int index) {
LinkedNode* cur = _dummyhead;
if(index >= _size || index <0 ) return;//为什么是>=而不是>
while(index--){
cur = cur->next;
}
LinkedNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
tmp = nullptr;
_size--;
}
整个链表下:
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) {
LinkedNode* cur = _dummyhead->next;//要创建一个cur指针来指向要操作的位置,不知道直接操作头指针
if(index >= _size || index < 0) return -1;//最后一个节点是size-1
while(index--){
cur = cur->next;//cur指向向后移
}
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) {
LinkedNode* newnode = new LinkedNode(val);
LinkedNode* cur = _dummyhead;
if(index > _size) return;
if(index < 0 ) index = 0;
while(index--){
cur = cur->next;
}
newnode->next = cur->next;
cur->next = newnode;//
_size++;
}
void deleteAtIndex(int index) {
LinkedNode* cur = _dummyhead;
if(index >= _size || index <0 ) return;//为什么是>=而不是>
while(index--){
cur = cur->next;
}
LinkedNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
tmp = nullptr;
_size--;
}
private:
int _size;
LinkedNode* _dummyhead;
};
一、(补充) 数组模拟链表
1.单链表
首先我们总结使用链表需要的三个数据(尽管链表结构体只有两个参数,但实际需要注意的是三个,参考易混淆点部分),一个是值,一个是下一个元素的地址,最重要的一个是表示当前节点的变量
一般的 : 我们用e[]数组储存链表里面的值,用ne[]数组储存链表每个元素下一个元素的地址,用idx表示某一个节点,idx的数量表示了链表中节点数量,其值表示了某一个节点
注意:idx总是 == 当前节点数 + 1,这样保证每次新建的e【idx】和ne【idx】是新节点
const int N = 1e6+10;
int e[N],ne[N],idx=1; //因为有一个头节点,所以idx为一
ne[0]=-1;
1.在第k个节点插入元素x
void to_insert(int k,int x)
{
e[idx]=x,ne[idx]=ne[k],ne[k]=idx++;
//注意顺序不能翻!!!
}
2.删除第k个节点后面的元素
void remove(int k)
{
ne[k]=ne[[k]];//直接让k<指向的地址>指向k+1<指向的地址>,即把k+1节点跳过
}
2.双链表
双链表的构成:
一个元素有四个数据,一个是元素的值,一个是下一个元素的地址,还有一个是上一个元素的地址,最后一个是当前节点,像这样的链表就是双链表
const int N = 1e6+10;
int e[N],l[N],r[N],idx;
void init()
{
r[0]=1;l[1]=0;idx=2;
}
1.双链表在第k个节点右边插入元素x
void to_insert(int k,int x)
{
e[idx]=x,r[idx]=r[k],l[idx]=k,l[r[k]]=idx,r[k]=idx++;
}
2.删除k节点的数
void remove(int k)
{
l[r[k]]=l[k];r[l[k]]=r[k];
}
二、双指针
2.1 寻找倒数第N个节点
fast比slow多走N步即可;
代码实现:
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummy = new ListNode(0);
dummy->next = head;
ListNode* fast = dummy;
ListNode* low = dummy;
while(n--)
fast = fast->next;
while(fast->next != nullptr){
fast = fast->next;
low = low->next;
}
ListNode* tmp = low->next;
low->next = low->next->next;
delete tmp;
tmp = nullptr;
return dummy->next;
}
};
2.2 链表相交
思路:先让长序列指针移动到和短序列相同位置,然后同时移动,知道指针相等;
注意:指针相等不是元素相等,指针相等时cur1==cur2,元素相等时cur1->val==cur2->val
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode* cur1 = headA;
ListNode* cur2 = headB;
int size1 = 0;
int size2 = 0;
ListNode* nul = new ListNode(0);
nul = nullptr;
while(cur1 !=nullptr){
cur1 = cur1->next;
size1++;
}
while(cur2 !=nullptr){
cur2 = cur2->next;
size2++;
}
cur1 = headA;
cur2 = headB;
if(size1 >= size2){
int k = size1 - size2;
while(k--) cur1 = cur1->next;
while(cur1 != cur2){
cur1 = cur1->next;
cur2 = cur2->next;
}
}else{
int k = size2 - size1;
while(k--) cur2 = cur2->next;
while(cur1 != cur2){
cur1 = cur1->next;
cur2 = cur2->next;
}
}
if(cur1 == nullptr) return nul;
else return cur1;
}
};
2.3 快慢指针(一倍数和两倍速)
1. 寻找单链表的中点
使用一个快指针(2倍数)一个慢指针(1倍数),快指针指到尾端时,慢指针指向中点
代码实现:
lass Solution {
public:
ListNode* middleNode(ListNode* head) {
ListNode* fast = head, *slow = head;
while(fast != nullptr && fast->next != nullptr){
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
};
2. 判断单链表是否包含环并找出环起点
可以使用快慢指针法,分别定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。
fast指针先移动,移到环里面时就一直沿着环循环,slow在后面,最终slow和fast在环内相遇,
那么相遇时: 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+y): x + y = n (y + z)
因为要找环形的入口,那么要求的是x,因为x表示 头结点到 环形入口节点的的距离。
所以要求x ,将x单独放在左面:x = n (y + z) - y
,
再从n(y+z)中提出一个 (y+z)来,整理公式之后为如下公式:x = (n - 1) (y + z) + z
注意这里n一定是大于等于1的,因为 fast指针至少要多走一圈才能相遇slow指针。
这个公式说明什么呢?
先拿n为1的情况来举例,意味着fast指针在环形里转了一圈之后,就遇到了 slow指针了。
当 n为1的时候,公式就化解为 x = z
,
这就意味着,从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点。
也就是在相遇节点处,定义一个指针index1,在头结点处定一个指针index2。
让index1和index2同时移动,每次移动一个节点, 那么他们相遇的地方就是 环形入口的节点。
代码实现:
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* fast = head;
ListNode* low = head;
ListNode* nul = new ListNode(0);
nul = nullptr;
int n = 0;
while(fast != nullptr && fast->next != nullptr){//fast每次跳两步,因此要判断fast->next
fast = fast->next->next;
low = low->next;
if(fast == low) {
ListNode* cur1 = fast;
ListNode* cur2 = head;
while(cur1 != cur2){
cur1 = cur1->next;
cur2 = cur2->next;
}
return cur1;
}//不要在这里写else然后return,不然每次指针移动都会导致返回空
}
return nul;
}
};
2.4 合并分解链表
1.合并两个链表
创建一个新链表存放即可
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
ListNode* cur1 = list1;
ListNode* cur2 = list2;
ListNode* dummyhead = new ListNode(0),*p = dummyhead;
while(cur1 != nullptr && cur2 != nullptr){
if(cur1->val > cur2->val){
p->next = cur2;
p = p->next;
cur2 = cur2->next;
}else{
p->next = cur1;
p = p->next;
cur1 = cur1->next;
}
}
if(cur1 != nullptr){
p->next = cur1;
}
if(cur2 != nullptr){
p->next = cur2;
}
return dummyhead->next;
}
};
2. 分解两个链表
注意要保持相对位置不变
我们创建两个链表,一个存放大于等于x的数,一个存放小于x的数,然后将两个链表合并就行了
链表的连接参考合并链表,但是要注意最后两个链表都链接结束后尾端会链接在一起,需要把队尾置为null
lass Solution {
public:
ListNode* partition(ListNode* head, int x) {
ListNode* p = head;
ListNode* dummyhead1 = new ListNode(0),*p1 = dummyhead1;
ListNode* dummyhead2 = new ListNode(0),*p2 = dummyhead2;
while(p != nullptr){
if(p->val >= x){
p1->next = p;
p1 = p1->next;
p = p->next;
// p1->next = nullptr;//要将指针置为空,防止产生环
}else{
p2->next = p;
p2 = p2->next;
p = p->next;
// p2->next = nullptr;
}
}
p1->next = nullptr;//在这里置为空也可以
p2->next = nullptr;
p2->next = dummyhead1->next;
return dummyhead2->next;
}
};
3. 合并 k 个有序链表
优先级队列,详情见堆栈章节
2.5 反转链表(双指针法:推荐)
首先我们呀知道反转整个链表的操作,部分操作如下图,具体操作见代码随想录 反转链表
那么对于反转链表指定区间,可以将指针先索引到要操作的区间开头,然后利用反转整个链表的思路反转中间部分,最后将前后与中间反转过的部分相连接
代码实现:
class Solution {
public:
ListNode* reverseBetween(ListNode* head, int left, int right) {
ListNode* dummyhead = new ListNode(0);
dummyhead->next = head;
ListNode* cur1 =dummyhead,*cur2=dummyhead;
int l = left-1,k=right-left;
while(l--){
cur1 = cur1->next;
cur2 = cur2->next;
}
ListNode* cur = cur1;
ListNode* curb = cur1->next;
cur1 = cur1->next;
cur2 = cur2->next->next;
while(k--){
ListNode* tmp = cur2->next;
cur2->next = cur1;
cur1 = cur2;
cur2 = tmp;
}
cur->next = cur1;
curb->next = cur2;
return dummyhead->next;
}
};
2.6 回文链表
可以先将指针移到链表中间,然后反转后半部分用来和前半部分比较
代码实现:
class Solution {
public:
bool isPalindrome(ListNode* head) {
ListNode*fast=head,*slow=head;
//令slow指向中点
while(fast!=nullptr && fast->next != nullptr){
fast = fast->next->next;
slow = slow->next;
}
//fast从起点开始
fast = head;
//使用双指针反转slow之后的部分链表,创建新指针moreslow
ListNode* moreslow = slow;
//为了在反转的第一步产生环,需要先把moreslow指向nullptr,但是要注意先把slow移动到后面,不然moreslow指向nullptr后,slow就找不到后面在哪了
slow = slow->next;
moreslow->next = nullptr;
//反转链表
while(slow != nullptr){
ListNode* tmp = slow->next;
slow->next = moreslow;
moreslow = slow;
slow = tmp;
}
//比较是否回文
while(moreslow != nullptr){
if(moreslow->val != fast->val) return false;
moreslow = moreslow->next;
fast = fast->next;
}
return true;
}
};
三、非双指针算法
3.1 两两交换链表中的节点
例:
初始时,cur指向虚拟头结点,然后进行如下三步:
操作之后,链表如下:
然后让cur指向->next()->next();
代码实现:
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* dummy = new ListNode(0);
dummy->next = head;
ListNode* cur = dummy;
while(cur->next != nullptr && cur->next->next != nullptr){
//保存两个节点的值防止覆盖
ListNode* node1 = cur->next;
ListNode* node2 = cur->next->next->next;
//注意按照123顺序防止覆盖
cur->next = node1->next;
cur->next->next = node1;
node1->next = node2;
//然后将指针后移两位
cur = cur->next->next;
}
return dummy->next;
}
};
3.2 反转链表(递归法)
等后面学会递归了再来总结这部分