链表面试专题

一、反转单向链表

private static void reverseNode(Node head) {
    Node pre = null;
    Node currentNode = head;

    while (currentNode != null) {
        Node next = currentNode.next;
        currentNode.next = pre;
        pre = currentNode;
        currentNode = next;
    }

二、反转双向链表

private static void reverseNode(Node head) {
        Node currentNode = head;
        Node temp = null;
        while (currentNode != null) {
            temp = currentNode.next;
            currentNode.next = currentNode.pre;
            currentNode.pre = temp;
            currentNode = temp;

        }
    }

三、判断一个链表是否为回文结构

[题目]给定一个单链表的头节点head,请判断该链表是否为回文结返回true;

[例子] 1->2->1,返回true;

1->2->2->1,返回true;

15->6->15,返回true;

1->2->3,返回false。

[要求]如果链表长度为N,时间复杂度达到0(N),额外空间复杂度大到0(1)

3.1 使用 Stack来解决(空间复杂度 N)

/**
 * 使用栈来解决,空间复杂度是 N
 */
private static boolean checkhuiwen(Node head) {
    if (head == null) {
        return false;
    }
    Stack<Node> stack = new Stack<>();
    Node p1 = head;
    while (p1 != null) {
        stack.push(p1);
        p1 = p1.next;
    }

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

}

3.2 使用快慢指针来解决(空间复杂度 1)

切成两边,然后从中间往两边进行比对,比对后,再给反转回来就行了~


/**
 * 使用指针来解决,空间复杂度是 1
 */
private static boolean checkhuiwen2(Node head) {
    if (head == null || head.next == null) {
        return true; // 空链表或者只有一个节点时,认为是回文结构
    }

    Node fastP = head;
    Node slowP = head;

    while (fastP.next != null && fastP.next.next != null) {
        slowP = slowP.next;
        fastP = fastP.next.next;
    }

    Node p1 = head;
    Node pre = null;
    Node next = null;
    while (true) {
        next = p1.next;
        p1.next = pre;
        if(p1 == slowP){
            break;
        }
        pre = p1;
        p1 = next;
    }

    Node pleft = slowP; // 前半部分反转后的头节点
    Node pRight = next;

    if(fastP.next == null){ // 链表长度奇数个
        pleft = pleft.next;
    }

    boolean flag = true;
    while (pleft != null && pRight != null) {
        if(pleft.value != pRight.value){
            flag = false;
            break;
        }
        pleft = pleft.next;
        pRight = pRight.next;
    }

    // 数组恢复原状
    head = reverseLink(slowP);
    slowP.next = next;

    return flag;
}

// <-1  2 -> 3 -> 4
public static Node reverseLink(Node head){
    Node pre = null;
    Node next = null;
    Node p1 = head;
    while(p1 != null){
        next = p1.next;
        p1.next = pre;
        pre = p1;
        p1 = next;
    }

    return pre; // 返回新的头结点
}

四、将单向链表切分左中右

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

(其实跟快速排序有点像,但是快排的空间复杂度是 NlogN,时间复杂度是 NlogN,并且快排是不稳定的)

[题目] 给定一个单链表的头节点head,节点的值类型是整型,再给定一个整数pivot。

实现一个调整链表的函数,将链表调整为左部分都是值小于pivot的节点,中间部分都是值等于pivot的节点,右部分都是值大于pivot的节点。

[要求]稳定性

[要求] 时间复杂度请达到0(N),额外空间复杂度请达到0(1)

public static class Node {
    private int value;
    private int index;
    private Node next;

}

public static void main(String[] args) {
    Node aNode = new Node();
    aNode.index = 0;
    aNode.value = 1;

    Node bNode = new Node();
    bNode.index = 1;
    bNode.value = 3;

    Node cNode = new Node();
    cNode.index = 2;
    cNode.value = 1;

    Node dNode = new Node();
    dNode.index = 3;
    dNode.value = 5;

    Node eNode = new Node();
    eNode.index = 4;
    eNode.value = 2;

    Node fNode = new Node();
    fNode.index = 5;
    fNode.value = 10;

    Node gNode = new Node();
    gNode.index = 6;
    gNode.value = 2;

    Node hNode = new Node();
    hNode.index = 7;
    hNode.value = 3;

    aNode.next = bNode;
    bNode.next = cNode;
    cNode.next = dNode;
    dNode.next = eNode;
    eNode.next = fNode;
    fNode.next = gNode;
    gNode.next = hNode;
    splitPartition(aNode ,3);
}

private static void splitPartition(Node head,int target) {
    Node smallHead = null;
    Node smallEnd = null;

    Node equalsHead = null;
    Node equalsEnd = null;

    Node biggerHead = null;
    Node biggerEnd = null;

    Node p = head;
    while(p != null){
        if(p.value < target){
            if(null != smallEnd){
                smallEnd.next = p;
            }
            smallEnd = p;

            if(null == smallHead){
                smallHead = p;
            }
        }
        else if(p.value == target){
            if(null != equalsEnd){
                equalsEnd.next = p;
            }
            equalsEnd = p;
            if(null == equalsHead){
                equalsHead = p;
            }
        } else {
            if(null != biggerEnd){
                biggerEnd.next = p;
            }
            biggerEnd = p;
            if(null == biggerHead){
                biggerHead = p;
            }
        }
        p = p.next;
    }

    // 极端情况下, target 都比链表中的值小,那么 biggerHead 和 biggerEnd 都会指向 null
    if(null != biggerEnd){
        biggerEnd.next = null;
    }
    // 极端情况下, target 都比链表中的值大,那么 smallHead 和 smallEnd 都会指向 null
    if(null != smallEnd){
        smallEnd.next = equalsHead;
    }
    // 极端情况下, target 跟链表中对比,没有相等的,那么 equalsHead 和 equalsEnd 都会指向 null
    if(null != equalsEnd){
        equalsEnd.next = biggerHead;
    }
}

五、复制含有随机指针节点的链表

rand指针是单链表节点结构中新增的指针,rand可能指向链表中的任意一个节点,也可能指向null。

给定一个由Node节点类型组成的无环单链表的头节点head,请实现一个函数完成这个链表的复制,并返回复制的新链表的头节点。

[要求] 时间复杂度0(N),额外空间复杂度0(1)

期望结果如下:

5.1 使用Map去复制

/**
 * 使用Map数组映射法
 * 空间复杂度是 N
 * 时间复杂度是 N
 */
private static Node copyLinkUseMap(Node head) {
    Node p1 = head;
    Map<Node, Node> map = new HashMap<>();
    // 复制节点并建立原节点和复制节点的映射
    while (p1 != null) {
        Node temp = new Node();
        temp.value = p1.value;
        temp.index = p1.index;
        map.put(p1, temp);
        p1 = p1.next;
    }

    p1 = head;
    // 链接复制节点的 next 和 random 指针
    while (p1 != null) {
        Node temp = map.get(p1);
        if(p1.next != null){
            temp.next = map.get(p1.next);
        }
        if(p1.random != null){
            temp.random = map.get(p1.random);
        }
        p1 = p1.next;
    }

    return map.get(head);// 返回复制链表的头节点
}

5.2  链表后面接着新节点

思路如下,

1、先全部连起来(新节点跟在老节点后面)

2、先处理 random 

3、再处理 next

整体流程看图:


 

/**
     * 使用跟屁虫方法
     * 空间复杂度是 1
     * 时间复杂度是 N
     */
    private static Node copyLinkUseAfterNode2(Node head) {
        Node p1 = head;
        while (p1 != null) {
            Node temp = new Node();
            temp.index = p1.index + 10;
            temp.value = p1.value;

            Node next = p1.next;
            p1.next = temp;
            temp.next = next;
            p1 = next;
        }
        p1 = head;
        int count = 1;
        Node prev = null;
        // 复制 random
        while (p1 != null) {
            if (count % 2 == 0 && prev != null && prev.random != null) {
                p1.random = prev.random.next;
            }
            prev = p1;
            count++;
            p1 = p1.next;
        }

        p1 = head;
        Node newHead = p1.next;
        Node p2 = head.next != null ? head.next.next : null;
        // 连接复制节点和原始节点
        while (p1 != null && p2 != null) {
            if (p2 != null) {
                p1.next.next = p2.next;
            }
            p1.next = p2;

            p1 = p2;
            if (p2 != null && p2.next != null) {
                p2 = p2.next.next;
            }
        }

        return newHead;
    }

六、判断单向链表有没有环

public static class Node {
        private int index;
        private Node next;
    }

    public static void main(String[] args) {
        Node node0 = new Node();
        node0.index = 0;

        Node node1 = new Node();
        node1.index = 1;

        Node node2 = new Node();
        node2.index = 2;

        Node node3 = new Node();
        node3.index = 2;

        Node node4 = new Node();
        node4.index = 2;

        node0.next = node1;
        node1.next = node2;
        node2.next = node3;
        node3.next = node4;
        node4.next = node1;
        // 0 -> 1 -> 2 -> 3 -> 4 -> 1

//        boolean containsLoop = checkLoopSet(node0);
        boolean containsLoop = checkLoopFS(node0);


        System.out.println(containsLoop);

    }

    /**
     * 使用快慢指针,也就是龟兔赛跑,乌龟一次走1步,兔子一次走2步,如果相遇,说明有环
     * 空间复杂度是 1
     */
    private static boolean checkLoopFS(Node head) {
        Node fast = head;
        Node slow = head;
        // 极端情况 1 -> 2 -> 1
        // 如果 fast指针走完了,说明没环
        while (fast != null && fast.next != null && fast.next.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) {
                return true;
            }
        }
        return false;
    }

    /**
     * 使用 Set, 空间复杂度是 N
     */
    private static boolean checkLoopSet(Node head) {
        Set<Node> set = new HashSet<>();
        Node p1 = head;
        // 如果没有环,那么最后的节点肯定是 null
        while (p1 != null) {
            if (set.contains(p1)) {
                return true;
            }
            set.add(p1);
            p1 = p1.next;
        }
        return false;
    }

