草庐IT

【刷题笔记】之牛客面试必刷TOP101(1)

快到锅里来呀 2023-04-15 原文

目录

1. 反转链表(双链表头插法 / 栈)

2.链表内指定区间反转

3. 链表中的节点每k个一组翻转

4. 合并两个排序的链表

5. 合并k个已排序的链表 


链接 :牛客面试必刷TOP101

1. 反转链表(双链表头插法 / 栈)

题目链接 反转链表_牛客题霸_牛客网 (nowcoder.com)

题目要求

题目分析(新建链表头插法)

public class Solution {
    public ListNode ReverseList(ListNode head) {
        ListNode newHead = null;
        while(head != null) {
            ListNode tmp = head.next;
            //使用头插法
            head.next = newHead;
            newHead = head;
            head = tmp;
        }
        return newHead;
    }
}

题目分析(栈)

import java.util.Stack;

public class Solution {
    public ListNode ReverseList(ListNode head) {
        Stack<ListNode> stack = new Stack<>();
        //入栈
        while(head != null) {
            stack.push(head);
            head = head.next;
        }
        if(stack.isEmpty()) {
            return null;
        }
        //出栈
        ListNode node = stack.pop();
        ListNode newHead = node;
        while(!stack.isEmpty()) {
            ListNode tmpNode = stack.pop();
            //建链表
            node.next = tmpNode;
            node = node.next;
        }
        //走到这里栈空 新链表建成
        node.next = null;
        return newHead;
    }
}

2.链表内指定区间反转

题目链接 链表内指定区间反转_牛客题霸_牛客网 (nowcoder.com)

题目要求

 题目分析

上代码(保存结点位置,切断链表,反转链表,再连接)

public class Solution {
public ListNode reverseBetween (ListNode head, int m, int n) {
    //1.建立虚拟头结点
    ListNode temp = new ListNode(-1);
    temp.next = head;
    
    //2.记录n的前一个节点
    ListNode pro = temp;
    for(int i = 0; i < m-1; i++) {
        pro = pro.next;
    }
    
    //3.找到n位置下的结点
    ListNode nNode = pro;
    for(int i = 0; i < n-m+1; i++) {
        nNode = nNode.next;
    }
    
    //4.记录m位置下的结点,和n后下一个结点的位置
    ListNode mNode = pro.next;
    ListNode cur = nNode.next;
    
    //5.断开m到n之间的连接
    pro.next = null;
    nNode.next = null;
    
    //6.反转m到n之间的链表
    reverseList(mNode);
    
    //7.连接反转后的子链表
    pro.next = nNode;
    mNode.next = cur;
    return temp.next;
    }
    
    //反转链表
    public ListNode reverseList(ListNode head){
        //取一个新的头结点
        ListNode newHead = null;
        while(head != null) {
        ListNode cur = head.next;
            //头插法
            head.next = newHead;
            newHead = head;
            head = cur;
        }
        return newHead;
    }
}

上代码(穿针引线)

import java.util.*;

public class Solution {
    //3.穿针引线
    public ListNode reverseBetween (ListNode head, int m, int n) {
        //1.设置虚拟头结点
        ListNode dummyNode = new ListNode(-1);
        dummyNode.next = head;
        
        //2.记录m前的一个节点
        ListNode pro = dummyNode;
        for(int i = 0; i < m-1; i++) {
            pro = pro.next;
        }
        
        //3.记录待反转的第一个结点和这个节点的下一个节点的位置
        ListNode cur = pro.next;

        //4.开始穿针引线
        for(int i = 0; i < n-m; i++) {
            ListNode next = cur.next;
            cur.next = next.next;
            next.next = pro.next;
            pro.next = next;
        }
        return dummyNode.next;
    }
}

3. 链表中的节点每k个一组翻转

题目链接 链表中的节点每k个一组翻转_牛客题霸_牛客网 (nowcoder.com)

题目要求 

题目分析

(1)直接暴力解法,根据K给链表分组,然后子链表反转,再重新连接子链表

上代码

public class Solution {
    /**
     * 
     * @param head ListNode类 
     * @param k int整型 
     * @return ListNode类
     */
    public ListNode reverseKGroup (ListNode head, int k) {
        //创建虚拟结点
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        
        //创建两个节点,分别表示每个分组的前一个和后一个节点
        ListNode pre = dummy;
        ListNode end = dummy;
        
        while(end.next != null) {
            //每k个一组反转,【pre,end】
            for(int i = 0; i < k & end != null; i++) {
                end = end.next;
            }
            //如果end为空,说明不满一组
            if(end == null) {
                break;
            }
            //记录每组的头结点,和下一组的头结点
            ListNode start = pre.next;
            ListNode next = end.next;
            
            //断开连接,进行分组反转
            end.next = null;
            pre.next = reverseList(start);
            
            //反转之后重新连接
            start.next = next;
            
            //重新给pre和end赋值
            pre = start;
            end = start;
        }
        return dummy.next;
    }
    
