链表

160. 相交链表 - 力扣(LeetCode)

题意:输入两个链表,找出它们的第一个公共结点。 当不存在公共节点时,返回空节点。

思路: 1. 用两个指针 p1,p2 分别指向两个链表 headA,headB 的头结点,同时向后遍历。
2. 当指针到达链表末尾时,重新定位到另一个链表的头结点。
3. 当它们相遇时,所指向的结点就是第一个公共结点。

解释
设A链表的非公共部分长度为LA,B链表的非公共部分长度为LB,公共部分长度为C。

360截图20201206224658719.jpg

A链表总长度为LA + C,B链表总长度为LB + C。
当指针按照题解方式走下去,p1第二次走到公共节点的时候,走过的长度为LA + C + LB,p2第二次走到公共节点的时候,走过的长度为LB + C + LA。p1 p2走过的长度相等,p1 p2 相遇。
所以,当p1 p2 相遇(相等)的时候,指向的节点就是公共节点。

1.jpg
7.jpg
13.jpg
16.jpg
20.jpg
23.jpg
26.jpg
29.jpg
32.jpg

/**
* 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) {
auto p = headA, q = headB;
while(p != q)
{
if(p) p = p->next;
else p = headB;
if(q) q = q->next;
else q = headA;
}

return p;
}
};

206. 反转链表 - 力扣(LeetCode)

假设链表为 1→2→3→∅,我们想要把它改成 ∅←1←2←3。

在遍历链表时,将当前节点的 next 指针改为指向前一个节点。由于节点没有引用其前一个节点,因此必须事先存储其前一个节点。在更改引用之前,还需要存储后一个节点。最后返回新的头引用。

/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* prev = nullptr;
ListNode* cur = head;
while(cur)
{
ListNode* next = cur->next;
cur->next = prev;
prev = cur;
cur = next;
}
return prev;
}
};

234. 回文链表 - 力扣(LeetCode)

题意:给定一个链表要求判断链表节点是否构成回文链表

思路:将链表节点依次加入数组中,将问题转化为判断数组是否是回文数组,时间复杂度

/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
bool isPalindrome(ListNode* head) {
vector<int> res;
bool flag = true;
ListNode* cur = head;
while(cur)
{
res.push_back(cur->val);
cur = cur->next;
}
for(int i = 0, j = res.size() -1; i < j; i ++, j --)
{
if(res[i] != res[j])
{
flag = false;
break;
}
}

return flag;
}
};

思路二:快慢指针

避免使用 O(n) 额外空间的方法就是改变输入。

我们可以将链表的后半部分反转(修改链表结构),然后将前半部分和后半部分进行比较。比较完成后我们应该将链表恢复原样。虽然不需要恢复也能通过测试用例,但是使用该函数的人通常不希望链表结构被更改。

该方法虽然可以将空间复杂度降到 O(1),但是在并发环境下,该方法也有缺点。在并发环境下,函数运行时需要锁定其他线程或进程对链表的访问,因为在函数执行过程中链表会被修改。

算法

整个流程可以分为以下五个步骤:

  1. 找到前半部分链表的尾节点。
  2. 反转后半部分链表。
  3. 判断是否回文。
  4. 恢复链表。
  5. 返回结果。

执行步骤一,我们可以计算链表节点的数量,然后遍历链表找到前半部分的尾节点。

我们也可以使用快慢指针在一次遍历中找到:慢指针一次走一步,快指针一次走两步,快慢指针同时出发。当快指针移动到链表的末尾时,慢指针恰好到链表的中间。通过慢指针将链表分为两部分。

/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* endOfFirstHalf(ListNode* head)
{
ListNode* fast = head;
ListNode* slow = head;
while(fast->next != nullptr && fast->next->next != nullptr)
{
fast = fast->next->next;
slow = slow->next;
}
return slow;
}

ListNode* reverseList(ListNode* head)
{
ListNode* prev = nullptr;
ListNode* cur = head;
while(cur != nullptr)
{
ListNode* nextTemp = cur->next;
cur->next = prev;
prev = cur;
cur = nextTemp;
}
return prev;
}

bool isPalindrome(ListNode* head) {
if(head == nullptr)
return true;

ListNode* firstHalfEnd = endOfFirstHalf(head);
ListNode* secondHalfEnd = reverseList(firstHalfEnd->next);

ListNode* l = head;
ListNode* r = secondHalfEnd;
bool flag = true;
while(flag && r != nullptr)
{
if(l->val != r->val)
flag = false;
l = l->next;
r = r->next;
}

return flag;
}
};

141. 环形链表 - 力扣(LeetCode)

题意:链表是否存在环路

思路:双指针,定义快慢指针,快指针每次移动两个节点,慢指针每次移动一个节点,每次判断快慢指针指向节点的下一个节点是否相同即可。时间复杂度,这题与两链表是否有公共路径类似

/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head) {
if(head == nullptr)
return false;
ListNode* fast = head;
ListNode* low = head;
while(fast->next != nullptr && fast->next->next != nullptr)
{
fast = fast->next->next;
low = low->next;
if(low-> next == fast->next)
{
return true;
}
}
return false;
}
};

142. 环形链表 II - 力扣(LeetCode)

  1. 快慢指针相遇时:当快慢指针相遇时,只能证明链表中有环,但相遇点不一定是环的入口节点。
  2. 环的入口节点定位:根据 Floyd 的算法,当快慢指针相遇后,将一个指针重新指向链表头部,然后两个指针以相同速度前进,再次相遇的点就是环的入口节点。
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
if (head == nullptr || head->next == nullptr) {
return nullptr;
}

ListNode* fast = head;
ListNode* slow = head;

// 判断是否有环
while (fast != nullptr && fast->next != nullptr) {
fast = fast->next->next;
slow = slow->next;

if (fast == slow) {
// 有环,定位入口节点
ListNode* ptr = head;
while (ptr != slow) {
ptr = ptr->next;
slow = slow->next;
}
return ptr;
}
}

return nullptr;
}
};

21. 合并两个有序链表 - 力扣(LeetCode)

/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
if(list1 == nullptr)
{
return list2;
}
else if(list2 == nullptr)
{
return list1;
}
else if(list1->val < list2->val)
{
list1->next = mergeTwoLists(list1->next, list2);
return list1;
}
else
{
list2->next = mergeTwoLists(list2->next, list1);
return list2;
}
}
};