两两交换链表中的节点
直观思路
从链表头开始,对之后的两个链表元素的链连接方式进行更改,在不断链的情况下完成链表中节点的交换,且只针对后两个链表元素都存在的情况,所以使用while循环,循环条件为while(cur->next and cur->next->next),此处cur为链表交换节点的前一个节点,同样创建虚拟头结点来简化交换操作,cur开始指向虚拟头结点dummy。即要交换之后的两个链表节点。创建了temp1和temp2用于保存cur之后的两个链表节点,为了保证不断链,两个链表节点的交换需要先考虑temp2后的节点,它由temp2->next来指示,具体的步骤如代码所示,先将cur的next指向temp2,即将第二个节点放到第一个结点位置,然后temp1的next指向temp2的next,这里保证不丢失temp2的next信息,然后temp2->next = temp1,此时已完成了链表节点的交换操作,但为了保证下次循环的进行,需对cur进行更新,cur = cur->next->next。
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode *dummy = new ListNode(0,head);
ListNode *cur = dummy;//使用虚拟头结点,最后返回是dummy->next;
while(cur->next and cur->next->next){
ListNode*temp1 = cur->next;//创建temp1保存cur后的第一个链表节点
ListNode*temp2 = cur->next->next;//创建temp2保存cur后的第二个节点
cur->next = temp2;//将cur的next指向temp2
temp1->next = temp2->next;//将temp1的next指向temp2后的节点,该节点可为nullptr
temp2->next = temp1;//完成对两个节点在链表中位置的交换
cur = cur->next->next;//更新cur,使cur到达下一个交换位置的前一个节点
}
return dummy->next;//返回
};
};
算法的时间复杂度为O(n),空间复杂度为O(1)。
删除链表的倒数第N个节点
直观思路
先遍历一遍链表,得到链表的长度length,根据倒数第N个节点,得到这个节点在正序中的索引,然后删除这个节点,算法时间复杂度O(n),空间复杂度O(1)。
这里同样使用虚拟头结点方便删除操作。
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode * dummyhead = new ListNode(0,head);
ListNode * cur = dummyhead;//使用虚拟头节点
int length = 0;
while(cur->next){
cur = cur->next;
length++;
}//遍历一遍链表,得到链表长度
int netindexpre = length - n;//计算得到正序的索引
cur = dummyhead;
for(int i = 0;i < netindexpre ;i++){
cur = cur->next;
}//cur到达正序索引前的位置
ListNode *temp = cur->next;
cur->next = cur->next->next;
delete temp;//删除目标节点
return dummyhead->next;//返回虚拟头节点的下一位,链表的头结点
}
};
双指针法
利用两个指针,指针cur_to_end要遍历到结尾,指针pre_to_n_to_delete用于找到倒数第n个结点前的结点。先用cur_to_end走n+1步,之后两个指针一起往前走,直到cur_to_end为空,这时,pre_to_n_to_delete所指向的结点为应删除结点前的结点,之后就是链表的删除操作。
这里我们同样使用虚拟头结点来简化。算法的时间复杂度和空间复杂度和直观思路相同。
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode*dummy = new ListNode(0,head);
ListNode*cur_to_end = dummy;
while(n-- and cur_to_end!=nullptr){
cur_to_end = cur_to_end->next;
}
cur_to_end = cur_to_end->next;
ListNode*pre_to_n_to_delete = dummy;
while(cur_to_end){
cur_to_end = cur_to_end->next;
pre_to_n_to_delete = pre_to_n_to_delete->next;
}
ListNode*temp = pre_to_n_to_delete->next;
pre_to_n_to_delete->next = pre_to_n_to_delete->next->next;
pre_to_n_to_delete = nullptr;
delete temp;
delete cur_to_end;
delete pre_to_n_to_delete;
return dummy->next;
}
};
链表相交
直观思路
首先得注意的点是,两链表相交,相交节点应该是共享的,若每个链表从头遍历,则应存在相同地址的区域,而不是节点上的值相同。
考虑暴力解法,先对两个链表都从头到尾遍历,获取链表长度,外循环较长链表节点,内循环较短的链表上的节点,若节点相同,则返回索引及该节点val值。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode*dummy1 = new ListNode(0,headA);
ListNode*dummy2 = new ListNode(0,headB);
ListNode* cur1 = dummy1;
ListNode* cur2 = dummy2;
int len1 = 0;
int len2 = 0;
while(cur1->next){
cur1=cur1->next;
len1++;
}
while(cur2->next){
cur2=cur2->next;
len2++;
}//获取len1和len2
if(len1==0 or len2 == 0){
return nullptr;
}
if(len1>len2){
swap(len1,len2);
swap(cur1,cur2);
swap(dummy1,dummy2);
}//更改长短,同时也要更换cur及dummy
cur1 = dummy1;//重置cur1
cur2 = dummy2;//重置cur2
for(int i = 0; i <= len1-1; i ++){
cur1 = cur1->next;
cur2 = dummy2;//记得重置cur2
for(int j = 0; j <= len2-1;j++){
cur2 = cur2->next;
if(cur1==cur2){
return cur1;
}
}
}
return nullptr;
}
};
若链表1长度为m,链表2长度为n,算法时间复杂度为O(mn),空间复杂度为O(1),不太理想。
双指针法
考虑这样一个事实,若两个链表有节点是相同的,那在节点后的所有元素应该是相同的(因为节点靠next和下一节点联系吗,相同节点的next是相同的),因此,我们可以考虑让较长的链表先走到剩余长度和较短链表相同的位置上(若两者有公共节点,两者再同时遍历相同的距离后到达一样的节点),然后两者一起向后走,若curA==curB,则为链表相交的第一个节点。(参考代码随想录)
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode*dummy1 = new ListNode(0,headA);
ListNode*dummy2 = new ListNode(0,headB);
ListNode* cur1 = dummy1;
ListNode* cur2 = dummy2;
int len1 = 0;
int len2 = 0;
while(cur1->next){
cur1=cur1->next;
len1++;
}
while(cur2->next){
cur2=cur2->next;
len2++;
}//获取len1和len2
if(len1==0 or len2 == 0){
return nullptr;
}
if(len2>len1){
swap(len1,len2);
swap(cur1,cur2);
swap(dummy1,dummy2);
}//更改长短,使len1为较短,同时也要更换cur及dummy
cur1 = dummy1;//重置cur1
cur2 = dummy2;//重置cur2
int gap = len1 - len2;
for(int i = 0;i<gap;i++){
cur1 = cur1->next;
}//cur1前进到剩余长度和len2相同的位置
while(cur1){
if(cur1 == cur2){
return cur1;
}
cur1 = cur1->next;
cur2 = cur2->next;
}
return nullptr;
}
算法时间复杂度为O(m+n),空间复杂度为O(1)。
环形链表
此题可以认为有两个问题,一是环形链表是否存在?二是若存在,则存在的位置在哪里?
针对问题一,选择步幅为1和2遍历链表的slow和fast两个指针,若存在环,则两个指针必会在链表的环中相遇。具体的解释可以参考卡哥视频把环形链表讲清楚! 如何判断环形链表?如何找到环形链表的入口? LeetCode:142.环形链表II_哔哩哔哩_bilibili
我这里还参考了leetcode上君君的评论,设环的长度为A,slow指针在入环的时候fast指针在环中的位置为B(取值范围为0~A-1),当slow指针和fast指针相遇时,slow指针在环中走到了C有
C%A = (B+2C)%A 等价于 An + C = B + 2C,合并得到 C= An - B,当n = 1时,0<=C<A,即slow指针在第一圈内必定与fast指针相遇
此处步幅1和2的选择也有一定数学依据,2是最小的质数,感兴趣可以继续深挖。
针对问题二,在确定了环一定存在的情况下,创建一个新的指针ptr从开始以步幅为1进行遍历链表,ptr和slow指针将在入环的位置相遇,具体可以参考上述卡哥的视频。或者leetcode Krahets的一个解答:1.fast = 2slow 2.fast = slow + nc(此处c代表在链表环中转了一圈),即可以推出slow = nc,此时从头开始走到入环处需要a+nc,而此时slow已经走了nc,所以只需再让head以步幅1走一个新的指针ptr,slow和ptr相遇的时候就是入环点a,具体演示可以搜索。代码如下
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode * dummy = new ListNode(0);
dummy->next = head;
ListNode*fast = dummy;
ListNode*slow = dummy;
int pos = -1;
while(fast->next&&fast->next->next){
fast = fast->next->next;
slow = slow->next;
if(fast == slow){
pos++;
break;
}
}//fast指针一次向后运行两步,slow指针一次向后一步,
//fast和slow一起出发,fast步幅大,所以条件是(fast->next and fast->next->next 非空)
//当fast和slow在环形链表中时,必定相遇,此时fast == slow,跳出循环,并置pos+1,表示有环
if(pos == -1)
return nullptr;//当pos为-1时,返回空指针,表示链表无环
ListNode*ptr = dummy;//新建节点ptr,以1的步幅向后移动
while(1){//因为ptr必定和slow会在环形链表的入链处相遇,在循环体内返回
ptr = ptr->next;
slow = slow->next;
if(ptr == slow){
return ptr;
}
}
}
};
算法的时间复杂度为O(n),空间复杂度为O(1)。
链表总结
我认为链表有几个重要的点需要考虑:
1.链表的增删,对链表的增删需要查找到需要增加或删除结点的前一个结点a,应确保不断链的情况下,对链表进行操作,对增加结点new来说,需要先将增加结点的next指针指向a->next,之后让a的next指针指向结点new。对于删除操作来说,需要当前结点a的next指向被删除结点的next,对于C++语言,需要释放被删除结点占用的空间(delete)。此外,对链表的增删需要考虑增删的结点是否为头结点,加入虚拟头结点可以很好的统一这个操作。
2.对链表的遍历需要创建指针Listnode* cur = dummyhead,选择while循环。
Listnode *dummy = new Listnode(0,head);
Listnode *cur = dummy;
while(cur){
cur = cur->next;
//添加你在遍历过程中的额外操作
}
3.对链表的大多数题目可以使用双指针法(或者说针对大部分的线性表数据结构都可以使用双指针法),选择快慢指针完成诸如链表的反转,查找环形链表等操作,快慢指针的更新方式和步幅大小需要根据题目的要求来做。
839

被折叠的 条评论
为什么被折叠?



