牛客网刷题之链表(二)

NB8 牛牛队列成环(判断是否有环)

描述:
农场里有一群牛,它们被组织成一个链表形式的队列。每头牛都有一个编号(每只牛编号唯一),编号范围是[-105, 105]。每头牛都有一个指针,指向它后面的一头牛。但是,有一些顽皮的牛可能会指向它们前面的某一头牛,从而形成一个环。
现在,给你一个链表的头节点 head,判断这个牛队列中是否有环。
在这里插入图片描述
问题分析:首先说一下这道题和链表中是否有环的区别:1. 其中链表有环,是最后结点的next指向前面的某个结点,构成一个环状;2.而这道题最后一个节点的next指向空,是没有环状结构的,但是前面结点中的值有的是重复的(每只牛编号唯一),这些重复的值可以构成“环状”,可以说是将值模拟成环状。

解题思路:链表中是否有环,通常用快慢指针来解决,慢指针走一步,快指针走两步,当有环时,快慢指针必相遇,为什么必相遇? 假设链表带环,两个指针最后都会进入环,快指针先进环,慢指针后进环。当慢指针刚进环时,可能就和快指针相遇了,最差情况下两个指针之间的距离刚好就是环的长度。此时,两个指针每移动一次,之间的距离就缩小一步,不会出现每次刚好是套圈的情况,因此:在满指针走到一圈之前,快指针肯定是可以追上慢指针的,即相遇。而且相遇之前这个环最多被遍历两边。 所以上面这道题就可以用快慢指针来解决,遍历重复数据最多将遍历两遍。

代码如下:

bool hasCycle(ListNode* head)
{
    
    
    // write code here
    ListNode* slow = head, * fast = head->next;
    while (fast != nullptr && fast->next != nullptr)
    {
    
    
        if (slow->val == fast->val)
            return true;
        slow = slow->next;
        fast = fast->next->next;

    }
    return false;
}

NB9 牛群分隔(重新排序)

描述:
农场里有一群牛,每头牛都有一个编号,编号由一个整数表示,整数范围是[0, 100],同时也表示牛的体重级别。牛群中的牛用单链表表示。
现在,农场主想要调整牛群的顺序,使体重较大的牛在一边,体重较小的牛在一边。给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有小于 x 的节点都出现在大于或等于 x 的节点之前。
你应当保留两个分区中每个节点的初始相对位置。
在这里插入图片描述
问题分析:要将单链表按照大小重新排序,使大的在一边,小的在一边,所以定义两个头结点,遍历单链表,一个头结点连接大的结点,另一个头结点连接小的结点,然后再进行合并,给nullptr的给nullptr即可。

代码如下:

ListNode* cow_partition(ListNode* head, int x)
{
    
    
    // write code here
    ListNode* headsmall = new ListNode(-1);
    ListNode* headgreat = new ListNode(-1);
    ListNode* cur = head, * cur1 = headsmall, * cur2 = headgreat;
    while (cur)
    {
    
    
        if (cur->val < x) //小的插入到headsmall
        {
    
    
            cur1->next = cur;
            cur1 = cur1->next;
        }
        else            //大的插入到headgreat
        {
    
    
            cur2->next = cur;
            cur2 = cur2->next;
        }
        cur = cur->next;
    }
    //将两个链表合并
    cur1->next = headgreat->next;
    cur2->next = nullptr;
    head = headsmall->next;
    //删除
    delete headsmall;
    delete headgreat;

    return head;
}

NB10 牛群旋转(链表旋转)

描述:
农场里有一群牛,每头牛都有一个编号,编号由一个整数表示,整数范围是[-100, 100]。牛群中的牛用单链表表示。
现在,农场主想要调整牛群的顺序。给你一个链表的头节点 head ,将链表每头牛向右移动 k 个位置。
在这里插入图片描述
问题分析:将链表向右移动 k 个位置,头结点就变成原来链表的倒数第k个结点,所以可以用快慢指针,找出倒数第k个位置的结点,然后将链表重新连接起来。倒数第k个位置结点的前一个结点的next需要修改为空,所以直接找倒数第k+1个结点。

代码如下:

ListNode* rotateLeft(ListNode* head, int k)
{
    
    
    //k可能比链表的长度大,所以先将k取余
    ListNode* cur = head;
    int length = 0;
    while (cur)
    {
    
    
        ++length;
        cur = cur->next;
    }
    k %= length;
    //快慢指针寻找第k+1个结点的位置
    ListNode* fast = head, * slow = head;
    while (k--)
    {
    
    
        fast = fast->next;
    }
    while (fast->next)
    {
    
    
        fast = fast->next;
        slow = slow->next;
    }
    //将链表重新进行连接
    fast->next = head;
    head = slow->next;
    slow->next = nullptr;

    return head;
}

