考虑将所有节点从头两两分组,每个组之间进行节点交换,在遍历每一组过程中过程中需要存储第一个节点用于组间连接操作。两两分组循环可以用一个对2取余的iter来标记。对单个节点的组并不需要操作,所以可以看作正常组的第一阶段。更重要的是交换顺序后组间的连接,此时需要一个存储上一组的尾节点,这里我们用last_iter_tail表示。此外,第一组节点的交换还要考虑头节点的更新。
初始:
1->2->3->4->null
第一组组内交换:
2->1->3->4->null
第二组组内交换:
2->1->3->null
4->3->null
一二组连接:
2->1->4->3->null
组间的交换很容易考虑到,组间的连接很容易出错
class Solution {
public:
//奇数个节点
//空链表
//用一个循环的0,1来对节点进行标记 %2
//遍历过程中需要对数据进行存储
ListNode* swapPairs(ListNode* head) {
int count = 0;
int iter = 0;
ListNode* last_iter_tail;
ListNode* swap;
ListNode* ptr = head;
//遍历有效节点
// 考虑循环的结束
while (ptr != nullptr){
//不做操作,只存住循环的前一个node
//对奇数的组不需要进行交换
if (iter == 0) {
swap = ptr;
iter = (iter + 1) % 2;
ptr = ptr->next;
continue;
}
//有变化,考虑head转变
if (iter == 1){
swap->next = ptr->next;
ptr->next = swap;
if (count++ == 0) {
head = ptr;
}
else {
last_iter_tail->next = ptr;
}
last_iter_tail = swap;
ptr = ptr->next->next;
iter = (iter + 1) % 2;
}
}
return head;
}
};
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
//虚拟节点,方便将头节点交换的情况整合到一般情况
ListNode dummy(0, head);
ListNode* prev = &dummy;
ListNode* curr = head;
int count = 0;
//同时对两个节点进行处理
while (curr != nullptr && curr->next != nullptr) {
ListNode* first = curr;
ListNode* second = curr->next;
//循环变量更新
curr = second->next;
second->next = first;
first->next = curr;
//prev指向上一组的尾节点
prev->next = second;
prev = first;
count += 2;
}
//只有0~1个节点,不需要处理,直接返回head
if (count < 2) {
return head;
}
return dummy.next;
}
};
In this updated implementation, we use a dummy node to simplify the handling of the head of the linked list, use a temporary variable to simplify the swapping of the pairs of nodes, and use a counter variable instead of a flag variable to count the nodes.
删除倒数第n个节点,我们只需要获取倒数第n-1个节点,
n-1->next = n->next即可完成删除,考虑到这是一个倒序访问,可以考虑用stack存储节点。
特殊情况要考虑倒数n-1为空,即删除的是头节点时,我们需要返回倒数第n节点的后驱节点n->next(当链表只有一个节点,后驱节点为nullptr,也符合)
使用n-1->next时一定要考虑n-1为空时的特殊情况,要建立这种敏感性。
注意这里的相交是指节点的地址相同而不仅仅是节点的数值相同
class Solution {
public:
//考虑头节点的删除
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* ptr = head;
stacks;
ListNode* temp = nullptr;
while (ptr != nullptr){
s.push(ptr);
ptr = ptr->next;
}
for (int i = 0; i < n; ++i){
if (i == n - 1){
temp = s.top();
s.pop();
if (s.empty()) return temp->next;
else s.top()->next = temp->next;
}
else s.pop();
}
return head;
}
};
本质上是相交链表的对齐,让链表A和链表B同步进行遍历,直到出现相同元素(逆向则是出现不同元素)时找到相交点。相交的链表从尾部开始是同步的,所以可以考虑用两个stack来同步循环,此时空间复杂度为O(N)。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
stack sA;
stack sB;
ListNode* ptr = headA;
while (ptr != nullptr) {
sA.push(ptr);
ptr = ptr->next;
}
ptr = headB;
while (ptr != nullptr) {
sB.push(ptr);
ptr = ptr->next;
}
ListNode* res = nullptr;
while (!sA.empty() && !sB.empty()) {
if (sA.top() == sB.top()){
res = sA.top();
sA.pop();
sB.pop();
continue;
}
else break;
}
return res;
}
};
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode* res = nullptr;
int lenA = 0;
int lenB = 0;
ListNode* ptr = headA;
while (ptr != nullptr){
++lenA;
ptr = ptr->next;
}
ptr = headB;
while (ptr != nullptr){
++lenB;
ptr = ptr->next;
}
int minLen = lenA < lenB ? lenA : lenB;
int lenGap = lenA < lenB ? lenB - lenA : lenA - lenB;
ListNode* orig = lenA < lenB ? headA : headB;
ListNode* sync = lenA < lenB ? headB : headA;
while (lenGap-- > 0){
sync = sync->next;
}
for (int i = 0; i < minLen; ++ i){
if (orig == sync) {
res = orig;
break;
}
orig = orig->next;
sync = sync->next;
}
return res;
}
};
一个指针走A+B,另一指针走B+A,假设链表A和B相交且L(A) != L(B),那么他们一定会在第二次相交点相遇:
aaaaccnbbbccn
bbbccnaaaaccn
(其中aaaa表示A中不相交的部分,bbb表示B中不相交的部分,cc表示相交的部分,n表示nullptr)
加入不相交,那么此时:
aaaabbn
bbaaaan
直到结束为nullptr时想等。
假如L(A) = L(B),A与B相交,则有:
aaaccn
bbbccn
A与B不相交,则有:
aaan
bbbn
均能在第一轮遍历得出结果
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode* ptr1 = headA;
ListNode* ptr2 = headB;
while (ptr1 != ptr2) {
ptr1 = (ptr1 == nullptr) ? headB : ptr1->next;
ptr2 = (ptr2 == nullptr) ? headA : ptr2->next;
}
return ptr1;
}
};
记录是否访问过该元素可以维护一个set进行查询,但是空间复杂度为O(N)
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
sets;
ListNode* ptr = head;
while(ptr != nullptr){
if (s.find(ptr) == s.end()) {
s.insert(ptr);
}
else break;
ptr = ptr->next;
}
return ptr;
}
};
假如没有循环,那么遍历一定会碰到相同的node值,由于要求空间为O(1), 只能利用当前步和一个指标来判断是否循环。假设循环长度为b,当前步就和走了kb步后的结果比较(缺乏考虑,但是没考虑到绕圈的情况,但是结果不变),从而得出循环周期的倍数kb。然后从头开始遍历,第一个走过nb步能返回的点就是循环起点。
效率比较低下,尝试步数利用了1+2+…+kb, 时间复杂度为O(N^2), 实际利用快慢双指针的话能讲复杂度降为O(N)。
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
int counter = 1;
int i;
ListNode* ptr = head;
ListNode* cmp = nullptr;
while (true) {
if (ptr == nullptr) return nullptr;
i = 0;
cmp = ptr;
while(i < counter){
if (cmp == nullptr) return nullptr;
cmp = cmp->next;
++i;
}
if (ptr == cmp) break;
counter++;
ptr = ptr->next;
}
ptr = head;
while (true){
i = 0;
cmp = ptr;
while (i < counter){
cmp = cmp->next;
++i;
}
if (ptr == cmp) break;
ptr = ptr->next;
}
return ptr;
}
};
考虑快指针每次2步,满指针每次走1步,那么当他们相遇时,有:
2f = s + kb, f = 2s, 从而得出 s = kb。假设非循环段长度为a。假如此时有另一个慢指针s2从起点出发,走过a步后,s1走过a+kb步,此时s1与s2相遇,相遇点为循环起点。时间复杂度为O(N)。
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* fast = head;
ListNode* slow = head;
//一旦遇到nullptr,则不存在环
while (fast != nullptr && fast->next != nullptr){
fast = fast->next->next;
slow = slow->next;
//快慢指针相遇
if (slow == fast){
ListNode* slow_start = head;
while (slow != slow_start){
slow = slow->next;
slow_start = slow_start->next;
}
return slow;
}
}
return nullptr;
}
};