链表
链表部分
1.(链表)707. 设计链表(模版,通过了valgrind测试)
实现单向链表,即每个节点仅存储本身的值和后继节点。除此之外,我们还需要一个哨兵(sentinel)节点作为头节点,和一个 size 参数保存有效节点数。如下图所示。
初始化时,只需创建头节点 head 和 size 即可。
实现 get(index) 时,先判断有效性,再通过循环来找到对应的节点的值。如下图所示。
实现 addAtIndex(index, val) 时,如果 index 是有效值,则需要找到原来下标为 index 的节点的前驱节点 pred,并创建新节点 to_add,将to_add 的后继节点设为 pred 的后继节点,将 pred 的后继节点更新为 to_add,这样就将 to_add 插入到了链表中。最后需要更新 size。这样的操作对于 index=0 也成立,如以下两张图所示。
实现 addAtIndex(index, val)时,可以借助 addAtHead(val) 和 addAtTail(val) 来实现。
实现 deleteAtIndex(index),先判断参数有效性。然后找到下标为 index 的节点的前驱节点 pred,通过将 pred 的后继节点更新为 pred 的后继节点的后继节点,来达到删除节点的效果。同时也要更新 size。如下图所示。
题解:
struct Node { int val; struct Node *next; }; typedef struct LinkedList{ struct Node *head; struct Node *tail; int size; } MyLinkedList; MyLinkedList* myLinkedListCreate() { MyLinkedList* obj=(struct LinkedList*)malloc(sizeof(struct LinkedList)); obj->head=NULL; obj->tail=NULL; obj->size=0; return obj; } struct Node *createNode(int val) { struct Node *node; node=(struct Node*) malloc(sizeof(struct LinkedList)); node->val=val; node->next=NULL; return node; } int myLinkedListGet(MyLinkedList* obj, int index) { if(index > (obj->size)-1 || index < 0) return -1; struct Node *temp=obj->head; for(int i=0;i<index;i++) { temp=temp->next; } return temp->val; } void myLinkedListAddAtHead(MyLinkedList* obj, int val) { if(obj->head==NULL) { obj->head=createNode(val); obj->tail=obj->head; } else { struct Node *temp=createNode(val); temp->next=obj->head; obj->head=temp; } obj->size++; } void myLinkedListAddAtTail(MyLinkedList* obj, int val) { if(obj->head==NULL) { obj->head=createNode(val); obj->tail=obj->head; } else { struct Node *temp=createNode(val); obj->tail->next=temp; obj->tail=temp; } obj->size++; } void myLinkedListAddAtIndex(MyLinkedList* obj, int index, int val) { if(index > (obj->size) || index < 0) return; else if(index == obj->size) { myLinkedListAddAtTail(obj,val); return; } else if(index == 0) { myLinkedListAddAtHead(obj,val); return; } struct Node *temp=obj->head; struct Node *temp_pre; struct Node *addNode=createNode(val); for(int i=0;i<index;i++) { temp_pre=temp; temp=temp->next; } temp_pre->next=addNode; addNode->next=temp; obj->size++; } void myLinkedListDeleteAtIndex(MyLinkedList* obj, int index) { if(index > (obj->size)-1 || index < 0) return; if(index == 0) { struct Node *temp=obj->head; obj->head=temp->next; free(temp); temp=NULL; obj->size--; } else { struct Node *temp=obj->head; struct Node *temp_pre; for(int i=0;i<index;i++) { temp_pre=temp; temp=temp->next; } if(index == (obj->size)-1) { obj->tail=temp_pre; free(temp); temp=NULL; obj->size--; } else { temp_pre->next=temp->next; free(temp); temp=NULL; obj->size--; } } } void myLinkedListFree(MyLinkedList* obj) { if(obj->size == 0) { obj->head=NULL; obj->tail=NULL; return; } else { struct Node *temp=obj->head; struct Node *temp_pre; for(int i=0;i<obj->size;i++) { temp_pre=temp; temp=temp->next; free(temp_pre); temp_pre=NULL; } obj->size=0; obj->head=NULL; obj->tail=NULL; } }
2.(链表)203. 移除链表元素
/** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */ struct ListNode* removeElements(struct ListNode* head, int val) { struct ListNode *tmp,*tmp_pre; if(!head) return NULL; while(head && head->val==val) { tmp=head; head=tmp->next; tmp_pre=tmp; tmp=tmp->next; free(tmp_pre); tmp_pre=NULL; } if(head==NULL) return NULL; tmp=head->next; tmp_pre=head; while(tmp!=NULL) { if(tmp->val==val) { tmp_pre->next=tmp->next; free(tmp); tmp=NULL; tmp=tmp_pre->next; } else { tmp_pre=tmp; tmp=tmp->next; } } return head; }
3.(链表)237. 删除链表中的节点
直接将下一个节点的值覆盖要删去的该节点的值。然后直接将该节点的next连接到原来next的下一个节点即可。(脑筋急转弯啊)
void deleteNode(struct ListNode* node) { node->val=node->next->val; node->next=node->next->next; }
4.(链表,双指针)19. 删除链表的倒数第 N 个结点
我们也可以在不预处理出链表的长度,以及使用常数空间的前提下解决本题。
由于我们需要找到倒数第 n 个节点,因此我们可以使用两个指针 first 和 second 同时对链表进行遍历,并且 first 比 second 超前 n 个节点。当 first 遍历到链表的末尾时,second 就恰好处于倒数第 n 个节点。
具体地,初始时 first 和 second 均指向头节点。我们首先使用 first 对链表进行遍历,遍历的次数为 n。此时,first 和 second 之间间隔了 n−1 个节点,即 first 比 second 超前了 n 个节点。
在这之后,我们同时使用 first 和 second 对链表进行遍历。当 first 遍历到链表的末尾(即 first 为空指针)时,second 恰好指向倒数第 n 个节点。
根据方法一和方法二,如果我们能够得到的是倒数第 n 个节点的前驱节点而不是倒数第 n 个节点的话,删除操作会更加方便。因此我们可以考虑在初始时将 second 指向哑节点,其余的操作步骤不变。这样一来,当 first 遍历到链表的末尾时,second 的下一个节点就是我们需要删除的节点。
题解:
struct ListNode* removeNthFromEnd(struct ListNode* head, int n) { // Fast slow pointer. struct ListNode *fast,*slow,*pre; slow=(struct ListNode*)malloc(sizeof(struct ListNode)); slow->val=0; slow->next=head; pre=slow; fast=head; for(int i=0;i<n;i++) fast=fast->next; while(fast) { fast=fast->next; slow=slow->next; } fast=slow->next; slow->next=fast->next; free(fast); head=pre->next; return head; }
5.(链表)83. 删除排序链表中的重复元素
struct ListNode* deleteDuplicates(struct ListNode* head) { struct ListNode *pre,*now; pre=(struct ListNode*)malloc(sizeof(struct ListNode)); pre->next=head; now=head; int temp_pre=-1000,temp; while(now) { temp=now->val; if(temp==temp_pre) { pre->next=now->next; free(now); now=pre->next; } else { now=now->next; pre=pre->next; temp_pre=temp; } } return head; }
6. (链表,dfs)430. 扁平化多级双向链表
当我们遍历到某个节点 node 时,如果它的 child 成员不为空,那么我们需要将 child 指向的链表结构进行扁平化,并且插入 node 与 node 的下一个节点之间。
因此,我们在遇到 child 成员不为空的节点时,就要先去处理 child 指向的链表结构,这就是一个「深度优先搜索」的过程。当我们完成了对 child 指向的链表结构的扁平化之后,就可以「回溯」到 node 节点。
为了能够将扁平化的链表插入 node 与 node 的下一个节点之间,我们需要知道扁平化的链表的最后一个节点 last,随后进行如下的三步操作:
这样一来,我们就可以将扁平化的链表成功地插入。
class Solution { public: Node* iter_link(Node* head) { Node *temp=head; Node *temp_next=nullptr; // 原先的next链 Node *temp_next_prev=nullptr; // next链新的前向链 if(!temp) return nullptr; while(temp->next || temp->child) // 当next或child不为空 { if(temp->child) // 先遍历孩子节点 { temp_next=temp->next; // 记录下原先的next链 temp->next=temp->child; // 将child链连接到next链,并且连接前向链 temp->next->prev=temp; temp_next_prev=iter_link(temp->child);// 深度优先搜索直到该child链条结束 temp->child=nullptr; } if(temp_next_prev) { temp_next_prev->next=temp_next; if(temp_next) // 可能没有后继链条 temp_next->prev=temp_next_prev; // 将后继链条接到child链条末尾 temp_next_prev=nullptr; // 清空。 } if(temp->next) temp=temp->next; } return temp; } Node* flatten(Node* head) { ios::sync_with_stdio(0); cin.tie(0);cout.tie(0); Node *temp=head; iter_link(temp); return temp; } };
7. (二叉树遍历)114. 二叉树展开为链表
思路
我们在前序遍历过程中,按照root->leftSubTree->rightSubTree进行遍历。因此我们可以按照如下的想法,进行递归原地构造:
- 观察是否有左右子树。如果没有,return。
- 有左子树,则将右子树记录下来,同时将左子树连接到right节点上,并进入到新的右子树根节点。
- 持续处理直到所有的左子树均被处理完毕。
- 有右子树,则进入到右子树的根节点中,并重复1,2,3条。
- 返回已经处理的树。
题解
void flatten(struct TreeNode* root) { if(!root) return; else if(!root->left && !root->right) return; struct TreeNode* rightSubTree;// 记录右子树的末尾。 struct TreeNode* temp=NULL; if(root->left) // 处理左子树。 { temp=root->right; root->right=root->left; root->left=NULL; } if(root->right) { rightSubTree=root->right; flatten(rightSubTree); } // 将temp接到右子树后面。保证temp仅有右子树。 flatten(temp); while(rightSubTree->right) { rightSubTree=rightSubTree->right; } rightSubTree->right=temp; while(rightSubTree->right) { rightSubTree=rightSubTree->right; } }
思路2
注意到前序遍历访问各节点的顺序是根节点、左子树、右子树。如果一个节点的左子节点为空,则该节点不需要进行展开操作。如果一个节点的左子节点不为空,则该节点的左子树中的最后一个节点被访问之后,该节点的右子节点被访问。该节点的左子树中最后一个被访问的节点是左子树中的最右边的节点,也是该节点的前驱节点。因此,问题转化成寻找当前节点的前驱节点。
具体做法是,对于当前节点,如果其左子节点不为空,则在其左子树中找到最右边的节点,作为前驱节点,将当前节点的右子节点赋给前驱节点的右子节点,然后将当前节点的左子节点赋给当前节点的右子节点,并将当前节点的左子节点设为空。对当前节点处理结束后,继续处理链表中的下一个节点,直到所有节点都处理结束。
void flatten(struct TreeNode* root) { if(!root) return; while(root) { while(root->left) { struct TreeNode* leftSubTree=root->left; struct TreeNode* leftRight=leftSubTree; // 记录左子树的最右边节点。 while(leftRight->right) { leftRight=leftRight->right; } leftRight->right=root->right; root->right=root->left; root->left=NULL; } root=root->right; } }
过程如下图所示
1 / \ 2 5 / \ \ 3 4 6 //将 1 的左子树插入到右子树的地方 1 \ 2 5 / \ \ 3 4 6 //将原来的右子树接到左子树的最右边节点 1 \ 2 / \ 3 4 \ 5 \ 6 //将 2 的左子树插入到右子树的地方 1 \ 2 \ 3 4 \ 5 \ 6 //将原来的右子树接到左子树的最右边节点 1 \ 2 \ 3 \ 4 \ 5 \ 6
8. (快慢指针、链表)61.旋转链表
思路
快慢指针处理新的head和tail就可以了。
题解
/** * 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: #pragma g++ optimize(2) ListNode* rotateRight(ListNode* head, int k) { // fast slow pt. ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); if(!head) return nullptr; if(!head->next || k==0) return head; ListNode *new_head,*fast,*slow,*temp; slow=head,fast=head,temp=head; int size=0; while(temp) { temp=temp->next; size++; } int real_k=k%size; if(real_k==0) return head; for(int i=0;i<real_k;i++) fast=fast->next; while(fast->next) { fast=fast->next; slow=slow->next; } new_head=slow->next; fast->next=head; slow->next=nullptr; return new_head; } };
9. 24.两两交换链表中的节点
思路
双指针即可。只需要交换值。而且注意非法访问问题。
题解
/** * 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* swapPairs(ListNode* head) { ListNode *l1,*l2; int temp; l1=head; if(head) l2=head->next; else l2=nullptr; while(l1 && l2) { temp=l2->val; l2->val=l1->val; l1->val=temp; l1=l2->next; if(l1) l2=l1->next; else l2=nullptr; } return head; } };
10. 206.反转链表(三指针模版)
思路
三指针即可原地反转。
/** * 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* reverseList(ListNode* head) { ios::sync_with_stdio(0); cin.tie(0);cout.tie(0); if(!head || !head->next) return head; // 原地交换。 ListNode *pre,*cur,*nxt; pre=head; cur=head->next; head->next=nullptr; nxt=cur->next; while(nxt) { cur->next=pre; pre=cur; cur=nxt; nxt=cur->next; } cur->next=pre; head=cur; return head; } };
11. 92.反转链表II
思路
穿针引线。需要注意边界处理即可。
题解
/** * 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* reverseBetween(ListNode* head, int left, int right) { if(!head->next) return head; if(left==right) return head; ListNode *leftNode,*rightNode,*left_pre; leftNode=head,rightNode=head,left_pre=nullptr; // Scan whole chain! for(int i=0;i<left-1;i++) { left_pre=leftNode; leftNode=leftNode->next; rightNode=rightNode->next; } for(int i=left-1;i<right-1;i++) rightNode=rightNode->next; // Then modify them. // Reverse needed; ListNode *pre,*cur,*next; pre=leftNode; cur=pre->next; next=cur->next; // reverse. while(next!=rightNode->next) { cur->next=pre; pre=cur; cur=next; next=next->next; } cur->next=pre; // boundary. if(left_pre) left_pre->next=rightNode; else head=rightNode; leftNode->next=next; return head; } };
12. 25. 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* reverseKGroup(ListNode* head, int k) { ios::sync_with_stdio(0); cin.tie(0);cout.tie(0); if(!head || k==1) return head; else if(!head->next) return head; ListNode *left,*right,*original_head; left=head;right=head;original_head=head; int flag=0; while(right) { for(int i=0;i<k-1;i++) { right=right->next; if(!right) break; } if (!right) break; // Reverse. ListNode *pre,*cur,*next; pre=left; cur=pre->next; next=cur->next; while(next!=right->next) { cur->next=pre; pre=cur; cur=next; next=next->next; } cur->next=pre; if(!flag) { head->next=next; head=right; flag=1; } else { original_head->next=cur; original_head=left; } left->next=next; // pin to next. left=next; right=next; } return head; } };
13. 2. 两数相加
/** * 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* addTwoNumbers(ListNode* l1, ListNode* l2) { ios::sync_with_stdio(0); cin.tie(0);cout.tie(0); ListNode *head=new ListNode; ListNode *temp=head; ListNode *temp_pre; int val; int carrier=0; while(l1 || l2) { val=0; temp_pre=temp; temp->next=new ListNode; if(l1) { val+=l1->val; l1=l1->next; } if(l2) { val+=l2->val; l2=l2->next; } temp->val=(val+carrier)%10; carrier=(val+carrier)/10; temp=temp->next; } if(carrier) temp->val=carrier; else temp_pre->next=nullptr; return head; } };
14. 445. 两数相加II
思路
反转链表后,思路同2.两数相加
。再将结果反转即可。
题解
/** * 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* reverseLists(ListNode* head) { if(!head->next) return head; ListNode *pre,*cur,*next; pre=head; cur=pre->next; next=cur->next; head->next=nullptr; while(next) { cur->next=pre; pre=cur; cur=next; next=next->next; } cur->next=pre; head=cur; return head; } ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) { l1=reverseLists(l1); l2=reverseLists(l2); ListNode *l3=new ListNode; ListNode *temp=l3,*temp_pre; int val; int carrier=0; while(l1 || l2) { val=0; temp_pre=temp; temp->next=new ListNode; if(l1) { val+=l1->val; l1=l1->next; } if(l2) { val+=l2->val; l2=l2->next; } temp->val=(val+carrier)%10; carrier=(val+carrier)/10; temp=temp->next; } if(carrier) temp->val=carrier; else { delete temp; temp_pre->next=nullptr; } l3=reverseLists(l3); return l3; } };
15. 21. 合并两个有序链表
题解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) { if(!list1 && !list2) return nullptr; if(!list1 && list2) return list2; if(!list2 && list1) return list1; ListNode *head=new ListNode,*temp=head,*temp_pre=new ListNode; temp_pre->next=head; while(list1 && list2) { temp->next=new ListNode; if(list1->val<list2->val) { temp->val=list1->val; list1=list1->next; temp=temp->next; temp_pre=temp_pre->next; } else if(list1->val>list2->val) { temp->val=list2->val; list2=list2->next; temp=temp->next; temp_pre=temp_pre->next; } else { temp->val=list1->val; temp=temp->next; temp_pre=temp_pre->next; list1=list1->next; temp->next=new ListNode; temp->val=list2->val; temp=temp->next; temp_pre=temp_pre->next; list2=list2->next; } } if(list1) temp_pre->next=list1; else if(list2) temp_pre->next=list2; else { delete temp_pre->next; temp_pre->next=nullptr; } return head; } };
思路二
1->4->5->null prehead 1->2->3->6->null prehead->1->4->5->null 1->2->3->6->null prehead->1 4->5->null | 1->2->3->6->null prehead->1 4->5->null | | 1->2->3 6->null prehead->1 4->5 | | | 1->2->3 6->null
/** * 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) { if(!list1 && !list2) return nullptr; if(!list1 && list2) return list2; if(!list2 && list1) return list1; ListNode *prehead=new ListNode; ListNode *prev=prehead; while(list1 && list2) { if(list1->val<=list2->val) { prev->next=list1; list1=list1->next; prev=prev->next; } else { prev->next=list2; list2=list2->next; prev=prev->next; } } prev->next=list1==nullptr?list2:list1; return prehead->next; } };
16. 23. 合并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* merge2Lists(ListNode *l1, ListNode *l2) { if(!l1 && !l2) return nullptr; if(l1 && !l2) return l1; if(!l1 && l2) return l2; ListNode *prehead=new ListNode; ListNode *prev=prehead; while(l1 && l2) { if(l1->val < l2->val) { prev->next=l1; prev=prev->next; l1=l1->next; } else { prev->next=l2; prev=prev->next; l2=l2->next; } } prev->next=l1==nullptr?l2:l1; return prehead->next; } ListNode* merge(vector<ListNode*>& lists,int left,int right) { if(left==right) return lists[left]; if(left > right) return nullptr; int mid=(right+left) >> 1; return merge2Lists(merge(lists,left,mid),merge(lists,mid+1,right)); } ListNode* mergeKLists(vector<ListNode*>& lists) { int n=lists.size(); ListNode *head=merge(lists,0,n-1); return head; } };
17. 86.分隔链表
思路
开两个链表,按原链表顺序,一个记录小于pivot链表的数值,一个记录大于等于pivot链表的数值,并将其相加。
题解
/** * 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* partition(ListNode* head, int x) { ListNode *small=new ListNode; ListNode *smallHead=small; ListNode *big=new ListNode; ListNode *bigHead=big; while(head) { if(head->val < x) { small->next=head; small=small->next; } else { big->next=head; big=big->next; } head=head->next; } big->next=nullptr; small->next=bigHead->next; return smallHead->next; } };
18. 141.环形链表
思路1 哈希表(待补充)
思路2 快慢指针
具体地,我们定义两个指针,一快一慢。慢指针每次只移动一步,而快指针每次移动两步。初始时,慢指针在位置 head
,而快指针在位置 head.next
。这样一来,如果在移动的过程中,快指针反过来追上慢指针,就说明该链表为环形链表。否则快指针将到达链表尾部,该链表不为环形链表。
细节
为什么我们要规定初始时慢指针在位置 head
,快指针在位置 head.next
,而不是两个指针都在位置 head
?
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode(int x) : val(x), next(NULL) {} * }; */ class Solution { public: bool hasCycle(ListNode *head) { if(!head || !head->next) return false; ListNode *slow=head,*fast=head->next; while(slow!=fast) { if(!slow->next) return false; if(!fast->next || !fast->next->next) return false; slow=slow->next; fast=fast->next->next; } return true; } };
19. 142.环形链表II
思路1 哈希表
思路2 快慢指针
- 将快慢指针指在head表头;
- 假设链表入表长度为a。进入链表后,在b处相遇。此时,我们很明显知道,快指针已经走过了
的距离,而快指针走过了 的距离。 - 为什么慢指针第一圈内就会与快指针相遇?设环周长
。慢指针速度为 。快指针速度则为 。因此最多只需要 就会追上(追击距离最远为 )。因此一圈内必然能够相遇。 - 这样我们就有:
。也就是相当于慢指针从相遇点到入环点的所需时间和慢指针从表头到入环点的时间相同。因此只需要在增加一个新的指针即可。
题解
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode(int x) : val(x), next(NULL) {} * }; */ class Solution { public: ListNode *detectCycle(ListNode *head) { if(!head || !head->next) return nullptr; ListNode *slow=head,*fast=head; while(1) { if(!slow->next) return nullptr; if(!fast->next || !fast->next->next) return nullptr; slow=slow->next; fast=fast->next->next; if(fast==slow) { ListNode *ptr=head; while(slow!=ptr) { slow=slow->next; ptr=ptr->next; } return ptr; } } } };
20. 876.链表的中间节点
快慢指针。
/** * 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 *slow=head,*fast=head; while(fast && fast->next) { slow=slow->next; fast=fast->next->next; } return slow; } };
21. 143.重排链表(408真题,要求O(1)空间复杂度)
思路1
采用线性表进行重排。一定要注意在前后指针相遇后,需要将最后的表尾部指向nullptr。
/** * 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: void reorderList(ListNode* head) { if(!head) return; vector<ListNode *> linear; ListNode *node=head; while(node) { linear.emplace_back(node); node=node->next; } int i=0,j=linear.size()-1; while(i!=j) { linear[i]->next=linear[j]; i++; if(i==j) break; linear[j]->next=linear[i]; j--; } linear[i]->next=nullptr; } };
/** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */ void reorderList(struct ListNode* head) { if(!head) return; int bufSize=512; struct ListNode **linear=(struct ListNode**) malloc(sizeof(struct ListNode*)*bufSize); int num=0; struct ListNode *read=head; while(read) { linear[num]=read; read=read->next; num++; if(num>=bufSize) { bufSize*=2; linear=(struct ListNode**) realloc(linear,sizeof(struct ListNode*)*bufSize); } } int i=0,j=num-1; while(i<j) { linear[i]->next=linear[j]; i++; if(i==j) break; linear[j]->next=linear[i]; j--; } linear[i]->next=NULL; }
思路2
2019年408统考真题第41题。
采用O(1)的空间复杂度。
- 寻找链表的中心节点。(快慢指针)
- 将链表的后半部分反转。(三指针)
- 在依顺序合并链表。(双指针)
1->2->3->4->5 1->2->3 4->5 1->2->3 5->4 1->5->2->4->3
/** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */ struct ListNode* find_mid(struct ListNode *head) { struct ListNode *slow=head,*fast=head; while(fast->next && fast->next->next) { slow=slow->next; fast=fast->next->next; } return slow; } struct ListNode* reverse_link(struct ListNode* head) { if(!head || !head->next) return head; struct ListNode *pre=head,*cur=pre->next,*next=cur->next; head->next=NULL; while(next) { cur->next=pre; pre=cur; cur=next; next=next->next; } cur->next=pre; return cur; } void merge_link(struct ListNode *l1,struct ListNode *l2) { struct ListNode *temp1,*temp2; while(l1 && l2) { temp1=l1->next; temp2=l2->next; l1->next=l2; l1=temp1; l2->next=l1; l2=temp2; } } void reorderList(struct ListNode* head) { if(!head) return; struct ListNode *mid=find_mid(head); // return the middle point of the list. struct ListNode *new_head=reverse_link(mid->next); mid->next=NULL; struct ListNode *l1=head,*l2=new_head; merge_link(l1,l2); }
22. 160.相交链表
思路:快慢指针
/** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */ struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) { if(!headA || !headB) return NULL; struct ListNode *first=headA,*second=headB; while(first!=second) { if(!first) first=headB; else first=first->next; if(!second) second=headA; else second=second->next; } return first; }
主要知识点:建立链表方法(头插法,尾插法),链表的插入,链表的删除,链表的遍历和寻找。
技巧:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了