NB11 牛群的合并(合并多个单链表)

描述:
农场主人有多个牛群,每个牛群中的牛都按照编号升序排列。现在农场主人想把所有牛群合并成一个大牛群,同时要求合并后的大牛群中的牛依然按照编号升序排列。请你编写一个程序,实现这个功能。
在这里插入图片描述
问题分析:这道题是合并多个有序链表,是在合并两个有序链表的基础上进行循环,用一个变量 head 指向合并的链表,第 i 次循环把第 i 个链表和 head 合并,答案保存到 head 中。

代码如下:

ListNode* mergeTwoLists(ListNode* l1, ListNode* l2)
{
    
    
    if (!l1 || !l2)
        return l1 ? l1 : l2;
    ListNode* head = new ListNode(-1);
    ListNode* cur = head, * p1 = l1, * p2 = l1;
    while (l1 && l2)
    {
    
    
        if (p1->val < p2->val)
        {
    
    
            cur->next = p1;
            p1 = p1->next;
        }
        else
        {
    
    
            cur->next = p2;
            p2 = p2->next;
        }
        cur = cur->next;
    }
    //至少有一个链表为空,cur->next连接不为空的链表
    cur->next = (p1 ? p1 : p2);

    cur = head->next;
    delete head;
    return cur;
}
ListNode* mergeKLists(vector<ListNode*>& lists)
{
    
    
    // write code here
    ListNode* head = nullptr;
    for (int i = 0; i < lists.size(); ++i)
        head = mergeTwoLists(head, lists[i]);
    
    return head;
}

NB12 牛群的身高排序(单链表排序)

描述:
农场主人记录了一群牛的身高,并将它们按照链表的形式存储。链表的头结点为 head,请你将这些身高数据按升序排列,并返回排序后的链表。
在这里插入图片描述
问题分析:这道题本意就是对单链表进行排序,第一种方法就是新建一个结点,进行插入,遍历,比较,排序,时间复杂度为O(n^2);第二种方法是归并排序 ,归并排序说简单些就是将一堆n个数分成n个块,然后将n个块弄成大点的n/2个块,直到合成一个块。那么如何归并呢?会合并两个有序单链表吧,也会寻找单链表的中间结点吧,在这两个算法基础上就可以对单链表进行排序。
在这里插入图片描述

归并排序代码如下:

//快慢指针寻找中间结点
ListNode* split(ListNode* head)
{
    
    
    ListNode* slow = head, * fast = head->next;

    while (fast != nullptr && fast->next != nullptr)
    {
    
    
        slow = slow->next;
        fast = fast->next->next;
    }

    ListNode* mid = slow->next;
    slow->next = nullptr;          //断尾

    return mid;
}
//合并两个有序链表
ListNode* mergeTwoLists(ListNode* head1, ListNode* head2)
{
    
    
    ListNode head(0), * p = &head;

    while (head1 != nullptr && head2 != nullptr)
    {
    
    
        if (head1->val < head2->val)
        {
    
    
            p->next = head1;
            head1 = head1->next;
        }
        else
        {
    
    
            p->next = head2;
            head2 = head2->next;
        }
        p = p->next;
    }
    //至少有一个链表为空,cur->next连接不为空的链表
    p->next = (head1 ? head1 : head2);

    return head.next;
}
ListNode* sortList(ListNode* head)
{
    
    
    // write code here
    if (head == nullptr || head->next == nullptr)
        return head;
    ListNode* head1 = head;
    ListNode* head2 = split(head);

    head1 = sortList(head1);
    head2 = sortList(head2);

    return mergeTwoLists(head1, head2);
}

插入代码如下:

ListNode* sortList(ListNode* head)
{
    
    
    // write code here
    if (head == nullptr || head->next == nullptr)
        return head;

    ListNode newhead(-1);
    newhead.next = head;
    ListNode* cur = head->next; //用来遍历head链表
    ListNode* p1 = &newhead; //用来进行插入
    newhead.next->next = nullptr; //末尾结点指针置空
    while (cur)
    {
    
    
        while (p1->next && cur->val > p1->next->val)
        {
    
    
        	p1 = p1->next;
        }

        ListNode* p2 = p1->next;
        p1->next = cur;
        ListNode* next = cur->next;
        cur->next = p2;
        cur = next;
        //插入完一个结点后,p1重新赋值
        p1 = &newhead;
    }
    return newhead.next;
}

NB13 牛的品种排序IV(0/1排序)

