⭐博客主页:️CS semi主页
⭐欢迎关注:点赞收藏+留言
⭐系列专栏:数据结构初阶
⭐代码仓库:Data Structure
家人们更新不易,你们的点赞和关注对我而言十分重要,友友们麻烦多多点赞+关注,你们的支持是我创作最大的动力,欢迎友友们私信提问,家人们不要忘记点赞收藏+关注哦!!!
我们用三个指针,cur先记录头结点,prev为空指针,进去以后判断如果是头删则判断的是prev为NULL,我们很容易判断的,所以就是头删,头结点为cur->next,然后将原本的头结点释放即可。而如果不是头结点,我们可以利用指针移动的快慢将prev移动到cur的前一位,next就为cur->next,将prev与next进行连接,再将cur进行释放即可。
if(head == NULL){
return NULL;
}
struct ListNode* cur=head;
struct ListNode* prev=NULL;
while(cur){
//如果当前结点是需要删除的结点
if(val==cur->val){
//保存一下下一个结点
struct ListNode* next=cur->next;
//如果删除的为头结点,更新头结点
//否则让当前结点的前面一个结点链接next结点
if(prev==NULL){
head=cur->next;
}
else{
prev->next = cur->next;
}
//释放当前节点,让cur指向next
free(cur);
cur=next;
}
else{
prev=cur;
cur=cur->next;
}
}
return head;
我们看图,利用三指针,第一个指针n1为头结点,第二个指针n2为头结点的next,第三个指针n3为头结点的next的next。我们先判断n2是不是空,这里为什么判断n2是因为我们想要将最后一个箭头反转过来的判断条件为n2刚好到空的时候,所以判断n2是否为空最为合适。我们每进行一步,将n2的指针指向反转,再利用迭代n1、n2和n3都往后移动一位,直到退出循环,返回新的头结点n1。
定义一个头结点为cur,头结点的next为next,拿一个尾插到新链表,一直到cur为空即可,思路贼简单。
// 三指针法
if(head==NULL || head->next==NULL)
return head;
struct ListNode* n1, *n2, *n3=NULL;
n1=head;
n2=n1->next;
n3=n2->next;
n1->next=NULL;
while(n2){
//反转
n2->next=n1;
//迭代
n1=n2;
n2=n3;
if(n3)
n3=n3->next;
}
return n1;
struct ListNode* cur = head;
struct ListNode* newhead = NULL;
while(cur){
struct ListNode* next=cur->next;
cur->next=newhead;
newhead=cur;
cur=next;
}
return newhead;
利用快慢指针,慢指针走一步,快指针走两步。判断条件为快指针以及快指针的下一个结点任意一个为空即跳出循环,而我们写的是进入循环的条件,所以必须是快指针以及快指针的下一个结点两者都需要不为空才继续。
ListNode* middleNode(ListNode* head) {
struct ListNode* slow=head;
struct ListNode* fast=head;
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
还是快慢指针进行解决问题,这次的快指针和慢指针的步频是一样的,只不过是快指针先走k步,然后慢指针再和快指针一起走,等到快指针为空的时候,慢指针就是我们要找的倒数第k个结点,返回慢指针即可。
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
ListNode* fast=pListHead;
ListNode* slow=pListHead;
while(k--){
if(fast==nullptr){
return nullptr;
}
else
fast=fast->next;
}
while(fast!=nullptr){
fast=fast->next;
slow=slow->next;
}
return slow;
}
无哨兵位的头结点解题相对来讲有点复杂,我们细细分析,我们将list1的头放到cur1上,将list2的头放到cur2上,然后创建一个新链表,判断如果头结点还没插入值,那么就将较小的先插入,如果有值,那就tail后面插入较小的值,一直到两个链表中的其中一个链表为空停止,而我们知道,有可能的情况是一个链表为空,另一个链表不为空,那么我们就将不为空的链表再在尾接上去即可。但其实看似没问题,那当然还是有bug的,倘若我们的cur1和cur2在刚开始就为空,那么循环是进不去的,这就需要我们在开头进行判断一下是否为空,一个为空,那么就将另一个直接返回。
思路那就更简单了,我们设立一个不存储任何有效数据的头结点guard,这样子就不需要判断头结点是否为空了,因为本来就存在头结点,链表不可能为空,其他与前一种一样,我们上代码看一下。
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
//不带哨兵位
if(list1==NULL)
return list2;
if(list2==NULL)
return list1;
ListNode* cur1=list1;
ListNode* cur2=list2;
ListNode* head=NULL,*tail=NULL;
while(cur1&&cur2){
if(cur1->val<cur2->val){
if(head==NULL){
head=tail=cur1;
}
else{
tail->next=cur1;
tail=tail->next;
}
cur1=cur1->next;
}
else{
if(head==NULL){
head=tail=cur2;
}
else{
tail->next=cur2;
tail=tail->next;
}
cur2=cur2->next;
}
}
if(cur1)
tail->next=cur1;
if(cur2)
tail->next=cur2;
return head;
}
};
//带哨兵位
ListNode* cur1=list1;
ListNode* cur2=list2;
ListNode* guard=NULL,*tail=NULL;
guard=tail=(ListNode*)malloc(sizeof(ListNode));
tail->next=NULL;
while(cur1&&cur2){
if(cur1->val<cur2->val){
tail->next=cur1;
tail=tail->next;
cur1=cur1->next;
}
else{
tail->next=cur2;
tail=tail->next;
cur2=cur2->next;
}
}
if(cur1!=NULL)
tail->next=cur1;
if(cur2!=NULL)
tail->next=cur2;
ListNode* next=guard->next;
free(guard);
return next;
既然题目是链表的分割,那我们就设置一个长头和长尾(ltail和lhead)以及短头和短尾(shead和stail)。将小于x的指针放到短的,大于或者等于的值放到长的链表中,最后短的tail指向长的head的next即可。
class Partition {
public:
ListNode* partition(ListNode* pHead, int x) {
// write code here
// 分区域进行分装,小于的数放在前一个链表,大于的数放在后一个链表
ListNode* cur=pHead;
ListNode* shead,*stail,*lhead,*ltail;
shead=stail=(ListNode* )malloc(sizeof(ListNode));
lhead=ltail=(ListNode* )malloc(sizeof(ListNode));
stail->next=NULL;
ltail->next=NULL;
while(cur)
{
if(cur->val<x)
{
stail->next=cur;
stail=cur;
}
else
{
ltail->next=cur;
ltail=cur;
}
cur=cur->next;
}
stail->next=lhead->next;
ltail->next=NULL;
ListNode* p=shead->next;
// free(shead);
// free(stail);
// free(lhead);
// free(ltail);
return p;
}
};
先找到中间结点,再从中间结点开始,对后半段逆置,前半段的数据和后半段的数据比较就可以了,如果相等则为回文,不是就不是回文。这边我们用到前面的找中间结点和逆置的函数。
class PalindromeList {
public:
struct ListNode* middleNode(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* cur = head;
struct ListNode* newhead = NULL;
while (cur) {
struct ListNode* next = cur->next;
cur->next = newhead;
newhead = cur;
cur = next;
}
return newhead;
}
bool chkPalindrome(ListNode * head) {
ListNode* mid = middleNode(head);
ListNode* rhead = reverseList(mid);
while(head&&rhead){
if(head->val != rhead->val){
return false;
}
head=head->next;
rhead=rhead->next;
}
return true;
}
};
思路很清奇,但也很常规,这道题可以运用将A链表内所有的数依次与B链表中的值进行比较地址即可,但我们想要提升提升,那就在空间复杂度为O(1),时间复杂度为O(N)的条件下进行判断链表是否相交。这需要用到快慢指针,先是算算两条链表的长度,计算出差值,让快指针先走差值,这样子不就是快慢指针在同一平台上了吗,那就快指针和慢指针一起走,直到遇到相交点(地址相同点),返回结点即可,遇不到那就返回空,但这其中蕴含的条件是如果相交,两条链表的尾结点是肯定地址一样的,因为不可能有相交后再分开的情况。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
//分别求两个链表的长度
//长的链表先走差距步
//再同时走,第一个地址相同的就为交点
ListNode* tail1 = headA;
ListNode* tail2 = headB;
int count1 = 1;
int count2 = 1;
while(tail1->next){
tail1=tail1->next;
count1++;
}
while(tail2->next){
tail2=tail2->next;
count2++;
}
int gap = abs(count1 - count2);
ListNode* longlist = headA, *shortlist = headB;
if(count1 < count2){
longlist = headB;
shortlist = headA;
}
if(tail1 != tail2){
return NULL;
}
while(gap--){
longlist=longlist->next;
}
while(longlist!=shortlist){
longlist=longlist->next;
shortlist=shortlist->next;
}
return longlist;
}
};
思路很简单,只需要用快慢指针即可,快指针的步伐是慢指针的两倍步数,两者只要相等即为相遇,两者找不到相等的地址则为不相遇,那就是没有环。
class Solution {
public:
bool hasCycle(ListNode *head) {
ListNode* fast, *slow;
fast = slow = head;
while(fast && fast->next){
slow=slow->next;
fast=fast->next->next;
if(fast == slow){
return true;
}
}
return false;
}
};
1、为什么slow走一步,fast走两步,它们会不会相遇?会不会错过?请证明:
2、当slow走一步,fast走三步,它们会不会相遇?
答案居然是不一定!!!终究是错付了。
看距离的大小,如果距离为1,则肯定能追到,如果为其他则需要分情况讨论,有可能永远追不到。
结论:一个指针从相遇点走,一个指针从起始点走,会在入口点相遇。
我们可以转化成为求交点的问题,将相遇点的下一个结点定义为lt1,将起始点定义为lt2,然后两个指针同时走,直到相遇,至于如何相遇的,我们在相交链表中提及到了,长的链表先走差距步,再两个链表同时走,直到在入口点相遇,返回入口点的指针。
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* fast, *slow;
fast = slow = head;
while(fast && fast->next){
slow = slow->next;
fast = fast->next->next;
if(slow == fast){
ListNode* meet = slow;
ListNode* start = head;
while(meet != start){
meet = meet->next;
start = start->next;
}
return meet;
}
}
return NULL;
}
};
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
//分别求两个链表的长度
//长的链表先走差距步
//再同时走,第一个地址相同的就为交点
ListNode* tail1 = headA;
ListNode* tail2 = headB;
int count1 = 1;
int count2 = 1;
while(tail1->next){
tail1=tail1->next;
count1++;
}
while(tail2->next){
tail2=tail2->next;
count2++;
}
int gap = abs(count1 - count2);
ListNode* longlist = headA, *shortlist = headB;
if(count1 < count2){
longlist = headB;
shortlist = headA;
}
if(tail1 != tail2){
return NULL;
}
while(gap--){
longlist=longlist->next;
}
while(longlist!=shortlist){
longlist=longlist->next;
shortlist=shortlist->next;
}
return longlist;
}
ListNode *detectCycle(ListNode *head) {
ListNode* fast, *slow;
fast = slow = head;
while(fast && fast->next){
slow = slow->next;
fast = fast->next->next;
if(slow == fast){
ListNode* meet = slow;
ListNode* lt1 = meet->next;
ListNode* lt2 = head;
meet->next=NULL;
return getIntersectionNode(lt1, lt2);
}
}
return NULL;
}
};
这道题目看起来非常的难懂,看题目来讲是在是太难理解了,那我们就简单翻译一下这道题目吧!这到题目就是定义一个结构体链表,存数据、next和random(随机指针),随机指针指向的位置不确定,但指向的是第几个数,就我们看实例1中的13指向的是第0个链表节点,那么就是[13,0],那我们最后依次输出指向不同的结点。
这道题的解题思路很清奇,先是需要我们在每个结点的后面插入一个相同数据的copy结点,然后再链接,之后便将复制的结点的random指向原本结点的random即可,形成一种新的链接关系,之后再将新的结点尾插到新的链表中即可,将原本被破坏的链表恢复,再返回新链表的头即可。
struct Node* copyRandomList(struct Node* head) {
// 1、拷贝信息
struct Node* cur = head;
while(cur){
struct Node* copy = (struct Node*)malloc(sizeof(struct Node));
copy->val = cur->val;
struct Node* next = cur->next;
// cur copy next
cur->next = copy;
copy->next = next;
cur = next;
}
// 2、random链接
cur = head;
while(cur){
struct Node* copy = cur->next;
if(cur->random == NULL){
copy->random = NULL;
}
else{
copy->random = cur->random->next;
}
cur = cur->next->next;
}
// 3、尾插
struct Node* copyhead = NULL, *copytail = NULL;
cur = head;
while(cur){
struct Node* copy = cur->next;
struct Node* next = copy->next;
// copy尾插
if(copyhead == NULL){
copyhead = copytail = copy;
}
else{
copytail->next = copy;
copytail = copytail->next;
}
// 恢复原链表
cur->next = next;
cur = next;
}
return copyhead;
}
单链表的OJ题有些难度很大,有些难度不大,但发现思路都是很新奇的,不和以前的数组那样思路是遍历二分这种的,在链表的练习中是有很多不一样的思路在里面的。