文章目录
一、力扣141. 环形链表
- 给定一个链表,判断链表中是否有环。
- 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
- 注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
- 如果链表中存在环,则返回 true 。 否则,返回 false 。
- 进阶:你能用 O(1)(即,常量)内存解决此问题吗?
(一)快慢指针----空间复杂度O(1)
- 思路:
- 定义两个指针,分别为快慢指针;
- 让慢指针一次走一步,而快指针一次走两步;
- 如果链表存在还,那么最后快慢指针肯定会在环中相遇;
- 所以如果快慢两个指针相遇了就说明该链表有环;
- 否则如果快指针先走到nullptr,那么就说明该链表无环。
- 代码如下:
bool hasCycle(ListNode* head)
{
if (head == nullptr || head->next == nullptr)
{
return false;
}
ListNode* slow = head;
ListNode* fast = head->next;
//为了能进入第一次循环,所以将快指针指向了头节点的下一个节点
while (slow != fast)
{
if (fast == nullptr || fast->next == nullptr)
{
return false;
}
slow = slow->next;
fast = fast->next->next;
}
return true;
}
(二)哈希表----空间复杂度O(N)
- 思路:
- 我们可以将链表中每个节点的访问次数记录下来,看会不会有出现访问量大于一次的;
- 如果出现,则说明该链表有环;
- 如果没有出现,就说明该链表无环。
- 代码如下:
bool hasCycle(ListNode* head)
{
if (head == nullptr || head->next == nullptr)
{
return false;
}
unordered_map<ListNode*, int> hash;
ListNode* phead = head;
while (phead != nullptr)
{
hash[phead]++;
if (hash[phead] > 1)
{
return true;
}
phead = phead->next;
}
return false;
}
二、力扣142. 环形链表 II
- 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
- 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。
- 说明:不允许修改给定的链表。
- 进阶:你是否可以使用 O(1) 空间解决此题?
- 输入输出示例:
(一)哈希表----空间复杂度O(N)
- 跟上个题目一样:
- 当我们用哈希表记录访问过的节点的次数时,只要找到第一个被访问了两次的节点,就找到了链表入环的第一个节点。
- 代码如下:
ListNode* detectCycle(ListNode* head)
{
if (head == nullptr || head->next == nullptr)
{
return nullptr;
}
unordered_map<ListNode*, int> hash;
ListNode* phead = head;
while (phead != nullptr)
{
hash[phead]++;
if (hash[phead] > 1)
{
return phead;
}
phead = phead->next;
}
return nullptr;
}
(二)快慢指针----空间复杂度O(1)
- 思路也是和第一个题一样,关键点就在于如何找到链表的入环节点【如果链表存在环,在快慢指针第一次相遇的地方,让一个指针从head开始走,一次走一步,另一个指针从第一次相遇点开始走,一次走一步,当两个指针第一次相遇时,相遇的节点就是链表的入环节点,具体原因在代码后面会有解释】。
- 代码如下:
ListNode* detectCycle(ListNode* head)
{
if (head == nullptr || head->next == nullptr)
{
return nullptr;
}
//定义快慢指针
ListNode* slow = head;
ListNode* fast = head;
//找到快慢指针第一次相遇的点
while (fast != nullptr)
{
slow = slow->next;
if (fast->next == nullptr)
{
return nullptr;
}
fast = fast->next->next;
if (slow == fast)//找到第一次相遇的点
{
slow = head;
while (slow != fast)
{
slow = slow->next;
fast = fast->next;
}
return slow;//找到入环的节点
}
}
return nullptr;
}
- 如下图所示,设链表中环外部分的长度为 a。slow 指针进入环后,又走了 b 的距离与 fast 相遇。此时,fast 指针已经走完了环的 n 圈,因此它走过的总距离为 a+n(b+c)+b=a+(n+1)b+nc;
- 任意时刻,fast 指针走过的距离都为 slow 指针的 2 倍;
- 因此得出:a+(n+1)b+nc=2(a+b)⟹a=c+(n−1)(b+c)
- 有了 a=c+(n-1)(b+c)的等量关系,我们会发现:从相遇点到入环点的距离加上 n−1 圈的环长,恰好等于从链表头部到入环点的距离。
三、面试题02.08. 环路检测
- 给定一个链表,如果它是有环链表,实现一个算法返回环路的开头节点。
- 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
- 输入输出示例:
(一)哈希表
- 思路以及代码都和环形链表II一样,在这里笔者就是为了加深对这道题的印象以及解法,相当于做了两遍⑧~踏实
- 代码:
ListNode* detectCycle(ListNode* head)
{
if (head == nullptr || head->next == nullptr)
{
return nullptr;
}
unordered_map<ListNode*, int> hash;
ListNode* phead = head;
while (phead != nullptr)
{
hash[phead]++;
if (hash[phead] > 1)
{
return phead;
}
phead = phead->next;
}
return nullptr;
}
(二)快慢指针
- 代码如下:
ListNode* detectCycle(ListNode* head)
{
if (head == nullptr || head->next == nullptr)
{
return nullptr;
}
ListNode* slow = head;
ListNode* fast = head;
while (fast != nullptr)
{
slow = slow->next;
if (fast->next == nullptr)
{
return nullptr;
}
fast = fast->next->next;
if (slow == fast)
{
slow = head;
while (slow != fast)
{
slow = slow->next;
fast = fast->next;
}
return slow;
}
}
return nullptr;
}
四、力扣160. 相交链表
- 编写一个程序,找到两个单链表相交的起始节点。
- 如下面的两个链表:
- 在节点 c1 开始相交。
- 输入输出示例:
- 提示:
(一)哈希表
- 思路:
- 首先可以先遍历其中的一个链表;
- 然后将链表中的节点存放在哈希表中;
- 接着再从头开始遍历另外一个链表;
- 一边遍历一边查看该节点有没有在链表中出现过;
- 如果出现过直接返回该节点;
- 如果没有出现过就继续遍历,直到链表结尾。
- 代码如下:
ListNode* getIntersectionNode(ListNode* headA, ListNode* headB)
{
if (headA == nullptr || headB == nullptr)
{
return nullptr;
}
unordered_map<ListNode*, int> hash;
ListNode* pheada = headA;
ListNode* pheadb = headB;
while (pheada != nullptr)
{
hash[pheada]++;
pheada = pheada->next;
}
while (pheadb != nullptr)
{
if (hash.find(pheadb) != hash.end())
{
return pheadb;
}
else
{
pheadb = pheadb->next;
}
}
return nullptr;
}
- 这种解法的时间复杂度为O(N+M),空间复杂度为O(N)或者O(M)。
- 注意:两个链表相交以后一定是合并成一条链表了,不可能出现交叉之后又分离的情况;
- 如下图所示:
(二)暴力的双重循环
- 双重循环的思路也是平常做题常用到的,也很容易想到:
- 就是将一个链表中的每个节点都与另一个链表中的节点比较,看是不是同一个节点;
- 如果是则返回该节点,如果不是,则继续遍历直到链表结尾。
- 代码如下:
ListNode* getIntersectionNode(ListNode* headA, ListNode* headB)
{
if (headA == nullptr || headB == nullptr)
{
return nullptr;
}
ListNode* pheada = headA;
while (pheada != nullptr)
{
ListNode* pheadb = headB;
while (pheadb != nullptr)
{
if (pheada == pheadb)
{
return pheada;
}
else
{
pheadb = pheadb->next;
}
}
pheada = pheada->next;
}
return nullptr;
}
- 该种解法的时间复杂度为O(N*M),空间复杂度为O(1)。
(三)双指针遍历
- 思路如下图所示:
- 1、先构建两个指针A和B指向链表A的头节点和链表B的头节点
- 2、指针A先遍历完A链表再遍历B链表所走过的节点数为a+(b-c)
- 3、指针B先遍历完B链表再遍历A链表所走过的节点数为b+(a-c)
- 4、两个指针所走过的节点数都相同
- 所以当c>0时,链表有公共尾部,指针A和B同时指向第一个公共节点
- 当 c = 0 时,链表没有公共尾部,指针A和B同时为nullptr
- 代码如下:
ListNode* getIntersectionNode(ListNode* headA, ListNode* headB)
{
ListNode* pheada = headA;
ListNode* pheadb = headB;
while (pheada != pheadb)
{
pheada = pheada != nullptr ? pheada->next : headB;
pheadb = pheadb != nullptr ? pheadb->next : headA;
}
return pheada;
}
五、力扣328. 奇偶链表
(一)分离节点之后再合并
- 思路:
- 将奇数位上的节点连接到奇链表上;
- 将偶数位上的节点连接到偶链表上;
- 然后再将两个链表合并起来即可
- 具体过程如下图所示:
- 代码如下:
ListNode* oddEvenList(ListNode* head)
{
if (head == nullptr || head->next == nullptr || head->next->next == nullptr)
{
return head;
}
ListNode* odd = head;//奇链表的头节点
ListNode* even = head->next;//偶链表的头节点
ListNode* evenHead = even;
while (even != nullptr && even->next != nullptr)
{
odd->next = even->next;
odd = odd->next;
even->next = odd->next;
even = even->next;
}
odd->next = evenHead;
return head;
}
六、力扣234. 回文链表
(一)利用栈来作辅助
- 首先,简单来说,回文链表就是链表的节点值倒着读和顺着读都一样;
- 因此我们可以利用栈先进后出的特点来和原来链表的节点值做一个比对;
- 看看是否将链表反过来之后他们的每个节点对应的val是否相等。
- 思路也比较简单:代码如下所示
bool isPalindrome(ListNode* head)
{
if (head == nullptr || head->next == nullptr)
{
return true;
}
ListNode* phead = head;
stack<int> st;
while (phead != nullptr)
{
st.push(phead->val);
phead = phead->next;
}
phead = head;
while (!st.empty())
{
if (st.top() != phead->val)
{
return false;
}
st.pop();
phead = phead->next;
}
return true;
}
(二)vector+双指针
- 思路和第一种解法的相差不大:
- 可以先将链表节点中的val值装入vector中,然后再从vector中利用双指针对两个指针所指向的节点的val值进行比较即可。
- 代码如下:
bool isPalindrome(ListNode* head)
{
if (head == nullptr || head->next == nullptr)
{
return true;
}
vector<int> res;
ListNode* phead = head;
while (phead != nullptr)
{
res.push_back(phead->val);
phead = phead->next;
}
for (int i = 0, j = res.size() - 1; i < j; i++, j--)
{
if (res[i] != res[j])
{
return false;
}
}
return true;
}
(三)快慢指针+反转链表
- 思路:
- 我们需要先找到前半部分节点的尾节点;
- 然后将后半部分节点反转;
- 然后比较反转链表与没有反转链表的节点值;
- 如果对应节点值有不相同的就说明不是回文链表;
- 否则就是回文链表。
- 代码如下:
//对链表进行反转
ListNode* reverse(ListNode* head)
{
if (head == nullptr || head->next == nullptr)
{
return head;
}
ListNode* pre = nullptr;
ListNode* cur = head;
ListNode* next = nullptr;
while (cur != nullptr)
{
next = cur->next;
cur->next = pre;
pre = cur;
cur = next;
}
return pre;
}
//找到链表的中间节点
ListNode* Findmidnode(ListNode* head)
{
if (head == nullptr || head->next == nullptr)
{
return head;
}
ListNode* fast = head;
ListNode* slow = head;
while (fast->next != nullptr && fast->next->next != nullptr)
{
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
bool isPalindrome(ListNode* head)
{
if (head == nullptr || head->next == nullptr)
{
return true;
}
//找到前半部分链表的尾节点
ListNode* end = Findmidnode(head);
//反转后半部分链表
ListNode* first = reverse(end->next);
//判断是否是回文链表
while (first != nullptr)
{
if (first->val != head->val)
{
return false;
}
first = first->next;
head = head->next;
}
return true;
}
旺财加油!✨