LeetBook链表专题题解
作者:互联网
链表
单链表
设计单链表
注意:中间删除结点和中间插入结点都不难,但是任意插入或删除结点的时候需要考虑到头插或头删。要注意的是如果头结点改变了,那head结点就要改变,所以当插入结点或者删除结点的时候需要特别小心头结点的插入删除。
class MyLinkedList {
private:
struct ListNode
{
int val;
ListNode* next;
ListNode(int v = 0) :val(v), next(nullptr) {}
};
int size;// 链表的长度
ListNode* head;// 头指针
public:
/** Initialize your data structure here. */
MyLinkedList() :size(0), head(nullptr) {
}
/** Get the value of the index-th node in the linked list. If the index is invalid, return -1. */
int get(int index) {
if (index + 1 > size)
return -1;
ListNode* cur = head;
while (index--)
{
cur = cur->next;
}
return cur->val;
}
/** Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. */
void addAtHead(int val) {
addAtIndex(0, val);
}
/** Append a node of value val to the last element of the linked list. */
void addAtTail(int val) {
addAtIndex(size, val);
}
/** Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. */
void addAtIndex(int index, int val) {
ListNode* newNode = new ListNode(val);
// 头插
if (index <= 0)
{
newNode->next = head;// newNode指向head,然后newNode变成head
head = newNode;
size++;
return;
}
// 索引大于链表的长度
if (index > size)
return;
// 中间插入结点
ListNode* prev = head;
while (--index)// 找到index前一个结点
{
prev = prev->next;
}
newNode->next = prev->next;
prev->next = newNode;
size++;// 链表长度+1
}
/** Delete the index-th node in the linked list, if the index is valid. */
void deleteAtIndex(int index) {
if (index < 0 || index >= size)// 没有索引会<0或者>=size
return;
if (index == 0)// 删除头结点
{
ListNode* delNode = head;
head = head->next;
delete delNode;
}
else// 删除中间结点
{
ListNode* prev = head;
while (-- index)// 找到index前一个结点
{
prev = prev->next;
}
ListNode* delNode = prev->next;
prev->next = delNode->next;
delete delNode;
}
size --;// 链表长度-1
}
};
链表中的双指针
环形链表
(快慢指针)
注意一点,因为fast和slow指针初始化的时候都需要初始化为head,所以就可以用fast!=slow
来作为while循环的条件
class Solution {
public:
bool hasCycle(ListNode *head) {
if (head == nullptr || head->next == nullptr) return false;
ListNode* fast = head, *slow = head;
// 方法1
while (fast && fast->next)
{
fast = fast->next->next;// fast每次走两步
slow = slow->next;// 慢指针每次走一步
if (fast == slow)
return true;
}
return false;
// 方法2
// while (fast != nullptr)
// {
// fast = fast->next;
// if (fast != nullptr)
// fast = fast->next;
// slow = slow->next;
// if (fast == slow)
// return true;
// }
// return false;
}
};
环形链表Ⅱ
(快慢指针)
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
// 判断是否有环
ListNode* fast = head, *slow = head;
ListNode* meetNode = nullptr;
while (fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
if (fast == slow)
{
meetNode = fast;
break;
}
}
// 如果链表中没有环就返回nullptr
if (meetNode == nullptr)
return nullptr;
// 让head和meetNode同时走知道相遇,相遇的位置就是入环口
while (head != meetNode)
{
head = head->next;
meetNode = meetNode->next;
}
return meetNode;
}
};
相交链表
(指针交错遍历1)
因为相交点之后的结点数是相同的,但是相交点之前的结点数不同,如果让两个指针走的结点数相同的话就可以同时到达相交点。所以让curA指针走完curA链表之后再去走curB链表,让curB指针走完curB链表之后,去走curA链表,这样就可以弥补相交点之前结点数可能不同,而导致走的结点数不同。
概括:走到尽头见不到你,于是走过你来时的路,等到相遇时才发现,你也走过我来时的路
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode* curA = headA, *curB = headB;
while (curA != curB)
{
curA = curA->next;
curB = curB->next;
// 如果同时走到nullptr就说明链表没有相交点
if (curA == nullptr && curB == nullptr) return nullptr;
// 如果链表走完一条链表就再去走另一条链表
if (curA == nullptr) curA = headB;
if (curB == nullptr) curB = headA;
}
return curA;
}
};
(指针交错遍历2)
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode* curA = headA, *curB = headB;
while (curA != curB)
{
curA = curA ? curA->next : headB;
curB = curB ? curB->next : headA;
}
return curA;
}
};
(链表相联找入环口)
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
// 找到链表A的尾巴
ListNode* cur = headA;
while (cur->next != nullptr)
cur = cur->next;
// 将链表A连接到链表B上
cur->next = headB;
// 找入环口
ListNode* fast = headA, *slow = headA;
while (fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
if (slow == fast)
{
ListNode* prev = headA;
while (prev != slow)
{
prev = prev->next;
slow = slow->next;
}
// 恢复链表
cur->next = nullptr;
return prev;
}
}
// 恢复链表
cur->next = nullptr;
return nullptr;
}
};
(补齐链表差数)
/**
* 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) {
int cntA = 0, cntB = 0;
ListNode* curA = headA, *curB = headB;
// 统计链表的长度
while (curA)
{
curA = curA->next;
cntA ++;
}
while (curB)
{
curB = curB->next;
cntB ++;
}
// greater是长的链表,less是短的链表
ListNode* greater = cntA > cntB ? headA : headB;
ListNode* less = greater == headA ? headB : headA;
// 让长链表先走差数步,补齐差数
int n = abs(cntA - cntB);
for (int i = 0; i < n; i ++)
greater = greater->next;
// 长链表和短链表同时走,直到相遇
while (greater != less)
{
greater = greater->next;
less = less->next;
}
return greater;
}
};
删除链表的倒数第N个结点
(快慢指针)
三个小技巧:
1)要找到倒数第N个结点可以fast指针先走n步这样再让slow指针从头结点开始和fast同时走,这样当fast到尾,slow指针就到倒数第n个结点,俩表中通常都会利用步数差和相对位置来简化算法。
2)设置虚拟头街点所以可以想删除中间节点一样删除头结点
3)slow指针要走到删除结点的上一个位置,从dummy开始走可以走到倒数第n+1个结点,如果从head开始走就只能到到倒数第n个结点
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* fast = head;
// 让fast指针先走n步
while (n --)
fast = fast->next;
// 将fast当做链表的尾和slow指针一起走
// 当fast指针到链表尾的时候,slow指针到了倒数第n个位置
// 因为头结点可能会删除,所以设置一个虚拟头结点
ListNode* dummy = new ListNode(-1);
dummy->next = head;
// 让slow从dummy开始走正好可以少走一步,这样就可以到删除结点的上一个位置
ListNode* slow = dummy;
while (fast != nullptr)
{
fast = fast->next;
slow = slow->next;
}
// 因为有虚拟头结点,所以删除结点可以不用考虑头结点
ListNode* delNode = slow->next;
slow->next = delNode->next;
delete delNode;
ListNode* newHead = dummy->next;
delete dummy;
return newHead;
}
};
经典问题
反转链表
题目要求:反转一个链表
(头插法)
class Solution {
public:
ListNode* reverseList(ListNode* head) {
// 链表的新的头结点
ListNode* newHead = nullptr;
// 用cur遍历原链表,去取出头结点头插
ListNode* cur = head;
while (cur != nullptr)
{
// 保留下一个结点
ListNode* next = cur->next;
// 头插结点
cur->next = newHead;
// 更新新的头结点
newHead = cur;
// 遍历下一个链表结点
cur = next;
}
return newHead;
}
};
(递归)
要注意的是newHead
在递归返回的时候是不变的,在找到反转链表的头以后,就会一直返回它的头结点。
一般情况下,head == nullptr
不会触发,因为遍历链表的时候到head->next == nullptr
就停止了,但是如果链表为空就需要用head == nullptr
特判一下。
其实写这个递归的时候分两个部分
1)第一是将除了头结点的原链表反转然后返回反转链表的头结点
2)第二是处理头结点,但要清楚这时候的头结点其实不仅是头结点,而是递归的时候的所有结点,而这些结点的处理方式都和当前的头结点的处理方式是一样的。所以只要将头结点的下一个节点的next指向头结点其实就已经达到了反转的效果,但是刚刚说了因为头结点的处理是所有结点的处理情况,所以还要将头结点本身指向nullptr
,虽然中间节点的指向nullptr
没有意义,但是这样最后整个链表的结尾可以指向nullptr
,否则最后会因为链表的尾结点不指向nullptr
而导致链表的循环会死循环
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (head == nullptr || head->next == nullptr)
return head;
// 将除了头结点的链表反转,并将后面链表的头结点返回
ListNode* newHead = reverseList(head->next);
// 处理头结点
head->next->next = head;
head->next = nullptr;
// 返回新链表的头结点
return newHead;
}
};
移除链表元素
题目要求:移除链表中值等于val的结点
(设置虚拟头结点1)
因为移除的结点是头结点所以设置虚拟头结点可以方便一点,这样删除头结点的步骤就和删除中间节点的处理方式是一样的。
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
// 设置虚拟头结点
ListNode* dummy = new ListNode(-1);
dummy->next = head;
ListNode* cur = dummy;
// 因为每一次循环都会往下走一次,所以要用cur!=nullptr确保存在cur
// 同时还要保证cur有下一个结点
while (cur != nullptr && cur->next != nullptr)
{
// 如果还有下一个结点并且下一个结点的值等于val就删除该节点的下一个结点
// 注意这里是while循环删除,因为可能下面连续几个结点的值都等于val
// 如果用if的话,在跳出循环后,cur=cur->next这样就不能删除cur这个节点了
while (cur->next != nullptr && cur->next->val == val){
ListNode* delNode = cur->next;
cur->next = delNode->next;
delete delNode;
}
cur = cur->next;
}
ListNode* newHead = dummy->next;
delete dummy;
return newHead;
}
};
(*设置虚拟头结点2+双指针)
这种方法更加好理解一点,用两个指针prev
和cur
,cur
用来在前面判断cur->val
是否等于val
,如果是prev
就可以跳过cur
结点直接指向cur
的下一个结点,然后cur
往下走一步,直到cur
的值不等于val
的时候,让prev = cur
然后这样循环下去。当cur
为空的时候停下。但要注意的是因为中间可能要删除掉结点cur
为了在后面可以让链表循环下去索要在进入循环的时候要保存一下cur
的下一个结点确保链表可以循环下去。
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode* dummy = new ListNode(-1);
dummy->next = head;
ListNode* prev = dummy, *cur = head;
while (cur != nullptr)
{
ListNode* next = cur->next;
// 如果cur的值为val就删除cur否则就让prev=cur进入下一次删除过程
if (cur->val == val)
{
prev->next = cur->next;
delete cur;
}
else
prev = cur;
cur = next;
}
prev = dummy->next;
delete dummy;
return prev;
}
};
(设置虚拟头结点3+单指针)
上面使用双指针实现的删除结点,这里少用了一个指针,其实都是一样的逻辑
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode* dummy = new ListNode(-1);
dummy->next = head;
ListNode* cur = dummy;
// 不用cur!=nullptr因为只有当cur->next存在的时候,cur=cur->next所以cur一定会存在
while (cur->next != nullptr)
{
if (cur->next->val == val)
{
ListNode* delNode = cur->next;
cur->next = cur->next->next;
delete delNode;
}
else
cur = cur->next;
}
cur = dummy->next;
delete dummy;
return cur;
}
};
(递归)
class Solution {
public:
// 返回删除过val节点后的链表的头结点
ListNode* removeElements(ListNode* head, int val) {
if (head == nullptr)
return head;
// head后面的链表已经删除过值等于val的结点了
head->next = removeElements(head->next, val);
// 处理头结点,如果head->val==val就返回head->next,否则返回head
if (head->val == val)
{
ListNode* next = head->next;
delete head;
return next;
}
else
return head;
}
};
奇偶链表
题目要求:将奇数位上的结点放在偶数位结点的前面
(尾插法创建两个链表+三指针)
class Solution {
public:
ListNode* oddEvenList(ListNode* head) {
// 如果结点数<=2个就直接返回链表本身
if (!head || !head->next || !head->next->next)
return head;
ListNode* cur = head->next->next;
// 创建出奇偶链表
ListNode* odd = head, *even = head->next;
ListNode* evenHead = even;
// 判断当前应该插入到哪个链表中
bool flag = true;
while (cur != nullptr)
{
// 用tmp表示当前应该插入到哪个链表中
ListNode* tmp = nullptr;
if (flag)
tmp = odd;
else
tmp = even;
// 保留cur下一个结点的备份
ListNode* next = cur->next;
// 让cur接在链表的后面
cur->next = nullptr;
tmp->next = cur;
// 让链表的头结点往后移动一位
if (flag)
odd = odd->next;
else
even = even->next;
// cur遍历原链表中结点
cur = next;
// 奇偶交换
flag = !flag;
}
// 让奇链表的后面街上偶链表
odd->next = evenHead;
// 让偶链表指向空
even->next = nullptr;
return head;
}
};
(创建奇偶链表+双指针)
这种方法好理解一点
class Solution {
public:
ListNode* oddEvenList(ListNode* head) {
ListNode* evenHead = new ListNode(-1);
ListNode* oddHead = new ListNode(-1);
ListNode* even = evenHead, *odd = oddHead;
int count = 1;// 用count判断奇偶链表
while (head != nullptr)
{
if ((count & 1) == 1)
{
odd->next = head;
odd = odd->next;
}
else
{
even->next = head;
even = even->next;
}
count ++;
head = head->next;
}
// 奇链表后面接上偶链表,偶链表尾指向nullptr
odd->next = evenHead->next;
even->next = nullptr;
// 删除结点防止内存泄漏
ListNode* newHead = oddHead->next;
ListNode* del = oddHead;
delete del;
del = evenHead;
delete del;
return newHead;
}
};
用双指针的这种方法这解决分割链表的问题,这题是将奇数位的结点和偶数位上的结点分割开来,利用这种方法还可以解决像:值<=val的结点放在值>val的结点前面
(*奇偶交错+单指针)
比较巧妙适用于这题
class Solution {
public:
ListNode* oddEvenList(ListNode* head) {
if (head == nullptr) return head;
ListNode* odd = head, *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;
}
};
回文链表
题目要求:判断链表是否为回文链表
(找中间结点+反转链表)
先找中间结点然后就可以将整个链表分成前后两个链表,将后半个链表反转一下,最后将两个链表比较,如果所有的结点的值都相同就是回文链表,否则就不是回文链表。(如果链表的结点个数是奇数个也没关系,比较链表的时候只要比较短链表的个数次就可以了)
class Solution {
public:
bool isPalindrome(ListNode* head) {
if (head == nullptr || head->next == nullptr) return true;
ListNode* fast = head, *slow = head;
while (fast != nullptr && fast->next != nullptr)
{
fast = fast->next->next;
slow = slow->next;
}
// 反转后半部分的链表
ListNode* cur = slow, *newHead = nullptr;
while (cur != nullptr)
{
ListNode* next = cur->next;
cur->next = newHead;
newHead = cur;
cur = next;
}
// 比较前后两个链表
while (newHead && head)
{
if (newHead->val != head->val)
return false;
newHead = newHead->next;
head = head->next;
}
return true;
}
};
找中间节点O(N)+反转链表O(N)+比较两个链表O(N),所以时间复杂度为O(N)
(用数组保存链表的值)
class Solution {
public:
bool isPalindrome(ListNode* head) {
vector<int> ans;
ListNode* cur = head;
// 将链表中结点的值放到数组中
while (cur != nullptr)
{
ans.push_back(cur->val);
cur = cur->next;
}
// 用双指针比较数组中的值是否回文
int i = 0, j = ans.size() - 1;
while (i < j)
{
if (ans[i++] != ans[j--])
return false;
}
return true;
}
};
双链表
在设计双链表的时候比单链表设计麻烦的一点是双链表不仅头结点需要特殊判断而且尾结点也需要特殊判断,因为单链表头结点的移动关系着head的移动所以头结点的增删需要特判但是尾结点并没有影响,但是双链表不仅头结点会影响head的移动而且因为双链表有两个指针一个prev
一个next
所以如果删除的是尾结点的话,就不能让尾结点后面的nullptr
再王前指向其他节点了,否则这时候就会报nullptr
解引用的错误。
设计链表
(1只用一个head)
class MyLinkedList {
private:
struct ListNode
{
int val;
ListNode* prev, *next;
ListNode(int v):val(v), prev(nullptr), next(nullptr) {}
};
int size;
ListNode* head;
public:
/** Initialize your data structure here. */
MyLinkedList() {
size = 0;
head = nullptr;
}
/** Get the value of the index-th node in the linked list. If the index is invalid, return -1. */
int get(int index) {
if (index + 1 > size)
return -1;
ListNode* cur = head;
while (index --)
{
cur = cur->next;
}
return cur->val;
}
/** Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. */
void addAtHead(int val) {
addAtIndex(0, val);
}
/** Append a node of value val to the last element of the linked list. */
void addAtTail(int val) {
addAtIndex(size, val);
}
/** Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. */
void addAtIndex(int index, int val) {
// index大于链表长度
if (index > size)
return ;
ListNode* newNode = new ListNode(val);
// 在第一个位置插入结点
if (index <= 0)
{
newNode->next = head;
newNode->prev = nullptr;
// 如果链表不是空的话(链表中有结点而不是空)就让原来的head指向新的结点
if (head != nullptr)
head->prev = newNode;
head = newNode;
}
else if (index < size)// 在中间位置插入结点
{
ListNode* cur = head;
while (index --)
cur = cur->next;
newNode->next = cur;
newNode->prev = cur->prev;
cur->prev->next = newNode;
cur->prev = newNode;
}
else if (index == size)// 在末尾位置插入结点
{
ListNode* cur = head;
while (cur->next != nullptr)
cur = cur->next;
newNode->next = nullptr;
newNode->prev = cur;
cur->next = newNode;
}
size ++;
}
/** Delete the index-th node in the linked list, if the index is valid. */
void deleteAtIndex(int index) {
// 如果删除第一个前面的结点或者超过size的长度就返回void
if (index < 0 || index >= size) return ;
// 删除头结点
if (index == 0)
{
// 重点注意当头结点被删除的时候要让head移动到当前newNode的位置
// 而且还有一种特殊的情况,如果链表中只有一个结点的话,那么head=nullptr就可以了
// 不用newNode->prev=nullptr,这时还要这样的话就会nullptr解引用报错
ListNode* delNode = head;
if (size == 1)
{
head = nullptr;
delete delNode;
}
else
{
delNode->next->prev = nullptr;
head = delNode->next;
delete delNode;
}
}
else if (index < size - 1)// 删除中间结点
{
ListNode* cur = head;
while (index --)
cur = cur->next;
ListNode* prevNode = cur->prev;
ListNode* nextNode = cur->next;
prevNode->next = nextNode;
nextNode->prev = prevNode;
delete cur;
}
else if (index == size - 1)// 删除尾结点
{
ListNode* cur = head;
while (cur->next != nullptr)
cur = cur->next;
cur->prev->next = nullptr;
delete cur;
}
size --;
}
};
用上面的代码可知双链表的删除要考虑的情况有很多,要特别小心一些细节问题
(2用head+tail)
设置虚拟头结点和尾结点以后就可以不用特殊考虑头结点和尾结点的插入和删除了,因为这时头尾和中间结点的增删都是一样的了。
和单链表一样在删除的时候需要特别的小心头结点和尾结点的删除的细节,这题难就难在需要分很多中情况需要讨论:
class MyLinkedList {
private:
struct ListNode
{
int val;
ListNode* prev, * next;
ListNode(int v):val(v), prev(nullptr), next(nullptr) {}
};
ListNode* head, *tail;
int size;
public:
/** Initialize your data structure here. */
MyLinkedList():size(0), head(nullptr), tail(nullptr) {}
/** Get the value of the index-th node in the linked list. If the index is invalid, return -1. */
int get(int index) {
int i = 0;
ListNode* cur = head;
while (cur && i < index)
{
cur = cur->next;
i ++;
}
if (cur) return cur->val;
else return -1;
}
/** Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. */
void addAtHead(int val) {
ListNode* newNode = new ListNode(val);
if (head != nullptr)
{
newNode->next = head;
head->prev = newNode;
head = newNode;
}
else
{
head = newNode;
tail = head;// 这里的顺序不能颠倒。一定是head赋值给tail
}
size ++;
}
/** Append a node of value val to the last element of the linked list. */
void addAtTail(int val) {
ListNode* newNode = new ListNode(val);
if (tail != nullptr)
{
newNode->prev = tail;
tail->next = newNode;
newNode->next = nullptr;
tail = newNode;
}
else
{
tail = newNode;
head = tail;
}
size ++;
}
// 在index前面的结点插入结点
void addAtIndex(int index, int val) {
if (index <= 0) addAtHead(val);
else if (index == size) addAtTail(val);
else if (index > size) return;
else
{
ListNode* cur = head;
while (index --)
cur = cur->next;
ListNode* newNode = new ListNode(val);
ListNode* prevNode = cur->prev;
prevNode->next = newNode;
newNode->prev = prevNode;
newNode->next = cur;
cur->prev = newNode;
size ++;
}
}
/** Delete the index-th node in the linked list, if the index is valid. */
void deleteAtIndex(int index) {
if (size == 0) return ;
if (index == 0)// 删除头结点
{
ListNode* delNode = head;
head = head->next;
// 链表中只有一个结点
if (head == nullptr)
{
tail = nullptr;
}
else // 链表中不止一个结点
{
head->prev = nullptr;
}
delete delNode;
size --;
}
else if (index < size - 1)// 删除中间节点
{
ListNode* cur = head;
while (index --)
cur = cur->next;
ListNode* delNode = cur;
ListNode* prevNode = cur->prev;
ListNode* nextNode = cur->next;
prevNode->next = nextNode;
nextNode->prev = prevNode;
delete delNode;
size --;
}
else if (index == size - 1)// 删除尾结点
{
ListNode* delNode = tail;
tail = tail->prev;
// 链表中只有一个结点
if (tail == nullptr)
{
head = nullptr;
}
else// 链表中不止一个结点
{
tail->next = nullptr;
}
delete delNode;
size --;
}
}
};
链表小结
如果你需要经常添加或删除结点,链表可能是一个不错的选择。
如果你需要经常按索引访问元素,数组可能是比链表更好的选择。
合并两个有序链表
(类似合并有序数组)
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if (l1 == nullptr) return l2;
if (l2 == nullptr) return l1;
ListNode* cur1 = l1, *cur2 = l2;
// 设置头结点方便放回头指针设置尾结点方便在链表末尾插入结点
ListNode* tail = nullptr, *head = nullptr;
// 先设置好头指针的第一个结点
if (cur1->val < cur2->val)
{
head = tail = cur1;
cur1 = cur1->next;
}
else
{
head = tail = cur2;
cur2 = cur2->next;
}
tail->next = nullptr;
// 当两个指针都不为nulltpr的时候继续
while (cur1 && cur2)
{
// 让链表后面接上值比较小的结点
if (cur1->val < cur2->val)
{
ListNode* next = cur1->next;
tail->next = cur1;
cur1->next = nullptr;
tail = cur1;
cur1 = next;
}
else
{
ListNode* next = cur2->next;
tail->next = cur2;
cur2->next = nullptr;
tail = cur2;
cur2 = next;
}
}
if (cur1) tail->next = cur1;
if (cur2) tail->next = cur2;
return head;
}
};
(递归版本)
class Solution {
public:
// 返回两个链表合并后的头结点
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
// 如果一个链表为空,就直接返回另一个链表
if (l1 == nullptr) return l2;
if (l2 == nullptr) return l1;
// 比较链表头的值的大小
// 让链表头值小的链表1后面接上链表1后面的部分和链表2合并的结果
if (l1->val < l2->val) {
l1->next = mergeTwoLists(l1->next, l2);
return l1;
} else {
l2->next = mergeTwoLists(l1, l2->next);
return l2;
}
}
};
(虚拟头结点)
这题因为要合并新的链表但是头结点没有定下来所以可以想到用虚拟头结点。
==一般来说虚拟头结点的最大作用就是:将所有的结点一视同仁==因为头结点的增删往往会导致链表的head指针的改变所以一般头结点要改变的时候需要特判头结点。
这题用用虚拟头结点的好处不仅可以省去头结点插入的麻烦,而且不用找两个链表中哪个链表的头结点的值比较大(将头结点一视同仁)。
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
// 设置虚拟头结点
ListNode* dummy = new ListNode(-1);
ListNode* cur = dummy;
while (l1 && l2)
{
if (l1->val < l2->val)
{
cur->next = l1;
l1 = l1->next;
}
else
{
cur->next = l2;
l2 = l2->next;
}
cur = cur->next;
}
// 将没接上的链表部分接上
if (l1) cur->next = l1;
else cur->next = l2;
ListNode* head = dummy->next;
delete dummy;
return head;
}
};
两数相加
题目要求:将链表的每个节点看成是一位数(逆置的,逆置的意思是链表的尾结点是数的最高位,头结点是数的最低位),将两个链表的两个数相加然后将结果的每一位数变成结点形成一个新的链表(还是逆置的)
(链表转数组+大数相加+数组转链表)
这题不可以链表转成数字因为至多有100个结点所以就算是用long long
也不能存储超过16位的数字,所以如果想将链表的值转换成数字的话就只能将数字存储在vector
中再用vector
+vector
大数相加,最后再将数组中的数转换成链表就ok了。
class Solution {
public:
vector<int> Add(vector<int>& v1, vector<int>& v2){
vector<int> sum;
int carry = 0;// 进位
int i = 0;
while (i < v1.size() || i < v2.size())
{
int num1 = 0;
if (i < v1.size())
num1 = v1[i];
int num2 = 0;
if (i < v2.size())
num2 = v2[i];
int t = num1 + num2 + carry;
sum.push_back(t % 10);
// 进位
if (t > 9) carry = 1;
else carry = 0;
i ++;
}
if (carry == 1) sum.push_back(1);
reverse(sum.begin(), sum.end());
return sum;
}
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
vector<int> v1, v2;
ListNode* cur1 = l1, * cur2 = l2;
// 链表转数组
while (cur1 != nullptr)
{
v1.push_back(cur1->val);
cur1 = cur1->next;
}
while (cur2 != nullptr)
{
v2.push_back(cur2->val);
cur2 = cur2->next;
}
// 大数相加
vector<int> sum = Add(v1, v2);
// 数组转链表(设置虚拟头结点)
ListNode* dummy = new ListNode(-1);
ListNode* cur = dummy;
while (!sum.empty())
{
ListNode* newNode = new ListNode(sum.back());
sum.pop_back();
cur->next = newNode;
cur = cur->next;
}
cur->next = nullptr;
ListNode* head = dummy->next;
delete dummy;
return head;
}
};
(链表直接转链表1)
也可以将链表每个结点的值相加的过程和链表的合并同时进行。
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* dummy = new ListNode(-1);
ListNode* cur = dummy;
ListNode* cur1 = l1, * cur2 = l2;
int carry = 0;
// 当两个链表都走完并且没有进位了就结束
while (cur1 || cur2 || carry == 1)
{
// 取出链表结点的值
int num1 = cur1 == nullptr ? 0 : cur1->val;
int num2 = cur2 == nullptr ? 0 : cur2->val;
int sum = num1 + num2 + carry;
ListNode* newNode = new ListNode(sum % 10);
cur->next = newNode;
cur = cur->next;
// 进位
carry = sum / 10;
// 两个链表同时往后走
if (cur1) cur1 = cur1->next;
if (cur2) cur2 = cur2->next;
}
ListNode* head = dummy->next;
delete dummy;
return head;
}
};
(链表转链表2)
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* dummy = new ListNode(-1);
ListNode* cur = dummy;
ListNode* cur1 = l1, * cur2 = l2;
int sum = 0;
// 当两个链表都走完并且没有进位了就结束
while (cur1 || cur2 || sum == 1)
{
if (cur1)
{
sum += cur1->val;
cur1 = cur1->next;
}
if (cur2)
{
sum += cur2->val;
cur2 = cur2->next;
}
cur->next = new ListNode(sum % 10);
cur = cur->next;
// 进位
sum /= 10;
}
ListNode* head = dummy->next;
delete dummy;
return head;
}
};
旋转链表
题目要求:给定一个链表和数字k,将链表右旋k个位置
总体的思路就是:找到倒数第k+1个位置,然后断开,让链表的尾结点指向原链表的头,最后将倒数第k个位置坐为新的头结点形成一个新的链表。
(快慢指针找倒数第k个结点1)
利用快慢指针,让fast指针先走k步,然后让fast指针和从头结点开始走的slow指针同时走,如果fast指针走到nullptr
,slow就走到了倒数第k个结点,如果fast走到最后一个结点而不是nullptr
,那slow就走到了倒数第k+1个结点。这就是利用了快慢指针的相对位置来解题。
注意:题利用双指针是在遍历一遍的情况下就可以找到倒数第k+1个结点的,但是因为k的值不确定,如果k大于链表的个数就不能右旋了,所以一定需要求出链表的结点总个数。
class Solution {
public:
ListNode* rotateRight(ListNode* head, int k) {
if (head == nullptr || head->next == nullptr || k == 0) return head;
// 算出链表的结点总数
int cnt = 0;
ListNode* cur = head;
while (cur)
{
cnt ++;
cur = cur->next;
}
// k一定小于链表的结点总数
k %= cnt;
// 用两个指针找到链表的末尾和倒数第k+1个位置
ListNode* fast = head, *slow = head;
while (k --)
fast = fast->next;
// 因为找倒数第k+1个结点所以fast到最后一个结点
// 找倒数第k个结点fast要到nullptr
while (fast->next)
{
slow = slow->next;
fast = fast->next;
}
// 断开链表,重新相连
if (slow->next == nullptr)
return head;
ListNode* newHead = slow->next;
slow->next = nullptr;
fast->next = head;
return newHead;
}
};
在断开链表的时候,要注意顺序。
在断开链表,重新相连的过程中哟三个任务:
1)找到倒数第k+1个结点的下一个结点作为新链表的开头
2)将倒数第k+1个结点指向nullptr
断开链表
3)将链表为结汇原来的链表头
上面这种方法就是按照(1,2,3)的顺序进行的,但是这时候有一个特殊的情况需要判断,如果链表中只有一个结点newHead
直接作为链表头,这时就出现了问题,因为只有一个结点所以slow->next
是nullptr
,但是一个结点旋转怎么可能旋转的没有结点了呢?所以要特判一下,如果slow->next
为空了,就直接返回头结点就可以了。
(快慢指针找倒数第k个结点2)
将上面那种方式换一个顺序,(3,1,2),先让链表形成一个闭环,这样就算是一个结点,但是slow->next
有指向会自己了。
class Solution {
public:
ListNode* rotateRight(ListNode* head, int k) {
if (head == nullptr || head->next == nullptr || k == 0) return head;
// 算出链表的结点总数
int cnt = 0;
ListNode* cur = head;
while (cur)
{
cnt ++;
cur = cur->next;
}
// k一定小于链表的结点总数
k %= cnt;
ListNode* fast = head, *slow = head;
while (k --)
fast = fast->next;
while (fast->next)
{
slow = slow->next;
fast = fast->next;
}
// 让链表型成闭环
fast->next = head;
ListNode* newHead = slow->next;
slow->next = nullptr;
return newHead;
}
};
(简单版)
这种方法很简单,上面再算结点总数和找到链表的末尾的时候都是单独用的一个循环,但是在找链表末尾的时候顺便就可以求出结点的总个数了,只要一开始计数的时候不从0开始而是直接从一开始这样少的一个结点就可以找补回来了。
还要一点是既然已经求出了结点的总个数了,就直接用一个指针走总数-k-1
步就可以走到倒数第k+1个位置了,不用双指针遍历了。下次如果遇到题目要求只能遍历一次找到倒数第k个结点的时候再用双指针。
class Solution {
public:
ListNode* rotateRight(ListNode* head, int k) {
if (head == nullptr || head->next == nullptr)
return head;
// 算出链表的结点总数,并让cur找到链表的末尾
int cnt = 1;
ListNode* cur = head;
while (cur->next)
{
cnt ++;
cur = cur->next;
}
// k一定小于链表的结点总数
k %= cnt;
// 这里特判一下,如果k是cnt的倍数或者是k原本就是0
// 说明原链表没有必要旋转
if (k == 0) return head;
// 让链表形成闭环
cur->next = head;
// 找到倒数第k+1个位置
for (int i = 0; i < cnt - k - 1; i ++)
head = head->next;
ListNode* newHead = head->next;
head->next = nullptr;
return newHead;
}
};
标签:结点,ListNode,cur,题解,head,next,链表,LeetBook 来源: https://blog.csdn.net/zhybiancheng/article/details/117817319