【链表面试题考察】

简介: 以下题目均为IO型。1.给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。

以下题目均为IO型。

1.给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点a549cf92e7f840eb8fddb6b1f68d7853.png

题目示例如上:

解题思路:

双指针问题,给定指针prev和cur,从头结点开始往后遍历,分两种情况讨论:

  1. 假如头节点为要删除的节点
  1. 假如中间节点为要删除的节点

prev记录要删除节点的前一个节点,cur记录要删除的节点

假如是第一种情况,cur->val == val时,

            if(cur == head)//1.头删
            {
                head = cur->next;
                free(cur);
                cur = head;
            }

假如是第二种情况,

prev->next = cur->next;
free(cur);
cur = prev->next;

10055f71c39d45319a213171ec5477ae.png

完整代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
//画图分析才会写
typedef struct ListNode ListNode;
struct ListNode* removeElements(struct ListNode* head, int val)
{
    ListNode*cur = head;
    ListNode*prev = head;
    while(cur)
    {
        if(cur->val==val)
        {
            if(cur == head)//1.头删
            {
                head = cur->next;
                free(cur);
                cur = head;
            }
            else//2.中间删除
            {
                prev->next = cur->next;
                free(cur);
                cur = prev->next;
            }
        }
        else
        {
            prev = cur;
            cur = cur->next;
        }
    }
    return head;
}

2.给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

849a9abae3664e3289ec3d5c687863ee.png

反转链表较为简单,下面介绍两种方法:

  1. 三指针遍历法

image.gif

给定三个指针prev为NULL,cur = head,next = cur->next,这样往后遍历即可。

typedef struct ListNode ListNode;
法1,三指针往后迭代,prev,cur,next往后,画图才会写
struct ListNode* reverseList(struct ListNode* head)
{
    ListNode*prev = NULL;
    ListNode*cur = head;
    while(head)
    {
        cur = cur->next;
        head ->next = prev;
        prev = head;
        head = cur;
    }
    return prev;
}
  1. 头插法

febe7922ebbe0e5262424aafbcc11817.gif

typedef struct ListNode ListNode;
法2,头插
struct ListNode* reverseList(struct ListNode* head)
{
    ListNode*newhead = NULL;
    ListNode*cur = head;
    while(cur)
    {
        ListNode*next = cur->next; 
        //头插
        cur->next = newhead;
        newhead = cur;
        //迭代往后
        cur = next;
    }
    return newhead;
}

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

1b9089415f2f49f6a9a7d2118f376ab0.png

只需要使用两个指针,fast和slow指针即可,快指针一次走两步,慢指针一次走一步,这样快指针走完时,慢指针一定走到中间节点。


5d16147d6f661fb6ab2a76fd5224f390.gif

//最优解:快慢指针,慢指针一次走一步,快指针一次走两步
struct ListNode* middleNode(struct ListNode* head)
{
    struct ListNode*slow = head,*fast = head;
    while(fast && fast->next)
    {
        fast = fast->next->next;
        slow = slow->next;
    }
    return slow;
}

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

e585b7f0ba0c4ae4acf9ffb9c0dc512b.png

解题思路:仍然使用快慢指针:但是这样的快慢指针不是像上一道题快指针走两步,慢指针走一步。

这里的快慢指针是快指针先走,慢指针后走。

fast先走k步,然后fast和slow同时走。

当fast == NULL时,slow即为倒数第k个

原因:快指针先走的k步,消除了倒数的概念。

typedef struct ListNode ListNode;
struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) 
{
    ListNode*fast = pListHead,*slow = pListHead;
    int count =0;
    while(count <k)
    {
        if(fast == NULL)
        {
            return NULL;
        }
        fast = fast ->next;
        count++;
    }
    while(fast)
    {
        slow = slow->next;
        fast = fast->next;
    }
    return slow;
}

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

de2cc8382d4044d88329a68829b44180.png

解题思路:


使用带哨兵位的头节点。2。不使用带哨兵位的头节点


使用带哨兵位的头节点时,好处在于不需要将最小的节点作为头节点,劣势在于由于哨兵位的头节点是malloc出来的,使用完之后需要释放,在释放之间还需要保存哨兵位的头节点的next。

