OJ第二篇(单链表)

文章目录

  • 移除链表元素
  • 反转链表
  • 链表的中间结点和链表中倒数第k个结点
  • 合并两个有序链表
  • 判断链表是否为回文结构

今天带来几道有关单链表的OJ题,话不多说,我们直接开始

移除链表元素

链接: 移除链表元素
OJ第二篇(单链表)_第1张图片

其实我们在看到一个题时,我们想的肯定先是它的一般情况,有的特殊情况能想到,有的想不到,在初学时,我们可以先把一般情况写好,在去考虑特殊情况,等有一定的经验了以后,对于简单的题,特殊情况也就信手拈来了。

对于这道题而言,要删除的元素在中间和尾都属于一般情况,因为head的值不会变,要删除的元素如果在开始并且不只有一个,我们就要首先处理特殊情况,处理完后它就变成了一般情况。
还有一种特殊情况就是空链表,这时我们就返回空指针就可以了

struct ListNode* removeElements(struct ListNode* head, int val){

struct ListNode*cur=head;

while(cur&&cur->val==val){//先处理要删除的元素在链表开始位置并且连续
    head=cur->next;//头节点肯定是要变化到下一个结点
    free(cur);
    cur=head;
}
    if(head==NULL)//前面的循环会把空链表的情况漏下来到这处理,同时这也会处理一个链表中全是要删除的元素
    return NULL;
while(cur->next!=NULL){//后面就是要处理的正常情况
    if(cur->next->val==val){
        struct ListNode*tmp=cur->next;
        cur->next=cur->next->next;
        free(tmp);
    }
    else{
        cur=cur->next;
    }
}
return head;
}

反转链表

链接: 反转链表
OJ第二篇(单链表)_第2张图片

这里其实没什么特殊情况,能拿出来说的就是第一个结点要指向空,第二个指向第一个,第三个指向第二个,后面以此类推。
当然这里也是要考虑一下空链表的情况,和上个题其实是类似的。因为空指针的很多操作是受限的,比如没有next

struct ListNode* reverseList(struct ListNode* head){
    if(head==NULL)//特殊处理
    return NULL;
struct ListNode*curprev=NULL;
struct ListNode*cur=head;
struct ListNode*curnext=head->next;
while(cur){
cur->next=curprev;
curprev=cur;
cur=curnext;
if(curnext){//如果为空了,就不能指向下一个了
    curnext=curnext->next;
}    

}
return curprev;
}

链表的中间结点和链表中倒数第k个结点

链接: 链表的中间结点
链接: 链表中的倒数第k个结点

我为什么把这两个题放到一块呢,因为这两个题都用到了一个思想,那就是快慢指针,只不过实现的方式不一样

OJ第二篇(单链表)_第3张图片

我们既然要找到中间结点,并且只能从第一个结点开始找,我们可能最普通的思路就是先数一下链表的长度,最后再走一半的长度。
但其实我们可以用到这里所说的快慢指针,快指针一次走二个结点,慢指针一次走一个结点,当快指针走到了最后,慢指针就走到了中间

struct ListNode* middleNode(struct ListNode* head){
struct ListNode*slow=head,*fast=head;
while(fast->next){
    slow=slow->next;//慢的走一步
    if(fast->next->next!=NULL){
        fast=fast->next->next;//快的走两步
    }
    else{
        fast=fast->next;//最后只剩一个结点了
    }
}
return slow;
}

OJ第二篇(单链表)_第4张图片

这里是倒数第k个结点,我们先让快指针走k个,再让快慢指针一起以相同的速度走,直到快指针走到最后,慢指针就走到了倒数第k个。
特殊情况就是k大于链表长度,这是也是返回空

这两道题的思想其实是大差不差的,就是快慢指针的运动方式是不同的

struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
struct ListNode*slow=pListHead,*fast=pListHead;
while(k--){//快指针先往前走k步
    if(fast==NULL)//这是为了处理k大于链表长度的情况
    return NULL;
    fast=fast->next;
}
while(fast){
    fast=fast->next;
    slow=slow->next;
}
return slow;
}

合并两个有序链表

链接:合并两个有序链表
OJ第二篇(单链表)_第5张图片

这里的较为简单的思路就是让两个指针指向两个链表中最小的元素,比较出一个最小的摘出来,依次找到最小的放到它后面,
那么问题就来了,第一个摘出来的元素放到哪,我们给一个头指针负责记录返回值,给一个尾指针负责记录下一个元素放到哪。
特殊情况是什么呢,有一个链表为空,那么返回另一个链表的头结点就可以
正常情况下到最后一个链表已经摘完了,只需要将另一个链表续到尾结点之后就可以了

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
    if(list1==NULL)//处理开始链表为空的特殊情况
    return list2;
    if(list2==NULL)
    return list1;
struct ListNode*head=NULL,*tail=NULL;
while(list1&&list2){
    if(list1->val<list2->val){//第一个链表中的元素小
        if(head==NULL){//处理摘出来的第一个元素
            head=tail=list1;
            list1=list1->next;
        }
        else{//处理后面的元素
        tail->next=list1;
        tail=tail->next;
        list1=list1->next;
    }
    
    }
    else{//第二个链表中的元素小
             if(head==NULL){
            head=tail=list2;
            list2=list2->next;
        }
        else{
        tail->next=list2;
        tail=tail->next;
        list2=list2->next;
    }
    }
}
if(list1){//最后list2为空的话,把list1后面的续接到tail后面
    tail->next=list1;
}
if(list2){
    tail->next=list2;
}
return head;
}

判断链表是否为回文结构

链接:判断链表是否为回文结构
OJ第二篇(单链表)_第6张图片

这个题也确实不那么容易想到思路,我直接说吧,就是找到中间结点然后把中间结点后面的结点逆置,最后再逐个进行比较。怎么感觉这么熟悉呢,这不就是我们前面的两个题的综合嘛,这里直接上代码了

    bool isPalindrome(struct ListNode* A) {
        // write code here
       struct ListNode*mid=middleNode(A);//返回中间结点
       struct ListNode*midset= reverseList(mid);//逆置后半段,然后返回头结点,实际上就是原链表中最后一个结点
        while(A&&midset){//让两个结点依次进行比较
            if(A->val!=midset->val)
            return false;//如果有不同的就直接返回
            A=A->next;
            midset=midset->next;
        }
        return true;//最后比较完都相同
    }

两个函数跟前面的一模一样,做题的时候别忘了写上

你可能感兴趣的:(OJ题讲解,数据结构,算法,c语言)