java 数据结构常考的OJ 链表,重点!重点!!!

Posted 鸢也

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java 数据结构常考的OJ 链表,重点!重点!!!相关的知识,希望对你有一定的参考价值。

0J面试题 链表

一、单链表

1.1删除链表中等于给定值val的所有节点

OJ链接

📜描述:

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点

📝大体思路:

定义一个引用变量cur来判断当前节点是否为删除的值的节点,在定义prev,是cur的前驱,用来跳过删除的值的节点

假设跳过的val是8:

❗️核心代码:

  if(cur.val == val) {
       prev.next = cur.next;
       cur = cur.next;
      }else {
       prev = cur;
       cur = cur.next;
  }

注意的细节:

1、head不能为空。
2、如果删除的值是第一个,那就放在最后处理

💬完整代码:

public void removeAllKey(int val) {
        if(this.head == null) {//head不能为空。
            return;
        }
        Node prev = this.head;
        Node cur = this.head.next;
        while (cur != null) {
            if(cur.val == val) {
                prev.next = cur.next;
                cur = cur.next;
            }else {
                prev = cur;
                cur = cur.next;
            }
        }
        //最后判断头节点
        if(this.head.val == val) {
            this.head = this.head.next;
        }
    }

1.2反转一个单链表

OJ链接

📜描述:

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

什么是反转链表?

但是我们可以用另外一种方式看

📝大体思路:

反转链表可以采用头插法的方式,链表的最后next域是一个null,所以我们就把第一个节点next域设成null,curNext的引用变量记录下一个节点,再用cur引用变量指向当前节点,用prev改变next域,使方向反转

❗️核心代码:

 while(cur != null) {
            Node curNext = cur.next;
            cur.next = newHead;
            newHead = cur;
            cur = curNext;
        }

注意细节:

1.因为反转设第一个节点next域设为空,所以newHead先设为null
2.如果只有一个节点head或没有节点直接返回head

完整代码:

public Node reverseList() {
        if(head == null||head.next == null)
            return head;

        Node cur = head;
        Node newHead = null;

        while(cur != null) {
            Node curNext = cur.next;
            cur.next = newHead;
            newHead = cur;
            cur = curNext;
        }
        return newHead;
    }

1.3链表的中间结点

OJ链接

📜描述:

给定一个头结点为 head 的非空单链表,返回链表的中间结点。

如果有两个中间结点,则返回第二个中间结点。

这道题比较简单,

📝大体思路:

举个例子,两个同学比赛400m,一个同学是另一个同学速度的两倍,快的同学跑到终点的时候,慢的同学刚好跑到了200m,正好是整个跑道的中点,所以我们也new一个fast和slow的引用变量,使fast速度是slow的两倍,使他多走一步,fast走到null,返回slow

❗️核心代码:

while(fast!=null && fast.next!=null) {
            fast=fast.next.next;
            slow=slow.next;
        }

注意细节:

如果有两个中间结点,则返回第二个中间结点。

💬完整代码:

