链表常见题型(java版)

直接上干货。。。。。

链表常见题型:

  1. 找到单链表的倒数第k个节点。
  2. 删除单链表中的某个结点(O(1))。
  3. 反转链表。
  4. 两个链表的第一个公共结点。
  5. 有环链表返回环路的开头节点(及判断是否有环)。
  6. 合并两个排序的链表。
  7. 删除链表中重复的结点。

先给出链表的定义:


    /**
     * 单链表定义
     */
    public static class Node<E>{
        private E element;//节点保存的元素
        private Node<E> next;//指向下一个节点的引用

        public Node(){}
        public Node(E element){
            this.element = element;
        }
        public Node(E element,Node<E> next){
            this.element = element;
            this.next = next;
        }

        public E getElement(){
            return element;
        }
        public Node<E> getNext(){
            return next;
        }
        public void setElement(E element){
            this.element = element;
        }
        public void setNext(Node<E> next){
            this.next = next;
        }
    }

关于鲁棒性:

鲁棒性也成为健壮性,是指程序能够判断输入是否合乎规范要求,并对不合要求的输入予以合理的处理。容错性是鲁棒性的一个重要体现。提高代码鲁棒性的有效途径是进行防御性编程,防御性编程是一种编程习惯,是指预见在什么地方可能会出现问题,并为这些可能出现问题制定处理方式。在面试时,最常用也是最有效的防御性编程是在函数入口添加代码以验证用户输入是否符合要求。

1. 找到单链表的倒数第k个节点。

这里假设最后一个节点为倒数第一个。思想:快慢指针。实现代码如下:


    public static Node<Integer> solution(Node<Integer> head,int k){
        // 判断k是否非法
        if(k < 1){return null;}
        // 判断是否为空
        if(head == null) return null;
        // 快指针
        Node<Integer> nAhead = head;
        // 慢指针
        Node<Integer> nBehind = head;
        // 快指针先走k-1步
        for(int i = 0;i<k-1;i++){
            // 判断是否存在倒数第k个节点
            if(nAhead.getNext() != null)
                nAhead = nAhead.getNext();
            else
                return null;
        }
        // 同时向后移动
        while(nAhead.getNext() != null){
            nAhead = nAhead.getNext();
            nBehind = nBehind.getNext();
        }
        return nBehind;
    }
  • 功能测试:第k个节点在中间,在头节点,在尾节点
  • 特殊测试:头节点为null,k为0,节点个数小于k个
  • 时间复杂度:O(n),空间复杂度为:O(1)

2. 删除单链表中的某个结点(O(1))。

假定你只能访问该节点。思想:互换位置。实现代码如下:


    // 不知道头节点,并且无法删除尾节点。
    public static boolean solution(Node<Integer> node){
        // 节点为空判断
        if(node == null) return false;
        // 节点为最后一个节点
        if(node.getNext() == null) return false;
        Node<Integer> temp = node.getNext();
        // 交换节点存储数据
        node.setElement(temp.getElement());
        node.setNext(temp.getNext());
        return true;
    }

注意:若知道头节点的话,能够保证平均时间复杂度为O(1)。若不知道头节点想删除尾节点的话,只能标记一下尾节点。

3. 反转链表

代码如下:


public static Node<Integer> solution(Node<Integer> head){
        //头节点为空
        if(head == null) return head;
        //只有一个节点
        if(head.getNext() == null) return head;
        //记录最后被反转的节点
        Node<Integer> temp1 = head;
        //记录将要反转的节点
        Node<Integer> temp2 = head;
        //记录将要反转的节点的下一个节点
        Node<Integer> temp3 = head.getNext();
        while(temp3 != null){
            temp2 = temp3;
            temp3 = temp2.getNext();
            temp2.setNext(temp1);
            temp1 = temp2;
        }
        head.setNext(null);
        head = temp1;
        /*while(temp1 != null){
            System.out.print(temp1.getElement() + ",");
            temp1 = temp1.getNext();
        }*/
        return head;
    }

注意:在调用方法的时候但是我们反应过来,直接solution(head);然后打印的head指向的链表。。。其实,你懂的,俺是菜鸟。。。。。