29e9ee12daa7a663c24776e6cdfc24bf.gif

哨兵位的头节点的好处是,不需要再把第一个小的值给head
typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
带哨兵位的头解法:
    if(list1==NULL)
        return list2;
    if(list2 == NULL)
        return list1;
    //带哨兵位的头节点
    ListNode*head = NULL;
    head = (ListNode*)malloc(sizeof(ListNode));
    ListNode*tail = head;
    while(list1 && list2)
    {   
        if(list1->val < list2->val)
        {
            tail->next = list1;
            tail = list1;
            list1 = list1->next;
        }
        else
        {
            tail->next = list2;    
            tail = list2;
            list2 = list2->next;
        }
    }
    if(list1)
    {
        tail->next = list1;
    }
    if(list2)
    {
        tail->next = list2;
    }
    //注意这里,不能用prevhead = head,然后return prevhead->next
    //因为prev = head,head已经free了,相当于prev非法访问了。
    //所以只能prev = head->next,return prev;
    ListNode*prevhead = head->next;
    free(head);
    return prevhead;
}

6.现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。

51ee1e4cadbb460583c5a7c53548dea9.png举个简单的例子:

dc54b7e30702475eb406c08d91cf443d.png

上面的链表是原链表,定值x是4,也就是小于4的节点需要在前面,大于4的节点在后面,并且不能改变原来链表的节点的顺序。下面的链表即为题目要求的链表。

注意:


7bb839c224a14375bf2be58b26918a53.png

由于原链表中的6的next仍然指向2,会造成出现环形链表的情况,会造成死循环,所以需要将6的next 置为NULL。

思路:将小于定值x和大于定值x的链表分开,分别连接后,最后在将小的链表和大的链表连接起来。

2d65997e6c9d43568a43f9ea788a5bec.png

class Partition {
  public:
    ListNode* partition(ListNode* pHead, int x) {
        // write code here
        struct ListNode*LessHead ,*LessTail,*GreaterHead,*GreaterTail;
        LessHead=LessTail=GreaterHead=GreaterTail= NULL;
        LessTail = LessHead= (struct ListNode*)malloc(sizeof(struct ListNode));
        GreaterTail= GreaterHead = (struct ListNode*)malloc(sizeof(struct ListNode));
        struct ListNode* cur = pHead;
        while (cur) 
        {
            if (cur->val < x)
            {
                LessTail->next = cur;
                LessTail = cur;
            } 
            else 
            {
                GreaterTail->next = cur;
                GreaterTail = cur;
            }
            cur = cur ->next;
        }
        //到这里说明cur走完链表了,此时需要将两个链表连接起来
        LessTail->next = GreaterHead->next;
        //注意:一定要将GreaterTail->next = NULL
        //假如链表为2->3->5->7->6->2,GreaterTail->next还指向2
        //造成链表成环形了,所以要置空
        GreaterTail->next = NULL;
        ListNode* newnode = LessHead->next;
        free(LessHead);
        free(GreaterHead);
        return newnode;
    }
};

7.

对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。

给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。

6fc88f5402704185ad859ba75388eb3c.png

思路:所谓的回文结构,其实就是判断链表是否对称。

  1. 先找出链表的中间节点,然后以该中间节点为界,将链表分割。
  2. 前面的链表称为list1,后面的链表称为list2。

将list2逆置,再将list1和list2的节点逐个判断,如果list1或者list2的节点都相等,那就是回文结构。

31338f5312934c051bfa291c08efe34b.gif

//链表回文的思路:
// 1、先找出链表的中间节点,(偶数个找第二个中间节点)
// 2.中间节点作为新的链表头进行逆置。
// 将新逆置后的链表和原链表的val相比,(因为原链表的某些节点的next存储着新链表的某些节点的地址)
ListNode* MidListNode(ListNode* A)
{
    ListNode*slow =A,*fast = A;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
    }
    return slow;
}
ListNode* reverseListNode(ListNode* mid)
{
    ListNode*prev = NULL;
    ListNode*cur = mid;
    while(cur)
    {
        ListNode*pnext = cur->next;
        cur->next = prev;
        prev = cur;
        //迭代往后
        cur = pnext;
    }
    return prev;
}
class PalindromeList {
public:
    bool chkPalindrome(ListNode* A) {
        // write code heretypedef struct ListNode ListNode;
        // write code here
        ListNode*mid = MidListNode(A);
        ListNode*rhead = reverseListNode(mid);
        ListNode*curA = A;
        ListNode*curR = rhead;
        while(curA && curR)
        {
            if(curA->val!=curR->val)
            {
                return false;
            }
            else
            {
                curA = curA->next;
                curR = curR->next;
            }
        }
        return true;
    }
};

