当前位置: 首页 > article >正文

LinkedList和链表之刷题课(上)

在上一节,我们自己实现了一个单链表的结构,接下来我们来写几个面试题巩固一下

1. 删除链表中等于给定val的所有结点

        解析题目:

如图,我们需要删除所有的值为12的结点

       在head非空的情况下,我们删除一个结点,首先要找到这个结点,然后把这个结点的前面一个结点的next指向这个节点的下一个,这个就是总体的步骤,不过我们不止删除一个结点,我们需要删除所有是val值的结点,因此综上,我们需要俩个指针,prev和cur,prev指向的是删除结点的前一个结点,cur就是删除结点的当前节点,因此我们一开始先设置prev = head,cur = head.next;然后我们进入循环,如果当前的cur.val==key,我们就需要让prev.next = cur.next,然后我们需要跟新cur,cur = cur.next,如果我们当前节点的值不等于key,那么我们需要让prev = cur;cur=cur.next;

        上面就大致解决了这个问题,但是我们还漏了一点,那就是head节点,head节点我们并没有判断它是否和val值一致.并且这行代码必须在删除完中间节点之后再删除,不然要用while,因为我们考虑到这种情况:1,1,1,2,2,1;我们如果一开始就删去1,那么第二个1是不可能被删除的,因为我们后期也没处理新的头结点的key,这一点同样也要在上一节博客修改一下.

整体代码:

 public void removeAllKey(int key) {
        //判断头节点是不是空的
        if (this.head == null) {
            return;
        }
        //判断头结点是不key
        if (head.val == key) {
            this.head = this.head.next;
        }
        ListNode prev = head;
        ListNode cur = head.next;
        //我们遍历除了head后面的元素
        while (cur != null) {
            if (cur.val == key) {
                prev.next = cur.next;
                cur = cur.next;
            } else {
                prev = cur;
                cur = cur.next;
            }
        }
    }

2. 翻转一个单链表

        解析题目:

        我们翻转一个链表,也就是要把从最后一个节点开始,把它的next指向它的前一个

        

    此时,我们可以采用头插法来,我们用cur记录当前节点,cur = head.next;然后我们需要把head.next置空,因为此刻head指向的节点是相当于我们翻转之后的最后一个节点.然后我们在把cur插到head前面的时候,我们需要用curNext来记录后面的节点,不然后续我们头插完,就找不然原先的链表了.然后我们需要跟新head,head = cur完成一轮插入,然后我们需要跟新cur,让cur指向curNext,再让curNext记录下一个节点.

还有个小地方,也就是我们如果链表head == null 或者head.next == null我们直接返回head即可,因为没有必要翻转.

整体代码:

 //TODO 翻转链表
    public ListNode reverseList() {

            //判断链表是不是空的
        if(head == null) {
            return null;
        }
        //如果只有一个结点,就直接返回head即可
        if(head.next == null){
            return head;
        }

        //其他情况就用头插法
        ListNode cur = head.next;
        head.next = null;
        while (cur != null) {
            //把head置空
            //记录下一个节点的位置
            ListNode curNext = cur.next;
            //把cur头插进去
            cur.next = head;
            //跟新head
            head = cur;
            //跟新cur
            cur = curNext;
        }
        //返回head
        return head;

    }

3. 给定一个带有头节点的非空单链表,返回链表的中间结点.如果有俩个中间结点,则返回第二个中间结点.

        解析题目:

        我们可以看见,我们返回中间结点,要分偶数个结点和奇数个结点.

        我们先来看法1: 1> 求出整个链表的大小;2>找到len/2的结点

因为过于简单,我们这里就不画图了,直接解释代码:

先判断整个链表head是不是null;然后进入主题,求len,我们的循环条件是cur!=null,因为我们需要遍历所有的结点,如果用cur.next!=null;我们就会漏掉最后一个结点.然后注意,我们需要让cur重新指向head,因为cur刚刚在遍历的时候已经为null了;我们再设置一个计数变量i,中间长度mid,我们每次cur-cur.next;我们就需要对i进行++;

  public ListNode  middleNode() {
        if(head==null){
            return null;
        }
    //先求整个链表的长度
        int len = 0;
        ListNode cur = head;
        while (cur != null) {
            len++;
            cur = cur.next;
        }
        cur = head;
        //再求长度/2找到这个中间节点
        int mid = len / 2;
        int i = 0;
        while (i != mid) {
            cur = cur.next;
            i++;
        }

        return cur;
    }

        法2 快慢指针,一开始fast和slow都指向head,然后fast走俩步,slow走一步;

        这里因为奇偶数结点会分俩个情况,fast结束的标志会有俩种情况,也就是:fast == null和fast.next == null;在fast满足上面的情况之一就会说明slow到达了中间结点了.

这里我们解决俩个问题即可:

1> 为什么先判断fast再判断fast.next;

2> 为什么不能用||要用&&

        整体代码:

public ListNode  middleNodeFastAndSlow() {
        if(head == null){
            return null;
        }
         //我们定义俩个指针
        ListNode fast = head;
        ListNode slow = head;
        while (fast != null && fast.next != null) {
            //fast一次走俩步,slow一次走一步
            fast = fast.next.next;
            slow = slow.next;
        }
        return slow;
        //俩个点
        //1.为什么不能用||要用&& 因为如果是偶数个,最后我们fast会为null,因此判断fast.null会显示空指针异常
        //2.为什么先要判断fast,再判断fast.next:因为如果是偶数个,fast最后会走到null,这时候先判断fast.next!=null会空指针异常

    }

