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

目录

本篇文章的主要内容

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

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

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

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

具体的题目要求如下:

链表OJ(上)----->(注意看我细节)_第1张图片

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

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

链表OJ(上)----->(注意看我细节)_第2张图片

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

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

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

链表OJ(上)----->(注意看我细节)_第3张图片

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

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

链表OJ(上)----->(注意看我细节)_第4张图片

2.LeetCode 206 反转单链表

链表OJ(上)----->(注意看我细节)_第5张图片

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

 链表OJ(上)----->(注意看我细节)_第6张图片

 解题代码:

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

链表OJ(上)----->(注意看我细节)_第7张图片

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

链表OJ(上)----->(注意看我细节)_第8张图片

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

这个测试用例要我们把链表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(lennext;
            }
        }
        return cur;
    }

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

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

链表OJ(上)----->(注意看我细节)_第9张图片

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

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

 

 链表OJ(上)----->(注意看我细节)_第10张图片

 

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.链表的回文结构

链表OJ(上)----->(注意看我细节)_第11张图片

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

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

链表OJ(上)----->(注意看我细节)_第12张图片

 

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语言的方式进行解题。

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

你可能感兴趣的:(数据结构,链表)