03_链表算法学习

About 9 minstudyalgorithm

一、快慢指针代码练习

求链表的中点值,慢指针步长是1,快指针的步长是2。快指针走完的时候,慢指针就是中点值。

public static class FastSlowPointer{

        public Node root;
        public Node current;

        public FastSlowPointer() {
            this.root = new Node();
            this.current = root;

        }

        public void push(Node node) {
            this.current.next = node;
            this.current = node;
        }

        // 1)输入链表头节点,奇数长度返回中点,偶数长度返回上中点
        public Node getMiddle1() {
            if(this.root == null || this.root.next == null || this.root.next.next == null) {
                return null;
            }
            Node fast = this.root.next.next.next;
            Node slow = this.root.next.next;
            while ( fast.next != null && fast.next.next != null) {
                fast = fast.next.next;
                slow = slow.next;
            }
            return slow;
        }

        // 2)输入链表头节点,奇数长度返回中点,偶数长度返回下中点
        public Node getMiddle2() {
            if(this.root == null || this.root.next == null || this.root.next.next == null) {
                return null;
            }
            Node fast = this.root.next.next;
            Node slow = this.root.next.next;
            while ( fast.next != null && fast.next.next != null) {
                fast = fast.next.next;
                slow = slow.next;
            }
            return slow;
        }

        // 3) 输入链表头节点,奇数长度返回中点前一个,偶数长度返回上中点前一个
        public Node getMiddle3() {
            if(this.root == null || this.root.next == null || this.root.next.next == null) {
                return null;
            }
            Node fast = this.root.next.next.next;
            Node slow = this.root.next;
            while ( fast.next != null && fast.next.next != null) {
                fast = fast.next.next;
                slow = slow.next;
            }
            return slow;
        }

        // 4)输入链表头节点,奇数长度返回中点前一个,偶数长度返回下中点前一个
        public Node getMiddle4() {
            if(this.root == null || this.root.next == null || this.root.next.next == null) {
                return null;
            }
            Node fast = this.root.next.next;
            Node slow = this.root.next;
            while ( fast.next != null && fast.next.next != null) {
                fast = fast.next.next;
                slow = slow.next;
            }
            return slow;
        }
    }

二、给定链表判断是否为回文

1、利用栈额外空间复杂度为O(N)

    // 用栈实现回文,先将节点放入栈中。之后弹出值与节点数据进行比较
    public static boolean isPalindromes(Node node) {
        Node current = node;
        Stack<Integer> stack = new Stack<>();
        while (current != null) {
            stack.push(current.val);
            current = current.next;
        }

        Node i = node;
        while (i != null) {
            if(i.val != stack.pop()) {
                return false;
            }
            i = i.next;

        }
        return true;
    }

2、利用栈额外空间复杂度为O(N/2)


    // 利用快慢指针实现,空间复杂度小一半
    public static boolean isPalindromes1(Node node) {
        // 利用快慢指针获取节点的中间数,奇数获取中间值,偶数获取中下节点
        Node fast = node.next;
        Node slow = node.next;
        while (fast.next !=null && fast.next.next !=null) {
            fast = fast.next.next;
            slow = slow.next;
        }

        // 将中间值后面的元素放入栈中
        Stack<Integer> stack = new Stack<>();
        while (slow != null) {
            stack.push(slow.val);
            slow = slow.next;
        }

        // 对比栈里面抛出的元素和节点元素,不一样则不是回文
        Node current = node;
        while (!stack.isEmpty()) {
            if(current.val != stack.pop()) {
                return false;
            }
            current = current.next;
        }
        return true;
    }

3、额外空间复杂度O(1)

1、用快慢指针找到中点值

2、将中点值后面的值逆序

3、对比两边的值不相等则保存返回值