4. 输入一个链表,输出该链表中倒数第k个结点

        题目解析:

这个题目其实也有俩个方法,但是我们这里主要用快慢指针来解题

假设我们求的是倒数第k个结点,我们先让fast先走k-1步,然后我们再让fast和slow一起走,直到fast ==null为止;这个东西我是这么理解的,我们相当于把fast作为一个我们自定义的动态边界条件,让它先走k-1步,再同时走,这样fast肯定会先到最后一个结点,然后此时我们的slow所在位置就是倒数第k个结点.slow和fast相差k-1步.

        然后我们这里要判断我们所要的结点的下标的合法性,此时我们我们可以优化一下k>size()这个条件,因为如果k很大,我们要遍历O(n),因为我们的fast要走k-1步,此时我们也需要一个循环,所以,我们直接去在这里面判断fast == null

        整体代码:

//TODO 输出链表倒数第k个结点
    //slow和fast总是差k-1
    public ListNode FindtheToTail(int k) {
        //先判断k的合法性
        if(k < 0) {//我们可以优化一下这个k>siez()
            return null;
        }
        //并且判断head是不是空
        if (head == null) {
            return null;
        }
        ListNode fast = head;
        ListNode slow = head;
        //fast走k-1步
        for (int i = 0; i < k-1; i++) {
            fast = fast.next;
            //TODO 优化size()不去遍历,这个就是去处理k太大的情况
            if(fast == null) {
                return null;
            }
        }
        //同时走
        while (fast != null) {
            fast = fast.next;
            slow = slow.next;
        }
        //fast到最后,slow的位置就是倒数第k个
        return slow;
    }

5. 将俩个有序链表合并成一个新的有序链表并返回.

        题目解析:

差不多是这么个情况,我们有俩个有序的链表,我们要把俩个合并成一个新的有序链表.我们先构造一个虚拟结点newH,然后我们再让list1和list2(此处,图上的head1就是list1,head2就是list2)互比较,小的就把该结点尾插到newH上去

具体步骤如图,我们要记录虚拟结点的位置,因此创建一个tmpH指向一开始的newH,我们把list1和list2的val进行比较,小的那一方,把该结点和newH连接在一起,如果是list1小,那么tmpH.next = list1;然后我们跟新list1,list=list.next;然后我们也要跟新newH,因为它指向的是链表的尾巴,我们进行尾插法,直到有一个链表为空为止

我们如图可以知道list2已经遍历完了,我们直接把list1剩下的元素连接过来即可,最后我们返回headH.next即可(因为headH是个虚拟结点)

        整体代码:

//TODO 合并俩个有序列表
    public static MySingleList.ListNode mergeTwoLists(MySingleList.ListNode list1, MySingleList.ListNode list2) {
         //先生成一个开头的结点
       MySingleList.ListNode newH = new MySingleList.ListNode(-1);
         //tmpH始终指向的是链表的尾巴
       MySingleList.ListNode tmpH = newH;

       //判断俩个链表是不
         while (list1 != null && list2 != null) {
            //先比较list1和list2指向的结点,我们把小的值链接上
             if(list1.val < list2.val) {
                 //小的话就把小的链接到newH上
                 tmpH.next = list1;
                 //跟新list1
                 list1 = list1.next;
             } else {
                 //小的话就把小的链接到newH上
                 tmpH.next = list2;
                 //跟新list2
                 list2 = list2.next;
             }
             System.out.println("while末尾");
             //tmpH始终指向的是链表的尾巴
             tmpH = tmpH.next;
         }
         //如果其中一个为空了,那么就把另外一个链接过来即可
         if(list1 != null) {
             tmpH.next = list1;
         }
         if (list2 !=null){
             tmpH.next = list2;
         }
         return newH.next;

    }


http://www.kler.cn/news/366117.html

相关文章:

  • csa练习1
  • Django进一步掌握(10月22日)
  • ESlint代码规范
  • 文本预处理——构建词云
  • C++游戏开发教程:从入门到进阶
  • opencv学习笔记(3):图像和视频的读取(C++)
  • STM32--TIM编码器接口
  • 从区别看本质:CRM和SCRM的全面对比
  • 江协科技STM32学习- P21 ADC模数转换器
  • 前端拥抱AI:LangChain.js 入门遇山开路之PromptTemplate
  • Python游戏开发超详细(基础理论知识篇)
  • 实现信创Linux麦克风摄像头RTMP推流(源码,银河麒麟、统信UOS)
  • 如何为工业未来赋能?通过CodeMeter为工业企业开辟工业自动化安全与灵活性之道
  • 羽毛球场馆预约小程序,提高场馆便捷性、利用率
  • 进程的控制(创建、终止、等待,程序替换)
  • 从0到1,搭建vue3项目
  • Midjourney计划推出一款升级版的网页工具
  • 接口自动化-Yaml文件引用CSV
  • Android View的事件分发机制
  • Java的SKU探秘之旅:API数据的极速捕获
  • 第71期 | GPTSecurity周报
  • 爬虫日常实战
  • 持续深化信创布局,途普科技与统信软件完成产品兼容性互认证
  • 《编程并不难:像学语文一样学习编程语言》
  • Vue 的 Diff 算法解析
  • 【Golang】Golang的GC垃圾回收机制