面试题 02.07. 链表相交
方法一:哈希集合
思路
先遍历一遍链表A,将每个结点存到一个哈希集合中,然后遍历链表B,判断结点是否存在于哈希集合中,若是则说明该结点是相交结点。
- 时间复杂度: O ( n + m ) O(n+m) O(n+m),n是链表A的长度,m是链表B的长度。
- 空间复杂度: O ( n ) O(n) O(n)。
C++代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
unordered_set<ListNode*> hashSet;
while (headA != NULL) {
hashSet.insert(headA);
headA = headA->next;
}
while (headB != NULL) {
if (hashSet.count(headB) != 0) return headB;
headB = headB->next;
}
return NULL;
}
};
方法二:两次遍历
思路
若两个链表相交,则两个链表最后的结点一定相同,首先分别遍历两个链表,记录两个链表的长度,再判断两个链表最后的结点是否相同,若不同直接返回null
,若相同说明两链表相交。
对于两个长度一致的相交链表,我们只需依次遍历两个链表的结点进行比较即可找到相交的结点,所以我们可以根据刚才记录的两链表的长度,先遍历较长的链表直到两链表剩余的长度一致,然后再接着同时遍历两个链表就可以找到相交结点。
- 时间复杂度: O ( n + m ) O(n+m) O(n+m)
- 空间复杂度: O ( 1 ) O(1) O(1)
C++代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if (headA == NULL || headB == NULL) return NULL;
// 获取两个链表的长度以及两个链表最后的结点
ListNode* finalA = headA;
ListNode* finalB = headB;
int sizeA = 1;
int sizeB = 1;
while (finalA->next != NULL) {
finalA = finalA->next;
++sizeA;
}
while (finalB->next != NULL) {
finalB = finalB->next;
++sizeB;
}
// 如果最后的结点相同则表明存在相交结点,否则反之
if (finalA != finalB) return NULL;
else {
// 遍历较长的链表直到两链表剩余长度一致
while (sizeA > sizeB) {
--sizeA;
headA = headA->next;
}
while (sizeB > sizeA) {
--sizeB;
headB = headB->next;
}
// 同时遍历两链表直到找到相交结点
while (headA != headB) {
headA = headA->next;
headB = headB->next;
}
}
return headA;
}
};
方法三:双指针
思路
设置两个指针pA
和pB
分别指向链表A、B的头部,然后依次遍历,当pA
(pB
)为NULL
时,让其指向链表B(A)的头部继续遍历,直到pA == pB
返回。
方法三本质上与方法二一致,都是为了对齐两个链表的遍历位置,方法三巧妙的变动两个指针的位置实现了这个目的,虽然方法三当两个链表不相交时,时间复杂度会比方法二高,但是方法三的代码十分简洁。
- 时间复杂度: O ( n + m ) O(n+m) O(n+m)
- 空间复杂度: O ( 1 ) O(1) O(1)
C++代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if (headA == NULL || headB == NULL) return NULL;
ListNode* pA = headA;
ListNode* pB = headB;
while (pA != pB) {
if (pA == NULL) pA = headB;
else pA = pA->next;
if (pB == NULL) pB = headA;
else pB = pB->next;
}
return pA;
}
};
看完讲解的思考
方法三真的太巧妙了。
代码实现遇到的问题
无。
142. 环形链表II
方法一:哈希集合
思路
使用一个哈希集合存储链表结点,遍历链表结点,若出现重复结点则说明链表成环且该结点是入环的第一个结点。
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( n ) O(n) O(n)
C++代码
/**
* 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) {
unordered_set<ListNode*> hashSet;
while (head != NULL) {
if (hashSet.count(head) != 0) return head;
hashSet.insert(head);
head = head->next;
}
return head;
}
};
方法二:三指针
思路
设置慢指针slow
和快指针fast
,slow
每次走一步,fast
每次走两步,当快慢指针都入环后,fast
一定会和slow
相遇,因为fast
相对于slow
的速度是每次走一步,所以不存在fast
跳过slow
的情况。
快慢指针找到第一次相遇的点,快慢指针相遇后,从起点开始再使用一个指针res
与慢指针同时向后遍历,当res
和慢指针相遇时所处的位置就是入环的第一个结点。(具体数学推导见代码随想录)
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( 1 ) O(1) O(1)
C++代码
/**
* 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) {
ListNode* slow = head;
ListNode* fast = head;
ListNode* res = head;
// 快慢指针相遇的位置
while (slow != NULL && fast != NULL) {
slow = slow->next;
fast = fast->next;
if (fast == NULL) return NULL;
fast = fast->next;
if (slow == fast) break;
}
// 慢指针和res相遇的位置即为答案
if (fast == NULL) return NULL;
while (res != slow) {
res = res->next;
slow = slow->next;
}
return res;
}
};
看完讲解的思考
真牛啊方法二。
代码实现遇到的问题
无
242. 有效的字母异味词
方法一:哈希表
思路
首先遍历字符串s记录每个字符出现的次数,然后再遍历t检验字符频数是否一致。
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( s ) O(s) O(s),s是字符串中出现的字符种类数,由于s是常量,不会大过26,所以空间复杂度也可以说是 O ( 1 ) O(1) O(1)。
C++代码
class Solution {
public:
bool isAnagram(string s, string t) {
if (s.size() != t.size()) return false;
unordered_map<char, int> hashMap;
for (char& c : s) {
++hashMap[c];
}
for (char& c : t) {
if (hashMap.find(c) == hashMap.end()) return false;
if (--hashMap[c] < 0) return false;
}
return true;
}
};
方法二:排序
思路
先排序,后逐个遍历对比是否一致。
- 时间复杂度: O ( ) O() O()
- 空间复杂度: O ( ) O() O()
C++代码
class Solution {
public:
bool isAnagram(string s, string t) {
if (s.size() != t.size()) return false;
sort(s.begin(), s.end());
sort(t.begin(), t.end());
return s == t;
}
};
看完讲解的思考
无
代码实现遇到的问题
无
最后的碎碎念
链表章节结束,进入一下章节哈希表咯。