好吧这里solution(head)方法中传递的是head的引用,因此执行完head的时候还是指向原来的第一个也就是现在的最后一个节点。。。

  1. 两个无环链表的第一个公共结点。

代码如下:


public static Node<Integer> solution(Node<Integer> head1,Node<Integer> head2){
        // 判空
        if(head1 == null || head2 == null) return null;
        // 同一个链表,不用再计算长度
        if(head1 == head2) return head1;
        // 计算长度差
        int length1 = 0;
        int length2 = 0;
        Node<Integer> temp1 = head1;
        Node<Integer> temp2 = head2;
        while(temp1.getNext() != null || temp2.getNext() != null){
            if(temp1.getNext() != null)
                temp1 = temp1.getNext();
            else
                length2++;
            if(temp2.getNext() != null)
                temp2 = temp2.getNext();
            else
                length1++;
        }
        temp1 = head1;
        temp2 = head2;
        // 链表1长,链表1先走length1步
        if(length1 != 0){
            // 先执行length1步
            for(int i = 0;i<length1;i++){
                temp1 = temp1.getNext();
            }
            while (temp1 != null){
                // 相等即为第一个公共节点
                if(temp1 == temp2){
                    return temp1;
                }
                else{
                    temp1 = temp1.getNext();
                    temp2 = temp2.getNext();
                }
            }
        }
        // 链表2长,链表2先走length2步
        else if(length2 != 0){
            // 先执行length2步
            for(int i = 0;i<length2;i++){
                temp2 = temp2.getNext();
            }
            while (temp1 != null){
                // 相等即为第一个公共节点
                if(temp1 == temp2){
                    return temp1;
                }
                else{
                    temp1 = temp1.getNext();
                    temp2 = temp2.getNext();
                }
            }

        }
        // 一样长
        else{
            while (temp1 != null){
                if(temp1 == temp2){
                    return temp1;
                }
                else{
                    temp1 = temp1.getNext();
                    temp2 = temp2.getNext();
                }
            }
        }
        return null;
    }
  • 测试: 不相交,同一个,在中间相交,在结尾相交,null;
  • 时间复杂度:O(max(m,n));
  • 上面的代码复用不好,相同的部分可以写成另一个方法。
  • 扩展为题:链表有环。

5. 有环链表返回环路的开头节点。

或求环的长度,或求节点总个数。代码如下:


    public static Node<Integer> solution(Node<Integer> head){
        // 定义两个节点变量,一个每次走两步,一个每次走一步
        Node<Integer> aheadNode = head;
        Node<Integer> behindNode = head;

        // 找到第一次相遇的地方
        while(aheadNode.getNext() != null){
            aheadNode = aheadNode.getNext().getNext();
            behindNode = behindNode.getNext();
            if(behindNode == aheadNode)  break;
        }
        // 慢节点指向head,然后两个指针每次走一步
        behindNode = head;
        while(behindNode != aheadNode){
            behindNode = behindNode.getNext();
            aheadNode = aheadNode.getNext();
        }
        return behindNode;
    }
  • 测试: 空,一个节点形成的环,中间节点形成的环,最后一个形成的环,没有环;
  • 时间复杂度:O(n)。

衍生问题:


    /**
     * 判断链表是否有环
     * 思路:快慢指针实现,若有环肯定会相交。
     **/
    public static boolean checkCircuit(Node<Integer> head){
        // 判空
        if(head == null) return false;
        // 定义两个节点,一个快节点,一个慢节点
        Node<Integer> aheadNode = head;
        Node<Integer> behindNode = head;
        // 绝对不能一次判断两个:behindNode.getNext().getNext()!= null
        while(behindNode.getNext() != null){
            behindNode = behindNode.getNext().getNext();
            if(behindNode == null) return false;
            aheadNode = aheadNode.getNext();
            if(behindNode == aheadNode) return true;
        }
        return false;
    }

  • 测试: 空,一个节点形成的环,中间节点形成的环,最后一个形成的环,没有环。
  • 时间复杂度:O(n)

1. 为什么一个快慢指针能够判断是否相交?

