链表面试题

72 阅读4分钟

今天和大家分析一下链表的题目😀

链表

1.反转链表

 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;
    }
​
  class ListNode {
     int val;
     ListNode next;
     ListNode() {}
     ListNode(int val) { this.val = val; }
     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 }

2.判断链表是否回文

笔试:

思路:栈(再省一点空间,只把右边的东西放入栈,快慢指针来寻找种中点位置)

    public static boolean isPalindrome(ListNode head) {
        if(head==null || head.next==null){
            return true;
        }
        ListNode slow=head;
        ListNode fast=head;
        Stack<ListNode> stack = new Stack<>();
        while( fast.next!= null && fast.next.next!=null){       //注意fast.next也要判断
            slow=slow.next;
            fast=fast.next.next;
        }
        slow=slow.next;
​
        while(slow!=null){
            stack.add(slow);
            slow=slow.next;
        }
​
        if(stack.isEmpty())return head.val==head.next.val;        //当链表长度为2的时候要分开讨论
​
        while(!stack.isEmpty()){
            if(stack.pop().val!=head.val){
                return false;
            }
            head=head.next;
        }
        return true;
    }

面试

思路:快慢指针(1->2->3<-2<-1)

public  static boolean isPalindrome2(ListNode head) {
        if(head==null || head.next==null){
            return true;
        }
        ListNode slow=head;
        ListNode fast=head;
        while(fast.next!= null && fast.next.next!=null){       //注意fast.next也要判断
            slow=slow.next;
            fast=fast.next.next;
        }
        fast=slow.next;//快指针来到慢指针的位置
        slow.next=null;//中间位置指向空
        ListNode temp=null;//相当于next
        while(fast!=null){
            temp=fast.next;
            fast.next=slow;    //slow相当于pre
            slow=fast;
            fast=temp;
        }
        temp=slow;
        fast=head;
        while (fast!=null && temp!=null){
            if(fast.val!=temp.val){
                return false;
            }
            fast=fast.next;
            temp=temp.next;
        }
        return true;
    }

3.将单向链表按某值划分成左边小、中间相等、右边大的形式

思路:

1.png

public static class Node {
        public int value;
        public Node next;
​
        public Node(int data) {
            this.value = data;
        }
    }
​
    public static Node listPartition2(Node head,int pivot){
        Node sh=null;
        Node st=null;
        Node eh=null;
        Node et=null;
        Node bh=null;
        Node bt=null;
        Node next=null;
​
        while (head!=null){
            next=head.next;
            head.next=null;
            if(head.value<pivot){
                if(sh==null){
                    sh=head;
                    st=head;
                }else{
                    st.next=head;
                    st=head;
                }
            }else if(head.value==pivot){
                if(eh==null){
                    eh=head;
                    et=head;
                }else{
                    et.next=head;
                    et=head;
                }
            }else{
                if(bh==null){
                    bh=head;
                    bt=head;
                }else{
                    bt.next=head;
                    bt=head;
                }
            }
            head=next;
        }
        if(st!=null){
            st.next=eh;
            et=et==null? st:et;
        }
        if(et!=null){
            et.next=bh;
        }
        return sh!=null? sh:eh!=null?eh:bh;
    }

4.复制含有随机指针节点的链表

public static class Node {
    public int value;
    public Node next;
    public Node rand;
​
    public Node(int data) {
      this.value = data;
    }
  }

rand指针是单链表节点结构中新增的指针,rand可能指向链表中的任意一个节 点,也可能指向null。给定一个由Node节点类型组成的无环单链表的头节点 head,请实现一个函数完成这个链表的复制,并返回复制的新链表的头节点。

思路:

1.hashmap(空间复杂度O(n))

2.省空间(空间复杂度O(1))

2.png

​
​
  public static Node copyListWithRand1(Node head) {
    HashMap<Node, Node> map = new HashMap<Node, Node>();
    Node cur = head;
    while (cur != null) {
      map.put(cur, new Node(cur.value));
      cur = cur.next;
    }
    cur = head;
    while (cur != null) {
      map.get(cur).next = map.get(cur.next);
      map.get(cur).rand = map.get(cur.rand);
      cur = cur.next;
    }
    return map.get(head);
  }
​
  public static Node copyListWithRand2(Node head) {
    if (head == null) {
      return null;
    }
    Node cur = head;
    Node next = null;
    // copy node and link to every node
    while (cur != null) {
      next = cur.next;
      cur.next = new Node(cur.value);
      cur.next.next = next;
      cur = next;
    }
    cur = head;
    Node curCopy = null;
    // set copy node rand
    while (cur != null) {
      next = cur.next.next;
      curCopy = cur.next;
      curCopy.rand = cur.rand != null ? cur.rand.next : null;
      cur = next;
    }
    Node res = head.next;
    cur = head;
    // split
    while (cur != null) {
      next = cur.next.next;
      curCopy = cur.next;
      cur.next = next;
      curCopy.next = next != null ? next.next : null;
      cur = next;
    }
    return res;
  }

5.两个单链表相交的一系列问题

给定两个可能有环也可能无环的单链表,头节点head1和head2。请实 现一个函数,如果两个链表相交,请返回相交的 第一个节点。如果不相交,返回null

思路:

分解问题:链表有无环

1.两个链表都没有环

3.png 2.两个链表都有环

4.png

