【牛客面试 TOP 101】链表篇(二)

目录

8.链表中倒数最后k个结点(简单)(快慢指针)

9.删除链表的倒数第n个节点(中等)(快慢指针)

10.两个链表的第一个公共结点(简单)(双指针)

11.链表相加(二)(中等)(反转链表)

12.单链表的排序(中等)(二分/归并排序+递归+快慢指针)

[13. 判断一个链表是否为回文结构(简单)(快慢指针+反转链表)](#13. 判断一个链表是否为回文结构(简单)(快慢指针+反转链表))

14.链表的奇偶重排(中等)(双指针)

15.删除有序链表中重复的元素-I(简单)(模拟+遍历)

16.删除有序链表中重复的元素-II(中等)(模拟+遍历)


8.链表中倒数最后k个结点(简单)(快慢指针)

题目:

思路:

流程演示:(来自于题解链表中倒数最后k个结点_牛客题霸_牛客网 (nowcoder.com)

代码:

java 复制代码
public class Solution {

    public ListNode FindKthToTail (ListNode pHead, int k) {
        ListNode fast = pHead;
        ListNode slow = pHead;
        //快指针先走k步
        for(int i=0;i<k;i++){
            if(fast==null){
                return null;
            }
            fast=fast.next;
        }
        //双指针同时开始走
        while(fast!=null){
            fast=fast.next;
            slow=slow.next;
        }
        return slow;
    }
}

9.删除链表的倒数第n个节点(中等)(快慢指针)

题目:

思路:

跟上一题差不多,区别就在于上面是找到对应的节点,这里是找到后要删除该节点,所以slow不能直接定位到该节点,而是定位到该节点的前一个节点,然后使用slow.next = slow.next.next进行删除,

同时要先写一个哑节点,指向head,fast和slow要从哑节点开始遍历(能完美解决删除头结点的情况),所以,fast要遍历n+1次,slow才开始走,跟上一题比,fast跟slow相差n+1个节点,(可以自己模拟一下就清晰了)

代码:

java 复制代码
public class Solution {
    
    public ListNode removeNthFromEnd(ListNode head, int n) {
        if (head == null || n <= 0) {
            return head;
        }
        // 使用哑节点(dummy node)简化边界情况处理
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode fast = dummy;
        ListNode slow = dummy;
        // 快指针先走 n+1 步
        for (int i = 0; i <= n; i++) {
            // 如果 n 大于链表长度,返回原链表
            if (fast == null) {
                return head;
            }
            fast = fast.next;
        }
        // 双指针同时移动,直到快指针到达末尾
        while (fast != null) {
            fast = fast.next;
            slow = slow.next;
        }
        // 删除目标节点
        if (slow.next != null) {
            slow.next = slow.next.next;
        }
        return dummy.next;
    }
}

10.两个链表的第一个公共结点(简单)(双指针)

题目:

(上面的示例写得不太清楚,反之只要知道,传入两个链表,找出他们的公共节点,没有则返回null)

思路:

依旧使用双指针,对于第一个链表,从头结点走到尾节点,一共是x1+y的距离,对于第二个链表则是x2+y的距离,如果我们给他们分别加上x2和x1,那么他们的距离就相等了,即x1+y+x2=x2+y+x1,也就是说,当两条链表分别走到尾时,不会停下,而是直接跳转到对面的链表继续遍历,这样操作最后一定会在公共点相遇,问题就解决了

代码:

java 复制代码
public class Solution {

    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        ListNode p1 = pHead1;
        ListNode p2 = pHead2;
        while(p1!=p2){
            p1=((p1==null)?pHead2:p1.next);
            p2=((p2==null)?pHead1:p2.next);
        }
        return p1;
    }
}

11.链表相加(二)(中等)(反转链表)

题目:

思路:

这题如果直接对其两个链表的尾节点开始相加的话会很麻烦,所以主要思路在于反转链表再相加,

第一次反转:先将原始的连个链表都反转了(反转方法看第一期),这样就方便对其上下链表的位数了,然后定义一个哑节点dummy用来永远指向答案链表头节点,定义一个pre用来辅助衔接答案到答案链表上,定义一个tmp用来存储相加的进位数

关键循环:每次循环都将两个链表的val值存进val中,然后使用val/10求出进位数tmp,然后使用val%10求出进位后的val,当作答案节点接入答案链表中。如果循环结束了但是还有进位tmp,则还要进行一次pre.next=new ListNode(tmp)操作

第二次循环:将求出的答案链表dummy.next反转即可得到答案

代码:

java 复制代码
public class Solution {

    public ListNode addInList (ListNode head1, ListNode head2) {
        head1=ReverseList(head1);
        head2=ReverseList(head2);
        ListNode dummy= new ListNode(0); //哑节点,用来指向头结点
        ListNode pre = dummy; //中间节点,用来连接下一个要接入的节点
        int tmp=0; //存储进位数
        while(head1!=null||head2!=null){
            int val=tmp; //赋值进位数给val
            if(head1!=null){
                val+=head1.val;
                head1=head1.next;
            }
            if(head2!=null){
                val+=head2.val;
                head2=head2.next;
            }
            tmp=val/10; //求出进位数tmp
            pre.next=new ListNode(val%10); //求出进位后的val
            pre=pre.next;
        }
        if(tmp>0){
            pre.next=new ListNode(tmp);
        }
        return ReverseList(dummy.next); //将求出的链表反转即可得到答案
    }

    public ListNode ReverseList (ListNode head) {
        ListNode pre=null;
        ListNode next=null;
        while(head!=null){
            next=head.next;
            head.next=pre;
            pre=head;
            head=next;
        }
        return pre;
    }
}

12.单链表的排序(中等)(二分/归并排序+递归+快慢指针)

题目:

思路:

这题的主要思路就是二分+递归 ,跟上一篇的"链表中的节点每k个一组翻转 "类似,先使用一个快慢指针找出当前链表的中间节点(注意起始位置和边界),然后将当前链表分成两部分分别递归,最后使用经典的哑节点dummy和辅助节点pre进行排序/合并左右链表,注意最后要判断是否合并完pre.next=((l!=null)?l:r),然后依次返回dummy.next即可

流程演示:(来自于题解单链表的排序_牛客题霸_牛客网 (nowcoder.com)

时间复杂度O(NlogN):N表示链表结点数量,二分归并算法O(NlogN)

空间复杂度O(1):仅使用常数级变量空间

代码:

java 复制代码
public class Solution {

    public ListNode sortInList (ListNode head) {
        //预处理/递归结束条件
        if (head == null || head.next == null) {
            return head;
        }
        //快慢指针找出中间节点
        ListNode fast=head.next;
        ListNode slow=head;
        while(fast!=null&&fast.next!=null){
            slow=slow.next;
            fast=fast.next.next;
        }
        //递归操作
        ListNode tmp=slow.next;//右半部分的头结点
        slow.next=null;//左半部分的边界
        ListNode l=sortInList(head);
        ListNode r=sortInList(tmp);
        //排序合并两个链表
        ListNode dummy=new ListNode(0);
        ListNode pre=dummy;
        while(l!=null&&r!=null){
            if(l.val<r.val){
                pre.next=l;
                l=l.next;
            }else{
                pre.next=r;
                r=r.next;
            }
            pre=pre.next;
        }
        pre.next=((l!=null)?l:r); //最后添加未对比的链表部分
        return dummy.next;
    }
}

13. 判断一个链表是否为回文结构(简单)(快慢指针+反转链表)

题目:

思路:

这题的主要思路就是先使用快慢指针 找出中间节点,将链表分为左右两部分,如果是奇数个节点(循环结束后fast!=null)则要将中间节点去除slow=slow.next,然后将后半部分的链表进行反转 ,反转后再与前半部分链表head进行逐一比较即可,循环结束条件是tmp!=null,只考虑后半部分即可,因为前后部分的节点都是一样数量的

流程演示:(来自于题解判断一个链表是否为回文结构_牛客题霸_牛客网 (nowcoder.com)

代码:

java 复制代码
public class Solution {

    public boolean isPail (ListNode head) {
        //快慢指针找出中间节点
        ListNode fast=head;
        ListNode slow=head;
        while(fast!=null&&fast.next!=null){
            fast=fast.next.next;
            slow=slow.next;
        }
        //如果fast不为空,说明链表的长度是奇数个,则跳过中间节点
        if(fast!=null){
            slow=slow.next;
        }
        //依次比较两个链表的值是否相等
        ListNode tmp=ReverseList(slow);
        while(tmp!=null){
            if(head.val!=tmp.val){
                return false;
            }
            head=head.next;
            tmp=tmp.next;
        }
        return true;
    }

    public ListNode ReverseList (ListNode head) {
        ListNode pre=null;
        ListNode next=null;
        while(head!=null){
            next=head.next;
            head.next=pre;
            pre=head;
            head=next;
        }
        return pre;
    }
}

14.链表的奇偶重排(中等)(双指针)

题目:

思路:

这题的思路主要是用两个链表分别记录奇数和偶数节点,最后再拼接在一起。因为是偶数在后面,所以使用一个哑节点evenHead来标记,奇数链表则直接用head即可。拆链表的时候先将偶数的下一个节点接入到奇数链表odd后(odd.next=even.next),偶数链表再将奇数节点的下一个接到自己后面(even.next=odd.next),循环条件even!=null是为了处理奇数个节点的链表的终止,even.next!=null是为了处理偶数个节点的链表的终止,最后别忘了将偶数链表的重新接在奇数链表后面(odd.next=evenHead)

流程演示:(来自于题解链表的奇偶重排_牛客题霸_牛客网 (nowcoder.com)

代码:

java 复制代码
public class Solution {

    public ListNode oddEvenList (ListNode head) {
        //预处理
        if(head==null||head.next==null){
            return head;
        }
        ListNode evenHead=head.next;//永远指向偶数链表的头结点
        ListNode odd=head;//记录奇数链表
        ListNode even=head.next;//记录偶数链表
        while(even!=null&&even.next!=null){
            odd.next=even.next;
            odd=odd.next;
            even.next=odd.next;
            even=even.next;
        }       
        odd.next=evenHead;//将偶数链表的重新接在奇数链表后面
        return head;
    }
}

15.删除有序链表中重复的元素-I(简单)(模拟+遍历)

题目:

思路:

这题很简单,使用一个辅助节点cur=head,一直遍历链表即可,如果当前节点的值跟下一个节点的值相等,就跳过cur.next=cur.next.next,否则正常遍历cur=cur.next

代码:

java 复制代码
public class Solution {

    public ListNode deleteDuplicates (ListNode head) {
        //预处理
        if(head==null||head.next==null){
            return head;
        }
        ListNode cur=head;
        while(cur!=null&&cur.next!=null){
            if(cur.val==cur.next.val){
                cur.next=cur.next.next;
            }else{
                cur=cur.next;
            }
        }
        return head;
    }
}

16.删除有序链表中重复的元素-II(中等)(模拟+遍历)

题目:

思路:

这题跟上一题的主要区别就是将出现两个及以上的数字全都删掉,而不是留下1个,因此辅助节点cur应该在每个要衔接的节点的前一个节点,方便全部跳过重复的,同时要有一个哑节点dummy,防止头结点出现重复的情况,方便删除头结点,最后就是核心的循环遍历链表了,遇到重复的值直接跳过即可,细节上看代码即可,思路跟上一题差不多

流程演示:(来自题解删除有序链表中重复的元素-II_牛客题霸_牛客网 (nowcoder.com)

代码:

java 复制代码
public class Solution {

    public ListNode deleteDuplicates (ListNode head) {
        //预处理
        if(head==null||head.next==null){
            return head;
        }
        ListNode dummy=new ListNode(0);//哑节点
        dummy.next=head;
        ListNode cur=dummy;//辅助节点
        while(cur.next!=null&&cur.next.next!=null){
            if(cur.next.val==cur.next.next.val){
                int temp=cur.next.val;
                while(cur.next!=null&&cur.next.val==temp){
                    cur.next=cur.next.next;
                }
            }else{
                cur=cur.next;
            }
        }
        return dummy.next;
    }
}

本篇文章到此结束,如果对你有帮助可以点个赞吗~

个人主页有很多个人总结的 Java、MySQL 等相关的知识,欢迎关注~

相关推荐
少许极端2 小时前
算法奇妙屋(二十三)-完全背包问题(动态规划)
java·算法·动态规划·完全背包
CoderCodingNo2 小时前
【GESP】C++五级练习(贪心思想考点) luogu-P1115 最大子段和
开发语言·c++·算法
Q741_1472 小时前
C++ 队列 宽度优先搜索 BFS 力扣 429. N 叉树的层序遍历 每日一题
c++·算法·leetcode·bfs·宽度优先
txinyu的博客2 小时前
make_shraed & make_unique 替代了new ? 什么场景使用new
开发语言·c++·算法
jinmo_C++2 小时前
Leetcode矩阵
算法·leetcode·矩阵
要加油哦~2 小时前
算法 | 整理数据结构 | 算法题中,JS 容器的选择
前端·javascript·算法
燃于AC之乐5 小时前
我的算法修炼之路--4 ———我和算法的爱恨情仇
算法·前缀和·贪心算法·背包问题·洛谷
MM_MS11 小时前
Halcon变量控制类型、数据类型转换、字符串格式化、元组操作
开发语言·人工智能·深度学习·算法·目标检测·计算机视觉·视觉检测
独自破碎E11 小时前
【二分法】寻找峰值
算法