Ref
https://mp.weixin.qq.com/s/hKjkITbCRcnZBafpjiwWJA(理论及java base)
https://blog.youkuaiyun.com/qq_29542611/article/details/79265973(单链表实现及算法详细 c实现(包含头结点))
https://blog.youkuaiyun.com/zhengnianli/article/details/79320703 (链表详细笔记,增加删除结点算法更好!)
补充:
c++模板类实现链表:
双向链表:https://blog.youkuaiyun.com/zhouzzz000/article/details/80627428
单链表:https://blog.youkuaiyun.com/qq_40584417/article/details/80637943
那每个节点结构是由数据域和指针域组成,数据域是存放数据的,而指针域存放下一结点的地址。
2、头结点:在开始结点之前的结点(可有可无)。其值域不包含任何信息。
3、开始结点:第一个元素所在的结点。
4、头指针:永远指向链表中第一个结点的位置
调整顺序或增减算法实质是 操作next指针:
头插法:(有头结点情况)1.将头结点的next指针赋值给newNode的next指针 2.头结点的next指针再指向newNode
尾插法:1.尾结点的next指针指向newNode 2.newNode的next指针指向nullptr
翻转:(从头上顺序插入元素即可逆转)
https://blog.youkuaiyun.com/setsuna_ogiso/article/details/80386982(手撕算法系列,无头结点的翻转)
https://blog.youkuaiyun.com/qq_29542611/article/details/79265973(头结点的翻转)
https://blog.youkuaiyun.com/weixin_33682719/article/details/93698970(图)
迭代法:
无头结点
//a b c d
ListNode* reverseList(ListNode* phead)
{
if(phead == nullptr)
{
return nullptr;
}
ListNode* pre = nullptr;//此处初始化的值,也就是最后一个node指向的值
ListNode* cur = phead;//此处初始化的值为开始结点的值:cur = head(没有头结点)
ListNode* next = nullptr;
while(cur!=nullptr) //第一次肯定进,第二次表示b的右面为null
{
next = cur->next; //b后面的保存给c,断开bc,用于步骤3,4的后移操作
cur->next = pre;//翻转操作:b的next指针指向a
pre = cur;//pre后移1位
cur = next; //cur后移1位
}
//循环完成后,pre就是翻转过来的头,尾为第一次循环时 cur -> next = pre 的pre值(此时cur为最后一个node)
return pre;
}
ListNode* reverseList(ListNode* list)
{
ListNode* cur = list;
ListNode* pre = NULL;
ListNode* next = NULL;
while(cur->next != NULL)
{
next = cur->next;
cur->next = pre;
//此步为下一次循环做的
//next->next = cur;
pre = cur;
cur = next;
}
return cur;
}
有头结点
int reverseList(ListNode* phead)
{
if(phead == nullptr)
{
return 0;
}
ListNode* pre = nullptr;
ListNode* cur = phead->next;
ListNode* next = nullptr;
while(cur!=nullptr)
{
next = cur->next;
cur->next = pre;
pre = cur;
cur = next;
}
phead -> next = pre;
return 1;
}
图并不是十分正确,不过概念大概如此
后移:
看成
注:头结点有无的区别
1.两者在程序上的直接体现就是:头指针只声明
而没有分配存储空间,头结点进行了声明并分配了一个结点的实际物理内存。
2.如果链表有头结点,头指针指向头结点;否则,头指针指向开始结点
3.有无头结点,对于头插法有区别:
a.有头结点,新插入的结点需要由头结点next指向
b.无头结点,直接插入第一个,不过头指针改变了!指向新增结点
1.从尾到头打印链表(运用栈先入后出就行)
2.链表中倒数第k个结点
剑指Offer(十五):反转链表
4.合并两个排序的链表(递归(终结条件:返回非nullptr的一个 递归基:next指向更小的),非递归(while循环将next指向小结点,最后一个为nullptr将另一个放最后))
5.复杂链表的复制(重难点)
这道题有两种思路。第一种普通思路:先顺序复制并且设置好next指针,让新节点的random指向老链表中对应的节点,并且开一个map,一边复制一边存下新旧对应节点地址映射。第二遍遍历根据map把新节点中的random指针都调整正确。这个map消耗了额外空间。
第二种思路:省去这个map,要求是仍然从老节点能直接找到对应的新节点,则方法为把新节点全都缀连在老节点之后。等设置好所有的random之后再拆分两个链表。
/*
struct RandomListNode {
int label;
struct RandomListNode *next, *random;
RandomListNode(int x) :
label(x), next(NULL), random(NULL) {
}
};
*/
class Solution {
public:
RandomListNode* Clone(RandomListNode* pHead)
{
copyLinkList(pHead);
setRandom(pHead);
return splitList(pHead);
}
private:
//深拷贝并连接链表,void函数因为会改变已有链表
void copyLinkList(RandomListNode* pHead)
{
RandomListNode* temp = pHead;
while(temp != nullptr)
{
RandomListNode* node = new RandomListNode(-1);
node->label = temp->label;
node->next = temp->next;
node->random = nullptr;//初始值为nullptr
temp->next = node;//将新结点连接在后面
temp = node->next;//往后移动
}
}
//修改random指针值
void setRandom(RandomListNode* pHead)
{
RandomListNode* node = pHead;
while(node != nullptr)
{
RandomListNode* next = node->next;
if(node->random != nullptr)
//if(next->random == nullptr)//此处是bug,如果不判断node->random为nullptr,则指向null时越界
{
next->random = node->random->next;
}
node = next->next;//往后循环2位
}
}
//拆分List
RandomListNode* splitList(RandomListNode* pHead)
{
if(pHead == nullptr || pHead->next == nullptr)
return nullptr;
RandomListNode* ListOdd = pHead;
RandomListNode* ListEven = pHead->next;
RandomListNode* head = ListEven;//决定返回奇数还是偶,但其实原有的就是奇数
//循环拆分(每轮4个),奇数指向奇数偶数指向偶数,nullptr时退出循环
while(ListEven != nullptr)
{
//处理奇数,赋值并后移
ListOdd->next = ListEven->next;
ListOdd = ListOdd->next;
//处理偶数
//此时,如果ListOdd为nullptr,ListOdd->next越界!需要将偶数赋值为nullptr
if(ListOdd == nullptr)
ListEven->next = nullptr;
else
ListEven->next = ListOdd->next;
ListEven = ListEven->next;
}
return head;
}
};
6.两个链表的第一个公共结点(公共结点后肯定就相同了)
(方法1 串联起来然后遍历 方法2 裁去长链表长部分,等长遍历,相同则为公共结点)
//找到第一个公共结点:公共结点后都是相同的了,因为next指针也相同
//方法1:将两个链表连接起来,然后依次遍历,最终总能重合!
//方法2:去掉长的链表的前部分,使相同长度,然后遍历下去
int getLength(ListNode* pHead)
{
int length = 0;
while(pHead != nullptr)
{
++length;
pHead = pHead->next;
}
return length;
}
ListNode* firstCommonNode(ListNode* pHead1, ListNode* pHead2)
{
int diff = getLength(pHead1) - getLength(pHead2);
ListNode* longNode = (diff>0)?pHead1:pHead2;//找到长的一个链表
ListNode* shortNode = (diff<=0)?pHead1:pHead2;//找到短的链表
int absDiff = abs(diff);//获取绝对值,用于cut这个值
for(int i = 0;i<absDiff;++i)
{
longNode = longNode->next;
}
while(longNode != nullptr && shortNode != nullptr)
{
if(longNode == shortNode)
break;
longNode = longNode->next;
shortNode = shortNode->next;
}
return longNode;
}
7.链表中环的入口结点
- 有一个单链表,其中可能有一个环,也就是某个节点的next指向的是链表中在它之前的节点,这样在链表的尾部形成一环。
- 如何判断一个链表是否存在环?设定两个指针slow,fast,均从头指针开始,每次分别前进1步、2步。如存在环,则两者相遇;如不存在环,fast遇到NULL退出。
- 如果链表存在环,如果找到环的入口点?当fast若与slow相遇时,slow肯定没有走遍历完链表或者恰好遍历一圈(整个是一个环)。于是我们从链表头与相遇点分别设一个指针,每次各走一步,两个指针必定相遇,且相遇第一点为环入口点。、
推导算法:
设快指针为p2,慢指针为p1,环长度为n,p1走过的路程为s
p2 = 2*p1
p = p + n(根据环路特性,所以相遇的点肯定如下:p2为p1加上n)
p2 = s+n
p1 = s
推导公式得出:
s = n
//链表中环的入口结点
//step1:使用快慢指针判断是否有环:有则返回碰撞点
//setp2:碰撞结点和初始结点递归,相遇结点为环入口(因为相差n为环的长度)
//step1
ListNode* getCycle(ListNode* pHead)
{
ListNode* slowHead = pHead;
ListNode* fastHead = pHead;
while(fastHead != nullptr && fastHead->next != nullptr)
{
fastHead = fastHead->next->next;
slowHead = slowHead->next;
if(slowHead == fastHead)
return fastHead;
}
return nullptr;//表示没有环
}
//step2
ListNode* getCycleEntry(ListNode* pHead)
{
ListNode* meetHead = getCycle(pHead);
if(meetHead == nullptr)
{
cout<<"without cycle!"<<endl;
return nullptr;
} else {
while(meetHead != pHead)
{
meetHead = meetHead->next;
pHead = pHead->next;
}
return pHead;
}
}
8.删除链表中重复的结点
1->2->3->3->4->4->5 处理后为 1->2->5。
特殊:1 -> 1 1,1,1->nullptr 1,1,1,2 ->2
1. 首先添加一个头节点,以方便碰到第一个,第二个节点就相同的情况
2.设置 pre ,last 指针, pre指针指向当前确定不重复的那个节点,而last指针相当于工作指针,一直往后面搜索。
//删除重复多余的结点
//考虑头结点需要删除的情况
ListNode* deleteRepeatNode(ListNode* pHead)
{
ListNode* pre = pHead;
while(pre != nullptr &&pre->next != nullptr)
{
ListNode* cur = pre->next;
if(cur->val == pre->val)
{
pre->next = cur->next;
cout<<pre->val<<cur->val<<endl;
// delete cur;
// cur = nullptr;
}
pre = pre->next;
}
return pHead;
}
//删除重复结点,包含自身
//用两个指针,pre为非重复的结点,cur是接下来遍历的结点
//2种情况:1.有重复,遍历cur到最后重复的,将pre指向cur->next,也就跳过了
//2.没有重复,cur和pre分别向后移
//注意点:1.新增头结点,用来删除开始就是重复的情况
//2.鲁棒性,用next指针前都要检测,并且放&&前面
//3.返回值需要不带头
ListNode* deleteDuplication(ListNode* pHead)
{
if(pHead == nullptr || pHead->next == nullptr)
return nullptr;
//以下为bug代码:未考虑 {1,1,1,2},会返回{1,2}
//需要新建一个头结点来避免该情况
/*
ListNode* result = pHead;
ListNode* pre = pHead;
ListNode* cur = pHead->next;
*/
ListNode* result = new ListNode(-1);
result->next = pHead;
ListNode* pre = result;
ListNode* cur = result->next;
while(cur != nullptr)
{
if((cur->next != nullptr) && (cur->val == cur->next->val))
{
//找到下一个没有重复的结点,特殊情况,到尾巴时,需要都删除,next->next要为nullptr
//出去条件:如果相等,但是下一个为nullptr!也就是尾结点
while((cur->next != nullptr) &&(cur->val == cur->next->val))
{
cur = cur->next;
}
pre->next = cur->next;
cur = cur->next;
} else {
pre = pre->next;
cur = cur->next;
}
}
//return result; //bug会返回带头结点
return result->next;//返回未带头结点
}
#include<iostream>
#include<stack>
#include<vector>
using namespace std;
struct ListNode {
int val;
struct ListNode *next;
ListNode(){};
ListNode(int x) :
val(x), next(NULL) {
}
};
//尾插法:包含头结点
ListNode* insertListToEnd(ListNode* list, int val)
{
ListNode* node = new ListNode;
node->val = val;
node->next = nullptr;
if (list == nullptr)
{
//增加头结点和第一个结点
ListNode* result = new ListNode;
result->next = node;
return result;
}
ListNode* temp = list;
while(temp->next != nullptr)
{
temp = temp->next;
}
temp->next = node;
return list;
}
//没有头结点的头插法
ListNode* insertListToFront(ListNode* list, int val)
{
ListNode* node =new ListNode(val);
node->next = list;
return node;
}
//有头结点,head永远不为nullptr,head->next为nullptr时,链表为空
ListNode* insertListToFrontWithHead(ListNode* list, int val)
{
if(list == nullptr)
{
//代表还没有头结点
list = new ListNode;
list->next = nullptr;
}
//将头结点之后赋给新结点,然后头结点下一个指向新结点
ListNode* node = new ListNode(val);
node->next = list->next;
list->next = node;
return list;
}
//包含头结点的打印
void printList(ListNode* list)
{
if (list == nullptr)
{
cout<<"without head ,error list"<<endl;
return;
}
ListNode* temp = list->next;
while(temp != nullptr)
{
cout<<" "<<temp->val;
temp = temp->next;
}
cout<<endl;
}
//未包含头结点的打印
void printListWithoutHead(ListNode* list)
{
if (list == nullptr)
{
cout<<"without head ,error list"<<endl;
return;
}
ListNode* temp = list;
while(temp != nullptr)
{
cout<<" "<<temp->val;
temp = temp->next;
}
cout<<endl;
}
vector<int> printListFromTailToHead(ListNode* list)
{
stack<ListNode*> sta;
ListNode* temp = list->next;
while(temp!=nullptr)
{
sta.push(temp);
temp = temp->next;
}
vector<int> result;
while(!sta.empty())
{
temp = sta.top();
result.push_back(temp->val);
sta.pop();
}
return result;
}
ListNode* reverseList(ListNode* list)
{
ListNode* pre = nullptr;
ListNode* cur = list->next;//b包含头结点
ListNode* next = nullptr;
while(cur != nullptr)
{
next = cur->next;
cur->next = pre;
pre = cur;
cur = next;
}
list->next = pre;
return list;
}
//递归处理:递归两个结点排序
ListNode* merge(ListNode* pHead1, ListNode* pHead2)
{
ListNode* pMergeHead = nullptr;
//终结条件:此时包含3种情况,1或2为空返回非空,都为空返回空
if((pHead1 == nullptr) || (pHead2 == nullptr))
{
ListNode* result = ((pHead1 != nullptr)?pHead1:pHead2);
return result;
}
//递归基:返回小的一个结点,并递归下一个结点来获取小的结点。其实就是两个数组排序
if(pHead1->val < pHead2->val)
{
pMergeHead = pHead1;
pMergeHead->next = merge(pHead1->next, pHead2);
} else {
pMergeHead = pHead2;
pMergeHead->next = merge(pHead1, pHead2->next);
}
return pMergeHead;
}
ListNode* mergenonRecursion(ListNode* pHead1, ListNode* pHead2)
{
if(pHead1 ==nullptr || pHead2 == nullptr)
{
return ((pHead1 !=nullptr)?pHead1:pHead2);
}
ListNode* node = new ListNode;
ListNode* pMergeHead = node;//需要返回的结果
//此处使用&&,只要有一个循环完,剩余的放最后就行
while(pHead1!=nullptr && pHead2 != nullptr)
{
//精彩的参数传递!:先操作node指向地址的值(当前结点),next指向下一个小结点.
//然后更改node指向下一个小结点,最后移动pHead!
if(pHead1->val < pHead2->val)
{
node->next = pHead1;//此处是修改地址区的值,意思是最小结点的下一个指向位置,node不停变换为最小结点
node = pHead1;//node更改为最小结点
pHead1 = pHead1->next;//移动pHead,因为使用过了
} else {
node->next = pHead2;
node = pHead2;
pHead2 = pHead2->next;
}
}
//最后,将剩余的放到最后即可
if(pHead1 !=nullptr || pHead2 != nullptr)
{
node->next = ((pHead1 !=nullptr)?pHead1:pHead2);
}
return pMergeHead;
}
//两种方法:递归 非递归
ListNode* merge2List(ListNode* list1, ListNode* list2)
{
ListNode* result = new ListNode;
ListNode* pHead1 = list1->next;
ListNode* pHead2 = list2->next;
result->next = merge(pHead1, pHead2);
return result;
}
//深拷贝链表
ListNode* copyList(ListNode* pHead)
{
if(pHead == nullptr)
return nullptr;
ListNode* node = new ListNode();
ListNode* copyHead = node;
//包含头结点,所以需要跳过phead,从next开始
while(pHead->next != nullptr)
{
pHead = pHead->next;
node -> next = new ListNode(pHead->val);
node = node ->next;
}
return copyHead;
}
//拆分链表(分为奇偶项)
ListNode* splitList(ListNode* pHead)
{
//如果全空或只有一个元素,则返回该链表
if(pHead == nullptr || pHead->next == nullptr)
return pHead;
ListNode* List1 = pHead;//奇数链表
ListNode* List2 = pHead -> next;//偶数链表
ListNode* splitHead = List2;//返回的是技术还是偶数
//此时如果只有3个元素,List1(为nullptr)->next(会报错吧?)
while(List2 != nullptr)
{
List1->next = List2->next;
List1 = List1->next;
//此处防止奇数次链表进来,开始没考虑到
if(List1 == nullptr)
List2->next = nullptr;
else
List2->next = List1->next;
List2 = List2->next;
}
return splitHead;
}
//深拷贝并连接链表
ListNode* copyLinkList(ListNode* pHead)
{
if(pHead == nullptr)
return pHead;
//ListNode* node = nullptr;
ListNode* result = pHead;
while(pHead != nullptr)
{
//相当于指定位置插入一个链表算法,不过需要循环插入,然后移动2格!
ListNode* node = pHead->next;//将后面的保存下来
pHead->next = new ListNode(pHead->val);//拷贝新的链表到next
pHead = pHead->next;//移动到新增的上面
pHead->next = node;//新增的next指向前面保存的
pHead = pHead->next;//再往后移动,完成此次循环,进入下次循环初始条件
}
return result;
}
//找到第一个公共结点:公共结点后都是相同的了,因为next指针也相同
//方法1:将两个链表连接起来,然后依次遍历,最终总能重合!
//方法2:去掉长的链表的前部分,使相同长度,然后遍历下去
int getLength(ListNode* pHead)
{
int length = 0;
while(pHead != nullptr)
{
++length;
pHead = pHead->next;
}
return length;
}
ListNode* firstCommonNode(ListNode* pHead1, ListNode* pHead2)
{
int diff = getLength(pHead1) - getLength(pHead2);
ListNode* longNode = (diff>0)?pHead1:pHead2;//找到长的一个链表
ListNode* shortNode = (diff<=0)?pHead1:pHead2;//找到短的链表
int absDiff = abs(diff);//获取绝对值,用于cut这个值
for(int i = 0;i<absDiff;++i)
{
longNode = longNode->next;
}
while(longNode != nullptr && shortNode != nullptr)
{
if(longNode == shortNode)
break;
longNode = longNode->next;
shortNode = shortNode->next;
}
return longNode;
}
//链表中环的入口结点
//step1:使用快慢指针判断是否有环:有则返回碰撞点
//setp2:碰撞结点和初始结点递归,相遇结点为环入口(因为相差n为环的长度)
//step1
ListNode* getCycle(ListNode* pHead)
{
ListNode* slowHead = pHead;
ListNode* fastHead = pHead;
while(fastHead != nullptr && fastHead->next != nullptr)
{
fastHead = fastHead->next->next;
slowHead = slowHead->next;
if(slowHead == fastHead)
return fastHead;
}
return nullptr;//表示没有环
}
//step2
ListNode* getCycleEntry(ListNode* pHead)
{
ListNode* meetHead = getCycle(pHead);
if(meetHead == nullptr)
{
cout<<"without cycle!"<<endl;
return nullptr;
} else {
while(meetHead != pHead)
{
meetHead = meetHead->next;
pHead = pHead->next;
}
return pHead;
}
}
//删除重复多余的结点
//考虑头结点需要删除的情况
ListNode* deleteRepeatNode(ListNode* pHead)
{
if(pHead == nullptr)
return nullptr;
ListNode* pre = pHead;
//ListNode* next = pHead->next;
while(pre != nullptr &&pre->next != nullptr)
{
ListNode* cur = pre->next;
//next = cur->next;
if(cur->val == pre->val)
{
pre->next = cur->next;
cout<<pre->val<<cur->val<<endl;
// delete cur;
// cur = nullptr;
}
pre = pre->next;
}
return pHead;
}
//删除重复结点,包含自身
//用两个指针,pre为非重复的结点,cur是接下来遍历的结点
//2种情况:1.有重复,遍历cur到最后重复的,将pre指向cur->next,也就跳过了
//2.没有重复,cur和pre分别向后移
//注意点:1.新增头结点,用来删除开始就是重复的情况
//2.鲁棒性,用next指针前都要检测,并且放&&前面
//3.返回值需要不带头
ListNode* deleteDuplication(ListNode* pHead)
{
if(pHead == nullptr || pHead->next == nullptr)
return nullptr;
//以下为bug代码:未考虑 {1,1,1,2},会返回{1,2}
//需要新建一个头结点来避免该情况
/*
ListNode* result = pHead;
ListNode* pre = pHead;
ListNode* cur = pHead->next;
*/
ListNode* result = new ListNode(-1);
result->next = pHead;
ListNode* pre = result;
ListNode* cur = result->next;
while(cur != nullptr)
{
if((cur->next != nullptr) && (cur->val == cur->next->val))
{
//找到下一个没有重复的结点,特殊情况,到尾巴时,需要都删除,next->next要为nullptr
//出去条件:如果相等,但是下一个为nullptr!也就是尾结点
while((cur->next != nullptr) &&(cur->val == cur->next->val))
{
cur = cur->next;
}
pre->next = cur->next;
cur = cur->next;
} else {
pre = pre->next;
cur = cur->next;
}
}
//return result; //bug会返回带头结点
return result->next;//返回未带头结点
}
int main() {
ListNode* head;
head = insertListToFrontWithHead(head,5);
head = insertListToEnd(head,3);
head = insertListToFrontWithHead(head,9);
head = insertListToEnd(head,6);
head = insertListToFrontWithHead(head,4);
printList(head);
head = reverseList(head);
printList(head);
vector<int> reserve = printListFromTailToHead(head);
for(auto it = reserve.begin();it!=reserve.end();++it)
{
cout<<" "<<*it;
}
cout<<endl;
//合并两个结点
/*
ListNode* phead1;
ListNode* phead2;
ListNode* merge;
phead1 = insertListToEnd(phead1,3);
phead1 = insertListToEnd(phead1,7);
phead1 = insertListToEnd(phead1,9);
phead1 = insertListToEnd(phead1,12);
phead2 = insertListToEnd(phead2,4);
phead2 = insertListToEnd(phead2,11);
phead2 = insertListToEnd(phead2,22);
phead2 = insertListToEnd(phead2,25);
printList(phead1);
printList(phead2);
merge = merge2List(phead1, phead2);
printList(merge);
//merge = mergenonRecursion(phead2,phead1);
//printList(merge);
*/
//简单链表深拷贝
/*
ListNode* copy = copyList(head);
printList(copy);
cout<<"addr is diff:"<<&(copy->next)<<" "<<&(head->next)<<endl;
*/
//拆分链表
printListWithoutHead(head);
ListNode* split = splitList(head->next);
printListWithoutHead(split);
//拷贝链表到后面
ListNode* copy = copyLinkList(head);
printListWithoutHead(copy);
//删除重复多余结点
ListNode* dele = deleteRepeatNode(copy);
printListWithoutHead(dele);
return 0;
}