七、单链表有环,找到入环点

    /**
     * 核心代码
     * @param head 头结点
     * @param hitNode 相遇点点
     * @return
     */
    private static Node getLoopFirstNode(Node head ,Node hitNode) {
        Node p1 = head;
        Node p2 = hitNode;
        while(p1 != p2){
            p1 = p1.next;
            p2 = p2.next;
        }
        return p1;
    }
public static class Node {
        private int index;
        private Node next;
    }

    public static void main(String[] args) {
        Node node0 = new Node();
        node0.index = 0;

        Node node1 = new Node();
        node1.index = 1;

        Node node2 = new Node();
        node2.index = 2;

        Node node3 = new Node();
        node3.index = 3;

        Node node4 = new Node();
        node4.index = 4;

        node0.next = node1;
        node1.next = node2;
        node2.next = node3;
        node3.next = node4;
        node4.next = node2;
        // 0 -> 1 -> 2 -> 3 -> 4 -> 1

//        Node hitLoopNode = checkLoopSet(node0);
        // 相遇的环形节点
        Node hitLoopNode = checkLoopFS(node0);
        if (null != hitLoopNode) {
            System.out.println("hitLoopNode -> " + hitLoopNode.index);
        } else {
            System.out.println("No");
        }

        Node loopFirstNode = null;
        // 如果无环
        if(null != hitLoopNode){
            loopFirstNode = getLoopFirstNode(node0, hitLoopNode);
            System.out.println("loopFirstNode -> " + loopFirstNode.index);
        } else {
            // 走无环的解法
        }




    }

    /**
     * @param head 头结点
     * @param hitNode 相遇点点
     * @return
     */
    private static Node getLoopFirstNode(Node head ,Node hitNode) {
        Node p1 = head;
        Node p2 = hitNode;
        while(p1 != p2){
            p1 = p1.next;
            p2 = p2.next;
        }
        return p1;
    }

    /**
     * 使用快慢指针,也就是龟兔赛跑,乌龟一次走1步,兔子一次走2步,如果相遇,说明有环
     * 空间复杂度是 1
     */
    private static Node checkLoopFS(Node head) {
        Node slow = head;
        Node fast = head;
        // 极端情况 1 -> 2 -> 1
        // 如果 fast指针走完了,说明没环
        while (fast != null && fast.next != null && fast.next.next != null) {
            slow = slow.next;
            fast = fast.next.next;
            if (fast == slow) {
                return fast;
            }
        }
        return null;
    }

