第一关 小白也能学会的链表
1 剑指Offer 52 两个链表的第一个公共节点
本质:找到两个有序数据段中的第一个相同数据
1.1 Set解决
解题:利用set的不重复性,首先把headA都塞到set中,再遍历headB找有没有已经出现在set中的节点即可。
注意! set的数据是ListNode* 不是 int。如果是int可能出现节点不同但是var相同的情况,而ListNode* 就不会。
#include<set>
using namespace std;
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
set<ListNode*> my_set;
while(headA!=NULL)
{
my_set.insert(headA);
headA = headA->next;
}
while(headB!=NULL)
{
if(my_set.find(headB) != my_set.end())
return headB;
else
headB = headB->next;
}
return NULL;
}
};
时间复杂度:O(2n)
空间复杂度:O(n)
1.2 stack解决
创建两个stack,将节点全部压入stack中然后同时出栈,等到出栈的元素不相等时,其上一个出栈的节点就是目标节点。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
//栈解决
#include<stack>
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
stack<ListNode*> stack_A;
stack<ListNode*> stack_B;
while(headA!=NULL)
{
stack_A.push(headA);
headA = headA->next;
}
while(headB!=NULL)
{
stack_B.push(headB);
headB = headB->next;
}
ListNode* pre = NULL;
while(stack_A.size()!=0 && stack_B.size()!=0)
{
if(stack_A.top() == stack_B.top())
{
pre = stack_A.top();
stack_A.pop();
stack_B.pop();
}
else
break;
}
return pre;
}
};
有一些细节需要注意:
- stack的pop()函数没有返回值,所以需要用stack的top()来保存栈顶元素。
- 记得用pre来保存上一个pop出来的节点。否则找到了不一样的那一个节点后找不到上一个节点了。
时间复杂度:O(3n)
空间复杂度:O(2n)
1.3 双指针法
这个思路比较难想,回归到本质,两个链表的末段相同,那么尝试把这A,B两个链表按照AB和BA的方式拼接,会发现末段依旧相同。那么双指针同时遍历直到相等即可。
/**
* 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) {
ListNode* p1 = headA;
ListNode* p2 = headB;
if(p1 == NULL || p2 ==NULL)
return NULL;
while(p1!=p2)
{
p1 = p1->next;
p2 = p2->next;
if(p1!=p2) //@1
{
if(p1==NULL)
p1 = headB;
if(p2==NULL)
p2 = headA;
}
}
return p1;
}
};
而链表的拼接,其实可以用虚拟的拼接:当指针遍历到链表结尾后,赋值为另一个表的表头。
不要忘记当存在空链表时,直接返回NULL,否则AB == BA 返回的就是第一个节点值,出错。
@1:这里要用一个if进行判断,因为如果两者相等,其实可以直接跳出循环,没必要再进行判断了。
这里会产生一个疑问,就是p1和p2同时到达节点末尾怎么办?
其实如果两个链表长度相同,那么比较的时候在结尾之前就可以找到相等的节点了,比原问题简单多了呢
时间复杂度:O(n)
空间复杂度:O(1)
2 Leetcode 234 回文链表
本质上是判断从头开始和从尾开始读是否相等,从数学上来说只需要判断一半即可。
显然想到Stack的数据结构。
那么开始操作:
这是我一开始写的一段错误代码,原因是stack中存储的是链表节点而不是链表值。很显然的错误!但是我就是没注意呀!
/**
* 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) {}
* };
*/
#include<stack>
class Solution {
public:
bool isPalindrome(ListNode* head) {
stack<ListNode*> my_stack;
ListNode* tmp = head;
int size = 0;
while(tmp!=NULL)
{
size++;
my_stack.push(tmp);
tmp = tmp->next;
}
int count = 0;
while(count < (size/2+1))
{
if(head != my_stack.top())
return false;
else
{
my_stack.pop();
head = head->next;
count++;
}
}
return true;
}
};
修改一下,存储并且比较val值的代码如下
/**
* 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) {}
* };
*/
#include<stack>
class Solution {
public:
bool isPalindrome(ListNode* head) {
stack<int> my_stack;
ListNode* tmp = head;
int size = 0;
while(tmp!=NULL)
{
size++;
my_stack.push(tmp->val);
tmp = tmp->next;
}
int count = 0;
while(count < (size/2))
{
if(head->val != my_stack.top())
return false;
else
{
my_stack.pop();
head = head->next;
count++;
}
}
return true;
}
};
结果正确
时间复杂度:O(n)
空间复杂度:O(n)
这道题目体现了算法的本质是减少重复。——吴军《计算之魂》
3 合并有序链表问题
3.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* mergeTwoLists(ListNode* list1, ListNode* list2) {
ListNode* new_head = new ListNode();
ListNode* res = new_head;
while(list1!=NULL && list2!= NULL)
{
if(list1->val >= list2->val)
{
res->next = list2;
list2 = list2->next;
}
else
{
res->next = list1;
list1 = list1->next;
}
res = res->next;
}
res->next = list1 == NULL ? list2 : list1;
return new_head->next;
}
};
首先是遍历两个链表,逐个插入,相等的情况下用else可以在下一次循环中成功处理。而最后某个链表遍历到头之后,可以直接接上未遍历完的链表。
3.2 合并k个有序链表
承接上面的问题,简化为多次合并两个链表即可解决问题。
/**
* 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* mergeKLists(vector<ListNode*>& lists) {
ListNode* res = NULL;
for(auto it = lists.begin(); it != lists.end(); it++)
{
res = mergeTwoLists(res, *it);
}
return res;
}
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
ListNode* new_head = new ListNode();
ListNode* res = new_head;
while(list1!=NULL && list2!= NULL)
{
if(list1->val >= list2->val)
{
res->next = list2;
list2 = list2->next;
}
else
{
res->next = list1;
list1 = list1->next;
}
res = res->next;
}
res->next = list1 == NULL ? list2 : list1;
return new_head->next;
}
};
4 双指针问题
4.1 寻找中间节点
876. 链表的中间结点 - 力扣(LeetCode)
只需要双指针一快一慢,快指针每次走2个,慢指针每次走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* middleNode(ListNode* head) {
ListNode* fast = head;
ListNode* slow = head;
while(fast!=NULL && fast->next!=NULL)
{
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
};
4.2 链表中倒数第k个节点
剑指 Offer 22. 链表中倒数第k个节点 - 力扣(LeetCode)
思路就是快指针先往前k次,然后再快慢指针同时向前,直到快指针到结尾,返回慢指针。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* getKthFromEnd(ListNode* head, int k) {
ListNode* slow = head;
ListNode* fast = head;
while( fast!=NULL && k>0 )
{
k--;
fast = fast->next;
}
// if(fast == NULL)
// return NULL;
// else
{
while(fast!=NULL)
{
fast = fast->next;
slow = slow->next;
}
}
return slow;
}
};
需要注意的是,有可能k大于链表长度,所以要考虑:当链表长度小于k的时候是返回什么?返回NULL还是第一个节点?
还有一点就是,这里第一个while直接用k—进行计数更加简介噢!
4.3 旋转链表
61. 旋转链表 - 力扣(LeetCode)
旋转链表比较好想到的一个思路就是,先找到要分割链表的两个链表尾,然后重新拼接。
需要注意的是如何保存最后拼接的顺序,如何保存新的链表头?
/**
* 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* rotateRight(ListNode* head, int k) {
int size = 0;
for(ListNode* tmp = head; tmp!=NULL; tmp = tmp->next)
size++;
if(size == 0)
return head;
k = k%size;
if(k == 0)
return head;
ListNode* pre = head;
ListNode* post = head;
while(k>0 && post!=NULL)
{
post = post->next;
k--;
}
while(post->next!=NULL)
{
post = post->next;
pre = pre->next;
}
post->next = head;
head = pre->next;
pre->next = NULL;
return head;
}
};
还有一个办法就是反转链表,先反转全部的链表,然后分割之后单独反转两个小链表,最后完成拼接。
似乎有点小麻烦……
5 删除链表元素专题
5.1 移除链表元素
203. 移除链表元素 - 力扣(LeetCode)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tlH1MekS-1689776120738)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/91e59d2c-560e-4f77-8861-d98ba4e4f312/image.png)]
删除链表中所有值为val的节点。
需要注意的是头节点的删除和中间的节点不同,所以创建了一个 dummy_head
dummy_head→next = head;
然后遍历的时候从dummy_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* removeElements(ListNode* head, int val) {
if(head == NULL)
return head;
ListNode* dommy_head = new ListNode(0, head);
ListNode* cur = dommy_head;
while(cur->next != NULL)
{
if(cur->next->val == val)
{
cur->next = cur->next->next;
}
else
cur = cur->next;
}
return dommy_head->next;
}
};
5.1.1 删除链表中倒数第k个元素
19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3bfGIPpc-1689776120739)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/084f4f31-0e2e-4bf8-b0ff-ca55545e184b/remove_ex1.jpg)]
双指针法遍历一次,找到待删除节点的前一个节点。
尤其要注意的是如果删除的是第一个节点时,直接删。
/**
* 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* fast = head;
ListNode* slow = head;
while(n>0)
{
fast = fast->next;
n--;
}
if(fast == NULL)
return head->next;
while(fast->next!=NULL)
{
fast = fast->next;
slow = slow->next;
}
slow->next = slow->next->next;
return head;
}
};
5.2 删除重复元素
83. 删除排序链表中的重复元素 - 力扣(LeetCode)
删除要考虑的情况无非下面几种:
- 删除头节点怎么办?
- 链表为空怎么办?
- 删除与否,指针怎么移动?
- 终点条件是什么?
/**
* 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* deleteDuplicates(ListNode* head) {
ListNode* cur = head;
if(head == NULL)
return head;
while(cur->next != NULL)
{
if(cur->val == cur->next->val)
{
cur->next = cur->next->next;
}
else
{
cur = cur->next;
}
}
return head;
}
};
82. 删除排序链表中的重复元素 II - 力扣(LeetCode)
这回是把所有的重复元素都删除了。
那么可能出现删除头节点的情况,那么我们先创建一个虚拟头。后续的情况只需要用一个cur即可完成。文章来源:https://www.toymoban.com/news/detail-630169.html
但是因为自己第一次写没写出来,所以对下面的标准代码进行逐行批注。文章来源地址https://www.toymoban.com/news/detail-630169.html
到了这里,关于小白也能学会的链表 | C++ | 算法与数据结构 (新手村)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!