文章目录
- 合并两个有序链表
- 反转链表
- 链表内指定区间反转
- 复制带随机指针的链表
- 环形链表
- 环形链表II
- 相交链表
- 移除链表元素
- 链表中倒数第k个节点
- 链表分割
- 链表的回文结构
- 链表的中间节点
- 旋转链表
- 链表排序
- 链表求和 (逆序求)
- 链表求和II (正序求)
- 重排链表
- 链表的奇偶重排
- 奇偶链表
- 反转链表II
- 删除链表中的节点
- 删除有序链表当中重复的元素I
- 删除有序链表当中重复的元素II
- 合并K个升序链表
- K个一组反转链表
- 交换链表中的节点
- 二进制链表转整数
- 链表随机节点
合并两个有序链表
https://leetcode.cn/problems/merge-two-sorted-lists/
1.定义一个哨兵位节点和一个tail节点标志尾节点
2.遍历两条有序链表,谁小就链接谁
3.最后还剩一条链表是没有遍历完成的,那么就让tail节点链接它
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
//1.新建哨兵位节点
ListNode* phead = new ListNode(-1);
ListNode* tail = phead;
//2.谁小就链接谁
while(list1 && list2)
{
if(list1->val > list2->val)
{
tail->next = list2;
tail = list2;
list2 = list2->next;
}
else
{
tail->next = list1;
tail = list1;
list1 = list1->next;
}
}
//3.判断谁还没有链接完
if(list1) tail->next = list1;
if(list2) tail->next = list2;
return phead->next;
}
};
反转链表
https://leetcode.cn/problems/reverse-linked-list/description/
prev:记录前一个节点 cur:当前遍历到的节点 next:保存cur的下一个节点
- 先保存下一个节点 然后更改cur的指向,指向前一个节点
- 然后迭代往后走
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* prev = nullptr;//记录前一个节点
ListNode* cur = head;//记录当前节点
ListNode* next = nullptr;//记录下一个节点
while(cur)
{
next = cur->next;//先保存下一个节点
cur->next = prev;//更改当前节点指向
//prev cur next 迭代往后走
prev = cur;
cur = next;
}
return prev;
}
};
链表内指定区间反转
https://www.nowcoder.com/practice/b58434e200a648c589ca2063f1faf58c?tpId=295&tqId=654&ru=%2Fpractice%2F6ab1d9a29e88450685099d45c9e31e46&qru=%2Fta%2Fformat-top101%2Fquestion-ranking&sourceUrl=%2Fexam%2Fcompany
复制带随机指针的链表
https://leetcode.cn/problems/copy-list-with-random-pointer/
1.在原链表节点之后拷贝一个节点
2.处理拷贝节点的random
指针
- 注意:拷贝节点的random指针指向的节点是其原链表节点的random指针指向的节点的下一个节点
- 坑点:有可能cur->random是空,也就是原来节点的random指针为空,那么当前拷贝节点的random指针也应该为空,否则cur->random->next 就会对空指针解引用!
3.分离两条链表
- 最好定义一个哨兵位节点和一个tail指针用于标记链接拷贝链表,
cur CopyCur next
三者的关系重新处理
class Solution {
public:
Node* copyRandomList(Node* head) {
if(head == nullptr ) return nullptr;
//1.在原节点后面copy一个节点
Node* cur = head;
while(cur)
{
Node* copy = new Node(cur->val);//拷贝节点
Node* next = cur->next;
//cur copy next 链接
cur->next = copy;
copy->next = next;
cur = next;//继续复制下一个节点
}
//2.处理拷贝节点的random指针
cur = head;
while(cur)
{
Node* curCopy = cur->next;//cur的拷贝节点
curCopy->random = cur->random == nullptr?nullptr:cur->random->next;
cur = curCopy->next;
}
//3.拆离拷贝链表
cur = head;
Node* pnewHead = new Node(-1);//哨兵位
Node* tail = pnewHead;
while(cur)
{
//cur copyCur next
Node* copyCur = cur->next;
Node* next = copyCur->next;
copyCur->next = nullptr;//让拷贝节点独立存在
tail->next = copyCur;
tail = tail->next;
//重新处理链接关系,向后走
cur->next = next;
cur = next;
}
return pnewHead->next;
}
};
环形链表
https://leetcode.cn/problems/linked-list-cycle/description/
方法:使用快慢指针,二者从头开始走,一个一次走两步,一个一次走一步,当二者相遇的时候,说明有环
class Solution {
public:
bool hasCycle(ListNode *head) {
//链表为空
//注意:一个节点也能成环! 自己指向自己
if(!head) return false;
//快慢指针
ListNode* fast = head;
ListNode* slow = head;
while(fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
//二者相遇 注意:该条件不能放在上面!!!因为最初fast和slow都指向head,该条件应该放在下面
if(slow == fast)
return true;
}
return false;
}
};
延申1:fast一次走两步,slow一次走一步,为什么一定能相遇?会不会在环里错过,永远遇不上
结论:slow一次走一步,fast一次走两步,如果存在环,slow和fast一定会在环内相遇
1.slow和fast,如果有环,一定是fast先进环,这时slow走了入环前距离的一半
2.随着slow进环,fast已经在环里面走了一段距离了(距离的多少跟环的大小有关)
- 假设slow进环的时候,slow和fast的距离为N,fast开始追赶slow
3.slow一次走一步,fast一次走两步,二者的距离变化为:N N- 1 N -2 … 0,当二者的距离变为0的时候,就是相遇了
延申2:fast一次走n步(n>2),slow一次走一步,fast和slow能相遇吗
结论:fast一次走n步(n>2),slow一次走一步,不一定会相遇
- 假设有环,fast一次走n步,slow一次走1步,fast和slow的距离不断减少n-1步
例子:假设fast一次走3步,如果slow进环之后,slow和fast的距离为N
如果N为偶数,那么二者之间的距离变化为:N N - 2 N - 4 … 2 0,此时二者相遇
如果N为计数,那么二者之间的距离变化为:N N - 2 N - 4 … 1 -1 ,二者距离变为-1,意味着fast超越了slow,此时fast和slow的距离为C -1 (假设C为环的大小)
- 如果C - 1 为偶数,那么下一轮fast可以追上slow,二者相遇
- 如果C - 1 为奇数,那么二者永远追不上
环形链表II
https://leetcode.cn/problems/linked-list-cycle-ii/description/
做法:
1.先看是否有环,快慢指针,fast一次走两步,slow一次走一步,如果存在环,fast和slow一定会相遇
2.假设相遇点为meetnode
,一个指针从链表的头开始走,一个指针从相遇点开始走,二者一次走一步,当二者相遇的时候,该位置就是入环节点
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
if(!head) return nullptr;
//快慢指针
ListNode* fast = head;
ListNode* slow = head;
while(fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
//二者相遇 注意:该条件不能放在上面!!!因为最初fast和slow都指向head,该条件应该放在下面
if(slow == fast)
{
//分别从相遇点和链表头开始走,一次走一步 此时相遇就是入环位置
ListNode* meet = slow;
slow = head;
while(slow != meet)
{
slow = slow->next;
meet = meet->next;
}
return meet;
}
}
return nullptr; //没有环
}
};
相交链表
https://leetcode.cn/problems/intersection-of-two-linked-lists/description/
方法1:将A链表的所有节点放到容器当中(要放地址,不能放值),然后遍历B链表,看能否在容器当中找到该元素,如果找到,那么该节点就是相交节点
class Solution {
public:
//方法1:用容器保存其中一个链表的节点,然后遍历另外一个链表进行比对
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
multiset<ListNode*> s;
ListNode* cur = headA;
while(cur)
{
s.insert(cur);
cur = cur->next;
}
cur = headB;
while(cur)
{
cout << cur->val << endl;
if(s.find(cur) != s.end())
return cur;
cur = cur->next;
}
return nullptr;//不相交
}
};
方法2:A中的每个结点和B分别比较(B和A比较也可以),看二者的地址是否一致 - O(N*M)
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode* curA = headA;
ListNode* curB = headB;
while(curA) //确定一个A节点
{
curB = headB;
while(curB)//遍历整条B链表
{
if(curA == curB)
{
return curA;
}
curB = curB ->next;
}
curA = curA->next;
}
return nullptr;
}
};
方法3:
1.先统计两条链表的长度,假设二者长度差距为gap
2.长链表先往后走gap
步,然后长短链表一起往后走,如果相遇,那么就是相交节点
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if(!headA || !headB) return nullptr;
//1.统计两条链表的长度
int lenA = 0;
int lenB = 0;
ListNode* cur = headA;
while(cur) lenA++,cur = cur->next;
cur = headB;
while(cur) lenB++,cur = cur->next;
//2.看哪条链表更长
ListNode* LongList = headA;
ListNode* shortList = headB;
if(lenA < lenB) //B链表更长
swap(LongList,shortList);
//3.长链表先走|lenA - lenB|步
int gap = abs(lenA - lenB);
while(gap--)
{
LongList = LongList -> next;
}
//4.二者一起走,相遇就是相交节点
while(LongList != shortList)
{
LongList = LongList->next;
shortList = shortList->next;
}
return LongList;
}
};
移除链表元素
https://leetcode.cn/problems/remove-linked-list-elements/description/
1.遍历链表,先保存下一个节点,然后将当前节点独立,判断当前节点的值是否是要删除的值
2.如果是,那么就要将当前节点是否,否则将其链接到哨兵位链表
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
if(!head) return nullptr;
ListNode* phead = new ListNode(-1);//哨兵位
ListNode* tail = phead;
ListNode* cur = head;
while(cur)
{
ListNode* next = cur ->next;//保存下一节点
//将当前节点独立!!否则其next还是指向原来的链表的内容
cur->next = nullptr;
if(cur->val != val)
{
tail->next = cur;
tail = tail->next;
}
else //当前节点要释放
{
delete cur;
}
cur = next;
}
return phead->next;
}
};
链表中倒数第k个节点
https://www.nowcoder.com/practice/529d3ae5a407492994ad2a246518148a?tpId=13&&tqId=11167&rp=2&ru=/activity/oj&qru=/ta/coding-interviews/question-ranking
做法:快慢指针,快指针先走k步,然后快慢指针一起走,最后慢指针指向的就是倒数第k个节点
while(k--):执行k次 while(--k):执行k-1次
坑点:有可能k是超过链表长度,那么有可能快指针提前走到空!
class Solution {
public:
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
ListNode* fast = pListHead;
ListNode* slow = pListHead;
while(k--) //快指针先走k步
{
if(fast == nullptr) //k超过了链表长度了
return nullptr;
fast = fast->next;
}
//快慢指针一起走,当fast为空,此时slow就是倒数第k个节点
while(fast)
{
fast = fast->next;
slow = slow->next;
}
return slow;
}
};
链表分割
https://www.nowcoder.com/practice/0e27e0b064de4eacac178676ef9c9d70?
现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。
1.使用两条链表,一条链表记录大于x的节点,一条链表记录小于x的节点。因为不能改变原来的数据顺序,所以要采取尾插,而不是头插
2.遍历原链表,首先记录下一个节点,把当前节点独立,如果当前节点的值<x,那么把当前节点尾插到记录小于x的节点的链表,否则将当前节点尾插到记录大于x的节点的链表
3.最后两条链表进行链接,注意:有可能所有节点的值>x,那么记录<x的节点的链表就是空
class Partition {
public:
ListNode* partition(ListNode* pHead, int x) {
// write code here
ListNode* BigThanXHead = nullptr,*BigThanXTail = nullptr;
ListNode* LessThanXHead = nullptr,*LessThanXTail = nullptr;
ListNode* cur = pHead;
while(cur)
{
//最好将当前节点单独摘下来!!!否则其next还是指向原来的内容
ListNode* next = cur->next;
cur->next = nullptr;
if(cur->val < x)
{
if(LessThanXHead == nullptr)
{
LessThanXTail = LessThanXHead = cur;
}
else
{
LessThanXTail->next = cur;
LessThanXTail = LessThanXTail->next;
}
}
else
{
if(BigThanXHead == nullptr)
{
BigThanXTail = BigThanXHead = cur;
}
else
{
BigThanXTail->next = cur;
BigThanXTail = BigThanXTail->next;
}
}
cur = next;
}
//两段链表进行链接
if(LessThanXHead != nullptr)
{
LessThanXTail->next = BigThanXHead;
}
return LessThanXHead != nullptr?LessThanXHead :BigThanXHead;
}
};
链表的回文结构
https://www.nowcoder.com/practice/d281619e4b3e4a60a2cc66ea32855bfa?tpId=49&&tqId=29370&rp=1&ru=/activity/oj&qru=/ta/2016test/question-ranking
方法1:将所有节点的值放到栈中,然后再次遍历链表判断值是否和栈顶节点一致
class PalindromeList {
public:
bool chkPalindrome(ListNode* A) {
stack<int> st;
ListNode* cur = A;
while(cur)
{
st.push(cur->val);
cur = cur->next;
}
cur = A;
while(cur)
{
if(cur->val != st.top())
return false;
cur = cur->next;
st.pop();
}
return true;
}
};
方法2:链表反转 + 返回链表的中点
1.找到链表的中间节点midNodePrev
,以head为头作为第一条链表,以midNodePrev->next为头作为第二条链表,然后将将midNodePrev的next指针置为空
2.反转第二条链表,然后两条链表遍历比较,如果值不想同,那么说明不回文
class PalindromeList {
public:
ListNode* reverseList(ListNode* head) { //链表反转
ListNode* prev = nullptr;//记录前一个节点
ListNode* cur = head;//记录当前节点
ListNode* next = nullptr;//记录下一个节点
while (cur) {
next = cur->next;//先保存下一个节点
cur->next = prev;//更改当前节点指向
//prev cur next 迭代往后走
prev = cur;
cur = next;
}
return prev;
}
//返回中点
ListNode* middleNode(ListNode* head) {
if (!head) return nullptr;
//快慢指针-快指针走两步,慢指针走一步,当快指针结束时,慢指针指向的就是中间节点
ListNode* fast = head, *slow = head;
while (fast && fast->next) {
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
bool chkPalindrome(ListNode* A) {
if(A == nullptr ) return false;
if(A->next == nullptr) return true;
ListNode* midNodePrev = middleNode(A);
ListNode* secondList = midNodePrev->next;
ListNode* firstList = A;
midNodePrev->next = nullptr;//分离两条链表
secondList = reverseList(secondList);
ListNode* curA = firstList,*curB = secondList;
while(curA && curB)
{
if(curA->val != curB->val) return false;
curA = curA->next;
curB = curB->next;
}
return true;
}
};
链表的中间节点
https://leetcode.cn/problems/middle-of-the-linked-list/description/)/
快慢指针:快指针走两步,慢指针走一步,当快指针结束时,慢指针指向的就是中间节点
class Solution {
public:
ListNode* middleNode(ListNode* head) {
if(!head) return nullptr;
//快慢指针-快指针走两步,慢指针走一步,当快指针结束时,慢指针指向的就是中间节点
ListNode* fast = head,*slow = head;
while(fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
};
旋转链表
https://leetcode.cn/problems/rotate-list/
链表排序
https://leetcode.cn/problems/sort-list/
两个子函数:找链表的中间节点的上一个 + 合并两条有序链表
做法:
- 1.如果链表为空 || 链表只有一个节点 那么直接返回
- 2.找链表的中间节点的上一个节点记为:
mid
,然后保存mid的下一个节点记为midNext
,然后将mid->next置为空 - 3.分别排序:以head为头的链表,以midNext为头的链表,然后两条链表进行归并
- $[head,mid] $ $ [mid->next,…]$
class Solution {
public:
//分割两段链表 ==>找链表的中间节点的上一个
ListNode* splitList(ListNode* head)
{
if(!head || !head->next) return head;
ListNode* fast = head;
ListNode* slow = head;
ListNode* prev = head;
while(fast&&fast->next)
{
prev = slow;
fast = fast->next->next;
slow = slow->next;
}
return prev;
}
//合并两条有序链表
ListNode* MergeSort(ListNode* head1,ListNode* head2)
{
ListNode* dummy = new ListNode(-1);
ListNode* tail = dummy;
while(head1 && head2)
{
if(head1->val < head2->val)
{
tail->next = head1;
tail = head1;
head1 = head1->next;
}
else
{
tail->next = head2;
tail = head2;
head2 = head2->next;
}
}
if(head1) tail->next = head1;
if(head2) tail->next = head2;
return dummy->next;
}
ListNode* sortList(ListNode* head) {
if(!head || !head->next) return head;
ListNode* mid = splitList(head);
ListNode* midNext = mid->next;
mid->next = nullptr;
//[head,mid] [mid->next,....]
ListNode* head1 = sortList(head);
ListNode* head2 = sortList(midNext);
return MergeSort(head1,head2);
}
};
问题:为什么是返回链表中点的上一个
因为如果是直接返回链表中点,假设链表现在为 4 − > 2 4->2 4−>2
- 那么此时mid就是2节点,然后midNext为空,然后又去递归sortList( h e a d : 4 − > 2 head:4->2 head:4−>2),得到的mid又是2,然后又去递归… 就会造成死循环
- 而如果返回的是链表的中点的上一个就不会有问题!
链表求和 (逆序求)
https://leetcode.cn/problems/sum-lists-lcci/
做法:定义一条临时链表维护计算结果
1.如果其中一条链表尾空,那么就返回另外一条链表
2.如果其中一条链表没有遍历完 || 进位不为0,都继续操作
- 当前计算节点相加的值,注意不要忽略进位!用当前计算值开辟一个节点然后计算向下一位的进位
class Solution {
public:
ListNode* addTwoNumbers(ListNode* list1, ListNode* list2) {
if(!list1 && !list2) return nullptr;
//如果其中一条链表尾空,那么就返回另外一条链表
if(!list1) return list2;
if(!list2) return list1;
ListNode* phead = new ListNode(-1);
ListNode* tail = phead;
int carry = 0;//进位
while(list1|| list2 || carry) //其中一条链表没有遍历完 || 进位不为0,都继续操作
{
int sum = 0;//当前计算节点相加的值,注意:每次都需要重新将sum置为0!!否则还是上次计算的值
if(list1 != nullptr)
{
sum += list1->val,list1 = list1->next;
}
if(list2 != nullptr)
{
sum += list2->val,list2 = list2->next;
}
sum += carry;//注意要加上进位值
carry = sum / 10; //坑点:carry的计算要放在sum取模之前,否则每次carry的值都是0
sum = sum % 10;
ListNode* newNode = new ListNode(sum);
tail -> next = newNode;
tail = tail->next;
}
return phead->next;
}
};
链表求和II (正序求)
https://www.nowcoder.com/practice/c56f6c70fb3f4849bc56e33ff2a50b6b?tpId=295&tqId=1008772&ru=/exam/company&qru=/ta/format-top101/question-ranking&sourceUrl=%2Fexam%2Fcompany
class Solution {
public:
ListNode* ReverseList(ListNode* head) //返回逆置之后的链表的头
{
ListNode* cur = head,*prev = nullptr,*next = nullptr;
while(cur)
{
//prev cur next
next = cur->next;
cur->next = prev;
//迭代往后走
prev = cur;
cur = next;
}
return prev;
}
ListNode* addInList(ListNode* head1, ListNode* head2) {
if(!head1 && !head2) return nullptr;
//如果其中一条链表为空,那么返回另外一条链表
if(!head1) return head2;
if(!head2) return head1;
//逆置两条链表
head1 = ReverseList(head1);
head2 = ReverseList(head2);
//正序求和
ListNode* phead = new ListNode(-1);
ListNode* tail = phead;
int carry = 0;
while(head1 || head2 || carry)
{
int sum = 0;
if(head1) sum += head1->val,head1 = head1->next;
if(head2) sum += head2->val,head2 = head2->next;
sum += carry;
carry = sum / 10;
sum = sum % 10;
tail->next = new ListNode(sum);
tail = tail->next;
}
//逆置结果链表
return ReverseList(phead->next);
}
};
重排链表
https://leetcode.cn/problems/reorder-list/
链表的奇偶重排
https://www.nowcoder.com/practice/02bf49ea45cd486daa031614f9bd6fc3?tpId=295&tqId=1073463&ru=%2Fpractice%2Fc087914fae584da886a0091e877f2c79&qru=%2Fta%2Fformat-top101%2Fquestion-ranking&sourceUrl=%2Fexam%2Fcompany
奇偶链表
https://leetcode.cn/problems/odd-even-linked-list/
做法:
反转链表II
https://leetcode.cn/problems/reverse-linked-list-ii/
删除链表中的节点
https://leetcode.cn/problems/delete-node-in-a-linked-list/description/
做法:假设要删除的节点为node,其下一个节点为nodeNext
- 1.将node节点的值变为nodeNext节点的值,然后让node->next链接nodeNext->next
- 2.然后删除nodeNext节点
思想:把nodeNext的数据搬到node,然后释放nodeNext节点,让node链接其下一个节点
注意:如果node是尾节点,那么就直接返回,不能处理
class Solution {
public:
void deleteNode(ListNode* node) {
//链表为空 || 尾节点
if(!node || !node->next) return ;
ListNode* nodeNext = node->next;
node->val = nodeNext->val;
node->next = nodeNext->next;
delete nodeNext;
}
};
删除有序链表当中重复的元素I
https://www.nowcoder.com/practice/c087914fae584da886a0091e877f2c79?tpId=295&tags=&title=&difficulty=0&judgeStatus=0&rp=0&sourceUrl=%2Fexam%2Fcompany
删除有序链表当中重复的元素II
https://www.nowcoder.com/practice/71cef9f8b5564579bf7ed93fbe0b2024?tpId=295&tqId=663&ru=%2Fpractice%2Fc56f6c70fb3f4849bc56e33ff2a50b6b&qru=%2Fta%2Fformat-top101%2Fquestion-ranking&sourceUrl=%2Fexam%2Fcompany
合并K个升序链表
https://leetcode.cn/problems/merge-k-sorted-lists/
1.使用一个小根堆进行操作,因为默认是大根堆,所以我们要自定义比较方式!
2.首先先把lists
当中所有非空的节点放到堆中,定义一条临时链表用于链接从堆中弹出的节点
3.如果堆不为空就一直处理:弹出当前堆顶节点,如果当前堆顶节点的下一个节点不为空就发给到堆中,将当前节点独立,将当前节点链表到临时链表当中
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
auto lambda = [](ListNode* l,ListNode* r){ return l->val > r->val;};
priority_queue<ListNode*,vector<ListNode*>,decltype(lambda)> pq; //默认是大根堆,我们要的是小根堆(排升序)
for(auto& head : lists)
{
if(head != nullptr) pq.push(head);
}
ListNode* phead = new ListNode(-1);
ListNode* tail = phead;
while(!pq.empty())
{
ListNode* top = pq.top();
pq.pop();
if(top->next)
pq.push(top->next);
//将当前节点独立
top->next = nullptr;
tail->next = top;
tail = tail->next;
}
return phead->next;
}
};
K个一组反转链表
https://leetcode.cn/problems/reverse-nodes-in-k-group/
交换链表中的节点
https://leetcode.cn/problems/swapping-nodes-in-a-linked-list/description/
二进制链表转整数
https://leetcode.cn/problems/convert-binary-number-in-a-linked-list-to-integer/description/
链表随机节点
https://leetcode.cn/problems/linked-list-random-node/solutions/1210211/lian-biao-sui-ji-jie-dian-by-leetcode-so-x6it/
做法:可以在初始化时,用一个数组记录链表中的所有元素,这样随机选择链表的一个节点,就变成在数组中随机选择一个元素
class Solution {
public:
vector<int> arr;
Solution(ListNode* head) {
while(head)
arr.push_back(head->val),head = head->next;
}
int getRandom() {
return arr[rand() % arr.size()];
}
};