链表OJ(上)----->(注意看我细节)

目录

本篇文章的主要内容

  1. 删除链表中所有值是val的节点
  2. 反转一个单链表
  3. 输入一个链表,返回链表的倒数第k个节点
  4. 返回链表的中间节点
  5. 链表的回文结构

在上一篇我们简单实现了单链表的增删查改的一些接口,那么在真实的面试环境下对于链表的考察并不是真的让你上手直接手写一个单链表。大多数场景下都会通过一些在线OJ的方式考察你的链表功底,这些题看似没有考你增删查改,却处处考察你你对链表增删查改的理解和掌握能力。

话不多说,开始正式进入我们今天的主题---->链表OJ

LeetCode  203    删除链表中所有值是val的节点

具体的题目要求如下:

 注意:我们之前的博客有介绍过,链表一共有8种结构,但如果OJ题目没有说明,默认情况下都是采用的没有带哨兵位头节点的单向不循环链表,也就是我们上篇博客中实现的那一种结构!

本题的思路是双指针法,一个指针记录前驱(prev),另一个指针记录当前的节点(cur),下面我们通过案例一来画图分析这个删除的流程

 画好了图,接下来我们开始实现代码:

struct ListNode* removeElements(struct ListNode* head, int val){
   struct ListNode* prev=NULL,*cur=head;
    while(cur)
    {
        if(cur->val !=val)
        {
            prev=cur;
            cur=cur->next;
        }
        else 
        {  
            //保存下一个节点
            struct ListNode* next=cur->next;
            prev->next=next;
            free(cur);
            cur=next;
        }
    }
    return head;
}

但在删除节点恰好是头节点的时候,会出现对空指针的解引用!

 所以我们最终改进的代码如下:

struct ListNode* removeElements(struct ListNode* head, int val){
   struct ListNode* prev=NULL,*cur=head;
    while(cur)
    {
        if(cur->val !=val)
        {
            prev=cur;
            cur=cur->next;
        }
        //需要删除的情况
        else 
        {   
             struct ListNode* next=cur->next;
            //删除头节点
            if(NULL==prev)
            {
                free(head);
                head=next;
                cur=next;
            }
            //保存下一个节点
           else
           {
              prev->next=next;
              free(cur);
               cur=next;
           }   
        }
    }
    return head;
}

2.LeetCode 206 反转单链表

 方法一:三个指针改变指针指向(并没有改变节点)

 

 解题代码:

struct ListNode* reverseList(struct ListNode* head){
    //没有节点和只有一个节点
    if(NULL==head || NULL==head->next)
    {
        return head;
    }
    struct ListNode* n0=NULL,*n1=head,*n2=head->next;
    while(n2)
    {   n2=n1->next;
        n1->next=n0;
        n0=n1;
        n1=n2;
        n2=n2->next;
    }
//连接上n1和n0,n1就是新的头节点
    n1->next=n0;
    return n1;
}

方法二:头插法反转单链表

我们知道链表头插就可以达到将数据反转的效果,而这也是题目所要求的,所以我们可以用头插法来完成这题

struct ListNode* reverseList(struct ListNode* head){
    struct ListNode* newhead=NULL;
    struct ListNode* cur=head;
    //头插法逆置
    while(cur)
    {
        struct ListNode* next=cur->next;
       //在头部插入数据
         cur->next=newhead;
      //新节点成为头节点
         newhead=cur;
         cur=next;
    }
    return newhead;
}

 有点难度了吧?接下来我们在来看第三道题:返回链表的倒数第k个节点

 本道题是在牛客网上的,在真实的面试环境中,面试官可能用牛客网进行考察,所以我们要学会看牛客网的测试用例以及输出!

这个测试用例要我们把链表1->2->3->4->5的返回倒数第一个数,有两种方法

方法一:求长度,把逆向改成正向

struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
    // write code here
    struct ListNode* cur=pListHead;
        int len=0;
        while(cur)
        {
            ++len;
            cur=cur->next;
        }
        //如果k大于长度,返回空
        if(len<k)
        {
            return NULL;
        }
        else
        {  cur=pListHead;
            int t=len-k;
            while(t--)
            {
                cur=cur->next;
            }
        }
        return cur;
    }

 这种方法简单易懂,但是走了两次循环,是否可以只用一次循环完成呢?可以,接下来我们使用的方法二就是利用快慢指针的方式来完成这个工作:

快慢指针的思路:快指针先走k步,慢指针开始和快指针同时走,当快指针走到空的时候,慢指针指向的节点就是倒数第k个节点。下面通过图片来形象讲解

 画好了图,有了思路分析清楚,代码写起来一跑就过:

/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 * };
 *
 * C语言声明定义全局变量请加上static,防止重复定义
 */

/**
 * 
 * @param pListHead ListNode类 
 * @param k int整型 
 * @return ListNode类
 */
struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
    // write code here
    
    struct ListNode*  slow=pListHead,*fast=pListHead;
    while(k--)
    {    if(NULL==fast)
      {
           return NULL;       
      }
        fast=fast->next;
    }
    while(fast)
    {
        slow=slow->next;
        fast=fast->next;
    }
    return slow;
}

 

 

 

Leetcode 876 返回中间节点

方法一还是用遍历求链表的长度,注意无论链表长度是奇数还是偶数,最终返回的节点的位置总是len/2+1,所以我们就可以写如下的代码

 

struct ListNode* middleNode(struct ListNode* head){
    int len=0;
    struct ListNode* cur=head;
    while(cur)
    {
        ++len;
        cur=cur->next;
    }
    int half=len/2+1;
    cur=head;
    while(--half)
    {
      cur=cur->next;
    }
    return cur;
}

当然我们也可以用快慢指针来解决这道题。

slow指针每次走一步,fast每次走两步,如果链表长度是奇数,那么fast走到尾节点的时候就是slow就是中间节点,当链表长

是偶数时,fast走到空的时候,slow就是中间节点。

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;
}

 快慢指针在链表的OJ题中应用特别多,尤其是在环状链表的题目中能大放异彩,对于环状链表的题目,我会在下一篇博客中详细介绍。

5.链表的回文结构

 回文就是左右对称,如果是数组,那么判断回文可以用下标进行比较,但是由于单链表的结构,因此判断单链表是否回文就相当困难!

那么处理这道题的方式就是就是找到中间节点,逆置中间节点以后的链表,接着从原链表的头和新链表的头进行比较得出结果,我们画图来分析:

 

class PalindromeList {
public:
    struct ListNode* reverseList(struct ListNode* head)
    {
        struct ListNode* cur=head,*newhead=NULL;
        while(cur)
        {
            struct ListNode* next=cur->next;
            cur->next=newhead;
            newhead=cur;
            cur=next;
        }
        return newhead;
    }
    bool chkPalindrome(ListNode* A) {
        struct ListNode* slow=A,*fast=A;
        while(fast && fast->next)
        {
            slow=slow->next;
            fast=fast->next->next;
        }
        struct ListNode* newhead=reverseList(slow);
        //遍历检查是否回文
       struct ListNode* cur=A;
       struct ListNode* ncur=newhead;
        while(ncur)
        {
            if(cur->val!=ncur->val)
            {
                return false;
            }
            cur=cur->next;
            ncur=ncur->next;
        }
        return true;
    }
};

 牛客网没有提供C语言的接口,所以这里我们选择C++,由于C++兼容C语言,所以我们完全可以使用纯C语言的方式进行解题。

这就是本篇博客的主要内容,如有不足之处希望指出,和热爱编程的同学一同进步

  • 11
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值