    //反转链表
    private ListNode reverseList(ListNode head){
        ListNode newHead = null;
        while(head != null) {
            ListNode temp = head.next;
            head.next = newHead;
            newHead = head;
            head = temp;
        }
        return newHead;
    }
}

(2)也可以使用栈来做

创建栈  先入栈,每次判断是否够k个节点,如果够,就出栈到新的链表中。如果不够就返回

每次要把第k+1个结点记住,因为后面要把每次出完栈的子链表连接在一起

    public ListNode reverseKGroup (ListNode head, int k) {
        if(head == null) {
            return head;
        }
        //1.创建栈,创建虚拟头结点
        Stack<ListNode> stack = new Stack<>();
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        ListNode pre = dummy;
        int n = k;
        
        while(pre != null && pre.next != null) {
            ListNode temp = pre.next;
            //2.入栈
            while(temp != null && n > 0) {
                stack.add(temp);
                temp = temp.next;
                n--;
            }
            
            //3.记录第k+1个结点,用作连接
            ListNode nextNode = stack.peek().next;
            
            //4.n=0,说明栈中刚好k个结点,出栈连接
            if(n == 0) {
                while(!stack.empty()) {
                    pre.next = stack.pop();
                    pre = pre.next;
                }
            }else {
                break;
            }
            //5.连接
            pre.next = nextNode;
            n = k;
        }
        return dummy.next;
    }

4. 合并两个排序的链表

题目链接  合并两个排序的链表_牛客题霸_牛客网 (nowcoder.com)

题目要求 

题目分析

(1)直接迭代

1.创建一个虚拟头结点,然后再来一个节点prev指向虚拟头结点

2.两个链表头结点先比较,小的结点,放在prev后面,然后这条链表节点向后走一位

3.继续重复比较,每放一个节点prev向后走一位

4.直到某一条链表为null,剩下的这个链表直接连在prev后面

上代码

    public ListNode Merge(ListNode list1,ListNode list2) {
        ListNode newHead = new ListNode(-1);
        ListNode prev = newHead;
        
        while(list1 != null && list2 != null) {
            if(list1.val <= list2.val) {
                prev.next = list1;
                list1 = list1.next;
            } else {
                prev.next = list2;
                list2 = list2.next;
            }
            prev = prev.next;
        }
        //合并之后,谁还有剩余的就连在prev的后面
        prev.next = list1 == null ? list2:list1;
        return newHead.next;
    }

方法二:递归   先分一下

情况1.两条链表一条为空,另一条不为空,直接返回不为null的链表

情况2.刚开始两条链表头结点比较,小的结点1的那条链表,就可以想象成确定了的(也可以想象成这个节点就是结果中的结点),然后继续往下递归比较,直到出现情况1的条件

 public ListNode Merge(ListNode list1,ListNode list2) {
        if(list1 == null) {
            return list2;
        }else if(list2 == null) {
            return list1;
        }else if(list1.val < list2.val) {
            list1.next = Merge(list1.next,list2);
             return list1;
        }else {
           list2.next = Merge(list1,list2.next);
           return list2;
        }
    }

5. 合并k个已排序的链表 

题目链接   合并k个已排序的链表_牛客题霸_牛客网 (nowcoder.com)

题目要求

题目分析

(1)最暴力直接的解法就是,两个两个合并

既然已经给了K个已经排好序的链表,那么for循环,让每两个链表合并之后,就和下一个合并

两个链表合并,上一个题已经写过了,可以迭代也可以递归

但是这样两个合并的方法,可能会超时(如果链表特别多的情况下),不建议使用

上代码

 public ListNode mergeKLists(ArrayList<ListNode> lists) {
        ListNode ans = null;
        for(int i = 0; i < lists.size(); i++) {
            ans = mergeTwoList(ans,lists.get(i));
        }
        return ans;
    }
    
    private ListNode mergeTwoList(ListNode l1,ListNode l2){
        if(l1 == null || l2 == null) {
            return l1 == null ? l2:l1;
        }
        ListNode newHead = new ListNode(0);
        ListNode tmp = newHead;
        while(l1 != null && l2 != null) {
            if(l1.val <= l2.val) {
                tmp.next = l1;
                l1 = l1.next;
            }else {
                tmp.next = l2;
                l2 = l2.next;
            }
            tmp = tmp.next;
        }
        tmp.next = (l1 == null) ? l2:l1;
        return newHead.next;
    }

