基础题:
1. 迭代法: 通过哨兵位头节点头插(不用考虑特殊情况) - 过于简单, 不予分析
2. 递归: 找出相同规模的子问题, 设计好递归函数(参数+返回值), 考虑好递归出口
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* dummy = new ListNode();
while (head) {
ListNode* nxt = head->next;
head->next = dummy->next;
dummy->next = head;
head = nxt;
}
return dummy->next;
}
};
/* recursion */
/**
* 1 -> 2 -> 3 -> 4 -> 5
* 处理到1时, 可将问题拆分为逆置1后面的链表, 返回逆置后的头节点
* 处理到2时, 可将问题拆分为逆置3后面的链表, 返回逆置后的头节点
* ...
* 此处需要注意的是, 递归返回时需要将当前节点链接到逆置链表上, 往下递归前需要保存相关节点
* 递归返回后将当前节点的后驱节点置空
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (!head || !head->next) return head;
ListNode* tail = head->next;
ListNode* res = reverseList(tail);
tail->next = head;
head->next = nullptr;
return res;
}
};
题目解析:
342 + 465 = 807
存储方式: 倒着存
342: 2 -> 4 -> 3
465: 5 -> 6 -> 4
807: 7 -> 0 -> 8
思路: 一个哨兵节点, 指向返回节点; 一个tail指针, 指向处理好的最后一个节点
注意点: 满10进1, 和一般算数一样, 最后返回一个指向第一个节点的指针
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* dummy = new ListNode();
ListNode* tail = dummy;
int carry = 0;
while (l1 || l2 || carry) {
int val = carry;
if (l1) {
val += l1->val;
l1 = l1->next;
}
if (l2) {
val += l2->val;
l2 = l2->next;
}
ListNode* new_node = new ListNode(val % 10);
carry = val / 10;
tail->next = new_node;
tail = new_node;
}
return dummy->next;
}
};
题目解析:
7243 + 564 = 7807
存储方式: 顺着存放
7243: 7 -> 2 -> 4 -> 3
564: 5 -> 6 -> 4
7807: 7 -> 8 -> 0 -> 7
思路: 低位到高位进位计算
对输入链表进行逆置(哨兵位头插)
数位计算时也使用哨兵位头插
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
auto reverseList = [&](ListNode* hd) -> ListNode* {
ListNode* guard = new ListNode();
while (hd) {
ListNode* nxt = hd->next;
hd->next = guard->next;
guard->next = hd;
hd = nxt;
}
return guard->next;
};
l1 = reverseList(l1);
l2 = reverseList(l2);
ListNode* dummy = new ListNode();
int carry = 0;
while (l1 || l2 || carry) {
int val = carry;
if (l1) {
val += l1->val;
l1 = l1->next;
}
if (l2) {
val += l2->val;
l2 = l2->next;
}
ListNode* new_node = new ListNode(val % 10);
new_node->next = dummy->next;
dummy->next = new_node;
carry = val / 10;
}
return dummy->next;
}
};
题目解析:
一次以两个节点为单位进行操作(如果有两个节点的话), 并注意保存第二个节点指向的下一节点
分别尾插第二个和第一个节点
注意处理不足两个节点的情况
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* dummy = new ListNode();
ListNode* first = head, *tail = dummy;
while (first) {
ListNode* second = first->next;
ListNode* nxt = (second == nullptr) ? nullptr : second->next;
if (second) {
tail->next = second;
tail = second;
}
tail->next = first;
tail = first;
first = nxt;
}
tail->next = nullptr;
return dummy->next;
}
};
经典题: 快慢指针
处理化两个指针, slow, fast
slow一次走一步, fast一次走两步 如果有环, fast, slow一定能在环中相遇
注意处理一下特例
class Solution {
public:
bool hasCycle(ListNode *head) {
if (!head || !head->next) {
return false;
}
ListNode* slow = head, *fast = head->next;
while (fast && slow != fast) {
slow = slow->next;
fast = fast->next;
if (fast) fast = fast->next;
}
return slow == fast;
}
};
简单题: 利用哨兵位头节点进行尾插
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
ListNode* dummy = new ListNode();
ListNode* tail = dummy;
while (list1 && list2) {
ListNode* nxt = nullptr;
if (list1->val <= list2->val) {
tail->next = list1;
tail = list1;
list1 = list1->next;
} else {
tail->next = list2;
tail = list2;
list2 = list2->next;
}
}
if (list1) tail->next = list1;
else tail->next = list2;
return dummy->next;
}
};
21合并两个有序链表的变形
本质上考察的还是合并两个链表, 每一步合并两个, 形成的新的链表和其他链表继续合并
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
int n = lists.size();
auto mergeTwoLists = [&](ListNode* l1, ListNode* l2) -> ListNode* {
ListNode* dummy = new ListNode();
ListNode* tail = dummy;
while (l1 && l2) {
if (l1->val <= l2->val) {
tail->next = l1;
tail = l1;
l1 = l1->next;
} else {
tail->next = l2;
tail = l2;
l2 = l2->next;
}
}
if (l1) tail->next = l1;
else tail->next = l2;
ListNode* res = dummy->next;
delete dummy;
return res;
};
ListNode* prev = nullptr;
for (int i = 0; i < n; ++i) {
prev = mergeTwoLists(prev, lists[i]);
}
return prev;
}
};
首先需要了解的是插入排序和打扑克牌中的摸牌 + 整理一样
链表中不同的是, 只能从该节点往后找,不能往前找
大体思路是: 利用插入排序思想, 排降序; 这样每次正好可以从头节点开始找
最后利用哨兵位头节点进行头插, 实现链表的逆序, 从而达到将降序转化为升序
class Solution {
public:
ListNode* insertionSortList(ListNode* head) {
ListNode* dummy = new ListNode();
while (head) {
ListNode* nxt = head->next;
ListNode* start = dummy->next, *prev = dummy;
int val = head->val;
while (start && val < start->val) {
prev = start;
start = start->next;
}
prev->next = head;
head->next = start;
head = nxt;
}
head = dummy->next;
dummy->next = nullptr;
while (head) {
ListNode* nxt = head->next;
head->next = dummy->next;
dummy->next = head;
head = nxt;
}
return dummy->next;
}
};