链表面试题,彻底巩固你的数据结构链表知识~

Posted 再吃一个橘子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了链表面试题,彻底巩固你的数据结构链表知识~相关的知识,希望对你有一定的参考价值。

1.删除链表中等于给定值 val 的所有节点

AC代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* removeElements(struct ListNode* head, int val){
    struct ListNode* prev = NULL, *cur = head;
    while(cur)
    {
        if(cur->val == val)
        {
            //1.头删
            //2.中间删除
            if(cur == head)
            {
                head = cur->next;
                free(cur);
                cur = head;
            }
            else
            {
                prev->next = cur->next;
                free(cur);
                cur = prev->next;
            }
        }
        else 
        {
            //迭代往后走
            prev = cur;
            cur = cur->next;
        }
    }
    return head;   
}

2.反转一个单链表

AC代码

思路1:

struct ListNode* reverseList(struct ListNode* head){
    if(head == NULL)
       return NULL;
    struct ListNode* n1, *n2, *n3;
    n1 = NULL;
    n2 = head;
    n3 = head->next;
    
    while(n2)
    {
        //翻转
        n2->next = n1;

        //迭代往后走
        n1 = n2;
        n2 = n3;
        if(n3 != NULL)
        {
            n3 = n3->next;
        }
    }
    return n1;
}

思路2:头插法

//思路2:头插
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;
}

3.给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。

思路:

其实有很简单也很low的思路,就是遍历这个链表两次【  时间复杂度是O(N) 】,即:

————第一次遍历链表,去查看这个链表的长度。

————第二次遍历链表,去查看这个链表的中间结点,所以时间复杂度是F(n) = n + n/2 = 1.5n,即:O(N)

优化的算法思路:

仅仅需要遍历一次即可!!!

——————快慢指针!

慢指针走一步,快指针走两步,则对于链表长度(奇数/偶数)分成两种情况:

  1. 奇数链表,fast指到最后一个节点
  2. 偶数链表,fast指到NULL

动态演示:

AC代码:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */


//双指针解法:
struct ListNode* middleNode(struct ListNode* head)
{
    struct ListNode* fast, *slow;
    fast = slow = head;
    while(fast && fast->next)
    {
        //慢指针1次走1步
        //快指针1次走2步
        slow = slow->next;
        fast = fast->next->next;
    }
    return slow;
}

4.输入一个链表,输出该链表中倒数第k个结点

奇妙思路1~:

快慢指针,保持fast和slow之间有k个距离。

 

考虑不周全代码:

/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 * };
 */

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

——————想想是否是错误的代码??

比如:测试用例6,{1,2,3,4,5}

——————链表长度一共才5个,那么想要找到倒数第6个结点,是肯定没有的,返回NULL即可。

——————————所以,fast往后移动k位的时候,需要判断fast是否==NULL,如果==NULL说明k大于链表长度!!(悟~

 完善代码:

/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 * };
 */

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

奇妙思路2~:

计数器

struct ListNode* FindKthToTail(struct ListNode* pListHead, int k) {
    // write code here
    int count = 0;
    struct ListNode* cur = pListHead;
    while (cur) 
    {
        count++;
        cur = cur->next;
    }
    cur = pListHead;
    while (cur)
    {
        if (count == k)
            return cur;
        else if (count < k)
            return NULL;
        count--;
        cur = cur->next;
    }
    return 0;
}

5.将两个有序链表合并为一个新的有序链表并返回。(新链表是通过拼接给定的两个链表的所有节点组成的)

思路:

不完善代码未AC代码:

——————没有考虑到特殊情况:

————链表其中一个是NULL的时候,tail->next是野指针。所以需要先判断l1  、l2是不是其中有一个是NULL!!

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */

//1.尾插
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
    struct ListNode* head = NULL , *tail = NULL;
    while(l1 && l2)
    {
        if(l1->val < l2->val)
        {
            //头节点
            if(head == NULL)
            {
                head = tail = l1;
            }
            else 
            {
                tail->next = l1;
                tail = tail->next;
            }
            l1 = l1->next;
        }
        else
        {
            //头节点
            if(head == NULL)
            {
                head = tail = l2;
            }
            else 
            {
                tail->next = l2;
                tail = tail->next;
            }
            l2 = l2->next;
        }
    }
    if(l1)
    {
        tail->next = l1;
    }
    else if(l2)
    {
        tail->next = l2;
    }

    return head;
}