4、将之前的逆序值还原

    // 不用堆,直接找到中值后进行数字比较
    public static boolean isPalindromes2(Node node) {
        Node fast = node;
        Node slow = node;
        while (fast.next != null && fast.next.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }

        // 逆序后面的数据
        Node newHead = slow.next; // fast是中间数后面的值
        slow.next = null; // 从中间切断
        Node temp = null;
        while (newHead!= null) {
            temp = newHead.next;
            newHead.next = slow;
            slow = newHead;
            newHead = temp;
        }

        temp  = node;
        fast = slow;
        boolean res = true;
        while (temp != null && fast!=null) {
            if(temp.val != fast.val) {
                res = false;
                break;
            }
            temp = temp.next;
            fast = fast.next;
        }

        // 重新逆序还原
        temp = slow.next;
        slow.next = null;
        while (temp != null) {
            fast = temp.next;
            temp.next = slow;
            slow = temp;
            temp = fast;
        }

        return res;
    }

三、将链表区分为左边小中间等右边大

1、思路

  • 创建6个变量小于指定数的头和尾节点、等于指定数的头和尾节点、大于指定数的头和尾节点

  • 遍历链表,将各个区域的节点用头尾进行串联

  • 将小于指定数的尾节点指向等于指定数的头节点,等于指定数的尾节点指向大于指定数的头节点

  • 边界值处理。

2、代码

    /**
     * 通过有限几个变量进行分区,额外空间复杂度低
     */
    public static Node partition(Node node, int pivot) {
        // 1、定义小于区域的头和尾节点,等于区域的头和尾节点,大于区域的头和尾节点
        Node smallHead = null, smallTail = null;
        Node equalHead = null, equalTail = null;
        Node bigHead = null, bigTail = null;

        // 2、遍历所有节点,将小于、等于、大于的区域通过头尾节点串连
        Node temp = null;
        while (node != null) {
            temp = node.next;
            node.next = null;
            if(node.val < pivot) {
                if(smallHead == null) {
                    smallHead = node;
                    smallTail = node;
                } else {
                    smallTail.next = node;
                    smallTail = node;
                }
            }

            if(node.val == pivot) {
                if(equalHead == null) {
                    equalHead = node;
                    equalTail = node;
                } else {
                    equalTail.next = node;
                    equalTail = node;
                }
            }

            if(node.val > pivot) {
                if(bigHead == null) {
                    bigHead = node;
                    bigTail = node;
                } else {
                    bigTail.next = node;
                    bigTail = node;
                }
            }
            node = temp;
        }

        // 3、将小于区域的尾节点连等于区域的头,等于区域的尾连大于区域的头
        if(smallHead != null) {
            // 小于区域存在值,则将小于区域的next指向等于区域的头
            smallTail.next = equalHead;
            // 等于区域有值则等于区域保持不变,否则等于区域的尾为小于区域的尾
            equalTail = equalTail == null ? smallTail : equalTail;
        }

        if(equalHead != null) {
            equalTail.next = bigHead;
        }

        if(smallHead != null) {
            return smallHead;
        }
        if(equalHead != null) {
            return equalHead;
        }

        return bigHead;
    }

四、复制存在指向任意节点的链表

可以利用hashmap实现,但是存在额外空间复杂度。如下代码:

    public static Node copyWithHashMap(Node head) {
        // 将新旧节点都放到map容器中
        Map<Node, Node> map = new HashMap<>();
        Node index = head;
        while (index != null) {
            map.put(index, new Node(index.val));
            index = index.next;
        }

        // 将容器中新节点的下一个和随机元素指向新节点的位置
        index = head;
        while (index != null) {
            map.get(index).next = map.get(index.next);
            map.get(index).random = map.get(index.random);

            index = index.next;
        }

        return map.get(head);
    }

最理想的方案就是利用有限几个变量指针实现如下:

1、思路

  • 创建克隆节点并将当前节点的next指向克隆节点,克隆节点的next指向当前节点的next(用node的结构去构建)

  • 每次遍历一对,克隆节点的random就是遍历节点random的next

  • 将结构分离出来

2、代码