一个指针a每次走1步,另一个指针b每次走两步。没有环肯定追不上。

现在考虑有环的情况,存在以下三种情况:

  1. a在b前一个节点;
  2. b在a前一个节点;
  3. a和b相遇。

显然,第三种情况自然证明了有环,第1和第2种情况见下图。

2. 为什么在相遇后a重新指向head,然后a和b每次走一步相遇时既是第一个公共节点?

具体见下图:

6. 合并两个排序的链表。

两种实现,递归和非递归。

7. 删除链表中重复的结点。

这题有两种,如下

重复的只保留一个


    public static Node<Integer> solution(Node<Integer> head){
    // 判空
        if(head == null) return head;
        Node<Integer> ahead = head;
        Node<Integer> curr = head.getNext();

        while(curr != null){
            if(ahead.getElement() == curr.getElement()) //相同就删除
                ahead.setNext(curr.getNext());
            else
                ahead = curr;//不同后移
            curr = curr.getNext();//无论相同与否当前指针都后移
        }
        return head;
    }

重复的一个不保留


    public static Node<Integer> solution2(Node<Integer> head){
        // 判空
        if(head == null) return head;
        // 即将判断的节点
        Node<Integer> curr = head.getNext();
        // curr的前一个节点
        Node<Integer> ahead = head;
        // 记录ahead的前一个节点
        Node<Integer> preAhead = null;
        // 记录ahead节点是否有重复
        boolean needDelete = false;
        while(curr != null){
            // 相同就删除
            if(ahead.getElement() == curr.getElement()){
                ahead.setNext(curr.getNext());
                needDelete = true;
            }
            else{
                if(needDelete){
                    // ahead节点重复需要删除,用后一个节点替换前一个节点,后设置preAhead的后一个节点。
                    // 若直接设置preAhead的话,开始重复的就不能处理
                    ahead.setElement(curr.getElement());
                    ahead.setNext(curr.getNext());
                    needDelete = false;
                }else{
                    // 不同后移
                    preAhead = ahead;
                    ahead = curr;
                }
            }
            // 无论相同与否当前指针都后移
            curr = curr.getNext();
        }

        if(needDelete){
            // 若全都相等则返回空
            if(preAhead == null){
                return null;
            }
            // 否则就是最后一个节点需要删除
            preAhead.setNext(null);
        }
        return head;
    }
  • 测试:全相等,开头相等,结尾相等,中间相等;
  • 时间复杂度:O(n);
  • 问题总结:在考虑问题时多注意起始条件,例如preAhead初始情况为null,若后面要操作它的话就要考虑清楚,前面是否有对它的赋值,否则就会出错。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-09 19:53:48

链表常见题型(java版)的相关文章

常见的链表排序(Java版)

上篇博客中讲解了九大内部排序算法,部分算法还提供了代码实现,但是那些代码实现都是基于数组进行排序的,本篇博客就以链表排序实现几种常见的排序算法,以飨读者. 快速排序的链表实现 算法思想:对于一个链表,以head节点的值作为key,然后遍历之后的节点,可以得到一个小于key的链表和大于等于key的链表:由此递归可以对两个链表分别进行快速.这里用到了快速排序的思想即经过一趟排序能够将小于key的元素放在一边,将大于等于key的元素放在另一边. 代码实现: 1 //快速排序 2 public stat

《剑指offer》面试题26 复杂链表的复制 Java版

(定义一个新的数据结构,每个节点除了具有普通链表的next域外,还有一个额外的引用指向任意节点.我们要对由该特殊数据结构形成的链表进行复制.) 我的方法:也就是克隆一个这种特殊链表,很快想到先不考虑原链表sibling域,复制出一个新的链表,然后再去给sibling域赋值.由于sibling可以指向任何节点,而且我们是根据原链表的sibling来确定新链表中的sibling,所以每次我们寻找新链表中某个节点的sibling,都要两个指针重新从头开始扫描以确定新链表中的sibling.所以时间复杂

链表常见的题型(java实现)

