数据结构与算法学习笔记(基础班六)---链表

链表面试题常用数据结构和技巧

1)使用容器(哈希表、数组等)

2)快慢指针

快慢指针

1)输入链表头节点,奇数长度返回中点,偶数长度返回上中点

2)输入链表头节点,奇数长度返回中点,偶数长度返回下中点

3)输入链表头节点,奇数长度返回中点前一个,偶数长度返回上中点前一个

4)输入链表头节点,奇数长度返回中点前一个,偶数长度返回下中点前一个

  • 代码如下
/**
 * 整体流程都是快指针一次走两步,慢指针一次走一步,当快指针走到链表尾部是,
 * 慢指针正好走到整个链表中部位置(位置需要根据要求作出些许调整)
 */
public class FastLowPoint01 {
    //输入链表头节点,奇数长度返回中点,偶数长度返回上中点
    public static Node getMidNode1(Node head){
        if(head == null || head.next == null || head.next.next == null){
            // 为空时返回空,一个节点奇数返回一个,两个节点偶数返回上中点
            return head;
        }
        // 至少有两个指针
        // o -> o -> o -> o -> o --null
        Node low = head;
        Node fast = head.next;
        while (fast != null && fast.next != null){
            low = low.next;
            fast = fast.next.next;
        }
        return low;
    }
    // 输入链表头节点,奇数长度返回中点,偶数长度返回下中点
    public static Node getMidNode2(Node head){
        if(head == null || head.next == null){
            return head;
        }
        // o -> o -> o -> o -> o --null
        Node low = head;
        Node fast = head;
        while (fast != null && fast.next != null){
            low = low.next;
            fast = fast.next.next;
        }
        return low;
    }
    // 输入链表头节点,奇数长度返回中点前一个,偶数长度返回上中点前一个
    public static Node getMidNode3(Node head){
        if (head == null || head.next == null || head.next.next == null){
            return null;
        }

        // o -> o -> o -> o -> o --null
        Node low = head;
        Node fast = head.next.next;
        while (fast != null && fast.next != null && fast.next.next != null){
            low = low.next;
            fast = fast.next.next;
        }
        return low;
    }

    // 输入链表头节点,奇数长度返回中点前一个,偶数长度返回下中点前一个
    public static Node getMidNode4(Node head){
        if(head == null || head.next == null){
            // o
            return null;
        }
        Node low = head;
        Node fast = head.next;
        // o -> o -> o -> o -> o --null
        while (fast != null && head.next != null && fast.next.next != null){
            low = low.next;
            fast = fast.next.next;
        }
        return low;
    }
}

常见面试题

  • 给定一个单链表的头节点head,请判断该链表是否为回文结构。
  1. 方法一,利用栈。
    1)先遍历一遍链表把节点加入栈中。
    2)弹出一个节点,从头遍历链表,比较。
public class PalindromeLinked {
    // 栈
    public static boolean isPalindrome(Node head){
        if(head == null){
            return true;
        }

        Stack stack = new Stack<>();
        Node cur = head;
        while (cur != null){
            stack.push(cur);
            cur = cur.next;
        }

        cur = head;
        while (!stack.isEmpty()){
            Node pop = stack.pop();
            if(pop.value != cur.value){
                return false;
            }
            cur = cur.next;
        }

        return true;
    }

    // 双指针
    public static boolean isPalindrome2(Node head){
        if(head == null || head.next == null){
            return true;
        }
        Node low = head;
        Node fast = head.next;
        // o -> o -> o -> o -> o
        while (fast != null && fast.next != null){
            low = low.next;
            fast = fast.next.next;
        }
        // 此时low指向链表中点,从low 开始反转链表

        Node pre = null;
        Node cur = low;

        while (cur != null){
            Node next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        Node tail = pre;
        cur = head;
        while (cur != null && pre != null){
            if(cur.value != pre.value){
                return false;
            }
            cur = cur.next;
            pre = pre.next;
        }
        pre = null;
        cur = tail;
        while (cur != null){
            Node next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        return true;
    }
}
  • 将单向链表按某值划分成左边小、中间相等、右边大的形式
    1)把链表放入数组里,在数组上做partition(笔试用)。
    2)分成小、中、大三部分,再把各个部分之间串起来(面试用)。
  • 克隆链表
    一种特殊的单链表节点类描述如下
    class Node {
    int value;
    Node next;
    Node rand;
    Node(int val) { value = val; }
    }
    rand指针是单链表节点结构中新增的指针,rand可能指向链表中的任意一个节点,也可能指向null。
    给定一个由Node节点类型组成的无环单链表的头节点 head,请实现一个函数完成这个链表的复制,并返回复制的新链表的头节点。
    【要求】
    时间复杂度O(N),额外空间复杂度O(1)
