文章目录
19.删除链表倒数第n个节点
思路:因为要求只用一趟扫描,所以采用双指针算法
1.由于题目没有给定头结点,建立一个虚拟节点head避免删除头结点时的问题
2.使first和second指针距离n+1,然后同时向后移动两个指针,直到second指向null
3.删除first后面的节点
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
auto dummy=new ListNode(-1); //创建虚拟头结点
dummy->next=head;
auto first=dummy,second=dummy; //注意双指针的初值必须赋值成虚拟头结点,否则无法正确删除第一个节点
while(n--)second=second->next; //second 后移n位
while(second->next){
first=first->next;
second=second->next;
}
first->next=first->next->next; //删除倒数第n个节点
return dummy->next; //返回头结点
}
};
237.删除链表中的节点
删除链表中任意给定的节点,由于不知道前一个指针的指向,只需要把当前节点复制成后一个节点,然后再把后一个节点删除即可
1.复制node->next的值给node
2.删除node->next
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
void deleteNode(ListNode* node) {
node->val=node->next->val;
node->next=node->next->next; //此处可以利用c++特性讲两语句合并成一句*(node)=*(node->next); 作用与两句话相同
}
};
83.删除排序链表中的重复元素
思路:让指针P从头往后遍历,遇到val相同时则删除后一个节点,遇到不同时则指向下一个节点
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if(!head)return head; //节点为空返回
ListNode * P=head;
while(P->next){ //这里写P->next防止报空指针错误(如果P指向最后一个节点,那么P->next—>val会出现null.val的空指针错误)
if(P->val==P->next->val)P->next=P->next->next;
else P=P->next;
}
return head;
}
};
61.旋转链表
思路:利用双指针算法
1.k%n去掉不必要的移动步骤
2.first走k步
3.first和second同时向后走,直到first指向末尾节点,此时second即为倒数第k个节点
4.first指向head,second指向null即可
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* rotateRight(ListNode* head, int k) {
if(!head)return NULL;
//遍历得到节点总数
int n=0;
for(auto P=head;P;P=P->next)n++;
k%=n;
//first是后节点,second是前节点
auto first =head,second=head;
while(k--)first=first->next;
while(first->next){
first=first->next;
second=second->next;
}
//后节点指向头,前节点断开指null
first->next=head;
head=second->next;
second->next=NULL;
return head;
}
};
24.两两交换链表中的节点
思路:先建立虚拟节点dummy
1.a,b指向要交换的两个节点
2.执行以下步骤
3.由于p的位置不会变化,移动p到a的位置,重复上述步骤
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
if(!head)return head;
auto dummy=new ListNode(0);
dummy->next=head;
for(auto p=dummy;p->next&&p->next->next;){ //保证p的下一个及下下个节点都存在
auto a=p->next,b=a->next;
p->next=b;
a->next=b->next;
b->next=a; //交换
p=a; //p移动到下一个要交换的一对节点前
}
return dummy->next;
}
};
206.反转链表
思路:题意需要反转链表,采用将从头到尾的节点指向全部倒置,最后取尾节点做新头结点的方法
1.初始化:a指向头,b指向a->next,a->next指向null
2.while(b)c指向b->next
3.交换指向b->next=a,然后a,b,c往后移动一位
4.返回a
/**
* 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)return head;
auto a=head,b=a->next;
a->next=NULL; //原头结点指向null
while(b){
auto c=b->next; //反转链表
b->next=a;
a=b,b=c;
}
return a;
}
};
92.反转链表II
思路如图所示,由于m,n使用过,用b,d代替
并且在反转链表时用p,q,o表示相邻的三个节点
class Solution {
public:
ListNode* reverseBetween(ListNode* head, int m, int n) {
if(m==n)return head;
auto dummy =new ListNode(0);
dummy->next=head;
auto a=dummy,d=dummy;
for(int i=0;i<m-1;i++)a=a->next;
for(int i=0;i<n;i++)d=d->next;
auto b=a->next,c=d->next;
auto p=b,q=p->next;
//反转链表,同上题,pqo是交换时的三个节点
while(q!=c){
auto o=q->next;
q->next=p;
p=q,q=o;
}
b->next=c;
a->next=d;
return dummy->next;
}
};
160.相交链表 **
思路:用两个指针分别从头开始扫描,每次只走一步,当走到null时仍然没有相遇,则交换头结点扫描,直到相遇 为止
1.扫描两个不相交的链表,则交换头结点后最后两个指针同时指向null,两节点均扫描a+b步
2.若扫描两个相交链表若链表1长a+c,链表2长b+c
则最坏情况下扫描a+b+c时两指针相遇,当a=b时,两节点会提前相遇
/**
* 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) {
auto p=headA,q=headB;
while(p!=q){
if(p)p=p->next;
else p=headA;
if(q)q=q->next;
else q=headB;
}
return p;
}
};
142.环形链表II **
思路:如图设置一个快指针fast一个慢指针slow,快指针每次走两步,慢指针每次走一步
1.如果快指针会无路可走(指向null)说明不存在环
2.否则让快慢指针同时前进,fast先进圈,当slow到达b时,fast在里面走了x位于c’,假设c’距离b是y,此时让fast开始追slow,那么fast会在距离b点y的对称点c又追上slow,这时让fast回到起点a,两指针开始以相同速度向后移动,则最后会相遇在b点
注解:相当于将相遇点c向后转动x正好到达环入口
/**
* 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) {
auto fast=head,slow=head;
while(fast){
slow=slow->next;
fast=fast->next;
if(fast)fast=fast->next;
else break; //不存在环,跳出
if(fast==slow){ //首次相遇
fast=head;
while(fast!=slow){ //再次相遇
fast=fast->next;
slow=slow->next;
}
return fast;
}
}
return NULL;
}
};
148.排序链表**
因为题目要求的时间复杂度是O(nlogn)空间复杂度是O(1)
所以满足的排序方式只有快排和归并,但又不能调用系统栈…所以只能手写归并
思路:自底向上的归并排序,就是原来用递归去写,是系统调用栈完成的循环,现在直接改自己用循环手写了
- 另外说明一下特殊情况,以链表长度是7这种特殊情况为例:
- 当要把区间分为长度是4时,第二个区间长度只有3,但是依然对这个区间进行归并排序,(因为可以分成左2右1的两个部分),但如果 链表长度是6,第二个区间长度只有2,不满足条件,所以第二个区间就不进行排序了,(实际上已经由上一层归并排序完成)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* sortList(ListNode* head) {
auto dummy =new ListNode(0);
dummy->next=head;
int n=0;
for(auto p=head;p;p=p->next)n++; //链表长
//从左右部分长度是1开始归并,区间长是2,往后加倍
for(int i=1;i<n;i*=2){
auto cur=dummy; //每次归并起点的前一个点
//计算次数,例如i=2时,每次归并应当至少需要三个节点,至多四个节点,所以3<=n<=6时只排一次7<=n<=10排两次...
for(int j=1;j+i<=n;j+=i*2){
auto left=cur->next,right=cur->next; //左右部分头结点
for(int k=0;k<i;k++)right=right->next; //调整右边部分指向
int l=0,r=0; //l,r至多为左右部分长度,但右半部分长度可能不够
while(l<i&&r<i&&right){
if(left->val<=right->val){
cur->next=left;
cur=left;
left=left->next;
l++;
}
else{
cur->next=right;
cur=right;
right=right->next;
r++;
}
}
//补足未插入的节点
while(l<i){
cur->next=left;
cur=left;
left=left->next;
l++;
}
while(r<i&&right){
cur->next=right;
cur=right;
right=right->next;
r++;
}
//每次归并结束后right指向下一个区间的第一个节点
cur->next=right;
}
}
return dummy->next;
}
};
另外附上链表归并排序的递归写法
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* sortList(ListNode* head) {
if(!head||!head->next)return head; //保证被排序区间至少有两个节点
auto slow=head,fast=head->next;
//快慢指针寻找中间节点
while(fast->next&&fast->next->next){
slow=slow->next;
fast=fast->next->next;
}
//head2是归并右半部分
auto head2=slow->next;
slow->next=NULL;
//递归处理左右两边
head=sortList(head);
head2=sortList(head2);
//处理最后一层区间
return merge(head,head2);
}
//归并排序
ListNode * merge(ListNode* list1,ListNode* list2){
auto dummy=new ListNode(0);
auto cur=dummy;
auto left=list1,right=list2;
while(left&&right){
if(left->val<=right->val){
cur->next=left;
left=left->next;
}
else{
cur->next=right;
right=right->next;
}
cur=cur->next;
}
if(left)cur->next=left;
else cur->next=right;
return dummy->next;
}
};