public static Node copyRandomNode(Node head) {
        if(head == null) {
            return null;
        }
        // 1、创建克隆节点并将当前节点的next指向克隆节点,克隆节点的next指向当前节点的next(用node的结构去构建)
        Node current = head, next = null;
        while (current != null) {
            next = current.next;
            Node node = new Node(current.val);
            node.next = next;
            current.next = node;
            current = next;
        }

        // 2、每次遍历一对,克隆节点的random就是遍历节点random的next
        current = head;
        Node copy = null;
        while (current != null) {
            copy = current.next;
            copy.random = current.random == null ? null : current.random.next;
            current = current.next.next;
        }

        // 3、将结构分离出来
        Node res = head.next;
        current = head;
        while (current != null) {
            next = current.next.next;
            copy = current.next;
            copy.next = next == null ? null : next.next;
            current.next = next;
            current = next;
        }
        return res;
    }

五、两个有环或无环列表获取相交节点

https://leetcode.cn/problems/3u1WK4/submissions/

1、思路

  • 给定一个单链表,如果无环则返回null(无环则一定会走到null),有环则返回第一个入环节点。得到loop1和loop2
    • 用容器:用hashSet获取闭合处节点,遍历链表,如果在hashSet里面存在则返回节点,不存在则返回null
    • 不用容器:利用快慢指针(快2慢1),快指针如果走到null则是无环直接返回null,如果有环则快指针和慢指针会相遇。相遇后快指针返回头节点后两个指针步长都为1。之后的相遇节点就是入环节点。
  • 分情况处理两个环的入环节点
    • loop1和loop2都为空,则两个无环链表相交
      • 用容器:利用hashSet将第一个环存入,之后用第二环判断元素存在返回存在的元素节点,不存在则返回null
      • 不用容器:遍历两个链表如果最后一个节点的地址相同则相交。长的链表先走两个节点的长度差后在判断两个节点地址是否相同,第一个相等的节点就是相交节点。
    • 两个链表其中一个有环,不可能相交
    • 两个链表都有环
      • loop1和loop2的地址相等,就是两个无环链表求第一个相交节点
      • loop1和loop2的地址不相等,遍历链表一如果在链表中没遇到loop2则无相交节点,遇到loop2则存在相交节点返回loop1或loop2都可以。