public class CopyLinked {

    public static Node copyLinked(Node oldHead){
        if(oldHead == null){
            return null;
        }

        // 在原链表中间增加复制节点
        Node cur = oldHead;
        while (cur != null){
            Node next = cur.next;
            cur.next = new Node(cur.value);
            cur.next.next = next;
            cur = next;
        }

        // 重新遍历链表,把克隆节点的random连好
        cur = oldHead;
        while (cur != null){
            // 获取原节点的random节点
            Node randomOld = cur.random;
            // 克隆节点的random节点
            cur.next.random = randomOld == null ? null : randomOld.next;
            cur = cur.next.next;
        }
        // 分离新老链表
        cur = oldHead;
        Node res = oldHead.next;
        while (cur != null){
            Node oldNext = cur.next.next;
            Node next = cur.next;
            cur.next = next.next;
            next.next = cur.next == null ? null : cur.next.next;
            cur = oldNext;
        }
        return res;
    }
}

  • 给定两个可能有环也可能无环的单链表,头节点head1和head2。请实现一个函数,如果两个链表相交,请返回相交的 第一个节点。如果不相交,返回null 。
    【要求】
    如果两个链表长度之和为N,时间复杂度请达到O(N),额外空间复杂度 请达到O(1)。
public class TwoLinked {

    public  Node getFirstNode(Node node1,Node node2){

        if(node1 == null || node2== null){
            return null;
        }
        // 如果量表都没有环
        if(isLoop(node1) == null && isLoop(node2) == null){
            // 两个链表都没有环
            return notLoop(node1,node2);
        }else if (isLoop(node1)!=null && isLoop(node2)!=null){
            // 两个链表都有环
            return bothLoop(node1,node2);
        }
        // 一个链表有环,一个链表没有环是不存在的
        return null;
    }

    private Node notLoop(Node node1,Node node2){
        // 两个无环链表,先看为指针是否相等
        int n1 = 0;
        int n2 = 0;
        Node cur1 = node1;
        Node cur2 = node2;
        while (cur1.next != null){
            n1++;
            cur1 = cur1.next;
        }

        while (cur2.next != null){
            n2++;
            cur2 = cur2.next;
        }
        if(cur1 != cur2){
            return null;
        }
        // 长链表
        cur1 = n1 > n2 ? node1 : node2;
        // 短链表
        cur2 = n1 > n2 ? node2 : node1;
        int n = Math.abs(n1-n2);
        // 长链表先走n步
        while (n > 0){
            cur1 = cur1.next;
            n--;
        }
        // 一起走
        while (cur1 != cur2){
            cur1 = cur1.next;
            cur2 = cur2.next;
        }
        return cur1;
    }

    private Node bothLoop(Node node1,Node node2){
        // 求两个节点的入环节点
        Node loop1 = isLoop(node1);
        Node loop2 = isLoop(node2);
        if(loop1 == loop2){
            // 变换成两个无环链表求第一个相交节点问题
            int n1 = 0;
            int n2 = 0;
            Node cur1 = node1;
            Node cur2 = node2;
            while (cur1 != loop1){
                n1++;
                cur1 = cur1.next;
            }
            while (cur2 != loop1){
                n2++;
                cur2 = cur2.next;
            }

            cur1 = n1 > n2 ? node1:node2 ;
            cur2 = n1 > n2 ? node2:node1 ;
            int n = Math.abs(n1-n2);
            while (n > 0){
                cur1 = cur1.next;
                n--;
            }
            while (cur1 != cur2){
                cur1 = cur1.next;
                cur2 = cur2.next;
            }
            return cur1;
        }else{
            Node cur = loop1.next;
            while (cur != loop1){
                if(cur == loop2){
                    return loop1;
                }
                cur = cur.next;
            }
            return null;
        }
    }

    // 判断链表是否有环,返回第一个入环节点
    private Node isLoop(Node node){
        if(node == null || node.next == null || node.next.next==null){
            // 一个节点,或者两个节点都没有环
            return null;
        }
        // 快慢指针
        Node fast = node.next.next;
        Node low = node.next;
        while (fast != low){
            if(fast.next == null || low.next == null){
                return null;
            }
            fast = fast.next.next;
            low = low.next;
        }
        // 此时fast 回到头结点
        fast = node;
        while (fast != low){
            // 再次相遇则为入环节点
            if(fast == low){
                break;
            }
            fast = fast.next;
            low = low.next;
        }

        return fast;
    }   
}
  • 能不能不给单链表的头节点,只给想要删除的节点,就能做到在链表上把这个点删掉?

你可能感兴趣的:(数据结构与算法学习笔记(基础班六)---链表)