情况1:两个链表不可能有交点,返回null;

情况2:两个链表的入环节点是同一个;

情况3:两个链表的入环节点不同

3.其中一个链表有环:不可能存在交点,所以结构均为假象,自己动手画出来就可以推翻(单链表只有1个next节点)

具体分析

1.如何判断一个链表有无环?

public static class Node {
    public int value;
    public Node next;
​
    public Node(int data) {
        this.value = data;
    }
}
​
public static Node getLoop(Node head){
    if(head==null || head.next==null || head.next.next==null){
        return null;
    }
    //使用快慢指针来判断是否有环?
    Node fast=head.next.next;
    Node slow=head.next;
    while(fast!=slow){
        //若快指针为Null必然无环
        if(fast.next!=null && fast.next.next!=null){
            return null;
        }
        slow=slow.next;
        fast=fast.next.next;
    }
    
    //跳出循环说明必然有环
    fast=head;
    while(fast!=slow){
        //当快慢指针走到同一个位置,则让快指针回到头节点,重新变成一步走一位;满指针原地不动,之后两个指针再次相遇的便是入环节点
        fast=fast.next;
        slow=slow.next;
    }
    return slow;
}

2.两个无环的链表如何判断是否存在交点?(对于上述两个链表都存在环的且入环节点相同的情况==判断两个无环链表的交点问题(将交点是为最后节点))

//先判断出那个是长链表(len=max),那个是短链表(len=min)
//长链表先走(max-min)步(交点只可能出现在此后,其他结构自行画图推翻)
public static Node noLoop(Node head1,Node head2){
    if(head1==null || head2==null){
        return null;
    }
    int n=0;//长度
    Node cur1=head1;
    Node cur2=head2;
    while(cur1!=null){
        n++;
        cur1=cur1.next;
    }
    while(cur2!=null){
        n--;
        cur2=cur2.next;
    }
    cur1=n>0?head1:head2;//将长链表的头赋值给cur1
    cur2=cur1==head1?head2:head1;//将短链表的值赋值给cur2
    n=Math.abs(n);//n记得取绝对值,n可能为负数
    //长链表先走(max-min)步
    while(n!=0){
        n--;
        cur1=cur1.next;
    }
    //寻找交点
    while(cur1!=cur2){
        cur1=cur1.next;
        cur2=cur2.next;
    }
    return cur1;
}

3.两个有环链表的交点

1.同一入环节点

2.不同入环节点(答案返回任意一个都可以)

public static Node bothLoop(Node head1,Node head2,Node loop1,Node loop2){
    Node cur1=null;
    Node cur2=null;
​
    //同一入环节点,可以视为无环节点相交问题(自行画出两个链表到同一入环节点的形状)
    if(loop1==loop2){
        cur1=head1;
        cur2=head2;
        int n=0;
        while(cur1!=null){
            n++;
            cur1=cur1.next;
        }
        while(cur2!=null){
            n--;
            cur2=cur2.next;
        }
        cur1=n>0?head1:head2;
        cur2=cur1==head1?head2:head1;
        n=Math.abs(n);
        while(n!=0){
            n--;
            cur1=cur1.next;
        }
        while(cur1!=cur2){
            cur1=cur1.next;
            cur2=cur2.next;
        }
        return cur1;
    }else{
        //不同入环节点,或者无相同的交点
        //直接从某一个链表的入环节点开始遍历,寻找一圈如果环内也存在另一链表的入环节点则就返回两个链表中任意一个入环节点即可
        cur1=loop1.next;
        while(cur1!=loop1){
            if(cur1==loop2){
                return loop1;
            }
            cur1=cur1.next;
        }
        //无交点
        return null;
    }
}

4.汇总主函数如何调度

public static Node getIntersectNode(Node head1,Node head2){
    if(head1==null || head2==null){
        return null;
    }
    Node loop1 = getLoop(head1);
    Node loop2 = getLoop(head2);
    
    if(loop1==null && loop2==null){
        return noLoop(head1,head2);
    }
    if(loop1!=null && loop2!=null){
        return bothLoop(head1,head2,loop1,loop2);
    }
    return null;
}

5.数组模拟双端队列(leetcode641)

public class MyCircularDeque {
    int[] nums;
    int he, ta, cnt, k;//he 头指针  ta 尾指针  cnt 当前队列中元素的个数  k 双端队列的大小
​
    public MyCircularDeque(int _k) {
        k = _k;
        nums = new int[k];
    }
​
    public boolean insertFront(int value) {
        if(isFull())return false;
        he=(he+k-1)%k;
        nums[he]=value;
        cnt++;
        return true;
    }
​
    public boolean insertLast(int value) {
        if(isFull())return false;
        nums[ta++]=value;
        cnt++;
        ta%=k;
        return true;
    }
​
    public boolean deleteFront() {
        if(isEmpty())return false;
        he=(he+1)%k;
        cnt--;
        return true;
    }
​
    public boolean deleteLast() {
        if(isEmpty())return false;
        ta=(ta-1+k)%k;
        cnt--;
        return true;
    }
​
    public int getFront() {
        return isEmpty()? -1:nums[he];
    }
​
    public int getRear() {
        return isEmpty()?-1:nums[(ta+k-1)%k];
    }
​
    public boolean isEmpty() {
        return cnt==0;
    }
​
    public boolean isFull() {
        return cnt==k;
    }
}