八、 两个单链表相交问题

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

[要求]如果两个链表长度之和为N,时间复杂度请达到0(N),额外空间复杂度请达到0(1)

8.0 全部情况分析

8.1 链表无环

public static class Node {
        private int index;
        private Node next;
    }

    public static void main(String[] args) {
        Node aNode = new Node();
        aNode.index = 11;

        Node bNode = new Node();
        bNode.index = 12;

        Node cNode = new Node();
        cNode.index = 13;

        aNode.next = bNode;
        bNode.next = cNode;
        // a -> b -> c


        Node aaNode = new Node();
        aaNode.index = 21;

        Node bbNode = new Node();
        bbNode.index = 22;

        aaNode.next = bbNode;
        bbNode.next = bNode;
        // aa -> bb -> b

        /*
                     a-> b -> c
                        ↗
                aa -> bb
         */
        Node hitNode = getFirstHitNodeNoLoop(aNode, aaNode);
        System.out.println(hitNode);
        if(null != hitNode){
            System.out.println(hitNode.index);
        }
    }

    private static Node getFirstHitNodeNoLoop(Node headA, Node headB) {
        // 如果有一个链表为空,直接返回 null
        if (headA == null || headB == null) {
            return null;
        }
        // 1. 先算出俩数组长度,为什么是1呢,因为下面的退出条件是 p1.next == null,也就是会少算一个
        int lenA = 1;
        int lenB = 1;
        Node p1 = headA;
        Node p2 = headB;
        while (p1.next != null) {
            lenA++;
            p1 = p1.next;
        }

        while (p2.next != null) {
            lenB++;
            p2 = p2.next;
        }
        // 如果最后的节点不是同一个,那么肯定不相交
        if (p1 != p2) {
            return null;
        }

        p1 = headA;
        p2 = headB;

        int diff = lenB - lenA > 0 ? lenB - lenA : lenA - lenB;
        while (diff > 0) {
            if (lenA > lenB) {
                p1 = p1.next;
            } else {
                p2 = p2.next;
            }
            diff--;
        }
        // 同时移动指针,找到相交节点
        while (p1 != null && p2 != null) {
            if (p1 == p2) {
                return p1;
            }
            p1 = p1.next;
            p2 = p2.next;
        }
        return null;
    }

