目录
求两个链表的交点(Easy)
LeetCode 160.Intersection of Two LinkedLists
已知链表A的头节点指针headA, 链表B的头节点指针headB,两个链表相交,求两个链表交点对应的节点。
题目要求:
- 如果两个链表没有交点,则返回NULL
- 在求交点的过程中,不可以破坏链表的结构或者修改链表的数据域
- 可以确保传入的链表A与链表B没有任何环
- *实现算法尽可能使时间O(n),空间复杂度O(1)
基本代码结构
Solve1:使用set求交集
大体思路
用set集合来求交点。原理就是通过set集合来判断出两个链表之间有没有相同(重复)的元素。通过将A链表中的值,逐个插入到一个set集合(test_set)中去,然后,再遍历B数组,判断B数组中的每个节点是否在set集合(test_set)中出现过,如果出现过,就把这个重复的元素给打印出来,那么这个第一个出现的元素肯定就是交点了!
细节设计
这里需要强调的是,我们插入的是节点对应的指针(地址),而绝非之前所一般使用的值域!示意图如下,发现两个链表第一个地址相同的节点,就是我们要找的两个链表的交点:
按照这个思路,可以编写代码如下:
代码实现
LeetCode 160.Intersection of Two LinkedLists(solve1).cpp
#include <stdio.h>
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
#include <set>
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
std::set<ListNode*> node_set;
while(headA){
node_set.insert(headA);
headA = headA->next;
}
while(headB){
if (node_set.find(headB) != node_set.end()){
return headB;
}
headB = headB->next;
}
return NULL;
}
};
int main(){
ListNode a1(1);
ListNode a2(2);
ListNode b1(3);
ListNode b2(4);
ListNode b3(5);
ListNode c1(6);
ListNode c2(7);
ListNode c3(8);
a1.next = &a2;
a2.next = &c1;
c1.next = &c2;
c2.next = &c3;
b1.next = &b2;
b2.next = &b3;
b3.next = &c1;
Solution solve;
ListNode *result = solve.getIntersectionNode(&a1, &b1);
printf("%d\n", result->val);
return 0;
}
Solve2:空间复杂度O(1)
solve1的解法可以完成求两个链表交点对应的节点的要求,但在空间复杂度上无法实现精简,这就需要一个更精简的思路来应对。
大体思路
从两个链表对齐的位置节点同时开始往后遍历,什么时候两个链表的节点的值一样了,就证明这个节点是两个链表的交点了。
细节设计
再看步骤3:
代码实现
#include <stdio.h>
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
int get_list_length(ListNode *head){
int len = 0;
while(head){
len++;
head = head->next;
}
return len;
}
ListNode *forward_long_list(int long_len,
int short_len, ListNode *head){
int delta = long_len - short_len;
while(head && delta){
head = head->next;
delta--;
}
return head;
}
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
int list_A_len = get_list_length(headA);
int list_B_len = get_list_length(headB);
if (list_A_len > list_B_len){
headA = forward_long_list(list_A_len, list_B_len, headA);
}
else{
headB = forward_long_list(list_B_len, list_A_len, headB);
}
while(headA && headB){
if (headA == headB){
return headA;
}
headA = headA->next;
headB = headB->next;
}
return NULL;
}
};
int main(){
ListNode a1(1);
ListNode a2(2);
ListNode b1(3);
ListNode b2(4);
ListNode b3(5);
ListNode c1(6);
ListNode c2(7);
ListNode c3(8);
a1.next = &a2;
a2.next = &c1;
c1.next = &c2;
c2.next = &c3;
b1.next = &b2;
b2.next = &b3;
b3.next = &c1;
Solution solve;
ListNode *result = solve.getIntersectionNode(&a1, &b1);
printf("%d\n", result->val);
return 0;
}
双链表类题目思路总结
- 1.通过set集合来解决两个链表之间,有重复性元素的问题,应知这种方法再时间复杂度和空间复杂度上不占优势。
- 2.通过从“对齐”位置查找,排查到两个链表的相同元素的问题,在时间复杂度和空间复杂度上开销好,优于set集合的方式。
链表求环(Medium)
LeetCode 142. Linked List Cycle II
已知链表中可能存在环,若有返回环起始节点,否则返回NULL.
基本代码结构
Solve1:使用set求环起始节点
大体思路
遍历链表每个节点地址,把其插入到set集合中去,并且每次在将节点的地址插入setjihe 前,在set中判断一下,set集合是否已经包含了这个这个对应的地址。如果发现有,那这个节点就是其实的节点了!
细节设计
1.遍历链表,将链表中节点对应的指针(地址),插入set
2.在遍历时插入节点前,需要在set中查找,第一个在set中发现的节点地址,即是链表环的起点。
代码实现
LeetCode 142. Linked List Cycle II(solve1).cpp
#include <stdio.h>
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
#include <set>
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
std::set<ListNode *> node_set;
while(head){
if (node_set.find(head) != node_set.end()){
return head;
}
node_set.insert(head);
head = head->next;
}
return NULL;
}
};
int main(){
ListNode a(1);
ListNode b(2);
ListNode c(3);
ListNode d(4);
ListNode e(5);
ListNode f(6);
a.next = &b;
b.next = &c;
c.next = &d;
d.next = &e;
e.next = &f;
//f.next = &c;
Solution solve;
ListNode *node = solve.detectCycle(&a);
if (node){
printf("%d\n", node->val);
}
else{
printf("NULL\n");
}
return 0;
}
Solve2:快慢指针赛跑
大体思路
当"赛道"是一个环形的时候,一个快指针和一个慢指针朝一个方向"跑",总会有跑的快的超过跑的慢的一圈的时候,快慢指针和这个思想是一样的,设定一个快指针(每次遍历走两步)和一个慢指针(每次遍历走一步),那么因为是个链表环,就肯定有一个位置会让快指针和慢指针“相遇”。
细节设计
细节方面,只要注意一点,一个指针一次走两步,一个指针一次走一步,先找出相遇的节点,然后,从起点和相遇的节点同时开始以相同的步数前进,当相遇的时候,相遇的节点就是环的起点。
如果有环,快指针和慢指针相遇的时候,快指针所走过的路程,恰好是慢指针的两倍,根据这个条件列等式方程的话,可以解出来"a段=c段",这个奇妙的答案!
代码实现
LeetCode 142. Linked List Cycle II(solve2).cpp
#include <stdio.h>
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode *fast = head;
ListNode *slow = head;
ListNode *meet = NULL;
while(fast){
slow = slow->next;
fast = fast->next;
if (!fast){
return NULL;
}
fast = fast->next;
if (fast == slow){
meet = fast;
break;
}
}
if (meet == NULL){
return NULL;
}
while(head && meet){
if (head == meet){
return head;
}
head = head->next;
meet = meet->next;
}
return NULL;
}
};
int main(){
ListNode a(1);
ListNode b(2);
ListNode c(3);
ListNode d(4);
ListNode e(5);
ListNode f(6);
ListNode g(7);
a.next = &b;
b.next = &c;
c.next = &d;
d.next = &e;
e.next = &f;
f.next = &g;
g.next = &c;
Solution solve;
ListNode *node = solve.detectCycle(&a);
if (node){
printf("%d\n", node->val);
}
else{
printf("NULL\n");
}
return 0;
}
链表环类题目思路总结
- 1.依旧像双链表问题一样,用set来设计,找重复节点。
- 2.用快慢指针的思路来进行设计。