描述:
在一个牧场中,有n头牛,牛的品种分为黑牛和白牛,用0和1分别表示。现在需要对牛群进行排序,使得相同品种的牛相邻,并按照黑牛和白牛的顺序排列。这些牛是按照链表的形式存储的。

请你在不使用库内置的sort函数的情况下解决这个问题。
在这里插入图片描述
题目分析:这道题常规操作就是定义两个头结点,一个头插0结点,另一个头插1节点,然后将两个链表连接起来;也可以定义一个头结点,记录第一个0结点插入的位置,在头结点后面插0结点,在第一个0结点后插1结点,两种思路大致一样。

ListNode* sortCowsIV(ListNode* head)
{
    
    
    // write code here
    ListNode newhead(-1);
    newhead.next = nullptr;
    ListNode* end = &newhead; //记录第一个0结点插入的位置
    int i = 1;

    ListNode* cur = head;
    while (cur)
    {
    
    
        ListNode* next = cur->next;
        if (cur->val == 0)
        {
    
    
            cur->next = newhead.next;
            newhead.next = cur;

            if (i)  //记录第一次0的位置
            {
    
    
                end = cur;
                --i;
            }
        }
        else
        {
    
    
            cur->next = end->next;
            end->next = cur;
        }
    }
    return newhead.next;
}

NB14 牛群编号的回文顺序(是否回文)

描述:
农场里有一些牛,每头牛都有一个编号(0-9)。这些牛按照一定的顺序站立,我们可以把这个顺序看作是一个单链表,牛的编号就是链表的节点。现在农场主想知道,这些牛的编号顺序是否是回文的。如果是,返回 true ;否则,返回 false 。
在这里插入图片描述
题目分析:可以将链表中的数据保存到一个vector数组中,然后判断这个数组是否回文。

bool isPalindrome(ListNode* head)
{
    
    
    // write code here
    vector<int> v;
    while (head)
    {
    
    
        v.push_back(head->val);
        head = head->next;
    }
    for (int i = 0, j = v.size() - 1; i < j; ++i, --j)
    {
    
    
        if (v[i] != v[j])
        {
    
    
            return false;
        }
    }

    return true;
}

NB15 牛群编号的回文顺序II(回文2)

描述:
农场里有一些牛,每头牛都有一个编号(0-9)。这些牛按照一定的顺序站立,我们可以把这个顺序看作是一个单链表,牛的编号就是链表的节点。现在农场主想知道,这些牛的编号顺序是否是回文的。如果是,则返回空链表;如果不是,返回最大的连续回文子链表的头节点(保证唯一)。
在这里插入图片描述
问题分析:判断一个单链表是否是回文链表,如果是,则返回空;如果不是,则返回链表的最大回文子链表。这里投机取巧一下,将链表输入到一个数组中,再找这个数组中最大的回文子序列。

bool isPalindrome(ListNode* head)
{
    
    
    // write code here
    vector<int> v;
    while (head)
    {
    
    
        v.push_back(head->val);
        head = head->next;
    }
    for (int i = 0, j = v.size() - 1; i < j; ++i, --j)
    {
    
    
        if (v[i] != v[j])
        {
    
    
            return false;
        }
    }

    return true;
}
ListNode* maxPalindrome(ListNode* head)
{
    
    
    // write code here
    if (isPalindrome(head)) //如果是回文,返回nullptr
        return nullptr;

    vector<ListNode*> v; //将链表搞到一个数组里,在这个数组中找最大子链长
    int max = 0;
    int p1 = 0, p2 = 0;
    int start = 0, end = 0;

    while (head)
    {
    
    
        v.push_back(head);
        head = head->next;
    }

    for (int i = 0; i < v.size(); ++i)
    {
    
    
        p1 = i;
        p2 = i + 1;
        while ((p1 >= 0 && p2 + 1 < v.size()) && (v[p1]->val == v[p2]->val || v[p1]->val == v[p2 + 1]->val)) //回文的个数可能是奇数,所以不但要比较相邻两个数,还要比较相隔一个数的两个数
        {
    
    
            if (v[p1]->val == v[p2 + 1]->val && p2 - p1 == 2) //回文的个数是奇数,++p2
                ++p2;
            --p1;   
            ++p2;
        }
        
        if (p2 - p1 > max) //将最大的子序列进行标记
        {
    
    
            start = p1;
            end = p2;
            max = p2 - p1;
        }
    }
    v[end]->next = nullptr;

    return v[start];
}

以上为牛客网面试高频TOP202中链表题。

猜你喜欢

转载自blog.csdn.net/weixin_68278653/article/details/131858948
今日推荐