下面的技巧通过一些后面的例题可以较好的理解熟练。
常用技巧
下面的题目难度不算太高,就用文字描述的形式解释思路、原理。
思路
解法:即前面介绍过的快慢双指针。
解法原理:快指针每次移动两步,而慢指针每次移动一步。如果链表中存在环,那么快指针相对于慢指针的速度差是一定的,因此快指针会逐渐靠近慢指针,最终追上它们。如果链表中不存在环,那么快指针会先到达链表的末尾。
代码
bool hasCycle(ListNode *head) {
// 快慢指针
ListNode* slow = head, *fast = head;
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if(slow == fast) return true;
}
return false;
}
思路
原理分析:我们设链表头节点到环入口节点的距离为a,环入口节点到相遇节点的距离为b,相遇节点到环入口节点的距离为c,则有以下关系:快指针走过的距离是慢指针的两倍,即2(a+b)=a+b+c+b,化简得到a=c。因此,在快慢指针相遇之后,让快指针重新指向头节点,然后快慢指针同时以相同的速度向前移动,当它们再次相遇时所在的节点就是环的入口节点。
代码
ListNode *detectCycle(ListNode *head) {
if(head == nullptr || head->next == nullptr) return nullptr;
ListNode* slow = head, *fast = head;
// 先判断是否有环
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if(slow == fast)
{
ListNode* meet = slow; // 相遇位置设为meet
while(meet != head)
{
head = head->next;
meet = meet->next;
}
return meet;
}
}
return nullptr;
}
思路
代码
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* PNode = new ListNode(0); // 虚拟头节点
PNode->next = head;
ListNode* slow = PNode, *fast = PNode;
// fast先走n+1步
for(int i = 0; i < n+1; ++i)
fast = fast->next;
// 两指针同步走
while(fast)
{
slow = slow->next;
fast = fast->next;
}
// 此时slow就是倒数第n-1节点
slow->next = slow->next->next;
return PNode->next;
}
思路
代码
ListNode* reverseList(ListNode* head) {
ListNode* newhead = nullptr; // 虚拟头指针
ListNode* cur = head;
// 利用头插翻转
while(cur)
{
ListNode* next = cur->next;
cur->next = newhead;
newhead = cur;
cur = next;
}
return newhead;
}
思路
如上图所示:
代码
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
int t = 0; // 记录相加和
ListNode* newhead = new ListNode(0); // 虚拟头节点
ListNode* cur1 = l1, *cur2 = l2; // 用于遍历链表
ListNode* prev = newhead; // 尾指针
while(cur1 || cur2 || t)
{
// 相加并移动指针
if(cur1){
t += cur1->val;
cur1 = cur1->next;
}
if(cur2){
t += cur2->val;
cur2 = cur2->next;
}
// 更新结果
prev->next = new ListNode(t % 10);
prev = prev->next;
t /= 10;
}
// 释放空间
prev = newhead->next;
delete newhead;
return prev;
}
思路
这里我们提供两种解法:① 递归 ② 循环(模拟过程)
解法一:递归
代码
ListNode* swapPairs(ListNode* head) {
if(head == nullptr || head->next == nullptr)
return head;
// 递归
auto tmp = swapPairs(head->next->next); // 翻转后的前一位节点
auto ret = head->next; // 新头节点
head->next->next = head; // 更改指针指向
head->next = tmp;
return ret;
}
解法二:循环
代码
ListNode* swapPairs(ListNode* head) {
if(head == nullptr || head->next == nullptr)
return head;
ListNode* prev = new ListNode(0);
ListNode* cur = head;
ListNode* next = cur->next, *nnext = next->next;
head = next; // 一轮交换后,head会到第二位,修正位置
while(cur && next) // cur与next都不为空时才进行交换
{
// 两两交换操作
prev->next = next;
next->next = cur;
cur->next = nnext;
// 移动指针
prev = cur;
cur = nnext;
next = cur ? cur->next : nullptr;
nnext = next ? next->next : nullptr;
}
return head;
}
思路
代码
void reorderList(ListNode* head) {
if(!head || !head->next || !head->next->next) return;
// 1. 找到链表中点
ListNode* slow = head, *fast = head;
while(fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
}
// 2. 翻转中点后的节点
ListNode* newhead = nullptr;
ListNode* cur = slow->next;
while(cur)
{
ListNode* next = cur->next;
cur->next = newhead;
newhead = cur;
cur = next;
}
slow->next = nullptr; // 切断前半部分与后半部分的连接
// 3. 合并链表(123 与 54 -> 1 5 2 4 3)
ListNode* cur1 = head, *cur2 = newhead;
while(cur1 && cur2) {
ListNode* next1 = cur1->next, *next2 = cur2->next;
cur1->next = cur2;
cur2->next = next1;
cur1 = next1;
cur2 = next2;
}
}
思路
代码
class Solution {
public:
struct cmp // 自定义比较
{
bool operator()(const ListNode* l1, const ListNode* l2){
return l1->val > l2->val;
}
};
ListNode* mergeKLists(vector<ListNode*>& lists) {
priority_queue<ListNode*, vector<ListNode*>, cmp> heap; // 创建小根堆
for(auto l : lists)
if(l) heap.push(l); // 将所有插入到堆中
// 合并链表
ListNode *head = new ListNode(0);
ListNode *prev = head;
while(!heap.empty()) // 堆有元素时,进行操作
{
ListNode* tmp = heap.top();
heap.pop();
prev->next = tmp; // 更新链表链接关系和prev
prev = tmp;
if(tmp->next) heap.push(tmp->next);
}
prev = head->next;
delete head;
return prev;
}
};
思路
代码
class Solution {
public:
ListNode* reverseKGroup(ListNode* head, int k) {
ListNode* cur = head;
int n = 0; // 记录待翻转链表个数
while(cur)
{
cur = cur->next;
n++;
}
n /= k;
ListNode* newhead = new ListNode(0);
ListNode* prev = newhead;
cur = head;
// 进行链表的分组旋转 n组 每组k个节点
for(int i = 0; i < n; ++i)
{
ListNode* tmp = cur; // tmp记录每次待翻转链表的头节点
for(int j = 0; j < k; ++j)
{
ListNode* next = cur->next;
cur->next = prev->next;
prev->next = cur;
cur = next;
}
prev = tmp; //
}
// 剩余不必翻转的,添加到链表中
prev->next = cur;
// 删除newhead释放空间
cur = newhead->next;
delete newhead;
return cur;
}
};