LeetCode-链表
链表(Linked List)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。
由于不必须按顺序存储,链表在插入的时候可以达到 O(1)O(1) 的复杂度,比另一种线性表 —— 顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要 O(n)O(n) 的时间,而顺序表相应的时间复杂度分别是 O(log\ n)O(log n) 和 O(1)O(1)。
对于链表相关的问题,应该注意以下几点:
头节点的使用,即dummyNode的使用,可以减少边界条件判断。涉及到头节点的操作,比如可能删除head等等,使用dummyNode可以减少判断。最后返回dummyNode.next
创建新的节点时,注意在栈上分配对象,如果使用new 需要delete。ps:后续一些题解没有及时更正该方面的问题。
-
快慢指针。多用于寻找中间节点等,衍生问题环形链表等。主要在于在O(N)的时间来快速获取到想要的节点。
使用快慢指针查找中间节点时,当fast和slow都指向head和slow指向head,fast指向head->next 获取到的中间节点及边界条件不一致,需要注意。
cut函数,cut(ListNode* node,int k) 将链表前k个值切割,返回第k+1个node;多用于分隔链表。
反转链表,需要pre=null,cur=head,next 三个指针, 每次进行更新,最后返回pre。
合并链表,注意dummyNode的使用以及边界判空。
删除节点,删除当前节点,可以将下个节点的值复制给当前节点,并指向下下个节点。另一种思路时需要找到删除节点的前驱。
时间复杂度,可以考虑一些辅助工作,如map,stack,set等。
另一个就是链表和排序算法以及树的遍历的结合。归并排序,dfs等等。可以等后续章节进行分析。
链表相关的问题也可以考虑递归的方式进行解决。从而简化问题。
下面介绍LeetCode常见的链表问题。
1.反转链表
206.反转链表。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (head==NULL || head->next==NULL){
return head;
}
ListNode* pre=NULL;
ListNode* cur=head;
ListNode* next;
while(cur){
next=cur->next;
cur->next=pre;
pre=cur;
cur=next;
}
return pre;
}
};
92.反转链表II
反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
class Solution {
public:
ListNode* reverseBetween(ListNode* head, int m, int n) {
if (head==NULL || head->next==NULL){
return head;
}
ListNode* pre=NULL;
ListNode* cur=head;
ListNode* next;
while(m>1){
pre=cur;
cur=cur->next;
m--;
n--;
}
// 需要记录下pre 和 tail;即反转后的头节点和尾节点
ListNode* con = pre; //反转后的头节点
ListNode* tail=cur; // 反转后的尾节点
while(n>0){
next=cur->next;
cur->next=pre;
pre=cur;
cur=next;
n--;
}
if(con){
con->next=pre; //进行连接
}else{
head=pre; //此时证明从第一个节点开始反转,那么头节点直接就是反转后的头节点
}
tail->next=cur; //尾节点的next设置为下一个节点cur
return head;
}
};
2.删除元素
237.删除链表中的节点
class Solution {
public:
void deleteNode(ListNode* node) {
//struct ListNode* tmp=node->next;
node->val=node->next->val;
node->next=node->next->next;
//delete tmp;
}
};
203.移除链表元素
删除链表中等于给定值 *val* 的所有节点。
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
if(head==NULL){
return NULL;
}
struct ListNode* t=new ListNode(-1);
t->next=head;
struct ListNode* pre=t;
while(pre->next!=NULL){
if (pre->next->val==val){
auto s=pre->next;
pre->next=pre->next->next;
delete s;
}else{
pre=pre->next;
}
}
return t->next;
}
};
19.删除链表的倒数第N个节点
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
if(head==NULL || head->next==NULL){
return NULL;
}
ListNode* first=head;
ListNode* second=head;
int i=0;
// 先走n步,找到需要删除的pre节点。
while(i<n){
first=first->next;
i++;
}
//主要为了判断是否是删除首元素
if(!first){
return head -> next;
}
while(first->next!=NULL){
first=first->next;
second=second->next;
}
ListNode* tmp=second->next;
second->next=second->next->next;
delete tmp;
return head;
}
};
83.删除排序链表中的重复元素
给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
示例 1:
输入: 1->1->2
输出: 1->2
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
ListNode* cur=head;
while(cur!=NULL &&cur->next!=NULL){
if(cur->next->val==cur->val){
cur->next=cur->next->next;
}else{
cur=cur->next;
}
}
return head;
}
};
82.删除排序链表中的重复元素II
给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字。
提示:需要两个指针;一个用来遍历,另一个用来删除。
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if (head == NULL || head->next == NULL) return head;
ListNode dummyNode(-1);
ListNode* pre=&dummyNode;
ListNode* cur=head;
ListNode* tmp;
int n,val;
while(cur){
tmp = cur;
for(n=0,val=cur->val;cur!=NULL && cur->val==val;++n){
cur=cur->next;
}
if (n==1){
pre->next=tmp;
pre=pre->next;
}
}
pre->next=NULL;
return dummyNode.next;
}
};
1171.从链表中删去总和值为0的连续节点
给你一个链表的头节点 head,请你编写代码,反复删去链表中由 总和 值为 0 的连续节点组成的序列,直到不存在这样的序列为止。删除完毕后,请你返回最终结果链表的头节点。
你可以返回任何满足题目要求的答案。(注意,下面示例中的所有序列,都是对 ListNode 对象序列化的表示。)
提示:我们可以考虑如果给的入参不是链表是数组的话,只需要求出前缀和,对于前缀和相同的项,那他们中间的部分即是可以消除掉的,比如以 [1, 2, 3, -3, 4] 为例,其前缀和数组为 [1, 3, 6, 3, 7] ,我们发现有两项均为 3,则 6 和 第二个 3 所对应的原数组中的数字是可以消掉的。换成链表其实也是一样的思路,把第一个 3 的 next 指向第二个 3 的 next 即可。
示例 1:
输入:head = [1,2,-3,3,1]
输出:[3,1]
提示:答案 [1,2,1] 也是正确的。
class Solution {
public:
ListNode* removeZeroSumSublists(ListNode* head) {
// 求和,当出现重复是 证明两个重复节点之间的数据可以被删除;
// 然后继续递归; 返回值为dummy->next;
if (head==NULL){
return head;
}
ListNode dummy(0);
dummy.next=head;
ListNode* dummyPtr=&dummy;
map<int,ListNode*> map;
map[0]=dummyPtr;
int sum=0;
while(head!=NULL){
sum+=head->val;
if(map.find(sum)!=map.end()){
map[sum]->next=head->next;
return removeZeroSumSublists(dummy.next);
}else{
map[sum]=head;
head=head->next;
}
}
return dummy.next;
}
};
3.合并链表
合并链表,需要注意头节点dummyNode的使用。另外为了降低复杂度,可以考虑使用归并算法。
21.合并两个有序链表
将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode dummy = ListNode(-1); // 创建栈空间对象即可。
ListNode* prev = &dummy; // 注意这里需要取地址&
while(l1 != NULL && l2 != NULL) {
if(l1->val <= l2->val) {
prev->next = l1;
l1 = l1->next;
} else {
prev->next = l2;
l2 = l2->next;
}
prev = prev->next;
}
prev->next = l1 != NULL ? l1 : l2;
return dummy.next;
}
};
23.合并K个有序链表
题解:已经实现了合并两个有序链表了,那么其实可以使用归并的思想进行合并k个链表,两两合并,时间复杂度为Nlogk
ListNode* mergeKLists(vector<ListNode*>& lists) {
int len = lists.size();
if(len < 1) return NULL;
while(len>1){
int m = (len + 1) / 2;
for(int i=0;i<m && i+m < len;++i){
lists[i] = mergeTwoLists(lists[i],lists[i+m]);
}
len = m;
}
return lists[0];
}
2. 两数相加
给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
题解:新的节点等与两个节点的(val+进位)%10; 下一轮的进位为(val+进位)/10;
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode *root=new ListNode(0);
ListNode *cur=root;
ListNode *tmp;
int c=0;
while(l1!=NULL || l2!=NULL || c!=0){ //注意边界条件,可能最后只有进位,需要创建新的节点
int v1=l1?l1->val:0;
int v2=l2?l2->val:0;
int sum=v1+v2+c;
c=sum/10;
tmp=new ListNode(sum%10);
cur->next=tmp;
cur=tmp;
if (l1){
l1=l1->next; // 注意边界条件
}
if (l2){
l2=l2->next;
}
}
return root->next;
}
};
445.两数相加II
给定两个非空链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储单个数字。将这两数相加会返回一个新的链表。你可以假设除了数字 0 之外,这两个数字都不会以零开头。
进阶:如果输入链表不能修改该如何处理?换句话说,你不能对列表中的节点进行翻转。
示例:
输入: (7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)
输出: 7 -> 8 -> 0 -> 7
题解:当当前值的计算依赖下一项的计算时,可以使用递归的方法来实现回溯。当前值=(val+下一项的进位)/10。需要两个链表等长。 另一种办法是借助额外的结构,来实现。例如栈/数组等。
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
vector<ListNode*> l1v,l2v;
to_vector(l1,l1v);
to_vector(l2,l2v);
int i=l1v.size()-1, j=l2v.size()-1;
int d = 0;
ListNode *head = NULL;
while(i >= 0 || j >= 0){
if(i >= 0) d += l1v[i--]->val;
if(j >= 0) d += l2v[j--]->val;
ListNode* p = new ListNode(d%10);
p->next = head;
head = p;
d /= 10;
}
if(d) {
ListNode* p = new ListNode(d);
p->next = head;
head = p;
}
return head;
}
void to_vector(ListNode* a,vector<ListNode*>& v){
while(a){
v.push_back(a);
a = a->next;
}
}
};
4.重排链表
链表按照指定的规则进行重新排序。比如首尾相连,交换链表节点等等。
24.两两交换链表中的节点
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例:
给定 1->2->3->4, 你应该返回 2->1->4->3.
题解:声明dummyNode,pre指向dummy。 start指向1,end指向2. 让pre.next=end;start.next=end.next;end.next=start. pre此刻再向前移动到start。
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode pre = new ListNode(0);
pre.next = head;
ListNode temp = pre;
while(temp.next != null && temp.next.next != null) {
ListNode start = temp.next;
ListNode end = temp.next.next;
temp.next = end;
start.next = end.next;
end.next = start;
temp = start;
}
return pre.next;
}
}
25. k个一组反转链表
给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。
k 是一个正整数,它的值小于或等于链表的长度。
如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
示例 :
给定这个链表:1->2->3->4->5
当 k = 2 时,应当返回: 2->1->4->3->5
当 k = 3 时,应当返回: 3->2->1->4->5
题解:合理利用cut函数,每k个一组,切割成链表,然后反转,然后注意进行链接。每次反转前判断下长度是否达到k。当对头节点有进行操作是,注意使用dummyNode。 主要要保持链表直接的连接。
class Solution {
public:
ListNode* reverseKGroup(ListNode* head, int k) {
ListNode dummyHead(0);
dummyHead.next=head;
auto cur=dummyHead.next;
auto tail = &dummyHead;
while(cur){
auto left = cur;
cur = cut(left, k); // left->@->@ right->@->@->@...
tail->next = reverseList(left,k);
while(tail->next){
tail=tail->next;
}
}
return dummyHead.next;
}
// k个一组进行反转,采用cut函数,连接的时候,注意保证尾部正确。
ListNode* cut(ListNode* head,int k){
auto p=head;
while(--k && p){
p=p->next;
}
if(!p){
return nullptr;
}
auto next=p->next;
p->next=nullptr;
return next;
}
ListNode* reverseList(ListNode* head,int k) {
struct ListNode* p=head;
while(p!=NULL){
p=p->next;
k--;
}
if (k>0){
return head;
}
struct ListNode* pre=NULL;
struct ListNode* cur=head;
struct ListNode* tmp=NULL;
while(cur){
tmp=cur->next;
cur->next=pre;
pre=cur;
cur=tmp;
}
return pre;
}
};
61.旋转链表
给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。
输入: 1->2->3->4->5->NULL, k = 2
输出: 4->5->1->2->3->NULL
解释:
向右旋转 1 步: 5->1->2->3->4->NULL
向右旋转 2 步: 4->5->1->2->3->NULL
题解:方法一:先连接,再旋转。再断开。方法二:可以先整体逆序,然后前k个逆序,后n-k个逆序。
class Solution {
public:
ListNode* rotateRight(ListNode* head, int k) {
if(head==NULL){
return NULL;
}
if (head->next==NULL){
return head;
}
ListNode *cur=head;
int n=1;
while(cur->next!=NULL){
cur=cur->next;
n++;
}
cur->next=head;
ListNode *new_tail = head;
int i=0;
for(i=0;i<n - k % n - 1;++i){
new_tail=new_tail->next;
}
ListNode* tmp=new_tail->next;
new_tail->next=NULL;
return tmp;
}
};
143.重排链表
给定一个单链表 L:L0→L1→…→Ln-1→Ln ,
将其重新排列后变为: L0→Ln→L1→Ln-1→L2→Ln-2→…
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
给定链表 1->2->3->4, 重新排列为 1->4->2->3.
给定链表 1->2->3->4->5, 重新排列为 1->5->2->4->3.
经典问题:考察快慢指针找中间节点;分隔链表;链表反转,链表合并。需要考虑将中间节点分配到第一个链表还是第二个链表。考虑清楚边界条件。
class Solution {
public:
void reorderList(ListNode* head) {
// 先快慢指针找到中间节点,然后对后面的反转,最后合并
if(head==NULL || head->next==NULL){
return ;
}
ListNode* slow=head;
ListNode* fast=head->next;
while(fast!=NULL && fast->next!=NULL){
fast=fast->next->next;
slow=slow->next;
}
ListNode* l2=slow->next;
slow->next=NULL;
ListNode* l1=head;
// 反转list2
ListNode* pre=NULL;
ListNode* cur=l2;
ListNode* next;
while(cur!=NULL){
next=cur->next;
cur->next=pre;
pre=cur;
cur=next;
}
l2=pre;
// 合并l1 l2
while(l2!=NULL){
slow=l1->next;
fast=l2->next;
l1->next=l2;
l2->next=slow;
l1=slow;
l2=fast;
}
return;
}
};
147.链表进行插入排序
class Solution {
public:
ListNode* insertionSortList(ListNode* head) {
if (head == NULL || head->next == NULL) {
return head;
}
ListNode h(-1);
h.next = head;
ListNode* pre = &h;
ListNode* cur = head;
ListNode* lat;
ListNode* tmp;
while (cur != NULL) {
lat = cur->next; // 记录下一个要插入排序的值
if (lat != NULL && lat->val < cur->val) {
// 只有 cur.next 比 cur 小才需要向前寻找插入点
while (pre->next != NULL && pre->next->val < lat->val) {
pre = pre->next; // 继续向后移动
}
// 找到要插入的位置,此时 pre 节点后面的位置就是 lat 要插入的位置
tmp = pre->next;
pre->next = lat;
cur->next = lat->next;// 保证cur不断
lat->next = tmp;
pre = &h;// 由于每次都是从前往后找插入位置,所以需要每次插入完成后要让 pre 复位
} else {
// 都这直接把 cur 指针指向到下一个
cur = lat;
}
}
return h.next;
}
};
148.排序链表
在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。
题解:要求时间复杂度,考虑到归并排序。两两合并,需要logn次。每次进行n个比较。需要注意的点;cut函数实现,合并链个有序链表;dummyNode使用;链表的连接;等等。
class Solution {
public:
ListNode* sortList(ListNode* head) {
ListNode dummyHead(0);
dummyHead.next=head;
auto p=head;
int length=len(p);
for (int size=1;size<length;size<<=1){
auto cur=dummyHead.next;
auto tail = &dummyHead;
while(cur){
auto left = cur;
auto right = cut(left, size); // left->@->@ right->@->@->@...
cur = cut(right, size); // left->@->@ right->@->@ cur->@->..
tail->next = merge(left, right);
while(tail->next){
tail=tail->next;
}
}
}
return dummyHead.next;
}
int len(ListNode *p){
int length=0;
while (p) {
++length;
p = p->next;
}
return length;
}
ListNode* cut(ListNode* head,int n ){
auto p=head;
while(--n && p){
p=p->next;
}
if(!p){
return nullptr;
}
auto next=p->next;
p->next=nullptr;
return next;
}
ListNode* merge(ListNode* l1, ListNode* l2) {
ListNode dummyHead(0);
auto p = &dummyHead;
while (l1 && l2) {
if (l1->val < l2->val) {
p->next = l1;
p = l1;
l1 = l1->next;
} else {
p->next = l2;
p = l2;
l2 = l2->next;
}
}
p->next = l1 ? l1 : l2;
return dummyHead.next;
}
};
5.分隔链表
86.分隔链表
给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前。
你应当保留两个分区中每个节点的初始相对位置。
题解:分解成两个链表,再连接。注意dummyNode的使用。
class Solution {
public:
ListNode* partition(ListNode* head, int x) {
if (head==NULL ||head->next==NULL){
return head;
}
ListNode l_head(-1);
ListNode r_head(-1);
ListNode* l=&l_head;
ListNode* r=&r_head;
auto l_tmp=l;
auto r_tmp=r;
while(head!=NULL){
if(head->val<x){
l->next=head;
l=l->next;
}else{
r->next=head;
r=r->next;
}
head=head->next;
}
l->next=r_tmp->next;
r->next=NULL;
return l_tmp->next;
}
};
109.有序链表转换二叉搜索树
给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树。
本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
示例:
给定的有序链表: [-10, -3, 0, 5, 9],
一个可能的答案是:[0, -3, 9, -10, null, 5], 它可以表示下面这个高度平衡二叉搜索树:
0
/ \
-3 9
/ /
-10 5
题解:二叉搜索树的前序遍历为有序数组。那么对于链表中间节点即为跟节点。采用递归进行遍历即可。
class Solution {
public:
TreeNode* sortedListToBST(ListNode* head) {
//核心思想,中心节点就是根节点,递归构造左右子节点。
if(head==NULL){
return NULL;
}
ListNode* mid=getMidNode(head);
TreeNode* node=new TreeNode(mid->val);
if (head==mid){
return node;
}
node->left=sortedListToBST(head);
node->right=sortedListToBST(mid->next);
return node;
}
// 查找中间节点并返回。并分割left链表最后指向NULL。
ListNode* getMidNode(ListNode *head){
ListNode* pre=NULL;
ListNode* slow=head;
ListNode* fast=head;
while(fast!=NULL && fast->next!=NULL){
pre=slow;
slow=slow->next;
fast=fast->next->next;
}
if (pre!=NULL){
pre->next=NULL;
}
return slow;
}
};
725.分隔链表
给定一个头结点为 root 的链表, 编写一个函数以将链表分隔为 k 个连续的部分。
每部分的长度应该尽可能的相等: 任意两部分的长度差距不能超过 1,也就是说可能有些部分为 null。
这k个部分应该按照在链表中出现的顺序进行输出,并且排在前面的部分的长度应该大于或等于后面的长度。
返回一个符合上述规则的链表的列表。
举例: 1->2->3->4, k = 5 // 5 结果 [ [1], [2], [3], [4], null ]
示例 1:
输入:
root = [1, 2, 3], k = 5
输出: [[1],[2],[3],[],[]]
解释:
输入输出各部分都应该是链表,而不是数组。
例如, 输入的结点 root 的 val= 1, root.next.val = 2, \root.next.next.val = 3, 且 root.next.next.next = null。
第一个输出 output[0] 是 output[0].val = 1, output[0].next = null。
最后一个元素 output[4] 为 null, 它代表了最后一个部分为空链表。
示例 2:
输入:
root = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], k = 3
输出: [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]]
解释:
输入被分成了几个连续的部分,并且每部分的长度相差不超过1.前面部分的长度大于等于后面部分的长度。
class Solution {
public:
vector<ListNode*> splitListToParts(ListNode* root, int k) {
// 遍历root的长度,得到len。
// if len<=k ; 那么每个都是独立节点,再增加k-len个空节点。
// if len>k; 那么切分为 len/k 个片段;其中 前 len%k个片段,长度为 len/k + 1;
// 实现cut(ListNode* head,int k); 切割前k个节点,返回第k+1个节点后的链表
// 循环切割,时间复杂度为O(N)
vector<ListNode*> vec;
ListNode* cur=root;
int len=getLen(root);
if (len<k){
for(int i=0;i<k;i++){
vec.push_back(cur);
cur=cut(cur,1);
}
}else{
int step=len/k;
int m=len%k;
int i=0;
for (i=0;i<m;i++){
vec.push_back(cur);
cur=cut(cur,step+1);
}
for(i=m;i<k;i++){
vec.push_back(cur);
cur=cut(cur,step);
}
}
return vec;
}
int getLen(ListNode* head){
if(head==NULL){
return 0;
}
int len=0;
ListNode* cur=head;
while(cur){
cur=cur->next;
len++;
}
return len;
}
ListNode* cut(ListNode* head,int k){
while(k>1 && head){
head=head->next;
k--;
}
if (head==NULL){
return NULL;
}
ListNode* tmp=head->next;
head->next=NULL;
return tmp;
}
};
6.使用额外结构降低时间复杂度
138.复制带随机指针的链表
给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。
要求返回这个链表的深拷贝。
题解:先全部复制一遍,再进行指针关联
class Node {
public:
int val;
Node* next;
Node* random;
Node() {}
Node(int _val, Node* _next, Node* _random) {
val = _val;
next = _next;
random = _random;
}
};
*/
class Solution {
public:
// hash 空间复杂O(N)
// 原地复制 空间复杂O(1) 复制节点 复制random 拆链表
Node* copyRandomList(Node* head) {
if(head==nullptr){
return nullptr;
}
map<Node*,Node*> map;
Node* cur=head;
while(cur){
map[cur]=new Node(cur->val,nullptr,nullptr);
cur=cur->next;
}
cur=head;
while(cur){
map[cur]->next=map[cur->next];
map[cur]->random=map[cur->random];
cur=cur->next;
}
return map[head];
}
};
817.链表组件
给定一个链表(链表结点包含一个整型值)的头结点 head。
同时给定列表 G,该列表是上述链表中整型值的一个子集。
返回列表 G 中组件的个数,这里对组件的定义为:链表中一段最长连续结点的值(该值必须在列表 G 中)构成的集合。
示例 1:
输入:
head: 0->1->2->3
G = [0, 1, 3]
输出: 2
解释:
链表中,0 和 1 是相连接的,且 G 中不包含 2,所以 [0, 1] 是 G 的一个组件,同理 [3] 也是一个组件,故返回 2。
示例 2:
输入:
head: 0->1->2->3->4
G = [0, 3, 1, 4]
输出: 2
解释:
链表中,0 和 1 是相连接的,3 和 4 是相连接的,所以 [0, 1] 和 [3, 4] 是两个组件,故返回 2。
class Solution {
public:
int numComponents(ListNode* head, vector<int>& G) {
set<int> m_set;
for (int i=0;i<G.size();i++){
m_set.insert(G[i]);
}
ListNode* cur=head;
int num = 0;
while(cur){
if(m_set.find(cur->val)!= m_set.end() && (cur->next==NULL || m_set.find(cur->next->val)== m_set.end())){
num++;
}
cur=cur->next;
}
return num;
}
};
1019.链表中下一个更大节点
给出一个以头节点 head 作为第一个节点的链表。链表中的节点分别编号为:node_1, node_2, node_3, ... 。
每个节点都可能有下一个更大值(next larger value):对于 node_i,如果其 next_larger(node_i) 是 node_j.val,那么就有 j > i 且 node_j.val > node_i.val,而 j 是可能的选项中最小的那个。如果不存在这样的 j,那么下一个更大值为 0 。
返回整数答案数组 answer,其中 answer[i] = next_larger(node_{i+1}) 。
注意:在下面的示例中,诸如 [2,1,5] 这样的输入(不是输出)是链表的序列化表示,其头节点的值为 2,第二个节点值为 1,第三个节点值为 5 。
示例 1:
输入:[2,1,5]
输出:[5,5,0]
示例 2:
输入:[2,7,4,3,5]
输出:[7,0,5,5,0]
示例 3:
输入:[1,7,5,1,9,2,5,1]
输出:[7,9,9,9,0,5,0,0]
class Solution {
public:
//题解,使用栈先进后出。找到第一个比较大的就更新对应的数组值。
vector<int> nextLargerNodes(ListNode* head) {
int count = 0; //计数,作为下标
vector<int> result;
stack<pair<int, int>> tmp; //first为val,second为下标
while (head)
{
result.push_back(0); //给result数组后面+0,1为保证长度,2是默认值(后无更大的值的话)为0
while (!tmp.empty() &&
head->val > tmp.top().first) //栈不为空且head指针的val值大于栈顶的元素的值
{
result[tmp.top().second] = head->val; //result数组修改,满足题意要求的最大值,然后出栈,继续循环
tmp.pop();
}
tmp.push(make_pair(head->val, count++)); //count++计数
head = head->next; //下一个节点
}
return result;
}
};
7.快慢指针
160.相交链表
编写一个程序,找到两个单链表相交的起始节点。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
int aLen=0;
int bLen=0;
ListNode *curA=headA;
ListNode *curB=headB;
while(curA!=NULL){
curA=curA->next;
aLen++;
}
while(curB!=NULL){
curB=curB->next;
bLen++;
}
int i=0;
curA=headA;
curB=headB;
if (aLen>bLen){
for(i=0;i<aLen-bLen;i++){
curA=curA->next;
}
}else{
for(i=0;i<bLen-aLen;i++){
curB=curB->next;
}
}
while(curA){
if(curA==curB){
return curA;
}else{
curA=curA->next;
curB=curB->next;
i++;
}
}
return NULL;
}
};
234.回文链表
请判断一个链表是否为回文链表。
class Solution {
public:
bool isPalindrome(ListNode* head) {
if(head==NULL||head->next==NULL){
return true;
}
ListNode* slow=head;
ListNode* fast=head->next;
while(fast!=NULL && fast->next!=NULL){
fast=fast->next->next;
slow=slow->next;
}
ListNode* r=resver(slow->next);
while(r!=NULL){
if(r->val!=head->val){
return false;
}
r=r->next;
head=head->next;
}
return true;
}
ListNode* resver(ListNode* head){
if(head==NULL||head->next==NULL){
return head;
}
ListNode* pre=NULL;
ListNode* cur=head;
ListNode* next;
while(cur!=NULL){
next=cur->next;
cur->next=pre;
pre=cur;
cur=next;
}
return pre;
}
};
328.奇偶链表
给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。
请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。
示例 1:
输入: 1->2->3->4->5->NULL
输出: 1->3->5->2->4->NULL
示例 2:
输入: 2->1->3->5->6->4->7->NULL
输出: 2->3->6->7->1->5->4->NULL
说明:
应当保持奇数节点和偶数节点的相对顺序。
链表的第一个节点视为奇数节点,第二个节点视为偶数节点,以此类推。
class Solution {
public:
ListNode* oddEvenList(ListNode* head) {
if(head==NULL||head->next==NULL){
return head;
}
ListNode* t1=head;
ListNode* t2=head->next;
ListNode* t2head=t2; //必须先保存,才可以保证链接。因为后续head->next 变了。
while(t2!=NULL && t2->next!=NULL){
t1->next=t2->next;
t1=t1->next;
t2->next=t1->next;
t2=t2->next;
}
t1->next=t2head;
return head;
}
};