(2)分治(归并排序)

题中给的是ArrayList类型的链表数组

定义left和right分别指向这个链表数组的左右两边进行分组,往下递归直到left==right

然后开始合并,这就和上一题一样了

public class Solution {
    //合并
    private ListNode Merge (ListNode list1,ListNode list2) {
        if(list1 == null) {
            return list2;
        }
        if(list2 == null) {
            return list1;
        }
        ListNode newHead = new ListNode(0);
        ListNode cur = newHead;
        //两个链表都不为null
        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;
        }
        cur.next = (list1 == null) ? list2 : list1;
        return newHead.next;
    }
    //划分区间
    private 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-1)/2;
        return Merge(divideMerge(lists,left,mid),divideMerge(lists,mid+1,right));
    }
    public ListNode mergeKLists(ArrayList<ListNode> lists) {
       //k个链表归并排序
        return divideMerge(lists,0,lists.size()-1);
    }
}

(3)使用优先级队列,本质上就是堆,内置的是小根堆

1.创建一个优先级队列,并且还要重载比较方法,构造一个比较链表结点值的小根堆

2.然后遍历每个链表的头,将不为null的结点加入的优先级队列中

3.加一个虚拟头结点,将每次从堆顶取出来的元素,加入到虚拟头结点的后面

4.每次从堆顶取出一个元素后,就要给堆中重新放入刚刚取出来的结点的下一个结点。并且还要判空

    //使用优先级队列
     public ListNode mergeKLists(ArrayList<ListNode> lists) {
         Queue<ListNode> pq = new PriorityQueue<>(
         (v1,v2) -> v1.val - v2.val);
         for(int i = 0; i < lists.size(); i++) {
             //不为null,就加入小根堆
             if(lists.get(i) != null) {
                 pq.add(lists.get(i));
             }
         }
         //加一个虚拟头结点
         ListNode newHead = new ListNode(-1);
         ListNode cur = newHead;
         while(!pq.isEmpty()){
             //小根堆,取出就是最小的元素
             ListNode temp = pq.poll();
             //连接
             cur.next = temp;
             cur = cur.next;
             //每次取出堆顶元素后,再放入链表的下一个元素进入小根堆
             if(temp.next != null) {
                 pq.add(temp.next);
             }
         }
         return newHead.next;
     }

