描述:
农场里有一群牛,它们被组织成一个链表形式的队列。每头牛都有一个编号(每只牛编号唯一),编号范围是[-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;
}
描述:
农场里有一群牛,每头牛都有一个编号,编号由一个整数表示,整数范围是[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;
}
描述:
农场里有一群牛,每头牛都有一个编号,编号由一个整数表示,整数范围是[-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;
}
描述:
农场主人有多个牛群,每个牛群中的牛都按照编号升序排列。现在农场主人想把所有牛群合并成一个大牛群,同时要求合并后的大牛群中的牛依然按照编号升序排列。请你编写一个程序,实现这个功能。
问题分析:这道题是合并多个有序链表,是在合并两个有序链表的基础上进行循环,用一个变量 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;
}
描述:
农场主人记录了一群牛的身高,并将它们按照链表的形式存储。链表的头结点为 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;
}
描述:
在一个牧场中,有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;
}
描述:
农场里有一些牛,每头牛都有一个编号(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;
}
描述:
农场里有一些牛,每头牛都有一个编号(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中链表题。