leetcode/牛客 链表面试题

一 

203. 移除链表元素

难度简单

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

示例 1:

输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]

示例 2:

输入:head = [], val = 1
输出:[]

示例 3:

输入:head = [7,7,7,7], val = 7
输出:[]

提示:

  • 列表中的节点数目在范围 [0, 104] 内
  • 1 <= Node.val <= 50
  • 0 <= val <= 50

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        ListNode* newhead = new ListNode(0);
        ListNode* tail = newhead;
        
        ListNode* cur = head;
        while(cur != nullptr)
        {
            if(cur->val != val)
            {
                tail->next = cur;
                tail = tail->next;
                cur = cur->next;
            }
            else
            {
                //tail->next = cur->next;
                cur = cur->next;
            }
        }
        tail->next = nullptr;
        return newhead->next;
    }
};

 现在看来真的是一道很基础的题了,最好的方法就是创建一个哨兵位,这样尾插就不需要考虑当前头结点是否为空了。

其次,基本思想就是逐个判断,不是则尾插,是则跳过。最后一行的tail->next = nullptr的原因是如上图的 5 -> 6 ,若没有这条语句,则6也会被带入返回链表中。 不写这条的替代方法就是else语句里tail->next = cur->next;也是同样的目的。

二 

206. 反转链表

难度简单

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

示例 1:

输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]

示例 2:

输入:head = [1,2]
输出:[2,1]

示例 3:

输入:head = []
输出:[]

提示:

  • 链表中节点的数目范围是 [0, 5000]
  • -5000 <= Node.val <= 5000
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* left = nullptr;
        ListNode* cur = head;
        while(cur!=nullptr)
        {
            ListNode* right = cur->next;
            cur->next = left;
            left = cur;
            cur = right;
        }
        return left;
    }
};

 没什么好说的,多练吧

 三

876. 链表的中间结点

难度简单

给定一个头结点为 head 的非空单链表,返回链表的中间结点。

如果有两个中间结点,则返回第二个中间结点。

示例 1:

输入:[1,2,3,4,5]
输出:此列表中的结点 3 (序列化形式:[3,4,5])
返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。
注意,我们返回了一个 ListNode 类型的对象 ans,这样:
ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL.

示例 2:

输入:[1,2,3,4,5,6]
输出:此列表中的结点 4 (序列化形式:[4,5,6])
由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。

提示:

  • 给定链表的结点数介于 1 和 100 之间。
class Solution {
public:
    ListNode* middleNode(ListNode* head) {
        ListNode* slow = head;
        ListNode* fast = head;
        while(fast && fast->next!=nullptr)
        {
            slow = slow->next;
            fast = fast->next->next;
        }
        return slow;
    }
};

 非常基础的一个快慢指针。

链表中倒数第k个结点(牛客)

知识点链表双指针

描述

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

示例1

输入:

1,{1,2,3,4,5}

复制返回值:

{5}

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
        if(nullptr == pListHead)
            return nullptr;
        ListNode* cur = pListHead;
        int num = 0;
        while(cur!=nullptr)
        { 
            cur = cur->next;
            num++;
        }
        if(!(k>=1&&k<=num))
            return nullptr;
        ListNode* fast = pListHead;
        ListNode* slow = pListHead;
        int n = 0;
        while(n++ != k)
        {
            fast = fast->next;
        }
        while(fast!=nullptr)
        {
            slow = slow->next;
            fast = fast->next;
        }
        return slow;
    }
};

求倒数第k个结点,如倒数第2个,则这个结点和尾结点之后的nullptr的距离就为2,则用双指针的知识,先创建一个快结点,走2步,然后快慢一起走,当快走到nullptr时,慢就是目标的倒数第二个结点。因为距离始终为2(k)。

其实链表长度num没有必要求,但是因为k可能不符合范围,所以求一下。 

五 

21. 合并两个有序链表

难度简单

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

示例 1:

输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

示例 2:

输入:l1 = [], l2 = []
输出:[]

示例 3:

输入:l1 = [], l2 = [0]
输出:[0]

提示:

  • 两个链表的节点数目范围是 [0, 50]
  • -100 <= Node.val <= 100
  • l1 和 l2 均按 非递减顺序 排列

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        ListNode* newnode = new ListNode{0,nullptr};
        ListNode* ret = newnode;
        while(list1&&list2)
        {
            if(list1->val<list2->val)
            {
                newnode->next = list1;
                list1 = list1->next;
            }
            else
            {
                newnode->next = list2;
                list2 = list2->next;
            }
            newnode = newnode->next;
        }
        if(list1)
        {
            newnode->next = list1;
        }
        else if(list2)
        {
            newnode->next = list2;
        }
        return ret->next;
    }
};

 创建哨兵位利于尾插,谁小则尾插谁。

