文档讲解
双指针 哈希表 哨兵结点 循环不变式
核心:对于反转链表的问题,使用的pre和cur这两个指针,反转开始之前一定是pre指向上一段的最后一个节点,而cur指向即将反转链表的头节点,这就是所谓的循环不变式。那么又有一个问题,怎么保持住链表不断连呢?我们需要保存一个节点的信息,那就是要反转的链表前一个结点的信息,保存在P0中,这又有一个问题,例如昨天做的反转整个链表的问题,从第一个结点就开始反转,那P0去保存什么呢?故这个时候需要引入哨兵结点,P0保存它的信息。
思路:这道题可以把两个结点看成一个子链表,即我们翻转一个链表,其大小为2,那么从上面就很清晰的看出需要两个指针,一个指向链表的前一个位置,一个指向链表的头节点,(因为初始化的时候,pre没有地方指,故创建一个哨兵结点),那么在循环中,我们应该怎么样写呢?
1.将pre的next指针指向翻转之后的头节点;
2.cur的next指针指向剩下的链表;
3.翻转链表操作
4.移动两个指针,维持循环不变式,将pre移动到这个链表的末尾;
5.cur移动到下一个链表的开头
时间复杂度:O(n)
空间复杂度:O(1)
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* dummy=new ListNode(0,head);
ListNode* pre=dummy;
ListNode* cur=dummy->next;
while(pre&&cur&&cur->next)
{
pre->next=cur->next;
cur->next=pre->next->next;
pre->next->next=cur;
pre=cur;
cur=pre->next;
}
return dummy->next;
}
};
后面拓展两道题:
92.反转链表ii
class Solution {
public:
ListNode* reverseBetween(ListNode* head, int left, int right) {
ListNode* dummy=new ListNode(0,head);
ListNode* p0=dummy;
for(int i=0;i<left-1;i++)
{
p0=p0->next;
}
//1<=left
ListNode* pre=nullptr;
ListNode* cur=p0->next;
for(int i=0;i<right-left+1;i++)
{
ListNode* temp=cur->next;
cur->next=pre;
pre=cur;
cur=temp;
}
p0->next->next=cur;
p0->next=pre;
return dummy->next;
}
};
class Solution {
public:
ListNode* reverseKGroup(ListNode* head, int k) {
int l_size=0;
ListNode* dummy=new ListNode(0,head);
ListNode* p0=dummy;
for(ListNode* cur=head;cur;cur=cur->next)
{
++l_size;
}
ListNode* cur=p0->next;
ListNode* pre=nullptr;
for(;l_size>=k;l_size-=k)
{
for(int i=0;i<k;i++)
{
ListNode* temp=cur->next;
cur->next=pre;
pre=cur;
cur=temp;
}
ListNode* temp=p0->next;
p0->next->next=cur;
p0->next=pre;
p0=temp;
}
return dummy->next;
}
};
思路:说实话,我拿到这道题的时候,第一瞬间是想到了双指针的,但是没想出来,所以想把遍历一遍,大小求出来,把边界考虑清楚了,做起来还是很容易的,但是代码随想录这种做法还是得学!(感觉学了也很难用出来)
时间复杂度:O(n)
空间复杂度:O(1)
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n)
{
ListNode* dummy=new ListNode(0,head);
int l_size=0;
for(ListNode* cur=head;cur;cur=cur->next)
{
++l_size;
}
int m=l_size-n;
if(m<0)return dummy->next;
ListNode*cur=dummy;
while(m--)
{
cur=cur->next;
}
ListNode* temp=cur->next;
cur->next=cur->next->next;
delete temp;
return dummy->next;
}
};
双指针算法做这道题,此题的双指针之间的距离就是来维持题目需要的n,如果快指针到达末尾了,那慢指针距离快指针的距离正好为n的话,这个时候是不是慢指针就是指向要删除的结点的呢?但我们要删除一个结点,应该知道他的上一个结点,故要保持距离为n+1!
时间复杂度:O(n)
空间复杂度:O(1)
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummy=new ListNode(0,head);
ListNode* fast=dummy;
ListNode* slow=dummy;
while(n--&&fast!=nullptr)
{
fast=fast->next;
}
//对于这里的处理有两种写法
/*
while(fast->next)
{
fast=fast->next;
slow=slow->next;
}
*/
fast=fast->next;//维持slow与fast之间的距离是n+1,让slow最后停在倒数第n+1的这个位置
while(fast)
{
fast=fast->next;
slow=slow->next;
}
slow->next=slow->next->next;
return dummy->next;
}
};
上面的代码随想录的答案,fast多走一步,来维持长度为n+1。其实我觉得这里不用这样处理!其中注释一个代码,换成另外一种也是可以的在这里fast指针不再到链表的外,而是到链表的最后一个元素,此时长度也是n,但是slow依然会停在倒数第n+1的位置!
面试题02.07.链表相交
思路:其实一刷这道题的时候,是懵逼的,不懂他想表达啥意思。其实这道题他就是想你去找到较大链表某个位置开始,找到末尾和较小链表从某个位置开始找到末尾,有没有一样的一段,只要第一个节点一样后面的都一样了,但是这个题还有一个坑比的地方,就是链表是否相交是比较的地址,这个是啥意思呢?用第一个leetcode上面第一个例子来举例,两个链表都是从1这个结点的next指针指向的下一个结点,那么这个时候下一个结点应该就是同一个结点了,而不是比较数值。简直是大坑啊!!,1和1前一个结点不一样,说明1和1只是存放的数值相同的不同结点,故不能判断那里的!
时间复杂度:O(n)
空间复杂度:O(1)
class Solution {
public:
int getlength(ListNode* head)
{
int length=0;
for(ListNode* cur=head;cur;cur=cur->next)
{
++length;
}
return length;
}
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
int lenA=getlength(headA);
int lenB=getlength(headB);
if(lenB>lenA)
{
swap(headA,headB);
swap(lenA,lenB);
}
int n=lenA-lenB;
while(n--)
{
headA=headA->next;
}
ListNode* cur1=headA;
ListNode* cur2=headB;
int flag=0;
while(cur1!=nullptr)
{
if(cur1==cur2)
{
return cur1;
}
cur1=cur1->next;
cur2=cur2->next;
}
return nullptr;
}
};
思路:1.第一种哈希表的做法,这个题一拿到就想到了hashmap;2.双指针破局!
时间复杂度:O(n)
空间复杂度:O(n)
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
unordered_map<ListNode*,int> map;
while(head!=nullptr)
{
if(map[head]!=0)
{
return head;
}
map[head]++;
head=head->next;
}
return nullptr;
}
};
双指针算法:使用快慢指针,如何体现快慢指针呢快指针每次走两步,慢指针每次只走一步!876.链表的中间结点这道题可以感觉一下这种用法。141.环形链表这道题较这个也会容易一点,这个题最关键的点在于如何求出入口呢?这个时候需要数学推导一下!
数学推导:快指针走过的路程:a+k(b+c);慢指针走过的路程:a+b
2(a+b)=a+b+c+(k-1)(b+c);=>a-c=(k-1)(b+c),这个等式啥意思呢?a是从头节点到入口的距离,从头节点走c步,剩下的距离是环状的整数倍,相当于这个整数倍对于实际位置没有影响,那么我们有一个指针从某一位置到入口的距离也是c,那就是慢指针,可能有人这个时候会觉得快指针不也是在那个位置吗?但是你别忘了,快指针一次是走两步的,所以头节点走到a-c这个位置的时候,慢指针一定是走到环的入口的。故接下来只需要头节点往前走,slow走就行了,他们相遇的时候一定是环状链表的入口!
时间复杂度:O(n)
空间复杂度:O(1)加粗样式
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* slow=head;
ListNode* fast=head;
while(fast!=nullptr&&fast->next!=nullptr)
{
slow=slow->next;
fast=fast->next->next;
if(slow==fast)
{
while(head!=slow)
{
head=head->next;
slow=slow->next;
}
return slow;
}
}
return nullptr;
}
};