List
链表结构
如无特殊说明,链表结构如下:
// 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) {}
}
链表模板1
链表的创建,打印,销毁。
#include <iostream>
#include <vector>
using namespace std;
struct ListNode {
int val;
ListNode* next;
ListNode():val(0),next(nullptr) {}
ListNode(int v):val(v),next(nullptr) {}
ListNode(int v, ListNode* p):val(0),next(p) {}
};
ListNode* createList(const vector<int>& nums)
{
ListNode dummy(0);
ListNode* node = &dummy;
for (const int& n: nums)
{
node->next = new ListNode(n);
node = node->next;
}
return dummy.next;
}
void printList(ListNode* head)
{
while (head)
{
cout << head->val << " ";
head = head->next;
}
cout << endl;
}
void destroyList(ListNode* head)
{
while (head)
{
ListNode* delNode = head;
head = head->next;
delete delNode;
}
}
int main(int argc, char* argv[])
{
vector<vector<int>> nums{{}, {1}, {1,2,3,4,5,6}};
for (auto& n: nums)
{
cout << "---------------" << endl;
ListNode* head = createList(n);
printList(head);
// ... do something here
printList(head);
destroyList(head);
}
return 0;
}
链表模板2
输入是字符串如:“1->2->3->4”,根据字符串创建链表。
#include <iostream>
#include <vector>
using namespace std;
struct ListNode {
int val;
ListNode* next;
ListNode():val(0),next(nullptr) {}
ListNode(int v):val(v),next(nullptr) {}
ListNode(int v, ListNode* p):val(0),next(p) {}
};
ListNode* createList(const vector<int>& nums)
{
ListNode dummy(0);
ListNode* node = &dummy;
for (const int& n: nums)
{
node->next = new ListNode(n);
node = node->next;
}
return dummy.next;
}
void printList(ListNode* head)
{
while (head)
{
cout << head->val;
head = head->next;
if (head) // 注意这里的链表打印!!
cout << "->";
}
cout << endl;
}
void destroyList(ListNode* head)
{
while (head)
{
ListNode* delNode = head;
head = head->next;
delete delNode;
}
}
// 函数参数是字符串!!
ListNode* createList(string& str)
{
if (str == "")
return nullptr;
ListNode dummy(0);
ListNode* node = &dummy;
int i = 0;
int n = str.size();
int num = 0;
while (i < n)
{
if (str[i] >= '0' && str[i] <= '9')
{
num = num * 10 + (str[i] - '0');
}
else if(str[i] == '-')
{
node->next = new ListNode(num);
node = node->next;
num = 0;
}
i++;
}
node->next = new ListNode(num);
return dummy.next;
}
int main(int argc, char* argv[])
{
string str1;
cin >> str1; // 1->2->3
string str2;
cin >> str2; // 0->1->4->5
ListNode* head1 = createList(str1);
ListNode* head2 = createList(str2);
// do something ...
destroyList(head1);
destroyList(head2);
return 0;
}
19. 删除链表的倒数第 N 个结点
问题描述:
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
要求只能使用一趟扫描。
其中:
链表中结点的数目为 sz
1 <= sz <= 30
0 <= Node.val <= 100
1 <= n <= sz
比如:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
解析:
- 该题可以用快慢指针来实现一趟扫描找出被删除节点。只要让两个节点指针相差(n-1)个节点,然后让快的节点指针指向链表的最后一个节点,那么慢的节点指针自然指向要删除的那个节点。
- 注意被删除节点为头节点的情况,也就是n == sz的情况,需要设置哨兵节点。
- 让两个快慢两个指针的差距为n,可以减少一个prevNode的变量。
代码实现:
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode dummy(0);
dummy.next = head;
ListNode* slow = &dummy;
ListNode* fast = slow;
for (int i = 0; i < n; i++)
fast = fast->next;
while (fast->next)
{
slow = slow->next;
fast = fast->next;
}
ListNode* temp = slow->next->next;
delete slow->next;
slow->next = temp;
return dummy.next;
}
};
21. 合并两个有序链表
问题描述:
将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例1:
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
解析:
- 总体思路是:每次比较链表l1和链表l2的值,那个小,就将该节点合入新链表中,然后继续拿小节点的next节点和之前大节点进行比较,直到其中一个链表为空。
- 定义一个虚拟节点,取值为INT_MIN,确保在排序之后,始终是第一个节点,那这样的话,虚拟节点的next节点就是真正的新链表指针。
代码实现:
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode dummy(0);
ListNode* node = &dummy;
while (l1 && l2)
{
if (l1->val < l2->val)
{
node->next = l1;
l1 = l1->next;
}
else
{
node->next = l2;
l2 = l2->next;
}
node = node->next;
}
node->next = l1?l1:l2;
return dummy.next;
}
};
23. 合并K个升序链表
问题描述:
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例:
输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
1->4->5,
1->3->4,
2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6
解析:
- 循环合并:思路跟<21. 合并两个有序链表>是一样的,不过需要设置循环进行两两合并。
- 分治合并,先两两合并,再两两合并 …
代码实现:
循环合并:
class Solution {
private:
ListNode* mergeTwoList(ListNode* root1, ListNode* root2)
{
ListNode dummy(0);
ListNode* node = &dummy;
while (root1 && root2)
{
if (root1->val < root2->val)
{
node->next = root1;
root1 = root1->next;
}
else
{
node->next = root2;
root2 = root2->next;
}
node = node->next;
}
node->next = root1?root1:root2;
return dummy.next;
}
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
ListNode* ret = nullptr;
for (int i = 0; i < lists.size(); i++)
{
ret = mergeTwoList(ret, lists[i]);
}
return ret;
}
};
分治合并:
class Solution {
private:
ListNode* mergeTwoList(ListNode* root1, ListNode* root2)
{
ListNode dummy(0);
ListNode* node = &dummy;
while (root1 && root2)
{
if (root1->val < root2->val)
{
node->next = root1;
root1 = root1->next;
}
else
{
node->next = root2;
root2 = root2->next;
}
node = node->next;
}
node->next = root1?root1:root2;
return dummy.next;
}
ListNode* mergeThem(vector<ListNode*>& lists, int l, int r)
{
if (l == r) // 关键!!索引相同,直接返回一个链表
return lists[l];
if (l > r) // 关键!!左索引大于右索引,返回nullptr
return nullptr;
int m = l + ((r - l) >> 1);
ListNode* left = mergeThem(lists, l, m); // 一直递归左边
ListNode* right = mergeThem(lists, m + 1, r); //一直递归右边
return mergeTwoList(left, right);
}
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
return mergeThem(lists, 0, (int)lists.size() - 1);
}
};
24. 两两交换链表中的节点
问题描述:
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例:
输入:head = [1,2,3,4]
输出:[2,1,4,3]
解析:
- 为了操作方便,创建一个虚拟节点,其next指针指向链表头节点,待两两交换后,其next指针仍然指向链表头节点。
- 步骤:
代码实现:
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode dummy(0);
dummy.next = head;
ListNode* prevNode = &dummy;
ListNode* currNode = head;
while (currNode && currNode->next)
{
// 注意赋值的是next...
prevNode->next = currNode->next;
currNode->next = currNode->next->next;
prevNode->next->next = currNode;
prevNode = currNode;
currNode = currNode->next;
}
return dummy.next;
}
};
61. 旋转链表
问题描述:
给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置。
示例:
输入:head = [1,2,3,4,5], k = 2
输出:[4,5,1,2,3]
解析:
- 首先遍历链表,获得链表节点数nodeCnt。
- 计算step = nodeCnt - k%nodeCnt,得到从最后一个节点移动到目的节点的步数。
- 将链表成环,并移动step步,则当前节点的下一个节点为新的头节点。
- 断环。
代码实现:
尾插法: – 需要不断断链,接链
class Solution {
public:
ListNode* rotateRight(ListNode* head, int k) {
if (head == nullptr || head->next == nullptr)
return head;
ListNode dummy(0);
dummy.next = head;
int n = 1;
while (head->next)
{
head = head->next;
n++;
}
k = n - (k % n);
for (int i = 0; i < k; i++)
{
head->next = dummy.next;
dummy.next = (dummy.next)->next;
head = head->next;
head->next = nullptr;
}
return dummy.next;
}
};
成环法:
class Solution {
public:
ListNode* rotateRight(ListNode* head, int k) {
if (head == nullptr || head->next == nullptr || k == 0)
return head;
ListNode* node = head;
int n = 1;
// 计算节点数
while (node->next)
{
node = node->next;
n++;
}
k = n - (k % n);
// 成环
node->next = head;
// 特定位置断环
for (int i = 0; i < k; i++)
node = node->next;
head = node->next;
node->next = nullptr;
return head;
}
};
82. 删除排序链表中的重复元素 II
问题描述:
存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除链表中所有存在数字重复情况的节点,只保留原始链表中没有重复出现的数字。返回同样按升序排列的结果链表。
示例1:
输入:head = [1,2,3,3,4,4,5]
输出:[1,2,5]
示例2:
输入:head = [1,1,1,2,3]
输出:[2,3]
解析:
- 创建一个哨兵节点,其next指针指向给定链表的头节点。
- 如果下一个结点的值和当前结点的值相等,则循环向后遍历直到找到一个和当前结点值不相等的结点;
- 反之,如果下一个结点的值和当前结点的值不相等,此值即为原始链表中没有重复出现的数字,将其加入到新链表中。
代码实现:
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
ListNode dummy(0);// 新建哨兵节点,注意这里取值不能用head,可能nullptr
dummy.next = head;
ListNode* prev = &dummy;
while (prev->next && prev->next->next)// 注意这里的条件
{
if (prev->next->val == prev->next->next->val)
{
int num = prev->next->val;
while (prev->next && num == prev->next->val)
{
ListNode* temp = prev->next->next;
delete prev->next;
prev->next = temp;
}
}
else // 不相等才需要迭代!
{
prev = prev->next;
}
}
return dummy.next;
}
};
86. 分隔链表
问题描述:
给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有小于 x 的节点都出现在大于或等于 x的节点之前。
你应当保留两个分区中每个节点的初始相对位置。
示例1:
输入:head = [1,4,3,2,5,2], x = 3
输出:[1,2,2,4,3,5]
解析:
分别创建两个链表,遍历给定链表,将小于x的节点和大于等于x的节点分别放到两个链表中,最后将两个链表合并既可。
代码实现:
class Solution {
public:
ListNode* partition(ListNode* head, int x) {
ListNode lHead(0);
ListNode rHead(0);
ListNode* lNode = &lHead;
ListNode* rNode = &rHead;
while (head)
{
if (head->val < x)
{
lNode->next = head;
lNode = lNode->next;
}
else
{
rNode->next = head;
rNode = rNode->next;
}
head = head->next;
}
lNode->next = rHead.next;
rNode->next = nullptr; // 注意这里需要置为nullptr !!
return lHead.next;
}
};
92. 反转链表 II
问题描述:
给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回反转后的链表。
示例1:
输入:head = [1,2,3,4,5], left = 2, right = 4
输出:[1,4,3,2,5]
解析:
- 前插法,过程如下图:
代码实现:
class Solution {
public:
ListNode* reverseBetween(ListNode* head, int left, int right) {
ListNode dummy(0);
dummy.next = head;
ListNode* prev = &dummy;
ListNode* last = nullptr;
// prev 和 last 固定不变,
// 分别表示(left - 1)个节点和待移动位置节点的前一个节点。
// 然后再prev和prev->next中间插入新节点。
for (int i = 1; i < left; i++)
prev = prev->next;
last = prev->next;
for (int i = 0; i < right - left; i++)
{
ListNode* move = last->next;
last->next = move->next;
move->next = prev->next;
prev->next = move;
}
return dummy.next;
}
};
25. K 个一组翻转链表
问题描述:
给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。
k 是一个正整数,它的值小于或等于链表的长度。
如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
示例:
输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]
解析:
模拟…
代码实现:
class Solution {
public:
ListNode* reverseList(ListNode* head)
{
ListNode* prev = nullptr;
ListNode* curr = head;
while (curr)
{
ListNode* temp = curr->next;
curr->next = prev;
prev = curr;
curr = temp;
}
return prev;
}
ListNode* reverseKGroup(ListNode* head, int k) {
if (head == nullptr || head->next == nullptr || k < 2)
return head;
ListNode dummy(0);
dummy.next = head;
ListNode* prev = &dummy;
ListNode* newHead = head;
while (newHead)
{
for (int i = 1; i < k; i++)
{
newHead = newHead->next;
if (newHead == nullptr)
return dummy.next;
}
ListNode* temp = newHead->next;
newHead->next = nullptr;
newHead = prev->next;
prev->next = reverseList(newHead);
newHead->next = temp;
prev = newHead;
newHead = temp;
}
return dummy.next;
}
};
138. 复制带随机指针的链表
问题描述:
给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。
构造这个链表的 深拷贝。 深拷贝应该正好由 n 个全新节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。
示例:
输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
解析:
- 构造新链表
遍历原来的链表并拷贝每一个节点,将拷贝节点放在原来节点的旁边,创造出一个旧节点和新节点交错的链表。如图:
拷贝节点构造新链表:
- 给拷贝节点的random赋值
迭代这个新旧节点交错的链表,并用旧节点的 random 指针去更新对应新节点的 random 指针。比方说, B 的 random 指针指向 A ,意味着 B’ 的 random 指针指向 A’ 。
- 现在 random 指针已经被赋值给正确的节点, next 指针也需要被正确赋值,以便将新的节点正确链接同时将旧节点重新正确链接。
代码实现:
class Solution {
public:
Node* copyRandomList(Node* head) {
if (head == nullptr)
return head;
Node* tempNode = head;
Node* currNode = head;
while (currNode != nullptr)
{
tempNode = currNode->next;
currNode->next = new Node(currNode->val);
currNode->next->next = tempNode;
currNode = tempNode;
}
currNode = head;
while (currNode != nullptr)
{
tempNode = currNode->next;// clone node
if (currNode->random != nullptr)
tempNode->random = currNode->random->next;
currNode = tempNode->next;
}
currNode = head;
Node* copyList = tempNode = head->next;
while (tempNode != nullptr && tempNode->next != nullptr)
{
currNode->next = currNode->next->next;
tempNode->next = tempNode->next->next;
currNode = currNode->next;
tempNode = tempNode->next;
}
currNode->next = nullptr;
return copyList;
}
};
142. 环形链表 II
问题描述:
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。
说明:不允许修改给定的链表。
示例:
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
解析:
- 通过快慢指针是否相遇得知链表中是否有环。
- 非环节点数有a个,环中节点数有b个,快指针走了f步,慢指针走了s步。
那么有如下关系:
1> f = 2s (快指针每次走2步,慢指针每次走1步,快路程是慢路程的2倍)
2> f = s + nb (快指针和慢指针相遇时,快指针比慢指针多走了n个环,也就是nb步)
上面两式相加得出:s = nb (don’t know it, just feel it…)
从head节点走到入环点需要走:a + nb(n>=0)
结合上面推导出的公式,需要走a + s就到入环点了。
也就是说:slow只需要走a步就是入环节点了。
如何知道slow刚好走了a步呢?只需要从head开始,和slow指针一起走,相遇时刚好就是a步了。
代码实现:
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
// slow 和fast一开始要相等!!
ListNode* slow = head;
ListNode* fast = head;
do
{
if (fast == nullptr || fast->next == nullptr)
return nullptr;
slow = slow->next;
fast = fast->next->next;
}
while (slow != fast);
slow = head;
while (slow != fast)
{
fast = fast->next;
slow = slow->next;
}
return slow;
}
};
143. 重排链表
问题描述:
给定一个单链表 L:L0→L1→…→Ln-1→Ln ,
将其重新排列后变为: L0→Ln→L1→Ln-1→L2→Ln-2→…
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例:
1> 给定链表 1->2->3->4, 重新排列为 1->4->2->3.
2> 给定链表 1->2->3->4->5, 重新排列为 1->5->2->4->3.
解析:
- 找到原链表的中点,并断链,分为左右两个链表。
- 将从原链表分开的右链表反转。
- 将左右两个链表按照题目要求合并。
代码实现:
class Solution {
public:
ListNode* reverseList(ListNode* head)
{
ListNode* prev = nullptr;
ListNode* curr = head;
while (curr)
{
ListNode* temp = curr->next;
curr->next = prev;
prev = curr;
curr = temp;
}
return prev;
}
ListNode* findMidNode(ListNode* head)
{
if (head == nullptr)
return nullptr;
ListNode* slow = head;
ListNode* fast = head->next;
while (fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
void reorderList(ListNode* head) {
if (head == nullptr || head->next == nullptr)
return;
ListNode* mid = findMidNode(head);
ListNode* rList = reverseList(mid->next);
mid->next = nullptr;
ListNode dummy(0);
ListNode* node = &dummy;
while (head && rList)
{
node->next = head;
head = head->next;
node = node->next;
node->next = rList;
rList = rList->next;
node = node->next;
}
node->next = head; // 注意这里需要赋值,因为左链表可能多一个节点!!
}
};
147. 对链表进行插入排序
问题描述:
对链表进行插入排序。
插入排序的动画演示如上。从第一个元素开始,该链表可以被认为已经部分排序(用黑色表示)。
每次迭代时,从输入数据中移除一个元素(用红色表示),并原地将其插入到已排好序的链表中。
示例:
输入: 4->2->1->3
输出: 1->2->3->4
解析:
对链表进行插入排序的具体过程如下。
-
首先判断给定的链表是否为空,若为空,则不需要进行排序,直接返回。
-
创建哑节点 dummy,令 dummy.next = head。引入哑节点是为了便于在 head 节点之前插入节点。
-
维护 lastSortedNode为链表的已排序部分的最后一个节点,初始时 lastSortedNode = head。
-
维护 currNode 为待插入的元素,初始时 curr = head.next。
-
比较 lastSortedNode 和 currNode 的节点值。
1)若 lastSortedNode.val <= currNode.val,说明 curr 应该位于 lastSortedNode 之后,将 lastSortedNode 后移一位,currNode 变成新的 lastSortedNode。
2)否则,从链表的头节点开始往后遍历链表中的节点,寻找插入 currNode 的位置。令 prevNode 为插入 currNode 的位置的前一个节点,进行如下操作,完成对 currNode 的插入:
lastSortedNode.next = currNode.next
currNode.next = prevNode.next
prevNode.next = currNode -
令 currNode = lastSortedNode.next,此时 currNode 为下一个待插入的元素。
-
重复第 5 步和第 6 步,直到 currNode 变成空,排序结束。
-
返回 dummy.next,为排序后的链表的头节点。
代码实现:
class Solution {
public:
ListNode* insertionSortList(ListNode* head) {
ListNode dummy(INT_MIN);
dummy.next = head;
ListNode* last = head;
while (last && last->next)
{
if (last->next->val >= last->val)
{
last = last->next;
}
else
{
ListNode* move = last->next;
last->next = last->next->next; // 注意这里是last->next!!
ListNode* node = &dummy;
while (move->val > node->next->val)
node = node->next;
move->next = node->next;
node->next = move;
}
}
return dummy.next;
}
};
160. 相交链表
问题描述:
编写一个程序,找到两个单链表相交的起始节点。
如下面的两个链表:
在节点c1开始相交
示例1:
输入:
intersectVal = 8 -->相交节点的值为 8
listA = [4,1,8,4,5]
listB = [5,0,1,8,4,5],
skipA = 2 -->在 A 中,相交节点前有 2 个节点
skipB = 3 -->在 B 中,相交节点前有 3 个节点。
输出:Reference of the node with value = 8
示例2:
输入:
intersectVal = 0 -->没有相交节点
listA = [2,6,4]
listB = [1,5]
skipA = 3 -->任意值
skipB = 2 -->任意值
输出:null -->这两个链表不相交,因此返回 null。
解析:
- 设置两指针pA和pB分别指向两个给定链表,并遍历。
- 当到达链表末尾时,将指针指向另外一个链表的头节点。当 pA到达链表的尾部时,将它重定位到链表 B 的头节点; 类似的,当 pB到达链表的尾部时,将它重定位到链表 A 的头节点。
- 若在某一时刻 pA和 pB相遇,则 pA/pB为相交结点,否则,两者皆为nullptr。
- 为什么呢?
1> 假设pA和pB走完了全程:
由于指针pA走了个A+B,指针pB走了个B+A,因此两者走过的长度是相等的
A和B结尾部分是相等的,因此A+B和B+A结尾部分也是相等的
因此当pA与pB相遇时,该节点就是相交节点
假设A和B没有交点
2> A和B均是以null结尾,因此A+B和B+A也是以null结尾
因此当pA和pB恰好走完全程后,他们同时到达null,此时他们是相等的
依然满足循环终止条件
代码实现:
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if (headA == nullptr || headB == nullptr)
return nullptr;
ListNode* nodeA = headA;
ListNode* nodeB = headB;
while (nodeA != nodeB)
{
if (nodeA == nullptr) // nodeA == nullptr,而不是nodeA->next == nullptr !!
nodeA = headB;
else
nodeA = nodeA->next;
if (nodeB == nullptr)
nodeB = headA;
else
nodeB = nodeB->next;
}
// 从循环出来的话,有两种情况:
// 1. 两个指针都指向同一个节点
// 2. 两个指针指向nullptr...
return nodeA;
}
};
or:
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if (headA == nullptr || headB == nullptr)
return nullptr;
ListNode* nodeA = headA;
ListNode* nodeB = headB;
while (nodeA || nodeB)
{
if (nodeA == nodeB)
return nodeA;
if (nodeA == nullptr)
nodeA = headB;
else
nodeA = nodeA->next;
if (nodeB == nullptr)
nodeB = headA;
else
nodeB = nodeB->next;
}
return nullptr;
}
};
234. 回文链表
问题描述:
请判断一个链表是否为回文链表。
示例:
1.
输入: 1->2
输出: false
2.
输入: 1->2->2->1
输出: true
3.
输入: 1
输出: true
解析:
- 时间复杂度为O(n),空间复杂度为O(n)的方法是创建一个数组,将链表中的值依次填写进去,然后判断数值的值是否回环。
- 时间复杂度为O(n),空间复杂度为O(1)的方法是找出链表中点,将中点左半边的链表反转,然后判断左右两边链表节点的值是否相同。注意判断完之后再反转回来,还原链表。
代码实现:
时间复杂度为O(n),空间复杂度为O(n)
class Solution {
public:
bool isPalindrome(ListNode* head) {
if (head == nullptr)
return false;
vector<int> iVec;
ListNode* currNode = head;
while (currNode != nullptr)
{
iVec.push_back(currNode->val);
currNode = currNode->next;
}
for (int i = 0, j = iVec.size()-1; i < j; i++, j--)
{
if (iVec[i] != iVec[j])
return false;
}
return true;
}
};
时间复杂度为O(n),空间复杂度为O(1)
class Solution {
public:
ListNode* findMiddleNode (ListNode* head)
{
if (head == nullptr)
return head;
ListNode* slow = head;
ListNode* fast = head->next;
while (fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
ListNode* reverseList(ListNode* head)
{
ListNode* prev = nullptr;
ListNode* curr = head;
while (curr)
{
ListNode* temp = curr->next;
curr->next = prev;
prev = curr;
curr = temp;
}
return prev;
}
bool isPalindrome(ListNode* head) {
if (head == nullptr || head->next == nullptr)
return true;
ListNode* mid = findMiddleNode(head);
ListNode* rHead = reverseList(mid->next);
ListNode* rNode = rHead;
mid->next = nullptr;
bool ret = true;
while (rNode && head)
{
if (rNode->val != head->val)
{
// 不直接return,是为了将反转的链表复原
ret = false;
break;
}
rNode = rNode->next;
head = head->next;
}
// 将反转的链表复原
mid->next = reverseList(rHead);
return ret;
}
};
328. 奇偶链表
问题描述:
给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。
请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。
示例:
输入: 1->2->3->4->5->NULL
输出: 1->3->5->2->4->NULL
解析:
代码实现:
class Solution {
public:
ListNode* oddEvenList(ListNode* head) {
if (head == nullptr)
return head;
// head是奇数链表的头节点,evenHead是偶数链表的头节点
ListNode* oddNode = head;
ListNode* evenNode = head->next;
ListNode* evenHead = evenNode;
while (evenNode && evenNode->next)
{
oddNode->next = evenNode->next;
evenNode->next = evenNode->next->next;
oddNode = oddNode->next;
evenNode = evenNode->next;
}
oddNode->next = evenHead;
return head;
}
};
2. 两数相加
问题描述:
给你两个非空的链表,表示两个非负的整数。它们每位数字都是按照逆序的方式存储的,并且每个节点只能存储一位数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例:
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.
解析:
直接模拟计算既可,注意进位
代码实现:
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
int res = 0;
ListNode result(0);// 哨兵节点
ListNode* node = &result;
while (l1 || l2 || res)// 注意这里的条件
{
int a = l1?l1->val:0;
int b = l2?l2->val:0;
res = a + b + res;
node->next = new ListNode(res%10);
res = res / 10;
l1 = l1?l1->next:nullptr;
l2 = l2?l2->next:nullptr;
node = node->next;
}
return result.next;
}
};
445. 两数相加 II
问题描述:
给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。
你可以假设除了数字 0 之外,这两个数字都不会以零开头。
示例:
输入:(7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 8 -> 0 -> 7
解析:
- 逆序类型的问题,首先想到用栈…
- 如果要求空间复杂度为1的话,那么就需要反转链表,再计算。计算完成之后,记得将链表反转回来!
代码实现:
使用栈辅助:
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
stack<int> s1, s2;
int flag = 0;
ListNode *node = nullptr;
while (l1 != nullptr)
{
s1.push(l1->val);
l1 = l1->next;
}
while (l2 != nullptr)
{
s2.push(l2->val);
l2 = l2->next;
}
while (!s1.empty() || !s2.empty() || flag)
{
int n1 = 0;
int n2 = 0;
if (!s1.empty())
{
n1 = s1.top();
s1.pop();
}
if (!s2.empty())
{
n2 = s2.top();
s2.pop();
}
int num = n1 + n2 + flag;
flag = num/10;
num %= 10;
// 这里有个技巧,让新创建的节点指向之前的链表,可以避免一次翻转!!
ListNode* temp = new ListNode(num);
temp->next = node;
node = temp;
}
return node;
}
};
不使用辅助空间:
class Solution {
public:
ListNode* reverseList(ListNode* head)
{
ListNode* prev = nullptr;
ListNode* curr = head;
while (curr)
{
ListNode* temp = curr->next;
curr->next = prev;
prev = curr;
curr = temp;
}
return prev;
}
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
// 先翻转链表,使低位在前!!
ListNode* node1 = l1 = reverseList(l1);
ListNode* node2 = l2 = reverseList(l2);
ListNode* result = nullptr;
int flag = 0;
while (node1 || node2 || flag)
{
int n1 = 0;
if (node1)
{
n1 = node1->val;
node1 = node1->next;
}
int n2 = 0;
if (node2)
{
n2 = node2->val;
node2 = node2->next;
}
ListNode* temp = new ListNode((n1 + n2 + flag) % 10);
flag = (n1 + n2 + flag) / 10;
temp->next = result;
result = temp;
}
// 运算结束后,需要将两个链表翻转回来!!
reverseList(l1);
reverseList(l2);
return result;
}
};
148. 排序链表
问题描述:
给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。
示例:
输入:head = [4,2,1,3]
输出:[1,2,3,4]
解析:
- 使用归并排序。
- 该问题转化为递归+找链表中点+合并链表。
代码实现:
class Solution {
private:
ListNode* findMiddleNode(ListNode* head)
{
if (head == nullptr || head->next == nullptr)
return head;
ListNode* slow = head;
ListNode* fast = head->next;
while (fast != nullptr && fast->next != nullptr)
{
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
ListNode* mergeList(ListNode* left, ListNode* right)
{
ListNode dummy;
ListNode* iNode = &dummy;
while (left != nullptr && right != nullptr)
{
if (left->val < right->val)
{
iNode->next = left;
left = left->next;
}
else
{
iNode->next = right;
right = right->next;
}
iNode = iNode->next;
}
iNode->next = left == nullptr?right:left;
return dummy.next;
}
public:
ListNode* sortList(ListNode* head) {
if (head == nullptr || head->next == nullptr)
return head;
// 找中点
ListNode* midNode = findMiddleNode(head);
ListNode* rightList = midNode->next;
midNode->next = nullptr; // 断链!!
// 递归
ListNode* left = sortList(head);
ListNode* right = sortList(rightList);
// 合并链表
return mergeList(left, right);
}
};