判断链表是否为回文的,即从左往右看和从右往左看一样。
例如 1->2->2->1是回文的
1->2->3->2->1是回文的
1->3->2->1不是回文的
简单做法:首先准备一个栈,将链表的元素从左往右依次入栈,然后重新对比链表和栈顶元素。
双指针法:首先找到链表的中间位置mid,可以通过快慢指针实现
然后扭转mid以后的结点,mid的下一个结点指向空,然后两个指针从两边依次对比,最后再把链表恢复。
bool isHuiWen(Node* head) {//head->next是链表第一个元素
Node* f = head;
Node* s = head;
while (f!=NULL && f->next != NULL) {
f = f->next->next;
s = s->next;
}
//此时s指向的就是中间结点,奇数个结点只有唯一一个中点,偶数个结点取中间两个都可以,这里取左边的
Node* pre = NULL;
Node* p = s;
while (p != NULL) {//扭转结点
Node* next = p->next;
p->next = pre;
pre = p;
p = next;
}
//左右对比
Node* l = head->next;
Node* r = pre;
bool flag = true;
while (l != NULL && r != NULL) {
if (l->data != r->data) {
flag = false;
break;
}
l = l->next;
r = r->next;
}
//此时pre是最右边的结点
p = pre;
pre = NULL;
while (p != NULL) {//恢复链表
Node* next = p->next;
p->next = pre;
pre = p;
p = next;
}
return flag;
}
给你一个链表,长度为偶数,假设链表为:L1->L2->L3->L4->R1->R2->R3->R4
要求将链表变为:L1->R4->L2->R3->L3->R2->L4->R1
分析:可以用到上一个题的双指针做法,先将链表转化为:
准备两个指针l和r分别指向链表两端,每次都穿插一个r结点。
void changeLink(Node* head) {
Node* fast = head;
Node* slow = head;
while (fast != NULL && fast->next != NULL) {
fast = fast->next->next;
slow = slow->next;
}
Node* mid = slow;
Node* pre = NULL;
Node* p = mid;
while (p != NULL) {
Node* next = p->next;
p->next = pre;
pre = p;
p = next;
}
Node* l = head->next;
Node* r = pre;
while (r!=mid) {
Node* lnext = l->next;
Node* rnext = r->next;
l->next = r;
r->next = lnext;
l = lnext;
r = rnext;
}
}
链表划分:给定一个链表的头结点和划分值v,要求小于v的结点在左边,等于v的在中间,大于v的在右边(类似于快排的划分)
解1:将链表放数组,做划分,然后在用链表串起来。
解2:只用有限个变量:
准备6个指针分别是:小于区域的头指针和尾指针,等于区域的头指针和尾指针,大于区域的头指针和尾指针。初始都为空
然后从左往右遍历链表,如果小于v,则判断小于区域的头指针是否空,若为空,则头指针和尾指针都指向该结点,否则,尾指针的next指向该结点,尾指针指向该结点……
最后小于区域的尾指针指向等于区域的头指针,等于区域的尾指针指向大于区域的头指针。
有陷阱:小于区域可能为空,等于区域也可能为空……
Node* change(Node* head,int v) {
Node* sHead = NULL;
Node* sTail = NULL;
Node* mHead = NULL;
Node* mTail = NULL;
Node* eHead = NULL;
Node* eTail = NULL;
Node* p = head;
while (p != NULL) {
Node* next = p->next;
if (p->val < v) {
if (sHead == NULL) {
sHead = p;
sTail = p;
}
else {
sTail->next = p;
sTail = p;
}
}
else if (p->val == v) {
if (mHead == NULL) {
mHead = p;
mTail = p;
}
else {
mTail->next = p;
mTail = p;
}
}
else {
if (eHead == NULL) {
eHead = p;
eTail = p;
}
else {
eTail->next = p;
eTail = p;
}
}
p->next = NULL;
p = next;
}
//连接
if (sTail != NULL) {//小于区域不为空
sTail->next = mHead;
if (mTail == NULL) {//等于区域为空
mTail = sTail;//sTail去连大于区域的头
}
}
if (mTail != NULL) {
mTail->next = eHead;
}
return sHead != NULL ? sHead : (mHead != NULL ? mHead : eHead);
}
拷贝链表,现在每个结点有两个指针,一个next(下一个),一个random(随意指)
class Node {
public:
int val;
Node* next;
Node* random;
Node() {
val = 0;
next = NULL;
random = NULL;
}
Node(int val) :val(val) {
next = NULL;
random = NULL;
}
};
现在要求赋值拷贝一个和原来一样的链表
解1:准备一张哈希表,key为原结点,value为新结点,遍历链表,将新结点都建出来,并且值都和原结点一样,但指针都悬空
然后取每一对键值对,新结点的next就是原来结点的next对应的value,新结点的random就是原来结点的random对应的value。
Node* copyList(Node* head) {
unordered_map<Node*, Node*>map;
Node* p = head;
while (p != NULL) {
map.emplace(p, new Node(p->val));
p = p->next;
}
for (auto it = map.begin(); it != map.end(); ++it) {
Node* old = (*it).first;
Node* newNode = (*it).second;
if (old->next == NULL) {
newNode->next = NULL;
}
else {
newNode->next = map[old->next];
}
if (old->random == NULL) {
newNode->random = NULL;
}
else {
newNode->random = map[old->random];
}
}
return map[head];
}
解2:只用有限几个变量,解1是人为的构造老新结点的对应关系,该解法在结构上构造对应关系。
例如
现在对链表做如下调整:
每个结点后面连接一个与该节点值相同的结点,新结点next指向原来结点的next,新结点的random暂时悬空,如下图
从头开始遍历,每次访问两个结点,例如1和1’,1’的random就指向1的random指向结点的下一个结点,如上图,这样就将新结点random全部设置好。此时结点next还没设置好。
从头开始遍历,每次访问两个结点,例如1和1’,1的next等于1’的next,1’的next等于1’的next的next
所以总共需要三个循环
Node* copyList2(Node* head) {
Node* p = head;
while (p != NULL) {//插入
Node* node = new Node(p->val);
node->next = p->next;
p->next = node;
p = node->next;
}
p = head;
while (p != NULL) {//设置random
Node* q = p->next;
if (p->random == NULL) {
q->random = NULL;
}
else {
q->random = p->random->next;
}
p = q->next;
}
p = head;
Node* newHead = head->next;
while (p != NULL) {//设置next
Node* q = p->next;
p->next = q->next;
if (q->next != NULL) {
q->next = q->next->next;
}
p = p->next;
}
return newHead;
}
时间复杂度O(N)
203. 移除链表元素
- 链表的创建
struct ListNode {
int val;
ListNode* next;
//构造函数
ListNode() : val(0), next(nullptr) {};//nullptr代表空指针
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode* next) : val(x), next(next) {}
}
- 链表删除:对于单结点,需要找到删除结点的上一个结点,由于头结点没有上一个结点,可以采取以下两种方法。
1.对头结点特殊处理
ListNode* removeElements(ListNode* head, int val) {
while(head!=nullptr&& head->val==val){
ListNode* temp=head;
head=head->next;
delete temp;
}
ListNode* cur=head;
while(cur!=nullptr && cur->next!=nullptr){
if(cur->next->val==val){
ListNode* temp=cur->next;
cur->next=temp->next;
delete temp;
}
else{
cur=cur->next;
}
}
return head;
}
2.创建一个虚拟头结点
ListNode* removeElements(ListNode* head, int val) {
ListNode* h0=new ListNode();
h0->next=head;
ListNode* cur=h0;
while(cur!=nullptr && cur->next!=nullptr){
if(cur->next->val==val){
ListNode* temp=cur->next;
cur->next=temp->next;
delete temp;
}
else{
cur=cur->next;
}
}
return h0->next;//返回真正的头结点
}
综合考察链表的各项操作
插入删除建一个虚拟头结点更方便
class MyLinkedList {
public:
struct LinkedNode {
int val;
LinkedNode* next;
LinkedNode(int val) :val(val), next(nullptr) {}
};
MyLinkedList() {
size = 0;
virHead = new LinkedNode(0);
}
int get(int index) {
if (index >= size || index < 0) {
return -1;
}
LinkedNode* cur = virHead->next;
while (index--) {
cur = cur->next;
}
return cur->val;
}
void addAtHead(int val) {
LinkedNode* newNode = new LinkedNode(val);
newNode->next = virHead->next;
virHead->next = newNode;
size++;
}
void addAtTail(int val) {
LinkedNode* cur = virHead;
while (cur->next != nullptr) {
cur = cur->next;
}
LinkedNode* newNode = new LinkedNode(val);
newNode->next = cur->next;
cur->next = newNode;
size++;
}
void addAtIndex(int index, int val) {
if (index < 0) {
addAtHead(val);
}
else if (index == size) {
addAtTail(val);
}
else if (index > size) {
return;
}
else {
LinkedNode* cur = virHead;
for (int i = 0; i < index; i++) {
cur = cur->next;
}
LinkedNode* newNode = new LinkedNode(val);
newNode->next = cur->next;
cur->next = newNode;
size++;
}
}
void deleteAtIndex(int index) {
if (index < 0 || index >= size) {
return;
}
LinkedNode* cur = virHead;
for (int i = 0; i < index; i++) {
cur = cur->next;
}
LinkedNode* temp = cur->next;
cur->next = temp->next;
delete temp;
size--;
}
private:
int size;
LinkedNode* virHead;
};
翻转链表:设三个指针,一个指向当前结点,一个指向前一个结点,另一个暂存当前结点的下一个结点。
流程:cur->next=pre,然后pre和cur后移,一直循环,终止条件是cur=nullptr,此时pre为翻转后的头结点。
```/**
* 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* cur=head;
ListNode* pre=nullptr;
while(cur!=nullptr){
ListNode* temp=cur->next;
cur->next=pre;
pre=cur;
cur=temp;
}
head=pre;
return head;
}
};
解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* temp=cur->next;
cur->next=pre;
pre=cur;
cur=temp;
return reverse(pre,cur);
}
ListNode* reverseList(ListNode* head) {
return reverse(nullptr,head);
}
};
第二种递归写法
从右往左翻转,右边是已经翻转好的,左边待翻转,每个子函数都向上返回翻转后的头结点。
/**
* 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) {
if(head == nullptr || head->next==nullptr){//终止条件
return head;
}
ListNode* last=reverseList(head->next);//翻转后的头结点
head->next->next=head;
head->next=nullptr;
return last;//返回给上一层头结点
}
};
涉及到头结点可能会改变的添加一个虚拟头结点会更方便
交换涉及到的中间变量可以通过画图来找,一般指向发生变化的结点所指向的结点需要保存。
/**
* 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) {
ListNode* virHead=new ListNode();//由于头结点会改变,设一个虚拟头结点
virHead->next=head;
ListNode* cur=virHead;//cur处于一对结点的前一个位置
while(cur->next!=nullptr && cur->next->next !=nullptr){
//交换涉及到3个中间变量需要保存
ListNode* firstPos=cur->next;//原来第1号位置
ListNode* secondpos=firstPos->next;//原来第2号位置
ListNode* next=secondpos->next;//下一对结点的1号位置
cur->next=secondpos;
secondpos->next=firstPos;
firstPos->next=next;
cur=firstPos;
}
return virHead->next;
}
};
不设虚拟头结点:由于在第一次交换时,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* swapPairs(ListNode* head) {
ListNode* last=nullptr;//交换后cur的前结点
ListNode* cur=head;//当前结点
ListNode* newHead=nullptr;//新的头结点
while(cur!=nullptr && cur->next!=nullptr){//保证是两两交换
ListNode* next=cur->next->next;//下一对结点的首结点
ListNode* first=cur;//1号结点
ListNode* second=cur->next;//2号结点
second->next=first;//交换
first->next=next;//保证最后一对结点的first->next==nullptr
if(last!=nullptr){
last->next=second;//连接前结点
}else{
newHead=second;//保存新的头结点
}
last=first;//更新cur的前结点
cur=next;
}
return newHead;
}
};
递归写法:对于这种链表的递归写法,首先要明确操作的顺序是什么样的,例如本题,如果要返回交换后的头结点 自然应该从后往前进行,下一层要返回给上一层交换后的头结点。
/**
* 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* f(ListNode* head){//返回从head开始交换后的头结点
if(head==nullptr || head->next==nullptr){//不足两个结点没法交换
return head;
}
ListNode* first=head;//原来1号位置
ListNode* second=head->next;//原来2号位置
ListNode* next=f(second->next);//下一层交换后的头结点
second->next=first;
first->next=next;
return second;
}
ListNode* swapPairs(ListNode* head) {
return f(head);
}
};
分析:
解1 :对于单链表,找第i个结点好找,但找倒数第k个结点,可以通过先计算出一个多少个结点,然后计算出要找的结点位于第j个结点
/**
* 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) {
ListNode* virHead=new ListNode();
virHead->next=head;
ListNode* tail=virHead;
int sum=0;//求结点的个数
while(tail->next!=nullptr){
tail=tail->next;
sum++;
}
int index=sum-n;//位于第index位置
ListNode* cur=virHead;
while(index--){
cur=cur->next;
}
ListNode* temp=cur->next;
cur->next=cur->next->next;
delete temp;
return virHead->next;
}
};
解2 双指针法
设两个指针 slow 和fast,要找倒数第n个结点,始终保持slow与fast之间的距离为n,(slow在左,fast在右),当fast指向nullptr是,slow就是倒数第n个结点。
本题要删除倒数第n个结点,因此实际上要找到倒数第n+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* removeNthFromEnd(ListNode* head, int n) {
ListNode* virHead=new ListNode();
virHead->next=head;
ListNode* slow=virHead;
ListNode* fast=virHead;
for(int i=0;i<n+1;i++){
fast=fast->next;
}
while(fast!=nullptr){//循环终止时slow指向要删除结点的上一个结点
slow=slow->next;
fast=fast->next;
}
ListNode* temp=slow->next;
slow->next=temp->next;
delete temp;
return virHead->next;
}
};
分析:若存在相交结点,那么一定存在一段结点重合。因此可以令两链表的尾部对齐,设两个指针从前往后查找,找到相同的即是相交结点。
/**
* 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) {
if(headA==NULL || headB==NULL){
return NULL;
}
int lena=0;
int lenb=0;
ListNode* cura=headA;
while(cura!=NULL){//求A的长度
lena++;
cura=cura->next;
}
ListNode* curb=headB;
while(curb!=NULL){//求B的长度
lenb++;
curb=curb->next;
}
if(lenb<lena){//长的对齐
cura=headA;
int index=lena-lenb;
while(index--){
cura=cura->next;
}
curb=headB;
while(cura!=NULL){
if(cura!=curb){
cura=cura->next;
curb=curb->next;
}
else{
return cura;
}
}
return NULL;
}
else{
curb=headB;
int index=lenb-lena;
while(index--){
curb=curb->next;
}
cura=headA;
while(cura!=NULL){
if(cura!=curb){
cura=cura->next;
curb=curb->next;
}
else{
return cura;
}
}
return NULL;
}
return NULL;
}
};
拓展:如果单链表有环呢,如何求相交结点?
这里分三种情况:
第一种:两个链表都有环,但不相交
第二种情况:两个链表有相同的入环结点
第三种情况
其中第一种和第三种情况可以放在一起:
从某一个链表的入口结点开始不断往后走,如果中途与另一个链表的入口结点相遇则是第三种情况,否则就回到了本结点不相交。
第二种情况转化为从头结点到入口结点组成的链表求相交结点,就是本题的做法。
第一步 先判断是否有环
设两个指针,一个指针一次走一个结点,另一个一次走两个结点,当slow指针也进入环时,由于每走一次,两指针的距离就减一,最终一定会减为0,即相遇;若没有环,快指针会走到尾部。
第二步 找入口结点
第一步可以得到相遇结点的位置,接下来通过列式求入口结点
设从头结点到入口结点的距离为L,从入口结点到相遇结点的距离为X,环的长度为C。
因此快指针走过的距离:L+nC+X,慢指针走过的距离:L+X,因为快指针每次走的距离是慢指针的2倍,所以L+nC+X=2(L+X),整理得:L=nC-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) {
ListNode* slow=head;
ListNode* fast=head;
bool flag=false;
while(fast!=NULL && fast->next!=NULL){//判断是否有环
slow=slow->next;
fast=fast->next->next;
if(slow==fast){
flag=true;
break;
}
}
if(!flag){
return NULL;
}
slow=head;
while(fast!=slow){
fast=fast->next;
slow=slow->next;
}
return fast;
}
};
总结
- 涉及到头结点可能变化的设一个虚拟头结点会比较方便。
- 要找倒数第n个结点,设两个指针,保持两指针的距离为n,当fast指针为nullptr时,slow即为倒数第n个结点。
- 判断是否有环,设两个指针slow、fast,slow移动步长1,fast移动步长2.