2、实现

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public static ListNode getIntersectionNode(ListNode listNode1, ListNode listNode2) {
        if(listNode1 == null || listNode2 == null) {
            return null;
        }

        ListNode loop1 = findFirstInputLoopNode(listNode1);
        ListNode loop2 = findFirstInputLoopNode(listNode2);

        // 两个无环链表相交
        if(loop1 == null && loop2 == null) {
            return notLoopNodeIntersect(listNode1, listNode2);
        }

        // 两个链表都有环
        if(loop1 != null && loop2 != null) {
            return loopNodeIntersect(listNode1, loop1, listNode2, loop2);
        }

        // 两个链表其中一个有环,不可能相交
        return null;
    }

    /**
     * 两个有环单链表
     * - loop1和loop2的地址相等,就是两个无环链表求第一个相交节点
     * - loop1和loop2的地址不相等,遍历链表一如果在链表中没遇到loop2则无相交节点,遇到loop2则存在相交节点返回loop1或loop2都可以。
     */
    public static ListNode loopNodeIntersect(ListNode listNodeOne, ListNode loopOne, ListNode listNodeTwo, ListNode loopTwo) {
        ListNode currentOne = null;
        ListNode currentTwo = null;
        if(loopOne == loopTwo){
            currentOne = listNodeOne;
            currentTwo = listNodeTwo;
            int i = 0;
            while (currentOne != loopOne) {
                currentOne = currentOne.next;
                i++;
            }

            while (currentTwo != loopTwo) {
                currentTwo = currentTwo.next;
                i--;
            }

            // 将长的赋值给lastNodeOne, 短的赋值给lastNodeTwo
            currentOne = i > 0 ? listNodeOne : listNodeTwo;
            currentTwo = currentOne == listNodeOne ? listNodeTwo : listNodeOne;

            i = Math.abs(i);
            while (i != 0) {
                currentOne = currentOne.next;
                i--;
            }
            while (currentOne != currentTwo) {
                currentOne = currentOne.next;
                currentTwo = currentTwo.next;
            }
            return currentOne;
        }

        // loop1和loop2的地址不相等,遍历链表一如果在链表中没遇到loop2则无相交节点,遇到loop2则存在相交节点返回loop1或loop2都可以。
        else {
            currentOne = listNodeOne.next;
            while (currentOne != loopOne) {
                if(currentOne == loopTwo) {
                    return loopTwo;
                }
                currentOne = currentOne.next;
            }
            return null;
        }
    }


    /**
     * 两个无环单链表
     *
     * 遍历两个链表如果最后一个节点的地址相同则相交。
     * 长的链表先走 两个节点的长度差 后在判断两个节点地址是否相同,第一个相等的节点就是相交节点。
     */
    public static ListNode notLoopNodeIntersect(ListNode listNodeOne, ListNode listNodeTwo) {
        ListNode lastListNodeOne = listNodeOne;
        int nodeLength = 0;
        while (lastListNodeOne.next != null) {
            lastListNodeOne = lastListNodeOne.next;
            nodeLength++;
        }

        ListNode lastListNodeTwo = listNodeTwo;
        while (lastListNodeTwo.next != null) {
            lastListNodeTwo = lastListNodeTwo.next;
            nodeLength--;
        }
        // 最后一个节点的地址不相同则不会相交
        if(lastListNodeOne != lastListNodeTwo) {
            return null;
        }

        // 将长的赋值给lastNodeOne, 短的赋值给lastNodeTwo
        lastListNodeOne = nodeLength > 0 ? listNodeOne : listNodeTwo;
        lastListNodeTwo = lastListNodeOne == listNodeOne ? listNodeTwo : listNodeOne;

        // 长的节点先走掉差值
        int abs = Math.abs(nodeLength);
        while (abs != 0) {
            lastListNodeOne = lastListNodeOne.next;
            abs--;
        }

        // 走到相等为止
        while (lastListNodeOne != lastListNodeTwo) {
            lastListNodeOne = lastListNodeOne.next;
            lastListNodeTwo = lastListNodeTwo.next;
        }
        return lastListNodeOne;
    }

    /**
     * 获取第一个入环节点,没有入环节点则返回null
     *
     * 实现方式:利用快慢指针(快2慢1),走到相交节点时快指针回到头,之后快指针步长改为1,
     * 重新相遇后的第一个节点就是入环的第一个的节点,快指针遇到null则链表节点无入环节点
     */
    public static ListNode findFirstInputLoopNode(ListNode head) {
        if(head == null || head.next == null || head.next.next == null) {
            return null;
        }
        ListNode fast = head.next.next, slow = head.next;
        boolean isIntersect = false, firstHead = true;
        while (fast != null) {
            if(fast == slow) {
                // 第二次相遇的节点直接返回
                if(isIntersect) {
                    return slow;
                }
                isIntersect = true;
            }
            slow = slow.next;
            if(fast.next == null) {
                return null;
            }
            if(isIntersect) {
                if(firstHead) {
                    firstHead = false;
                    fast = head;
                }
                fast = fast.next;
            } else {
                fast = fast.next.next;
            }
        }
        return null;
    }
}

六、单链表删除节点

思路:遇到当前节点是删除节点时,将当前节点置为下一个节点,并将当前节点的下一个节点置为下下一个节点。

    /**
     * 1、删除节点返回必须是节点,可能删除的就是头节点
     * 2、只是给删除的节点,不给头节点。只能往后遍历,获取不到前面的节点。 无法删除最后一个节点
     */
    public static Node level(Node node, Node level) {
        if(node == null || level == null) {
            return node;
        }
        Node current = node;
        // 只删除单链表无环
        while (true) {
            if(current == level) {
                current.val = current.next.val;
                current.next =  current.next.next;
                break;
            } else if(current.next.next == null) {
                current.next = null;
                break;
            }
            current = current.next;
        }
        return node;
    }
Last update:
Contributors: gaoqisen