public Node middleNode() {
        if(head == null) return head;
        Node fast = head;
        Node slow = head;
        while(fast != null && fast.next!=null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        return slow;
    }

1.4链表中倒数第k个节点

OJ链接

📜描述

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

我们只需要遍历一遍链表

📝大体思路:

我们还是new一个fast和一个slow,fast和slow始终差k-1步,当fast走到了k-1步的时候,fast和slow一起走,直到fast为null,返回slow。

❗️核心代码:

 while( k-1 != 0) {
            if(fast.next != null) {
                fast = fast.next;
                k--;
            }else{
                return null;
            }
        }

        while(fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }
        return slow;

注意细节:

1、head为空直接返回null;
2、判断k合不合法,k超过了总链表长度我们直接在k-1!=0直接限制,只需要判断k<0即可;

完整代码:

 public Node findKthToTail(int k) {
        if(head == null) return null;
        if(k < 0 ) {
            return null;
        }

        Node fast = head;
        Node slow = head;

        while( k-1 != 0) {
            if(fast.next != null) {
                fast = fast.next;
                k--;
            }else{
                return null;
            }
        }

        while(fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }

1.5合并两个有序链表

OJ链接

📜描述:

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

📝大体思路:

定义一个傀儡节点,和tmp引用变量指向这个傀儡对象,使两个链表headA,headB互相比较,谁小就把他放在傀儡节点的后面,tmp指向最新的节点,循环;最后如果tmp在headB链表中next域为null,直接tmp的下一个节点直接指向headA,反之headA,同理。最后返回傀儡节点。

❗️核心代码:

 while (headA != null && headB != null) {
            if (headA.val < headB.val) {
                tmp.next = headA;
                headA = headA.next;
            } else {
                tmp.next = headB;
                headB = headB.next;
            }
            tmp = tmp.next;
        }
   

注意细节:

傀儡节点可以赋值负数,不影响

💬完整代码:

public static Node mergeTwoLists (Node headA, Node headB){
        if (headA == null) return headB;
        if (headB == null) return headA;
        if (headA == null && headB == null) return null;

        Node newH = new Node(0);
        Node tmp = newH;

        while (headA != null && headB != null) {
            if (headA.val < headB.val) {
                tmp.next = headA;
                headA = headA.next;
            } else {
                tmp.next = headB;
                headB = headB.next;
            }
            tmp = tmp.next;
        }
        if (headB == null) {
            tmp.next = headA;
        }
        if (headA == null) {
            tmp.next = headB;
        }
        return newH;
    }

1.6链表分割

OJ链接

📜描述

现有一链表的头指针 Head,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。

📝大体思路:

最后分割顺序保持不变,所以可以采用尾插法的思想。
竟然是链表分割那我们就可以分为两段一段是<x的,另一段是>=x的,第一段定义起点bs和终点be,另一段as和ae,他们都设置为null,cur这个引用变量每次与x比较,将<x放左边,bs始终保持不变,be随着变化往后移,>x放右边,反之同理,最后串起来就ok。

❗️核心代码:

 while(cur != null) {
            if(cur.val < x) {
                //第一次插入
                if(bs == null) {
                    bs = cur;
                    be = cur;
                }else {
                    be.next = cur;
                    be = be.next;
                }
            }else {
                //第二部分第一次插入
                if(as == null) {
                    as = cur;
                    ae = cur;
                }else {
                    ae.next = cur;
                    ae = ae.next;
                }
            }
            cur = cur.next;

注意细节:

考虑如果链表全<x的时候,第二段就没有,as就是null直接返回第一段就行了,
如果>x的时候,那么第一段就没有节点,返回第二段as,

💬完整代码:

public Node partition( int x) {

        Node cur = this.head;
        Node bs = null;
        Node be = null;
        Node as = null;
        Node ae = null;

        while(cur != null) {
            if(cur.val < x) {
                //第一次插入
                if(bs == null) {
                    bs = cur;
                    be = cur;
                }else {
                    be.next = cur;
                    be = be.next;
                }
            }else {
                //第二部分第一次插入
                if(as == null) {
                    as = cur;
                    ae = cur;
                }else {
                    ae.next = cur;
                    ae = ae.next;
                }
            }
            cur = cur.next;
        }
     if(bs == null) {
            return as;
        }
        //bs!=null
        be.next = as;
        if(as != null) {
            ae.next = null;
        }
        return bs;
    }
  /*if(bs == null) {
            return as;
        }else {
            be.next = as;
            ae.next = null;//注意问题,ae可能为空
            return bs;
        }*/

1.7删除链表中重复的节点

OJ链接

📜描述

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

📝大体思路:

定义cur引用变量,如果当前val域与下一个节点val域相同就让他跳过下个节点,如果是多个挨在一起的就使用循环,循环结束cur还在重复的节点上,所以多走一步;如果节点不重复,定义一个傀儡节点和tmp引用变量,cur用来判断是否为重复节点,不是则tmp的next域等于cur,cur直到为null停下来

❗️核心代码:

 while (cur != null) {
    //cur.next != null : 判断是不是只有一个节点 或者 是不是尾巴节点
           if(cur.next != null && cur.val == cur.next.val) {
                //cur再走的过程当中,有可能剩下的都是相同的
                while ( cur.next != null && cur.val == cur.next.val) {
                    cur = cur.next;
                }
                cur = cur.next;//多走一步

注意细节:

1、循环结束,要多走一步;
2、万一只有一个节点cur.next为空,会出现空指针异常,所以要加上cur.next!=null;
3、如果链表都重复也要加上cur.next!=null,避免空指针异常
4、最后tmp需要手动设置null,有可能不是null,避免程序错误

💬完整代码:

 public Node deleteDuplication() {

        Node newHead = new Node(-1);
        Node tmp = newHead;
        Node cur = this.head;
        while (cur != null) {
            //cur.next != null : 判断是不是只有一个节点 或者 是不是尾巴节点
            if(cur.next != null && cur.val == cur.next.val) {
                //cur再走的过程当中,有可能剩下的都是相同的
                while ( cur.next != null && cur.val == cur.next.val) {
                    cur = cur.next;
                }
                cur = cur.next;//多走一步
            }else {
                tmp.next = cur;
                tmp = tmp.next;
                cur = cur.next;
            }
        }
        tmp.next = null;//手动设置,防止最后一个节点是重复的
        return newHead.next;
    }

1.8链表的回文结构

OJ链接

📜描述

对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。

给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。

测试样例:

1->2->2->1
返回:true

📝大体思路:

快慢指针+反转,1.2和1.3的结合,总体来说掌握中间节点和反转链表,就会很简单,反转完毕后,slow和head一起走直到相遇停止。

❗️核心代码:

 //1、找中间节点
        Node slow = this.head;
        Node fast = this.head;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        //slow 指向的节点 就是中间节点
        //2、进行翻转
        Node cur = slow.next;
        while (cur != null) {
            Node curNext = cur.next;
            cur.next = slow;
            slow = cur;
            cur = curNext;
        }

注意细节:

如果回文结构是1->2->2->1,最后一起走的时候head的next域等于slow就好了,不然按照正常执行代码是会出现错误

💬完整代码:

//链表回文
    public boolean chkPalindrome() {
        if(this.head == null) {
            return true;
        }
        if(this.head.next == null) {
            //只有一个节点
            return true;
        }
        //1、找中间节点
        Node slow = this.head;
        Node fast = this.head;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        //slow 指向的节点 就是中间节点
        //2、进行翻转
        Node cur = slow.next;
        while (cur != null) {
            Node curNext = cur.next;
            cur.next = slow;
            slow = cur;
            cur = curNext;
        }
        //翻转完毕 slow指的地方就是 最后一个节点
        while (slow != head) {
            if(slow.val != head.val) {
                return false;
            }
            if(head.next == slow) {
                return true;
            }
            head = head.next;
            slow = slow.next;
        }
        return true;
    }

1.9环形链表

OJ链接

📜描述:

给定一个链表,判断链表中是否有环。

📝大体思路:

也是需要用到快慢指针思想,fast比slow的速度快一倍,如果他们相遇就有环,不能相遇无环。

❗️核心代码:

fast = fast.next.next;
slow = slow.next;

注意细节:

为什么fast走两步而不是三步,因为会错过与slow的最佳相遇时机,走两步刚刚好

💬完整代码:

 //链表是否有环
    public boolean hasCycle1() {
        Node fast = this.head;
        Node slow = this.head;

        while(fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if(slow == fast) {
                return true;
            }
        }
        return false;
    }

2.0 环形链表 ||

OJ链接

📜描述

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

📝大体思路:

需要一个公式证明一个结论,看图说话。

有图可知:

由这个公式可以推出,x的路程==y的路程,就可以写代码了

❗️核心代码:

while(fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if(slow == fast) {
                break;
            }
        }

注意细节:

break跳出来的时候,有可能是不满足循环条件跳出来的,也有可能是条件语句跳出来的

💬完整代码:

  //给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
    public Node detectCycle() {
        Node fast = this.head;
        Node slow = this.head;

        while(fast != null && fast.next != 以上是关于java 数据结构常考的OJ 链表,重点!重点!!!的主要内容,如果未能解决你的问题,请参考以下文章

关于集合中一些常考的知识点总结

面试官常考的15道链表题,你会多少?建议收藏

Leetcode 前 400 重点 250 题

九章算法总结Java 搞定链表-面试常考题目精选

笔试常考的Java基础

近3年常考的Spring面试题及答案