7.2 整体解法

public static class Node {
        private int index;
        private Node next;
    }

    public static void main(String[] args) {
        Node a1Node = new Node();
        a1Node.index = 11;

        Node a2Node = new Node();
        a2Node.index = 12;

        Node a3Node = new Node();
        a3Node.index = 13;

        a1Node.next = a2Node;
        a2Node.next = a3Node;
        a3Node.next = a2Node;
        // 11 -> 12 -> 13 -> 12


        Node b1Node = new Node();
        b1Node.index = 21;

        Node b2Node = new Node();
        b2Node.index = 22;

        b1Node.next = b2Node;
        b2Node.next = a2Node;


        /*
                   11-> 12 -> 13
                        ↗
                21 -> 22
         */
        // 找到a的入环节点,null表示没有环
        Node aLoopHitNode = getLoopHitNode(a1Node);
        Node bLoopHitNode = getLoopHitNode(b1Node);
        System.out.println("aLoopHitNode -> " + aLoopHitNode);
        if (null != aLoopHitNode) {
            System.out.println("aLoopHitNode -> " + aLoopHitNode.index);
        }
        System.out.println("bLoopHitNode -> " + bLoopHitNode);
        if (null != bLoopHitNode) {
            System.out.println("bLoopHitNode -> " + bLoopHitNode.index);
        }

        Node sameHitNode = null;
        // 情况一:a链表和b链表都无环,可能相交,也可能不相交
        if (null == bLoopHitNode && null == aLoopHitNode) {
            sameHitNode = getSameHitNodeNoLoopWithEnd(a1Node, null, b1Node, null);
        }
        // 情况二:2.1 俩链表一个有环,一个没环,肯定不相交
        if (null != bLoopHitNode && null == aLoopHitNode) {
            sameHitNode = null;
        }
        // 情况二:2.2 俩链表一个有环,一个没环,肯定不相交
        if (null == bLoopHitNode && null != aLoopHitNode) {
            sameHitNode = null;
        }

        // 情况三:俩链表都有环
        if (null != bLoopHitNode && null != aLoopHitNode) {
            // 3.1 碰撞点是同一个,说明在环前面(或者刚好环的入口)
            if (aLoopHitNode == bLoopHitNode) {
                // 为什么是 aLoopHitNode.next 与 bLoopHitNode.next?
                // 因为如果两个链表的相交点,刚好是入环点的话,那么他们终止节点应该往后挪一个,进入这个方法后,才会使得 p1 = p2
                sameHitNode = getSameHitNodeNoLoopWithEnd(a1Node, aLoopHitNode.next, b1Node, bLoopHitNode.next);
            } else {
                // 判断是否共享同一个环
                Node p1 = aLoopHitNode.next;
                while (p1 != aLoopHitNode) {
                    if (p1 == bLoopHitNode) { // 碰上了,肯定是同一个环,一定有交点
                        sameHitNode = aLoopHitNode;
                        break;
                    }
                    p1 = p1.next;
                }
                // 3.2 如果a绕了一圈,没找到b,说明不是一个环
                if(p1 == aLoopHitNode){
                    sameHitNode = null;
                }
            }
        }

        System.out.println("sameHitNode -> " + sameHitNode);

        if (null != sameHitNode) {
            System.out.println(sameHitNode.index);
        }
    }

    /**
     * 2个无环链表求交点
     *
     * @return 返回null表示没交点
     */
    private static Node getSameHitNodeNoLoopWithEnd(Node aHead, Node aEnd, Node bHead, Node bEnd) {
        if (aHead == aEnd || bHead == bEnd) {
            return null;
        }
        int aLen = 1;
        int bLen = 1;
        Node p1 = aHead;
        Node p2 = bHead;
        while (p1.next != aEnd) {
            aLen++;
            p1 = p1.next;
        }
        while (p2.next != bEnd) {
            bLen++;
            p2 = p2.next;
        }
        // 情况1: 终止节点不是一个,那肯定不相交
        if (p1 != p2) {
            return null;
        }
        // 情况2:终止节点是一个,需要找到相交点
        p1 = aHead;
        p2 = bHead;
        // 看看俩链表相差多少
        int diff = Math.abs(aLen - bLen);
        while (diff > 0) {
            if (aLen > bLen) {
                p1 = p1.next;
            } else {
                p2 = p2.next;
            }
            diff--;
        }

        while (p1 != p2) {
            p1 = p1.next;
            p2 = p2.next;
        }
        return p1;
    }


    /**
     * 快慢指针拿到入环节点
     */
    private static Node getLoopHitNode(Node head) {
        Node fast = head;
        Node slow = head;
        if (null == head) {
            return null;
        }
        if (fast.next == null || fast.next.next == null) {
            return null;
        }

        // 看下是否能碰撞上
        while (fast.next != null && fast.next.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) {
                break;
            }
        }
        // 说明没环,退出是因为 fast.next == null
        if (fast != slow) {
            return null;
        }
        // 有环的情况下,slow指向 head,然后slow和fast每次走一步
        slow = head;
        while (slow != fast) {
            slow = slow.next;
            fast = fast.next;
        }
        return slow;
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值