8.给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null

5b14d74c1176433c8290ec54663298e6.png

思路:给定一个fast指针和slow指针,先分别遍历两个链表,记录长度,让fast指针指向长链表,fast指针先走 长链表长度-链表长度,随后slow指针从短链表的头开始,和fast链表一起往后走,每个指针一次走一步,如果相等,则相等的点为相交点。


原因:快指针先走的长度差消除了长短链表的长度差。

 typedef struct ListNode ListNode;
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) 
{
    ListNode*curA = headA;
    ListNode*curB = headB;
    int i = 0;
    int j = 0;
    //先记录两个链表的长度,随后让长链表先走k步,再一起走,就消除了长度差,时间复杂度为O(N),空间复杂度为O(1);
    //还可以再遍历完两个链表的同时,记录两个链表的尾结点,如果尾结点不相同,那肯定不相交,直接返回NULL
    ListNode*TailA = NULL;
    ListNode*TailB = NULL;
    while(curA)
    {
        ++i;
        TailA = curA;
        curA = curA->next;
    }
    while(curB)
    {
        ++j;
        TailB = curB;
        curB = curB->next;
    }
    if(TailA !=TailB)
    {
        return NULL;
    }
    if(i>j)
    {
        int k = i-j;
        curA = headA;
        curB = headB;
        while(k--)
        {
            curA = curA->next;//长链表先走k步
        }
        while(curB && curA)
        {
            if(curA == curB)
            {
                return curA;
            }
            else
            {
                curA = curA->next;
                curB = curB->next;
            }
        }
    }
    else
    {
        int k = j-i;
        curA = headA;
        curB = headB;
        while(k--)
        {
            curB = curB->next;//长链表先走k步
        }
        while(curB && curA)
        {
            if(curA == curB)
            {
                return curA;
            }
            else
            {
                curA = curA->next;
                curB = curB->next;
            }
        }
    }
    return NULL;
}

 

相关文章
|
6月前
|
存储 算法 索引
链表面试题
链表面试题
链表面试题
|
5月前
|
存储 SQL 算法
LeetCode 83题:删除排序链表中的重复元素【面试】
LeetCode 83题:删除排序链表中的重复元素【面试】
|
6月前
【一刷《剑指Offer》】面试题 17:合并两个排序的链表
【一刷《剑指Offer》】面试题 17:合并两个排序的链表
|
6月前
【一刷《剑指Offer》】面试题 16:反转链表
【一刷《剑指Offer》】面试题 16:反转链表
|
6月前
【一刷《剑指Offer》】面试题 15:链表中倒数第 k 个结点
【一刷《剑指Offer》】面试题 15:链表中倒数第 k 个结点
|
6月前
|
机器学习/深度学习
【一刷《剑指Offer》】面试题 13:在 O(1) 时间删除链表结点
【一刷《剑指Offer》】面试题 13:在 O(1) 时间删除链表结点
|
6月前
【一刷《剑指Offer》】面试题 5:从尾到头打印链表
【一刷《剑指Offer》】面试题 5:从尾到头打印链表
|
6月前
|
算法 搜索推荐 索引
数据结构与算法⑥(第二章OJ题,下)后八道链表面试题(下)
数据结构与算法⑥(第二章OJ题,下)后八道链表面试题
46 0
|
6月前
|
算法 程序员 索引
数据结构与算法⑥(第二章OJ题,下)后八道链表面试题(中)
数据结构与算法⑥(第二章OJ题,下)后八道链表面试题
41 0
|
6月前
|
算法 C语言 C++
数据结构与算法⑥(第二章OJ题,下)后八道链表面试题(上)
数据结构与算法⑥(第二章OJ题,下)后八道链表面试题
32 0