【牛客网面试必刷TOP101】链表篇(二)
- 前言
- 刷题网站
- 刷题!
- BM4 合并两个排序的链表
- 思路一:双指针
- 思路二:递归(扩展思路)
- BM5 合并k个已排序的链表
- 思路一:归并排序思想
- BM6 判断链表中是否有环
- 思路一:双指针
- 总结
前言
链表是数据结构中重要的一个章节,他的重要性也不言而喻
面试都会遇到这类的题目,以下是链表的常考的题目。
刷题网站
https://www.nowcoder.com/activity/oj
刷题!
BM4 合并两个排序的链表
https://www.nowcoder.com/share/jump/6243324481684295523891
思路一:双指针
step 1:判断空链表的情况,只要有一个链表为空,那答案必定就是另一个链表了,就算另一个链表也为空。
step2:新建一个空的表头后面连接两个链表排序后的节点,两个指针分别指向两链表头。
step3:遍历两个链表都不为空的情况,取较小值添加在新的链表后面,每次只把被添加的链表的指针后移。
step4:遍历到最后肯定有一个链表还有剩余的节点,它们的值将大于前面所有的,直接连在新的链表后面即可。
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
//一个已经为空了,直接返回另一个
if(list1 == null)
return list2;
if(list2 == null)
return list1;
//加一个表头
ListNode head = new ListNode(0);
ListNode cur = head;
//两个链表都要不为空
while(list1 != null && list2 != null){
//取较小值的节点
if(list1.val <= list2.val){
cur.next = list1;
//只移动取值的指针
list1 = list1.next;
}else{
cur.next = list2;
//只移动取值的指针
list2 = list2.next;
}
//指针后移
cur = cur.next;
}
//哪个链表还有剩,直接连在后面
if(list1 != null)
cur.next = list1;
else
cur.next = list2;
//返回值去掉表头
return head.next;
}
}
思路二:递归(扩展思路)
step 1:每次比较两个链表当前节点的值,然后取较小值的链表指针往后,另一个不变,两段子链表作为新的链表送入递归中。
step2:递归回来的结果我们要加在当前较小值的节点后面,相当于不断在较小值后面添加节点。
step 3:递归的终止是两个链表有一个为空。
public ListNode Merge(ListNode list1,ListNode list2) {
//当一个已经为空,则返回另一个
if(list1==null){
return list2;
}
if(list2==null){
return list1;
}
//先放较小的值节点
if(list1.val<=list2.val){
//递归往下
list1.next=Merge(list1.next,list2);
return list1;
}else{
//递归往下
list2.next=Merge(list1,list2.next);
return list2;
}
}
BM5 合并k个已排序的链表
https://www.nowcoder.com/share/jump/6243324481684295501744
思路一:归并排序思想
分治:分治即“分而治之”,“分”指的是将一个大而复杂的问题划分成多个性质相同但是规模更小的子问题,子问题继续按照这样划分,直到问题可以被轻易解决;
“治”指的是将子问题单独进行处理。
经过分治后的子问题,需要将解进行合并才能得到原问题的解,因此整个分治过程经常用递归来实现。
step 1:从链表数组的首和尾开始,每次划分从中间开始划分,划分成两半,得到左边n/2个链表和右边n/2个链表。
step 2:继续不断递归划分,直到每部分链表数为1。
step3:将划分好的相邻两部分链表,按照两个有序链表合并的方式合并,合并好的两部分继续往上合并,直到最终合并成一个链表。
//合并两个链表
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2){
if(list1==null){
return list2;
}
if(list2==null){
return list1;
}
//加表头
ListNode head=new ListNode(0);
ListNode cur=head;
while(list1!=null&&list2!=null){
if(list1.val<=list2.val){
cur.next=list1;
list1=list1.next;
}
else{
cur.next=list2;
list2=list2.next;
}
cur=cur.next;
}
//哪个表有剩,加后面
if(list1!=null){
cur.next=list1;
}else{
cur.next=list2;
}
return head.next;
}
//划分合并区间
ListNode divideMerge(ArrayList<ListNode> lists,int left,int right){
if(left>right){
return null;
}else if(left==right){
return lists.get(left);
}
int mid=(left+right)/2;
//不断递归。直到每部分链表个数为1
return Merge(divideMerge(lists, left, mid), divideMerge(lists, mid + 1, right));
}
//最后合并每个数
public ListNode mergeKLists(ArrayList<ListNode> lists) {
//将每个数排序并合并
return divideMerge(lists,0,lists.size()-1);
}
}
BM6 判断链表中是否有环
https://www.nowcoder.com/share/jump/6243324481684295413149
思路一:双指针
我们可以用双指针技巧,同向访问的双指针,速度是快慢的,只要有环,二者就会在环内不断循环,且因为有速度差异,二者一定会相遇。
step 1:设置快慢两个指针,初始都指向链表头。
step 2:遍历链表,快指针每次走两步,慢指针每次走一步。
step3:如果快指针到了链表末尾,说明没有环,因为它每次走两步,所以要验证连续两步是否为NULL。
step4:如果链表有环,那快慢双指针会在环内循环,因为快指针每次走两步,因此快指针会在环内追到慢指针,二者相遇就代表有环。
public class Solution {
public boolean hasCycle(ListNode head) {
//先判断链表为空的情况
if(head == null)
return false;
//快慢双指针
ListNode fast = head;
ListNode slow = head;
//如果没环快指针会先到链表尾
while(fast != null && fast.next != null){
//快指针移动两步
fast = fast.next.next;
//慢指针移动一步
slow = slow.next;
//相遇则有环
if(fast == slow)
return true;
}
//到末尾则没有环
return false;
}
}
总结
链表题中要想到判断是否为空链表,要考虑特殊情况!
💓感谢阅读!