链表
使用C++
建议顺序读入·。`
链表基础
重点在于:
-
- 虚拟头结点在什么情况下添加, carl哥说,在对链表增删时需要添加虚拟头结点。对链表进行查找或者没有改动链表元素时,就不需要;其原因在于,虚拟头结点的添加,在于在循环中便于对第一个节点操作。 而无需特别处理。
-
- 首先在于对问题的抽象,抽象成怎么能够在一次次迭代中,去处理问题。这种应该有点分治的味道,抽象成一个个相同的子问题。比如下面的翻转链表的题。虽然其采用双指针,但是这种指针的操作,怎么能够在一次次循环迭代中处理。
-
移除链表元素
这道题题意为在链表中删除给定的元素,主要是设计两个指针,分别是cur和prev, 每次迭代更新prev为cur,好让cur等于被移除的元素时,可以去删除这个元素。由于删除的元素可能是第一个元素所以使用虚拟头节点
核心代码
while(cur != nullptr)
{
if(cur->val == val )
{
ListNode * tmp = cur ;
pre ->next = cur->next ;
cur = pre; // 因为要和 !=val的情况保持一致性(为了下面的pre= cur ; cur= cur->next ; 保证执行)所以这么设置
delete(tmp ) ;
}
pre = cur ;
cur= cur->next ;
}
完全代码
ListNode* removeElements(ListNode* head, int val) {
ListNode* dumy = new ListNode() ;
dumy->next = head ;
ListNode* pre = dumy,
*cur = dumy->next ;
if(cur== nullptr)
{
return nullptr ;
}
while(cur != nullptr)
{
if(cur->val == val )
{
ListNode * tmp = cur ;
pre ->next = cur->next ;
cur = pre; // 因为要和 !=val的情况保持一致性(为了下面的pre= cur ; cur= cur->next ; 保证执行)所以这么设置
delete(tmp ) ;
}
pre = cur ;
cur= cur->next ;
}
return dumy->next ;
}
设计链表
这个题的难点在于对于类的设计把握,链表类的对象构造时要生成虚拟头结点,类成员有指向头结点的指针head,还有链表的大小。 每次对链表的添加和删除,获取都从head开始操作。另外是找节点是第几个因此画图是必须的,仔细也是必须的。
核心代码
class MyLinkedList {
public:
int _size ;
ListNode* head ;
MyLinkedList() {
head = new ListNode() ; // 因为要对链表进行增删,所以需要虚拟头结点
_size = 0 ;
}
完全代码
class MyLinkedList {
public:
int _size ;
ListNode* head ;
MyLinkedList() {
head = new ListNode() ;
_size = 0 ;
}
int get(int index) {
if(_size-1< index)
return -1 ;
ListNode *tmp = head ->next;
while(index--)
{
tmp = tmp->next ;
}
return tmp->val ;
}
void addAtHead(int val)
{
ListNode * tmp = new ListNode(val);
tmp->next = head->next ;
head->next = tmp ;
_size++ ;
}
void addAtTail(int val) {
ListNode* tmp = new ListNode(val) ;
ListNode * cur = head ;
if(head->next == nullptr)
{
head->next = tmp ;
_size++ ;
return ;
}
while(cur->next != nullptr)
{
cur = cur->next ;
}
cur->next = tmp ;
_size++ ;
}
void addAtIndex(int index, int val)
{
ListNode * tmp = head ;
if(index >_size)
{
return ;
}
// index = index+1 ;
while(index--)
{
tmp= tmp->next;
}
ListNode *t = new ListNode(val) ;
t->next = tmp->next ;
tmp->next =t ;
_size++ ;
}
// void print()
// {
// ListNode * tmp = head ;
// tmp = tmp->next;
// while(tmp!=nullptr)
// {
// cout<<tmp->val>>" ";
// tmp = tmp->next;
// }
// }
void deleteAtIndex(int index)
{
if(index>=_size) // 如果index
{
return ;
}
ListNode * tmp = head ;
while(index--)
{
tmp= tmp->next ;
}
ListNode *de = tmp->next ;
tmp->next = tmp->next->next;
delete(de) ;
_size-- ;
return ;
}
};
反转链表
题意是把整个链表反转,需要在O(1)的空间复杂度,扣扣头,发现只有更改指针指向,
原本以为使用双指针就可,但发现虽然一次迭代可以更改指针方向,
fast->next = slow ; 但是如果要迭代,抽象成一个个相同子问题,那么就需要第三个指针指向下一次要迭代处理的起始元素。当然看了卡哥的题解, 也可以是双指针,我这里的第三个指针,是用一个临时节点替代的,本质上还是三指针,不过感觉我这里要清楚点哈哈哈。 但是代码行数都差不多
另外还需要处理少于三个元素的链表
核心代码
while(fast!= nullptr)
{
mid-> next = slow ; //核心语句
slow = mid ;// 为下一次迭代继续, 重新设置slow
mid = fast ;//为下一次迭代继续,重新设置mid
fast = fast->next ; //为下一次迭代继续,重新设置fast
}
卡哥代码
ListNode* reverseList(ListNode* head) {
ListNode* temp; // 保存cur的下一个节点
ListNode* cur = head;
ListNode* pre = NULL;
while(cur) {
temp = cur->next; // 保存一下 cur的下一个节点,因为接下来要改变cur->next
cur->next = pre; // 翻转操作
// 更新pre 和 cur指针
pre = cur;
cur = temp;
}
return pre;
}
完全代码
ListNode* reverseList(ListNode* head) {
//使用三指针 slow , mid , fast ; 因为二指针不能在fast->next = slow 之后更新
ListNode* slow =nullptr, *mid = nullptr , * fast = nullptr ;
if(head)
{
slow = head ;
if(slow ->next != nullptr)
{
mid = slow->next ;
if(mid->next != nullptr)
{
fast = mid->next ;
}
}
}
if(slow== nullptr)
{
return nullptr ;
}
else if(slow!= nullptr && mid == nullptr)
{
return slow ;
}
else if(slow != nullptr&& mid!= nullptr && fast == nullptr)
{
mid->next = slow ;
slow->next = nullptr ;
return mid ;
}
else if(slow != nullptr && mid != nullptr && fast != nullptr)
{
slow ->next = nullptr ;
while(fast!= nullptr)
{
mid-> next = slow ;
slow = mid ;
mid = fast ;
fast = fast->next ;
}
mid->next = slow ;
return mid ;
}
return nullptr ;
}
两两交换链表中的节点
这道题卡了半天,做不出来,最后看题解看了一眼,发现有一个cur用于在下一次迭代 连接上一次迭代的头结点。 少想了一步。
题意在于把相邻的两个节点交换。也是使用三指针。当然也可以用卡哥的临时指针替换第三个指针
核心代码
while(slow != nullptr && mid != nullptr && fast != nullptr) //三指针的判断当然必须以有三个元素为条件
{
mid->next =slow ;
cur->next =mid ;
slow->next = fast ;
cur =cur->next->next ;
slow = fast ;
fast = nullptr , mid = nullptr ; // 因为还不确定下次迭代有没有节点,先将fast,mid全部设为nullptr
if(slow->next)
{
mid = slow->next;
if(mid->next)
{
fast = mid->next ;
}
}
}
[删除链表倒数第N个节点 ]
题意如题;这回只要想一想,我遍历下链表,找到倒数第N个节点,自然而然是双指针了,一个快指针指向了尾元素的下一个元素的时候,慢指针指向的是要删除的第N个节点的前面节点。这时要画图,要达到的结果的图
核心代码
while(fast!=nullptr)
{
step++ ;
if(step>n+1) // 因为slow要在被删除节点的前一个节点所以特别注意条件
{
slow = slow->next ;
}
fast=fast->next ;
}
完全代码
ListNode* removeNthFromEnd(ListNode* head, int n) {
// 使用快慢指针, 快指针到达nullptr时, 慢指针刚好到达倒数第n个节点
ListNode * dummy = new ListNode() ;
dummy->next = head ;
ListNode * fast =dummy , *slow = dummy ;
int step = 0 ;
if(dummy->next == nullptr)
{
return nullptr ;
}
while(fast!=nullptr)
{
step++ ;
if(step>n+1) // 因为slow要在被删除节点的前一个节点所以特别注意条件
{
slow = slow->next ;
}
fast=fast->next ;
}
cout<<slow->val <<endl ;
ListNode * tmp = slow->next;
slow->next = slow->next->next ;
delete(tmp) ;
return dummy->next ;
}
链表相交
这个题的题意是给两个链表,找出并返回两个单链表相交的起始节点。
此时不要想用什么方法解决,想将问题抽象,是否可以抽象成数学模型;
先从怎么找到第一个相交的结点先退一步,判断两个节点是否相同可以用指向两个节点的指针是否相等。 我们有一个工具迭代,还有一个是遍历, 我们能从迭代获取什么吗,迭代是抽象成相同的小问题 好像可以用,那遍历呢,哦,扣破脑袋,也想不到竟然要遍历整个链表获取链表的节点数目呀,再去相减得到的差,较长的链表又有指针从头遍历,遍历的次数是这个差,遍历完成后,较短的链表有一个指向头结点的指针。两个指针慢慢比较 所以要花至少一半的时间去看清题意,从抽象数学模型的角度去看问题。 再用工具去解决。
所以核心代码
while(pA != nullptr)
{
numlistA++ ;
pA = pA->next ;
}
// cout<<"A"<<numlistA ;
while(pB != nullptr)
{
numlistB++ ;
pB = pB->next ;
}
if(numlistA > numlistB)
{
int dif = 0 ;
dif = numlistA - numlistB ;
ListNode * tmpA = headA ;
while(dif--)
{
tmpA = tmpA->next ;
}
ListNode * tmpB = headB ;//较短链表有个指针指向头结点
while(tmpA != tmpB && tmpA!= nullptr && tmpB != nullptr) // 比较,相同的就是头结点。
{
tmpA = tmpA->next ;
tmpB = tmpB->next ;
}
if(tmpA == tmpB)
{
return tmpA ;
}
}
else
{
int dif = 0 ;
dif = numlistB - numlistA ;
ListNode * tmpB = headB ;
while(dif--)
{
tmpB = tmpB->next ;
}
ListNode * tmpA = headA ;
while(tmpA != tmpB && tmpA!= nullptr && tmpB != nullptr)
{
tmpA = tmpA->next ;
tmpB = tmpB->next ;
}
if(tmpA == tmpB)
{
return tmpA ;
}
}
[环形链表](https://leetcode.cn/problems/linked-list-cycle-ii/description/)
又是一道非常难的数学题:
记住:快慢指针 , 快指针 一次走两步,慢指针一次走一步,相遇第一次时,一个指针指向头,一个指针指向相遇节点, 然后每次两指针都走一步,直到遇到。
// 难点计算相遇时,两指针分别走的距离的关系 ; 关系计算时应该要分为起点到链表入口节点的距离。
// 记忆, : 指针相遇时点到入的距离等于起点到环入口的距离 ; slow 一次走一步,fast一次走两步
ListNode * slow =head , *fast = head ;
while(fast!=nullptr && fast->next != nullptr)
{
slow = slow->next ;
fast = fast->next->next;
if(slow==fast)
{
// 如果slow和fast相遇,相遇节点到环入口的距离等于链表起点到环入口的距离。
ListNode*tmp1 = slow ;
ListNode*tmp2 = head ;
while(tmp1!=tmp2)
{
tmp1=tmp1->next;
tmp2 = tmp2->next ;
}
return tmp1 ;
}
}
return nullptr ;
哈哈找到一个直观的题解了
在遍历过程中用哈希表去记录每个节点,之后第一个发现遍历过一次的结点,就是入口。以后还是靠简单数学过活吧,这种高深的东西。
核心代码
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
unordered_set<ListNode *> visited;
while (head != nullptr) {
if (visited.count(head)) {
return head;
}
visited.insert(head);
head = head->next;
}
return nullptr;
}
};
作者:力扣官方题解
链接:https://leetcode.cn/problems/linked-list-cycle-ii/solutions/441131/huan-xing-lian-biao-ii-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。