改善代码的AC代码:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */

//1.尾插
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
    //如果一个链表为NULL,直接返回另一个链表
    if(l1 == NULL)
    {
        return l2;
    }

    if(l2 == NULL)
    {
        return l1;
    }
    struct ListNode* head = NULL , *tail = NULL;
    while(l1 && l2)
    {
        if(l1->val < l2->val)
        {
            //头节点
            if(head == NULL)
            {
                head = tail = l1;
            }
            else 
            {
                tail->next = l1;
                tail = tail->next;
            }
            l1 = l1->next;
        }
        else
        {
            //头节点
            if(head == NULL)
            {
                head = tail = l2;
            }
            else 
            {
                tail->next = l2;
                tail = tail->next;
            }
            l2 = l2->next;
        }
    }
    if(l1)
    {
        tail->next = l1;
    }
    else if(l2)
    {
        tail->next = l2;
    }

    return head;
}

完善精简的AC代码:

struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
    struct ListNode* head = NULL , *tail = NULL;
    //如果一个链表为NULL,直接返回另一个链表
    if(l1 == NULL)
    {
        return l2;
    }

    if(l2 == NULL)
    {
        return l1;
    }
    
    //先取一个小的做第一个结点
    if(l1->val < l2->val)
    {
        head = tail = l1;
        l1 = l1->next;
    }
    else 
    {
        head = tail = l2;
        l2 = l2->next;
    }

    //链接
    while(l1 && l2)
    {
        if(l1->val < l2->val)
        {
            tail->next = l1;
            tail = tail->next;
            l1 = l1->next;
        }
        else
        {
            tail->next = l2;
            tail = tail->next;
            l2 = l2->next;
        }
    }
    if(l1)
    {
        tail->next = l1;
    }
    else if(l2)
    {
        tail->next = l2;
    }

    return head;
}

但是我们发现,取一个小的作起始结点判断很麻烦(重复冗长),所以我们可以采用带哨兵位的头节点的方式来省去判断起始结点

————注意malloc以后的结点,最好要free掉,不free虽然能编译运行成功,但是又内存泄漏的风险。所以,在free(head)之前需要记录一下head的值再free(head)。

再完善的终极代码:

//1.尾插
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){

    //如果一个链表为NULL,直接返回另一个链表
    if(l1 == NULL)
    {
        return l2;
    }

    if(l2 == NULL)
    {
        return l1;
    }
    
    // //先取一个小的做第一个结点
    // if(l1->val < l2->val)
    // {
    //     head = tail = l1;
    //     l1 = l1->next;
    // }
    // else 
    // {
    //     head = tail = l2;
    //     l2 = l2->next;
    // }
    struct ListNode* head = NULL , *tail = NULL;
    //带哨兵位
    head = tail = (struct ListNode*)malloc(sizeof(struct ListNode));


    //链接
    while(l1 && l2)
    {
        if(l1->val < l2->val)
        {
            tail->next = l1;
            tail = tail->next;
            l1 = l1->next;
        }
        else
        {
            tail->next = l2;
            tail = tail->next;
            l2 = l2->next;
        }
    }
    if(l1)
    {
        tail->next = l1;
    }
    else if(l2)
    {
        tail->next = l2;
    }

    struct ListNode* list = head->next;
    free(head);
    return list;
}

以上是关于链表面试题,彻底巩固你的数据结构链表知识~的主要内容,如果未能解决你的问题,请参考以下文章

数据结构 Java 版玩转链表链表面试题及个人题解

数据结构 Java 版玩转链表单链表+链表面试题

Java 数据结构——单链表面试题

Java 数据结构——单链表面试题

链表面试题之判断是否为回文链表—三种方式解决,总有一种方法让面试官满意

链表面试题 02.01. 移除重复节点(链表不一定有序)