有关【刷题笔记】之牛客面试必刷TOP101(1)的更多相关文章

  1. 【Java 面试合集】HashMap中为什么引入红黑树,而不是AVL树呢 - 2

    HashMap中为什么引入红黑树,而不是AVL树呢1.概述开始学习这个知识点之前我们需要知道,在JDK1.8以及之前,针对HashMap有什么不同。JDK1.7的时候,HashMap的底层实现是数组+链表JDK1.8的时候,HashMap的底层实现是数组+链表+红黑树我们要思考一个问题,为什么要从链表转为红黑树呢。首先先让我们了解下链表有什么不好???2.链表上述的截图其实就是链表的结构,我们来看下链表的增删改查的时间复杂度增:因为链表不是线性结构,所以每次添加的时候,只需要移动一个节点,所以可以理解为复杂度是N(1)删:算法时间复杂度跟增保持一致查:既然是非线性结构,所以查询某一个节点的时候

  2. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  3. 牛客网专项练习30天Pytnon篇第02天 - 2

    1.在Python3中,下列关于数学运算结果正确的是:(B)a=10b=3print(a//b)print(a%b)print(a/b)A.3,3,3.3333...B.3,1,3.3333...C.3.3333...,3.3333...,3D.3.3333...,1,3.3333...解析:    在Python中,//表示地板除(向下取整),%表示取余,/表示除(Python2向下取整返回3)2.如下程序Python2会打印多少个数:(D)k=1000whilek>1:    print(k)k=k/2A.1000 B.10C.11D.9解析:    按照题意每次循环K/2,直到K值小于等

  4. 西安华为OD面试体验 - 2

    西安华为OD面试体验开始投简历技术面试进展工作进展开始投简历去年一整年一直在考研和工作之间纠结,感觉自己的状态好像当时的疫情一样差劲。之前刚毕业的时候投了个大厂的简历,结果一面写算法的时候太拉跨了,虽然知道时dfs但是代码熟练度不够,放在平时给足时间自己可以调试通过,但是熟练度不够那面试当时就写不出来被刷了。说真的算法学到后期我感觉最重要的是熟练度和背板子(对于我这种普通玩家来说),面试题如果一上来短时间内想不出思路就完蛋了。然后由于当时找的工作不是很理想就又想考研了。但是考研是有风险的,我自我感觉自己可能冲不上那个学校,而找工作一个没成可以继续找嘛。本着抱着试试看的态度在boss上投了简历,

  5. Unity Shader 学习笔记(5)Shader变体、Shader属性定义技巧、自定义材质面板 - 2

    写在之前Shader变体、Shader属性定义技巧、自定义材质面板,这三个知识点任何一个单拿出来都是一套知识体系,不能一概而论,本文章目的在于将学习和实际工作中遇见的问题进行总结,类似于网络笔记之用,方便后续回顾查看,如有以偏概全、不祥不尽之处,还望海涵。1、Shader变体先看一段代码......Properties{ [KeywordEnum(on,off)]USL_USE_COL("IsUseColorMixTex?",int)=0 [Toggle(IS_RED_ON)]_IsRed("IsRed?",int)=0}......//中间省略,后续会有完整代码 #pragmamulti_c

  6. ruby-on-rails - rails 生成 rspec :install config/environments/development. rb:1:in `<top (required)>': 未定义方法 `configure' - 2

    首先,这是我的版本:Greg-Nowickis-MacBook-Pro:sample_appGreg_Nowicki$ruby-vruby2.0.0p451(2014-02-24revision45167)[x86_64-darwin13.1.0]Greg-Nowickis-MacBook-Pro:sample_appGreg_Nowicki$rails-vRails4.0.4我正在学习HartlRails教程并安装rspec进行测试。我已将gem'rspec-rails'添加到我的gemfile中,当我运行railsgeneraterspec:install时,这就是我得到的:Gre

  7. Tcl脚本入门笔记详解(一) - 2

    TCL脚本语言简介•TCL(ToolCommandLanguage)是一种解释执行的脚本语言(ScriptingLanguage),它提供了通用的编程能力:支持变量、过程和控制结构;同时TCL还拥有一个功能强大的固有的核心命令集。TCL经常被用于快速原型开发,脚本编程,GUI和测试等方面。•实际上包含了两个部分:一个语言和一个库。首先,Tcl是一种简单的脚本语言,主要使用于发布命令给一些互交程序如文本编辑器、调试器和shell。由于TCL的解释器是用C\C++语言的过程库实现的,因此在某种意义上我们又可以把TCL看作C库,这个库中有丰富的用于扩展TCL命令的C\C++过程和函数,所以,Tcl是

  8. [面试直通版]操作系统核心之进程、线程与协程(下) - 2

    点击->操作系统复习的文章集目录操作系统线程线程是什么进程与线程的关系用户态/内核态操作系统资源管理内核态用户态内核态/用户态切换程序运行类型分析计算密集型IO密集型结合进程,线程来理解程序运行类型分析协程基础上下文切换协程协程为什么叫协作式线程?协程的优缺点操作系统线程典型问题:简述进程和线程的区别以下内容带您一步步了解线程是什么比进程更小的独立运行的基本单位-线程(Threads)线程的提出主要是为了提高系统内程序并发执行的程度,从而进一步提升系统的吞吐量,充分发挥多核CPU的优越性而设计的引入进程是为了操作系统更加方便地管理程序,使得多个程序能并发管理和执行而线程则是为了减少程序在并发执

  9. 计算机网络笔记:TCP三次握手和四次挥手过程 - 2

    TCP是面向连接的协议,连接的建立和释放是每一次面向连接的通信中必不可少的过程。TCP连接的管理就是使连接的建立和释放都能正常地进行。三次握手TCP连接的建立—三次握手建立TCP连接①若主机A中运行了一个客户进程,当它需要主机B的服务时,就发起TCP连接请求,并在所发送的分段中用SYN=1表示连接请求,并产生一个随机发送序号x,如果连接成功,A将以x作为其发送序号的初始值:seq=x。主机B收到A的连接请求报文,就完成了第一次握手。客户端发送SYN=1表示连接请求客户端发送一个随机发送序号x,如果连接成功,A将以x作为其发送序号的初始值:seq=x②主机B如果同意建立连接,则向主机A发送确认报

  10. 华为数通笔记VXLAN&BGP EVPN - 2

    VXLAN简介定义RFC定义了VLAN扩展方案VXLAN(VirtualeXtensibleLocalAreaNetwork,虚拟扩展局域网)。VXLAN采用MACinUDP(UserDatagramProtocol)封装方式,是NVO3(NetworkVirtualizationoverLayer3)中的一种网络虚拟化技术。目的随着网络技术的发展,云计算凭借其在系统利用率高、人力/管理成本低、灵活性/可扩展性强等方面表现出的优势,已经成为目前企业IT建设的新趋势。而服务器虚拟化作为云计算的核心技术之一,得到了越来越多的应用。服务器虚拟化技术的广泛部署,极大地增加了数据中心的计算密度;同时,为

随机推荐