前言🐱
hello,好久不见,这几天一直在刷蓝桥杯的题和英雄哥的九日集训的题,没什么时间更新了。忽略想起数据结构的复习还没完成呢,那就再次开始吧!
NO1.合并两个有序链表🐕
数组+排序:
先把2个链表链接起来,开辟一个数组,把所有的val放进去,并排序,重新遍历链表,把排好序的val赋值过去
注意排序的个数
注意考虑空链表的情况
注意,数组的大小尽量比最大的节点数大一点就行。
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2)
{
//考虑空链表,list1为空,list2为空,或者都为空
if(list1 == nullptr)
return list2;
if(list2 == nullptr)
return list1;
//直接把两个链表链接起来
ListNode* cur1 = list1;
while(cur1->next)
{
cur1 = cur1->next;
}
//此时cur指向的是list1最后一个节点
cur1->next = list2;
//把链接好的链表的val放到数组里面并排序
//由题目知,节点数目不超过50
int arr[110];
int i = 0, count = 0;//count记录节点个数
cur1 = list1;
while(cur1)
{
count++;
arr[i++] = cur1->val;
cur1 = cur1->next;
}
//重置cur1
cur1 = list1;
//把数组按升序排序
sort(arr,arr+count);//数组没初始化,后面是随机值,整个排序不合理,只需排序所有的节点个数
i = 0;//重置i
while(count--)
{
cur1->val = arr[i++];
cur1 = cur1->next;
}
return list1;
}
};
尾插:
取小的节点尾插到新链表—》归并思想
单链表尾插,每次都得找尾,效率太低了(N^2),我们可以加一个tail指针,来随时标记尾。
让tail动起来就行
当有一个链表插完了,直接链接到剩下那个链表的剩下节点。
第一次尾插时需要确定好头。
注意考虑空链表或单个节点的情况。
struct ListNode *mergeTwoLists(struct ListNode *list1, struct ListNode *list2)
{
struct ListNode *head = NULL, *tail = NULL;
//考虑空指针
if (list1 == NULL && list2 != NULL)
return list2;
if (list2 == NULL && list1 != NULL)
return list1;
if (list1 == NULL && list2 == NULL)
return NULL;
//先取个小的去做头,方便后面尾插,这种情况下 head tail为空
if (list1->val < list2->val)
{
head = tail = list1;
list1 = list1->next;
}
else
{
head = tail = list2;
list2 = list2->next;
}
while (list1 && list2)
{
if (list1->val < list2->val)
{
tail->next = list1;
list1 = list1->next;
tail = tail->next;
}
else
{
tail->next = list2;
list2 = list2->next;
tail = tail->next;
}
}
// l1或者l2为空就出来了
//list1为空时链接到list
if (list1 == NULL)
tail->next = list2;
else if (list2 == NULL)
tail->next = list1;
return head;
}
if (list1 == NULL && list2 != NULL)
return list2;
if (list2 == NULL && list1 != NULL)
return list1;
if (list1 == NULL && list2 == NULL)
return NULL;
//可以写得简洁一点
if (list1 == NULL)
return list2;
if (list2 == NULL)
return list1;
哨兵位:
哨兵位头结点不存储有效数据的
这样就不需要一开始就判断空链表的情况了。
也不需要担心tail head为空的情况。
注意尾插完了之后,需要return head的下一个节点
struct ListNode *mergeTwoLists(struct ListNode *list1, struct ListNode *list2)
{
struct ListNode *head = NULL;
struct ListNode *tail = NULL;
//带哨兵位的写法
head = tail = (struct ListNode *)malloc(sizeof(struct ListNode));
tail->next = NULL;
while (list1 && list2)
{
if (list1->val < list2->val)
{
tail->next = list1;
list1 = list1->next;
tail = tail->next;
}
else
{
tail->next = list2;
list2 = list2->next;
tail = tail->next;
}
}
// l1或者l2为空就出来了
if (list1)
tail->next = list1;
else if (list2)
tail->next = list2;
//开辟的哨兵位用完要释放,但不能直接free,直接free就找不到了,先记录一下位置再free
struct ListNode *node = head;
head = head->next;
free(node);
node = NULL;//也可以不置空,反正没人访问得到,但置空是个好习惯。
return head;
}
C++写法
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2)
{
ListNode *head = nullptr, *tail = nullptr;
//带哨兵位
head = tail = new ListNode;
tail->next = nullptr;
while (list1 && list2)
{
if (list1->val < list2->val)
{
tail->next = list1;
list1 = list1->next;
tail = tail->next;
}
else
{
tail->next = list2;
list2 = list2->next;
tail = tail->next;
}
}
// l1或者l2为空就出来了
if (list1)
tail->next = list1;
else if (list2)
tail->next = list2;
//开辟的哨兵位用完要释放
ListNode *node = head;
head = head->next;
delete node;
node = nullptr;
return head;
}
};
NO.2链表分割🐺
现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。
哨兵位:
哨兵位方便尾插
设置两个链表头,遍历原链表,一个追加小数链表,一个追加大数链表,最后将小数链表粘到大数链表前边即为结果。
class Partition
{
public:
ListNode *partition(ListNode *pHead, int x)
{
//借助哨兵位
struct ListNode *lessHead = NULL;
struct ListNode *lessTail = NULL;
struct ListNode *greaterHead = NULL;
struct ListNode *greaterTail = NULL;
//开辟哨兵位
lessHead = lessTail = (struct ListNode *)malloc(sizeof(struct ListNode));
greaterHead = greaterTail = (struct ListNode *)malloc(sizeof(struct ListNode));
lessTail->next = NULL;
greaterTail->next = NULL;
struct ListNode *cur = pHead;
//把小于x的插到一个链表,大于x的插到一个链表
while (cur != NULL)
{
if (cur->val < x)
{
lessTail->next = cur;
cur = cur->next;
lessTail = lessTail->next;
}
else
{
greaterTail->next = cur;
cur = cur->next;
greaterTail = greaterTail->next;
}
}
//链接2个链表,注意带了哨兵位
lessTail->next = greaterHead->next;
//防止成环,要让最后的那个值指向NULL
greaterTail->next = NULL;
list = lessHead->next;
//释放空间
free(lessHead);
lessHead = NULL;
free(greaterHead);
greaterHead = NULL;
return list;
}
};
牛客的调试太难了,只能靠猜测极端情况
1.空链表过得去
2.全大于/全小于x也过得去
3.有可能成环成环了就陷入死循环,牛客就会报内存受限,其实不是真的内存不足,而是要考虑死循环的情况
N0.3链表的回文结构👻
开数组:
开一个int[900]的数组,用数组判断,虽然也能通过牛客的判断,但实际不符合空间复杂度O(1)的要求
class PalindromeList {
public:
bool chkPalindrome(ListNode* A) {
int arr[900];
int count = 0, i = 0;
ListNode* cur = A;
while(cur)
{
count++;
arr[i++] = cur->val;
cur = cur->next;
}
//判断数组中的数是否为回文结构
count--;
i = 0;
int tmp = count;
while(tmp--)
{
if(arr[i] == arr[count])
{
i++, count--;
}
else
return false;
}
return true;
}
};
逆置+快慢指针:
先找到中间节点
翻转后半部分链表
一一比较
class PalindromeList
{
public:
//先找到中间节点
struct ListNode *midNode(struct ListNode *head)
{
struct ListNode *slow = head;
struct ListNode *fast = head;
while (fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
//翻转后半部分
struct ListNode *ReverseList(struct ListNode *head)
{
//头插法
struct ListNode *newHead = NULL;
struct ListNode *cur = head;
while (cur != NULL)
{
struct ListNode *next = cur->next;
cur->next = newHead;
newHead = cur;
cur = next;
}
head = newHead;
return head;
//再一一比较
}
bool chkPalindrome(ListNode *A)
{
struct ListNode *mid = midNode(A);
struct ListNode *rHead = ReverseList(mid);
while (A && rHead)
{
if (A->val == rHead->val)
{
A = A->next;
rHead = rHead->next;
}
else
return false;
}
return true;
}
};
奇数个是怎么过的呢?奇数个理论上不是应该让中间的前一个指向NULL才行吗?
其实是虽然后半部分逆置了,但2还是指向3的,3的next为NULL
只要奇数个偶数个的情况能过就行。
NO.4相交链表🐹
依次比较
A链表每个节点依次跟B链表每个节点依次比较,如果有相等,就是相等,第一个相等的就是交点。
时间复杂度O(M*N)
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
//考虑空链表,恰好最后返回的也是nullptr
ListNode *curA = headA, *curB = headB;
while(curA)
{
ListNode *TmpCurA = curA, *TmpCurB = curB;
//先挨个遍历B链表
while(curB)
{
if(curA == curB)
return curA;
curB = curB->next;
}
//B回到初始,A回到初始下一个
curB = TmpCurB;
curA = TmpCurA->next;
}
return nullptr;
}
};
快慢指针走长度差:
先遍历到尾结点,判断是否相交。
如果相交,让长的链表先走差距步,再同时走找交点。
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{
//考虑空链表
if(headA == NULL || headB == NULL)
{
return NULL;
}
//先算2个链表长度
int lenA = 0;
int lenB = 0;
struct ListNode* curA = headA;
struct ListNode* curB = headB;
while(curA->next != NULL)
{
curA = curA->next;
lenA++;
}
//各自都少计算一步,相减没影响,这样方便后面判断是否相交
while(curB->next != NULL)
{
curB = curB->next;
lenB++;
}
//不相交,此时curA->nxet == NULL
if(curA != curB)
{
return NULL;
}
//注意要把curA curB的位置调整回到一开始
curA = headA;
curB = headB;
//让长的链表先把多出来的长度走了,再一起走
if(lenA > lenB)
{
int len = lenA - lenB;
while(len--)
{
curA = curA->next;
}
}
if(lenA < lenB)
{
int len = lenB - lenA;
while(len--)
{
curB = curB->next;
}
}
//一起走,看谁的指针相同
while(curA != curB)
{
curA = curA->next;
curB = curB->next;
}
return curA;
}
写法二:
用abs判断差距步
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{
//考虑空链表
if(headA == NULL || headB == NULL)
{
return NULL;
}
//先算2个链表长度
int lenA = 0;
int lenB = 0;
struct ListNode* curA = headA;
struct ListNode* curB = headB;
while(curA->next != NULL)
{
curA = curA->next;
lenA++;
}
//各自都少计算一步,相减没影响,这样方便后面判断是否相交
while(curB->next != NULL)
{
curB = curB->next;
lenB++;
}
//不相交,此时curA->nxet == NULL
if(curA != curB)
return NULL;
//另一种写法
struct ListNode* longList = headA;
struct ListNode* shortList = headB;
if(lenB > lenA)//假设错了就及时修改
{
longList = headB;
shortList = headA;
}
int gap = abs(lenB - lenA);
while(gap--)
{
longList = longList->next;
}
while(longList != shortList)
{
longList = longList->next;
shortList = shortList->next;
}
return longList;
}
不先判断是否相交也行,如果不相交最后返回的也是NULL
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{
//考虑空链表
if(headA == NULL || headB == NULL)
return NULL;
//先算2个链表长度
int lenA = 0;
int lenB = 0;
struct ListNode* curA = headA;
struct ListNode* curB = headB;
while(curA != NULL)
{
curA = curA->next;
lenA++;
}
//各自都少计算一步,相减没影响,这样方便后面判断是否相交
while(curB != NULL)
{
curB = curB->next;
lenB++;
}
struct ListNode* longList = headA;
struct ListNode* shortList = headB;
if(lenB > lenA)
{
longList = headB;
shortList = headA;
}
int gap = abs(lenB - lenA);
while(gap--)
{
longList = longList->next;
}
while(longList != shortList)
{
longList = longList->next;
shortList = shortList->next;
}
return longList;
}
NO5. 环形链表🐜
快慢指针
判断是否带环很简单,利用快慢指针即可。如果带环,快指针必然会追上慢指针。
//快慢指针
bool hasCycle(struct ListNode *head) {
struct ListNode* slow = head;
struct ListNode* fast = head;
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if(slow == fast)
{
return true;
}
}
return false;
}
仅仅是这样就够了吗?
如果让你求环形入口点该怎么办呢?
NO6.环形链表 II🐎
NO.7环形链表衍生问题🦏
1.
为什么slow走一步,fast走两步,他们一定会在环内相遇呢,会不会永远追不上?请证明:
不会。假设slow进环的时候,fast跟slow的距离是N,紧接着追击的过程中,slow每走一步,fast走2步,也就是他们自己距离缩减1,迟早会追上。
2.
slow走一步,fast走3步,4步,x步呢?一定会相遇吗?
假设slow进环的时候,fast和slow距离为N,环的长度为C。
当fast走3步时,他们每走一次,距离缩小2步,如果N是偶数就会相遇,奇数就不会相遇,变成-1时,实际相差C-1,如果C-1也是奇数,那么永远追不上。
N为奇数时,假设环的长度为C,当他们差距为-1时,其实也就是差距为C-1
1、C-1为偶数时,那么他们最终就能相遇。
2、C-1为奇数时,那么就永远也追不上了。当fast走4步时,slow走1步,每走一次,距离缩小3。
如果C-1 是3的倍数,那么就能追上。如果不是3的倍数,那么最终就始终在C-1或C-2循环,追不上。
C-2同理。
3.
求环的入口点
由图推出公式 :2*(L+X) = L+N * C+X
L是起点到入口点的距离,X是相遇点到入口点的距离,环的长度是C
注意如果环比较小,快指针可能会走了好多圈他们才相遇,因此是N*C
也就是一个指针从head走,一个从meet走,他们会在入口相遇
上面3个问题都弄清楚了,那么代码不就也手到擒来吗。
快慢指针:
struct ListNode *detectCycle(struct ListNode *head) {
struct ListNode* slow = head;
struct ListNode* fast = head;
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if(slow == fast)
{
//相遇
//通过证明,一个指针从head走
//一个从meet走,他们会在入口相遇
struct ListNode* meet = fast;
while(meet != head)
{
meet = meet->next;
head = head->next;
}
return head;
}
}
return NULL;
}
当然,快慢指针代码看似简单,实际原来却很复杂,还有没有其他思路呢?
当然有啦,结合前面的题,有没有什么想法呢?链表相交问题。
转换成相交问题:
找到相遇点后,让fast的下一个节点作为B的头,并把fast的next置空,然后再去找相交点即可:
无需翻转链表找到入口后再翻转回来。
class Solution {
public:
ListNode *getIntersectionNode(ListNode* headA, ListNode* headB)
{
if(headA == nullptr || headB == nullptr)
return nullptr;
//先算两个链表长度
int lenA = 0, lenB = 0;
ListNode *curA = headA, *curB = headB;
//2个长度都少算1不影响,方便后面算相交
while(curA->next)
{
++lenA;
curA = curA->next;
}
while(curB->next)
{
++lenB;
curB = curB->next;
}
//算完长度后,恰好指向的是各自最后一个节点,进而判断是否相交
if(curA != curB)
return nullptr;
//假设A是长的,如果不是就反过来
ListNode *longList = headA, *shortList = headB;
if(lenB > lenA)
{
longList = headB;
shortList = headA;
}
int gap = abs(lenA - lenB);
//让长的先走差距步
while(gap--)
longList = longList->next;
while(longList != shortList)
{
longList = longList->next;
shortList = shortList->next;
}
return longList;
}
ListNode *detectCycle(ListNode *head) {
ListNode *slow = head, *fast = head;
while(fast && fast->next)
{
//slow走一步,fast走2步,如果有环必会相遇
slow = slow->next;
fast = fast->next->next;
if(slow == fast)
{
//相遇了把环断开,转换为判断链表是否相交问题
ListNode* headB = fast->next;
fast->next = nullptr;
return getIntersectionNode(head, headB);
}
}
//出循环说明不成环
return nullptr;
}
};
尾声🦊
🌹🌹🌹
写文不易,如果有帮助烦请点个赞~ 👍👍👍
Thanks♪(・ω・)ノ🌹🌹🌹
😘😘😘
👀👀由于笔者水平有限,在今后的博文中难免会出现错误之处,本人非常希望您如果发现错误,恳请留言批评斧正,希望和大家一起学习,一起进步ヽ( ̄ω ̄( ̄ω ̄〃)ゝ,期待您的留言评论。
附GitHub仓库链接