链表是面试中最常见的一种题型,因为他的每个题的代码短,短短的几行代码就可以体现出应聘者的编码能力,所以它也就成为了面试的重点. 链表常见的操作有1.打印链表的公共部分,2.删除链表的倒数第K个节点,3.翻转单向链表,4.环形约瑟夫环问题,5.判断链表是否是一个回文链表,6.两个链表生成相加链表,7.删除无序链表中重复出现的节点,8.删除指定值得节点,9.合并两个有序的单链表,10.环形链表的插入 import java.util.*; /********** *@Author:Tom-shush

栈和队列常见题型(java版)

直接上干货..... 栈和队列常见题型: 实现栈和实现队列. 两个栈实现一个队列. 设计栈,使得pop,push和min时间复杂度为O(1). 滑动窗口的最大值. 栈的进出序列. 实现栈和实现队列 主要包括:栈,队列,循环队列. package com.sywyg; /** * 实现栈 * 数组应该是Object类型的 * 注意top的设置影响出栈和获取栈顶元素. * size也可以用top代替 */ class MyStack<E>{ // 栈元素个数 private int size; /

剑指Offer面试题15(Java版):链表中倒数第K个结点

题目: 输入一个链表,输出该链表中倒数第k哥结点. 为了符合大多数人的习惯,本题从1开始计数,即链表的尾结点是倒数第1个结点. 例如一个链表有6个结点,从头结点开始它们的值依次是1,2,3,4,5,6.这个链表的倒数第3个结点是值为4的结点 为了得到第K个结点,很自然的想法是先走到链表的尾端,再从尾端回溯K步.可是我们从链表结点的定义可疑看出本题中的链表 是单向链表,单向链表的结点只有从前往后的指针而没有从后往前的指针,因此这种思路行不通. 既然不能从尾节点开始遍历这个链表,我们还是把思路回到头

有关链表的常见题型

一.线性表基本概念 1.1 顺序存储-顺序表: 线性表的顺序存储又称为顺序表,用一组地址连续的存储单元,一次存储线性表中的数据元素,从而使得逻辑上相邻的两个元素物理位置也相邻. 优点:可以进行随机访问,只要知道下标索引即可在O(1)时间内找到指定元素. 缺点:插入和删除需要移动大量元素. 基本操作:插入.删除.查找(二分查找).排序(归并排序)下一篇重点讲关于数组的常见题型 1.2 链式存储-单链表.双链表.循环链表 单链表:每个链表节点除了存放自身信息之外,还携带一个指向后继的指针.只能从头节

java数据结构:单链表常见操作代码实现

一.概述: 本文主要总结单链表常见操作的实现,包括链表结点添加.删除:链表正向遍历和反向遍历.链表排序.判断链表是否有环.是否相交.获取某一结点等. 二.概念: 链表: 一种重要的数据结构,HashMap等集合的底层结构都是链表结构.链表以结点作为存储单元,这些存储单元可以是不连续的.每个结点由两部分组成:存储的数值+前序结点和后序结点的指针.即有前序结点的指针又有后序结点的指针的链表称为双向链表,只包含后续指针的链表为单链表,本文总结的均为单链表的操作. 单链表结构: Java中单链表采用No

Java版贪吃蛇(比较完善的版本)

很认真的写的一个java版的贪吃蛇游戏,图形界面,支持菜单操作,键盘监听,可加速,减速,统计得分,设定运动速度,设定游戏背景颜色等!应该没有Bug了,因为全被我修改没了.哈哈. 下面是项目各包及类的层次关系: 游戏的主要运行界面截图如下: 下面是部分代码,详细源码见此链接:http://pan.baidu.com/s/1bnubnzh //Snake类: package com.huowolf.entities; import java.awt.Color; import java.awt.Gr

AKKA文档(java版)

目前我正在翻译AKKA官网文档.翻译:吴京润 译者注:本人正在翻译AKKA官网文档,本篇是文档第一章,欢迎有兴趣的同学加入一起翻译.更多内容请读这里:https://tower.im/projects/ac49db18a6a24ae4b340a5fa22d930dc/lists/ded96c34f7ce4a6bb8b5473f596e1008/show/https://tower.im/projects/ac49db18a6a24ae4b340a5fa22d930dc/todos/640e53d