链表
- NB1 删除链表峰值
- NB2 牛群排列去重
- NB3 调整牛群顺序
- NB4 牛群的重新分组
- NB5 牛群的重新排列
- NB6 合并两群能量值(合并有序单链表)
- NB7 牛群的能量值(单链表相加)
以下题全部出自牛客网。
题目题目考察的知识点链表:
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表的分类有单向、双向等多种类型。其中单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。单链表中的数据是以结点来表示的,每个结点的构成:元素 (数据元素 的映象) + 指针 (指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。
与链表有关的题基本都是插入,删除,交换顺序等,解决这些问题通常将链表的指针进行修改,有的简单有的复杂;比如删除链表的头结点和别的结点代码不一样,或者容易对空结点容易解引用等,这时可以加一个头结点,一些问题就可以得到很好的解决。
NB1 删除链表峰值
描述:
农场主人有一群牛,他给每只牛都打了一个编号,编号由整数表示。这些牛按照编号的大小形成了一个链表。现在农场主人想删除链表中比前后结点值都大的牛的编号,你能帮他设计一个算法来实现这个功能吗?注意,只考虑删除前,首尾的牛的编号不删除。
问题分析: 要删除链表中比前后结点值都大的编号,并且只考虑删除前,首尾的编号不删除,所以用三个指针:prev,cur,next,prev指向删除结点的前一个结点,cur指向将要删除的结点,next指向删除结点的下一个,这样就能很容易将结点连接起来。
代码如下:
ListNode* deleteNodes(ListNode* head)
{
// write code here
ListNode* prev = head, * cur = prev->next, * next = cur->next;
while (next)
{
if ((cur->val > prev->val) && (cur->val > next->val)) //删除cur,prev不移动
{
prev->next = next;
free(cur);
}
else //不删除cur,prev向后移动
{
prev = prev->next;
}
cur = next;
next = cur->next;
}
return head;
}
NB2 牛群排列去重
描述:
农场里有一群牛,每头牛都有一个独特的编号,编号由一个整数表示,整数范围是[0, 200]。牛群中的牛用单链表表示,链表已经按照非降序排列。
因为一些事故,导致一头牛可能多次出现在链表中。给你一个链表的头 head,删除链表中所有重复的编号,只留下所有牛的不重复编号。返回已排序的链表。
问题分析: 这道题就是单链表的删除,可以保留相同值结点的第一个,用三个指针:prev,cur,next,prev指向删除结点的前一个结点,cur指向将要删除的结点,next指向删除结点的下一个。
代码如下:
ListNode* deleteDuplicates(ListNode* head)
{
// write code here
if (head == nullptr)
return head;
ListNode* prev = head, * cur = prev->next, * next = cur->next;
while (cur) //因为可能需要删除结尾,所以结束标志为cur
{
if (cur->val == prev->val)
{
prev->next = next;
free(cur);
}
else
{
prev = prev->next;
}
cur = next;
next = cur->next;
}
}
NB3 调整牛群顺序
描述:
农场里有一群牛,每头牛都有一个编号,编号由一个整数表示,整数范围是[0, 100]。牛群中的牛用单链表表示。
现在,农场主想要调整牛群的顺序。给你一个链表,将链表中的倒数第 n 个结点移到链表的末尾,并且返回调整后的链表的头结点。
问题分析: 首先找出倒数第 n 个结点,使用快慢指针方法,然后这个倒数第 n 个结点可能是头结点,头结点可能是需要改变的,所以有两种思路:第一种,将改变的结点判断一下,若为头结点,则修改头结点的指针,若不是,则正常处理;第二种,构造一个新的头结点,然后再按正常处理就行(构造新的头结点可以很好的处理问题)。
如下为第二种思路,代码如下:
//NB3 调整牛群顺序
ListNode* moveNthToEnd(ListNode* head, int n)
{
// write code here
if (n == 1)
return head;
ListNode* newhead = new ListNode(-1);
newhead->next = head;
ListNode* cur = newhead;
ListNode* prev = newhead, * end = newhead; //prev指向的是交换结点的前一个指针
//寻找倒数第k个结点使用快慢指针方法
for (int i = 0; i < n; ++i)
{
end = end->next;
}
while (end->next)
{
prev = prev->next;
end = end->next;
}
cur = prev->next;
prev->next = cur->next;
end->next = cur;
cur->next = nullptr;
head = newhead->next; //头结点可能被改变,所以更新头结点
free(newhead);
return head;
}
NB4 牛群的重新分组
描述: 农场里有一群牛,每头牛都有一个编号,编号由一个整数表示,整数范围是[0, 1000]。牛群中的牛用单链表表示。
现在,农场主想要重新分组牛群。给定一个单链表的头指针 head 和一个整数 k,每 k 个节点一组进行翻转,请你返回修改后的牛群链表。
k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
问题分析: 这道题需要改变头结点,所以新建一个头结点,然后需要翻转单链表,翻转单链表可以用链表头插, 将需要翻转的结点一个一个头插到某个结点的后面,总之,这道题用头插不是很容易。
代码如下:
int isreverse(ListNode*& tmp, int& k) //判断是否翻转
{
int n = 0;
while (tmp)
{
++n;
if (n == k)
{
if (tmp->next == nullptr) //因为如果最后一个结点头插,最后一个结点的指针将头插到前一个,此时tmp后面就不为空,所以这里附空
{
tmp = nullptr;
}
return 1;
}
tmp = tmp->next;
}
return 0;
}
ListNode* reverseKGroup(ListNode* head, int k)
{
// write code here
ListNode* newhead = new ListNode(-1);
newhead->next = head;
ListNode* prev = newhead; //prev是开始进行翻转的前一个结点
ListNode* end = prev->next; //end是每次头插第一个结点
//头插k个结点
ListNode* cur = prev->next; //当前节点
ListNode* next = cur->next; //下一个结点
while (cur)
{
ListNode* tmp = cur; //tmp进行遍历
if (isreverse(tmp, k)) //够翻转,进行翻转
{
for (int i = 0; i < k; ++i)
{
if (i == 0)
{
end = cur;
}
//头插
cur->next = prev->next;
prev->next = cur;
//更新结点
cur = next;
if (cur->next)
next = next->next;
}
//连接结点
prev = end;
end->next = cur;
}
else
break;
if (tmp == nullptr) //如果全部翻转,将最后一个结点指空,end是每次头插第一个结点,也就是最后一个结点
{
end->next = nullptr;
break;
}
}
head = newhead->next;
delete(newhead);
return head;
}
NB5 牛群的重新排列
描述:
农场里有一群牛,每头牛都有一个编号,编号由一个整数表示,整数范围是[-500, 500]。牛群中的牛用单链表表示。
现在,农场主想要改变牛群的排列顺序。给定一个单链表的头指针 head 和两个整数 left 和 right,其中 left <= right。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的牛群链表。
问题分析:这道题可以用头插来做,也可以用栈来做,相比上一道要容易些,大致思路和上一个差不多;prev指向第一个翻转结点的前一个,end指向第一个翻转的结点,cur指向翻转的结点,next指向翻转结点的下一个结点,然后对第left到第right个的结点头插,最后在将头插的和未投插的结点连接起来。
ListNode* reverseBetween(ListNode* head, int left, int right)
{
// write code here
ListNode* newhead = new ListNode(-501);
newhead->next = head;
ListNode* prev = newhead; //prev是开始进行翻转的前一个结点
int n = 1;
while (n < left)
{
prev = prev->next;
++n;
}
ListNode* cur = prev->next; //cur是需要翻转的结点
ListNode* next = cur->next; //next是翻转的结点的下一个结点
ListNode* end = cur; //end是第left个结点
while (left <= right)
{
//头插
cur->next = prev->next;
prev->next = cur;
//更新结点
cur = next;
if (next)
next = next->next;
++left;
}
//连接
end->next = cur;
head = newhead->next;
delete newhead;
return head;
}
NB6 合并两群能量值(合并有序单链表)
描述:
农场里有两群牛,每群牛都有一定的能量值。能量值由一个整数表示,整数范围是[-100, 100]。每群牛的能量值已经按照非递增顺序排列,并存储在链表中。
现在,你需要将这两群牛的能量值合并为一个新的非递增链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
题目分析:这道题就是合并两个有序单链表,首先判断l1,l2是否有空,1. 若都为空,则返回空;2. 若一个为空,另一个不为空,则返回不为空的一个;3. 两个都不为空,开始遍历两个链表,比较两个结点的大小,插入大的那一个结点,若遍历某个链表为空,则将不为空的链表进行插入,否则继续遍历。
代码如下:
ListNode* mergeEnergyValues(ListNode* l1, ListNode* l2)
{
// write code here
ListNode* head, * cur;
//先判断是否有空
if (l1 == nullptr && l2 != nullptr)
return l2;
else if (l1 != nullptr && l2 == nullptr)
return l1;
else if (l1 == nullptr && l2 == nullptr)
return nullptr;
//给head附头
if (l1->val >= l2->val)
{
head = cur = l1;
l1 = l1->next;
}
else
{
head = cur = l2;
l2 = l2->next;
}
while (l1 || l2)//两个都空结束
{
//判断遍历时是否有空
if (l1 == nullptr && l2 != nullptr)
{
cur->next = l2;
return head;
}
else if (l1 != nullptr && l2 == nullptr)
{
cur->next = l1;
return head;
}
if (l1->val >= l2->val)
{
cur->next = l1;
cur = cur->next;
l1 = l1->next;
}
else
{
cur->next = l2;
cur = cur->next;
l2 = l2->next;
}
}
cur->next = nullptr;
return head;
}
ListNode* mergeEnergyValues(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;
}
NB7 牛群的能量值(单链表相加)
描述:
农场里有两群牛,每群牛都有一定的能量值。能量值由一个非负整数表示,且每头牛的能量值只有一位数字。能量值按照逆序的方式存储在链表中,即链表的第一个节点表示个位,第二个节点表示十位,以此类推。
现在,你需要将这两群牛的能量值相加,然后以相同的逆序形式返回表示和的链表。
注意:除了数字0之外,这两个数都不会以0开头。
问题分析:单链表相加,遍历两个链表,将两个链表相加,注意链表的长短不一样,还有就是最后一位需不需要进位。
ListNode* addEnergyValues(ListNode* l1, ListNode* l2)
{
ListNode* head = new ListNode(-1);
ListNode* cur = head;
int m = 0; //是否进位
//有一个链表为空则退出
while (l1 && l2)
{
int n = l1->val + l2->val + m;
m = 0;
if (n > 9)
m = 1;
cur->next = new ListNode(n%10);
cur = cur->next;
l1 = l1->next;
l2 = l2->next;
}
if (l1)
l2 = l1;
//if处理后,l2一定不为空
while (l2)
{
int n = l2->val + m;
m = 0;
if (n > 9)
m = 1;
cur->next = new ListNode(n % 10);
cur = cur->next;
l2 = l2->next;
}
//处理最后进位的数
if (m == 1)
cur->next = new ListNode(m);
return head->next;
}