大家好,这里是小编的博客频道
小编的博客:就爱学编程
很高兴在
CSDN
这个大家庭与大家相识,希望能在这里与大家共同进步,共同收获更好的自己!!!
目录
废话不多说,我们直接看题。
1.相交链表
(1)题目描述
(2)解题思路
简单思路:如果相交,则必有交点及之后两链表长度一样,各自加上交点之前不同或相同的链表长度,就是各自的链表总长度。所以我们用两个指针分别指向长链表和短链表,先让指向长链表的指针走差距步,再同时走就可以走到相交点(如果存在)。
代码实现:
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
//先得到两条链表的长度
int lA = 0, lB = 0;
struct ListNode* failA = headA, * failB = headB;
while(failA){
failA = failA->next;
++lA;
}
while(failB){
failB = failB->next;
++lB;
}
if(failB != failA) return NULL;
int gap = lA > lB ? lA-lB : lB-lA;
//一个小小的技巧找到长的链表并走差距步
struct ListNode* longlist = headA, * shortlist = headB;
if(lA < lB){
longlist = headB;
shortlist = headA;
}
while(gap--){
longlist = longlist->next;
}
//依次比较
while(longlist && shortlist){
if(longlist == shortlist){
//该处不可用值相等作为相等条件
return longlist;
}
longlist = longlist->next;
shortlist = shortlist->next;
}
return NULL;
}
巧妙思路:只有当链表
headA
和headB
都不为空时,两个链表才可能相交。因此首先判断链表headA
和headB
是否为空,如果其中至少有一个链表为空,则两个链表一定不相交,返回NULL
。
当链表headA
和headB
都不为空时,创建两个指针p1
和p2
,初始时分别指向两个链表的头节点headA
和headB
,然后将两个指针依次遍历两个链表的每个节点。具体做法如下:
- 每步操作需要同时更新指针
p1
和p2
。- 如果指针
p1
不为空,则将指针p1
移到下一个节点;如果指针p2
不为空,则将指针p2
移到下一个节点。- 如果指针
p1
为空,则将指针p1
移到链表headB
的头节点;如果指针p2
为空,则将指针p2
移到链表headA
的头节点。- 当指针
p1
和p2
指向同一个节点(NULL
也包含在内)时,返回它们指向的节点(NULL
)。
证明方法:
情况一:两个链表相交
链表
headA
和headB
的长度分别是m
和n
。假设链表headA
的不相交部分有a
个节点,链表headB
的不相交部分有b
个节点,两个链表相交的部分有c
个节点,则有a+c=m
,b+c=n
。
如果a=b
,则两个指针会同时到达两个链表相交的节点,此时返回相交的节点;
如果a!=b
,则指针p1
会遍历完链表headA
,指针p2
会遍历完链表headB
,两个指针不会同时到达链表的尾节点,然后指针p1
移到链表headB
的头节点,指针p2
移到链表headA
的头节点,然后两个指针继续移动,在指针p1
移动了a+c+b
次、指针p2
移动了b+c+a
次之后,两个指针会同时到达两个链表相交的节点,该节点也是两个指针第一次同时指向的节点,此时返回相交的节点。
情况二:两个链表不相交
链表
headA
和headB
的长度分别是m
和n
。考虑当m=n
和m!=n
时,两个指针分别会如何移动:
如果m=n
,则两个指针会同时到达两个链表的尾节点,然后同时变成空值NULL
,此时返回NULL
;
如果m!=n
,则由于两个链表没有公共节点,两个指针也不会同时到达两个链表的尾节点,因此两个指针都会遍历完两个链表,在指针p1
移动了m+n
次、指针p2
移动了n+m
次之后,两个指针会同时变成空值NULL
,此时返回NULL
。
代码实现:
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
if (headA == NULL || headB == NULL) {
return NULL;
}
struct ListNode *p1 = headA, *p2 = headB;
while (p1 != p2) {
p1 = p1 == NULL ? headB : p1->next;
p2 = p2 == NULL ? headA : p2->next;
}
return p1;
}
(3)复杂度分析
说明:此处小编不完全采用大O的渐进表示法,而用相对精准的表示法,以此看出解法二的优势之处。并且以不相交为例。
- 解法一:时间复杂度:
2m + 2n
;空间复杂度:O(1)
; - 解法二:时间复杂度:
2m + 2n
或2m(也是2n)
;空间复杂度:O(1)
;
实际上,小编认为两种解法都各有优势和劣势:
- 解法一代码较长但是可读性强,思路也好想;
- 解法二代码简洁运行速度略胜一筹,但思路不好想,可读性也不强。
2.随机链表的复制
(1)题目描述
- 简单来说,就是复制一条带有随机指针的单链表。
(2)解题思路
- 我们首先将该链表中每一个节点拆分为两个相连的节点,例如对于链表 A→B→C,我们可以将其拆分为 A→A ′ →B→B ′ →C→C ′ 。对于任意一个原节点 S,其拷贝节点 S ′ 即为其后继节点。
- 这样,我们可以直接找到每一个拷贝节点 S ′的随机指针应当指向的节点,即为其原节点 S 的随机指针指向的节点 T 的后继节点 T ′ 。需要注意原节点的随机指针可能为空,我们需要特别判断这种情况。
- 当我们完成了拷贝节点的随机指针的赋值,我们只需要将这个链表按照原节点与拷贝节点的种类进行拆分即可,只需要遍历一次。同样需要注意最后一个拷贝节点的后继节点为空,我们需要特别判断这种情况。
代码实现:
struct Node* copyRandomList(struct Node* head) {
if (head == NULL)
return NULL;
//
struct Node* cur = head;
while (cur) {
struct Node* copy = (struct Node*)malloc(sizeof(struct Node));
copy->val = cur->val;
struct Node* next = cur->next;
cur->next = copy;
cur = copy->next = next;
copy->random = NULL;
}
//yyds
cur = head;
while (cur) {
if (cur->random != NULL)
cur->next->random = cur->random->next;
cur = cur->next->next;
}
//
cur = head;
struct Node dummy = {
};
struct Node* phead = &dummy;
while(cur){
struct Node* copy = cur->next;
struct Node* next = copy->next;
phead = phead->next = copy;
cur = cur->next = next;
}
return dummy.next;
}
(3)复杂度分析
时间复杂度:
O(n)
。
空间复杂度:
O(1)
。注意返回值不计入空间复杂度。