输入:l1 = [1,2,4], l2 = [1,3,4] 输出:[1,1,2,3,4,4]

拿这个例子来说,其实结束之后l1的1指向了l2的1,l1的2指向了l2的3。

因为每次在新的链表中尾插下一个结点之前,上一个被尾插的结点的链表已经向前走一步了,所以不会导致链表的指向改变之后丢失next的情况,所以没问题。(若值拷贝一个新的结点进行尾插,就不会有这些顾虑了)

六 

CM11 链表分割

较难  通过率:21.95%  时间限制:3秒  空间限制:32M

知识点编程基础链表

描述

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

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};*/
class Partition {
public:
    ListNode* partition(ListNode* pHead, int x) {
        // write code here
        if(pHead == nullptr)
            return nullptr;
        ListNode* g = new ListNode(0);
        ListNode* l = new ListNode(0);
        ListNode* g_tail = g;
        ListNode* l_tail = l;
        ListNode* cur = pHead;
        while(cur!=nullptr)
        {
            if(cur->val>=x)
            {
                g_tail->next = cur;
                g_tail = g_tail->next;
                cur = cur->next;
            }
            else
            {
                l_tail->next = cur;
                l_tail = l_tail->next;
                cur = cur->next;
            }
        }
        g_tail->next = nullptr;
        l_tail->next = g->next;
        return l->next;
    }
};

设原链表为 1 3 8 5 6 4 7 1 9 4  x = 5 ;

此题不可以只创建一个新链表,然后遍历两边原链表进行尾插,若第一次尾插val<x的结点,第二次尾插val>=x的结点,这样是不对的,因为在第一次尾插后,3已经不指向8了,而是指向了4。

解决办法1:就是如上代码:单次遍历,创建两个链表,小于的结点存储在一个链表,大于的存储在另一个链表,最后让小链表的尾接上大链表的头。注意g_tail最后要将next置为空,如上例子,如果不将9的next置为空,则9默认指向4。

其实还有个办法,就是值拷贝如下所示,解决了上述创建一个链表,直接尾插时原链表的元素指向被改变的问题。进行值拷贝就不会出现上述问题了。

class Partition {
public:
    ListNode* partition(ListNode* pHead, int x) {
        // write code here
        ListNode* cur = pHead;
        ListNode* newhead = new ListNode(0);
        ListNode* tail = newhead;
        // 双次遍历,值拷贝
        while(cur != nullptr)
        {
            if(cur->val < x)
            {
                ListNode* newnode = new ListNode(cur->val);
                tail->next = newnode;
                tail = newnode;
            }
                cur = cur->next;
        }
        cur = pHead;
        while(cur != nullptr)
        {
            if(cur->val >= x)
            {
                ListNode* newnode = new ListNode(cur->val);
                tail->next = newnode;
                tail = newnode;
            }
                cur =cur->next;
        }
        return newhead->next;
    }
};

七 

OR36 链表的回文结构

较难  通过率:29.50%  时间限制:3秒  空间限制:32M

知识点链表

描述

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

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

测试样例:

1->2->2->1
返回:true
/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};*/
class PalindromeList {
public:
    bool chkPalindrome(ListNode* A) {
        // write code here
        // 找中间结点
        ListNode* slow = A;
        ListNode* fast = A;
        while(fast && fast->next)
        {
            slow = slow->next;
            fast = fast->next->next;
        }
        ListNode* mid = slow; // 中间结点
        // 逆转中间结点
        ListNode* left = nullptr;
        ListNode* cur = mid;
        while(cur != nullptr)
        {
            ListNode* right = cur->next;
            cur->next = left;
            left = cur;
            cur = right;
        }
        // 此时left就是那个中间结点
        mid = left;
        while(mid != nullptr)
        {
            if(A->val != mid->val)
                return false;
            A = A->next;
            mid = mid->next;
        }
        return true;
    }
};

思路: 1 2 3 4 3 2 1 则先找中间结点,为4,然后以中间结点为头结点,进行链表逆置,之后为1 2 3 1 2 3 4  其实第一个3仍然指向4,因为在逆置 4 3 2 1 时并没有改变第一个3的指向,然后以逆置后的头结点和最初的头结点为开始,进行遍历判断是否相等。 

1 2 3 4 4 3 2 1 仍然将后面的4 3 2 1逆置,后为1 2 3 4 1 2 3 4 第一个4仍然指向第二个4,遍历判断是否相等。

突然有一种新的想法,可否创建一个双向链表,然后逐个插入,之后从头和尾向中间遍历判断。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值