数据结构——链表
从尾到头打印链表
代码与逻辑
vector<int> demo;
while(head!=NULL){
demo.push_back(head->val);
head=head->next;
}
vector<int> res;
for(int i=demo.size()-1; i>=0; i--){
res.push_back(demo[i]);
}
return res;
1.遍历链表顺序存储到数组demo;
2.将数组demo逆置存储到结果数组;
get
vector的使用:
定义数组时无需考虑长度,动态分配;
push_back():在数组末尾插入元素;
size():返回vector中元素的个数;
reverse():用于反转在[first,last)范围内的顺序(包括first指向的元素,不包括last指向的元素),reverse函数没有返回值。使用需要包含头文件
#include <algorithm>
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* ListNode(int x) :
* val(x), next(NULL) {
* }
* };
*/
#include <cstddef>5
#include <vector>
class Solution {
public:
vector<int> printListFromTailToHead(ListNode* head) {
vector<int> res; //result
stack<int> s; //stack 实现逆序 栈先进后出
//从链头开始顺序入栈
while(head!=NULL){
s.push(head->val); //入栈
head=head->next; //头指针后移实现遍历
}
//栈先进后出,从栈顶输出,实现逆序输出。栈底向栈顶是正序
while(!s.empty()){
res.push_back(s.top()); //栈顶元素加入res 使用pushback实现变长数组增加元素
s.pop(); //弹出栈顶元素
}
return res;
}
};
反转链表
代码
ListNode* ReverseList(ListNode* pHead) {
ListNode *res = NULL;
ListNode *cur = pHead;
while(cur!=NULL){
//暂存待处理的下一个节点,cur为当前处理中的节点
ListNode *temp = cur->next;
//逆置,原本res在cur之前,将next指向res实现逆置
cur->next=res;
//res指向已经处理过的、逆序的一串节点的头节点(最后被处理完的那个节点
res=cur;
//处理下一个节点,暂存在temp中
cur=temp;
}
return res;
}
};
get
cur指向当前节点
temp暂存cur之后要处理的下一个节点
res为结果
合并两个排序的列表
代码
class Solution {
public:
ListNode* Merge(ListNode* pHead1, ListNode* pHead2) {
//一个已经为空了,直接返回另一个
if (pHead1 == NULL)
return pHead2;
if (pHead2 == NULL)
return pHead1;
//加一个表头
ListNode* head = new ListNode(0);
ListNode* cur = head;
//两个链表都要不为空
while (pHead1 && pHead2) {
//取较小值的节点
if (pHead1->val <= pHead2->val) {
cur->next = pHead1;
//只移动取值的指针
pHead1 = pHead1->next;
} else {
cur->next = pHead2;
//只移动取值的指针
pHead2 = pHead2->next;
}
//指针后移
cur = cur->next;
}
//哪个链表还有剩,直接连在后面
if (pHead1)
cur->next = pHead1;
else
cur->next = pHead2;
//返回值去掉表头
return head->next;
}
};
get
使用双指针思想,较小的节点接入结果链表,指针后移
结果链表:
//加一个表头
ListNode* head = new ListNode(0);
ListNode* cur = head;
随着处理,cur不断后移,接入处理完的、结果链表中的节点
head为该结果链表的表头,为空节点
返回结果时:
//返回值去掉表头
return head->next;
两个链表的第一个公共节点
代码
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
if(pHead1==NULL || pHead2==NULL){
return NULL;
}
//无环——一旦相同,此后完全相同
//get length
int len1,len2;
len1=len2=0;
ListNode* a=pHead1;
while(a) {
len1++;
a=a->next;
}
ListNode* b=pHead2;
while(b) {
len2++;
b=b->next;
}
//the longer one's head goes first until equal
a=pHead1;
b=pHead2;
if(len1>len2){ //p1 more
for(int i=0; i<len1-len2;i++){
a=a->next;
}
}
else if(len1<len2){
for(int i=0; i<len2-len1; i++){
b=b->next;
}
}
//two lists equal, once same val, stop
while((a->val != b->val) && (a!=NULL) && (b!=NULL)){
a=a->next;
b=b->next;
}
return a;
}
};
get
思路:
双指针
- 遍历两个链表得到各自的长度
- 双指针回到链表头,较长的指针后移,直到双指针至表尾的长度相同
- “无环”,则在公共的一串元素之前,所有元素均不相同。一旦遇到相同元素,则之后一定是相同元素串。
- 遍历等长的链表串,双指针同步后移,在均未到表尾的前提下,遇到相同元素即停止。
- 到表尾也停止,返回双指针指向的null
注意:
判断条件加()
while((a->val != b->val) && (a!=NULL) && (b!=NULL))
开始时先判断是否有空的列表,有则直接返回null
链表中倒数第k个元素
代码
ListNode* FindKthToTail(ListNode* pHead, int k) {
// write code here
ListNode* a=pHead;
int len=0;
while(a){
len++;
a=a->next;
}
if(len<k){
return nullptr;;
}
int ddl = len-k;
ListNode* res = pHead;
for(int i=0; i<ddl; i++){
res = res->next;
}
return res;
}
};
get
两次遍历
- 得到表长
- 通过表长得到倒数第k个为正向第 length-k+1个,遍历得到
注意:
正向第 length-k+1个
遍历时
int ddl = len-k;
for(int i=0; i<ddl; i++)
或者
int ddl = len-k+1;
for(int i=0; i<=ddl; i++){
删除元素中重复的点
代码
ListNode* deleteDuplication(ListNode* pHead) {
int MaxSize = 1001;
int count[MaxSize];
ListNode* a = pHead;
//遍历链表,节点值作为数组的下标,对应数组元素+1
if(a==NULL){
return NULL;
}
while (a != NULL) {
count[a->val]++;
a = a->next;
}
//遍历数组,元素值为1的纳入结果
pHead->next = NULL;
a = pHead;
for (int i = 0; i < MaxSize; i++) {
if (count[i] != 1) {
continue;
} else {
ListNode* temp = new ListNode(i);
a->next = temp;
a = a->next;
}
}
return pHead->next;
}
get
以空间换时间
值有范围,新建数组,容量为所有值的数量。
遍历链表,节点值作为数组的下标,对应数组元素+1。
遍历数组,只考虑元素值为1的数组元素,标明元素有且仅有一个。其余则为未出现或重复出现。
元素值为1的数组元素,下标作为链表节点的值,尾插法归入结果链表中。
删除链表中的元素
代码
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* ListNode(int x) : val(x), next(nullptr) {}
* };
*/
ListNode* deleteNode(ListNode* head, int val) {
// write code here
if(head==nullptr){
return nullptr;
}
ListNode* res=head;
ListNode* a=head;
ListNode* aPre=a;
if(a->val==val){
head=head->next;
return head;
}
while(a!=nullptr){
if(a->val!=val){
aPre=a;
a=a->next;
}
else{
a=a->next;
aPre->next=a;
}
}
return res;
}
get
要删除的元素在链表中时,保存pre进行删除。
要删除的节点在表头时,头指针后移——需要单独处理这种情况
链表中环的入口节点
代码
/*
ListNode* EntryNodeOfLoop(ListNode* pHead) {
ListNode* fast=pHead;
ListNode* slow=pHead;
while(fast!=nullptr){
slow=slow->next;
if(fast->next==nullptr){
return nullptr;
}
fast=fast->next->next;
if(fast==slow){
fast=pHead;
while(fast!=slow){
fast=fast->next;
slow=slow->next;
}
return fast;
}
}
return nullptr;
}
get
思路:
快慢指针
当链表中有环时,使用快慢指针(快的一次走两个,慢的一次走一个),两指针终将相遇。
若可以相遇,表示有环。
算法逻辑:
假设从头节点到环的入口节点的前一个节点一共有a个,环中的节点有b个。
设fast指针走过的节点数是f,slow指针走过的节点数是s。
那么有以下两个结论:
f = 2 * s (即快指针走过的节点数一定是慢指针的两倍)
f = s + nb (当两者相遇时,快指针一定已经绕环走了n圈)
由上面两个等式可以得出,f = 2nb,s = nb
故可知,两指针相遇时,慢指针已经走了nb步。
已知我们要走到入口节点,需要走a + kb步,而这时s = nb只要再走a即可到达入口。
我们把快指针移动到头节点,然后两个指针一步一步往后走,当它们相遇时所处的位置就是入口节点
——相遇时两者在入口节点,则快指针走了a,同步的慢指针走了a。故两者同步后移,相遇时即为入口节点。