链表

1.链表的定义

/**
 * description
 *
 * @since 2022-03-01
 */
public class ListNode {
    public ListNode next;
    public int val;

    public ListNode(int val) {
        this.val = val;
        this.next = null;
    }
}

2.链表的操作

2.1 追加



/**
 * description
 *
 * @since 2022-03-01
 */
public class ListNodeOperation {
    // append
    public void append(int val) {
        if (head == null) {
            head = new ListNode(val);
            return;
        }
        ListNode cur = head;
        while (cur.next != null) {// 循环结束的条件是cur指向最后一个节点元素
            cur = cur.next;
        }
        cur.next = new ListNode(val);
    }
}

2.2 遍历


/**
 * description
 *
 * @since 2022-03-01
 */
public class ListNodeOperation {
    // 遍历
    public void print(ListNode head) {
        if (head == null) {
            return;
        }
        ListNode cur = head;
        while (cur != null) {
            System.out.println(cur.val);
            cur = cur.next;
        }
    }
}

2.3 删除


/**
 * description
 *
 * @since 2022-03-01
 */
public class ListNodeOperation {
    // 删除 双指针实现
    public ListNode remove(ListNode head, int val) {
        if (head == null) return head;
        ListNode dummyNode = new ListNode(-1);
        dummyNode.next = head;
        ListNode pre = dummyNode, cur = head;
        while (cur != null) {
            if (cur.val == val) {
                pre.next = cur.next;
                return dummyNode.next;
            }
            pre = pre.next;
            cur = cur.next;
        }
        return dummyNode.next;
    }
}

2.4 修改


/**
 * description
 *
 * @since 2022-03-01
 */
public class ListNodeOperation {
    // 删除
    public ListNode set(ListNode head, int oldVal, int newVal) {
        if (head == null) return head;
        ListNode cur = head;
        while (cur != null && cur.val != oldVal) {
            cur = cur.next;
        }
        // 循环结束的条件是cur == null 或者cur.val == oldVal;
        if (cur != null) {
            cur.val = newVal;
        }
        return head;
    }
}

2.4 查询


/**
 * description
 *
 * @since 2022-03-01
 */
public class ListNodeOperation {
    // 查询
    public ListNode get(ListNode head, int val) {
        if (head == null) return head;
        ListNode cur = head;
        while (cur != null && cur.val != val) {
            cur = cur.next;
        }
        // 循环结束的条件是cur == null 或者cur.val == oldVal;
        if (cur != null) {
            return cur;
        }
        return null;
    }
}

2.5 插入


/**
 * description
 *
 * @since 2022-03-01
 */
public class ListNodeOperation {
    // 插入
    public ListNode insert(ListNode head, int oldVal, int val) {
        if (head == null) return head;
        ListNode insertedNode = new ListNode(val);
        ListNode cur = head;
        while (cur != null && cur.val != oldVal) {
            cur = cur.next;
        }
        // 循环结束的条件是cur == null 或者cur.val == oldVal;
        if (cur != null) {
            ListNode tmpNode = cur.next;
            cur.next = insertedNode;
            insertedNode.next = tmpNode;
        }
        return head;
    }
}

2.6 反转


/**
 * description
 *
 * @since 2022-03-01
 */
public class ListNodeOperation {
    // 反转链表reversal,迭代实现
    public ListNode reversal(ListNode head) {
        if (head == null || head.next == null) return head;
        ListNode preNode = null;
        ListNode curNode = head;
        while (curNode != null) {
            ListNode nextNode = curNode.next; // 1.存储后向节点
            curNode.next = preNode; // 2.将当前节点的指针指向前向节点
            preNode = curNode; // 3.更新前向节点为当前节点
            curNode = nextNode; // 4.更新当前节点为后向节点
        }
        return preNode;
    }

    // 反转链表reversal,递归实现
    public ListNode reversalWithRecursion(ListNode head) {
        if (head == null || head.next == null) return head;
        ListNode last = reversalWithRecursion(head.next);
        head.next.next = head;
        head.next = null;
        return last;
    }
}

2.8 反转前N个节点

private ListNode nextNode = null; // 后向节点
// 反转以head为起点的n个节点,返回新的头节点
public ListNode reverseFirstNNode(ListNode head, int n) {
    if (head == null) return head;
    if (n == 1) {
        // 记录第n + 1个节点
        nextNode = head.next;
        return head;
    }
    // 以head.next为起点,需要反转前n-1个节点
    ListNode last = reverseFirstNNode(head.next, n - 1);
    head.next.next = head;
    head.next = nextNode; // 让反转之后的head节点和后面的节点连起来
    return last;
}

public ListNode reverseFirstNNode2(ListNode head, int n) {
    if (head == null) return head;
    // 双指针 + 头插法
    ListNode dummyNode = new ListNode(-1);
    dummyNode.next = head;
    ListNode g = dummyNode, p = head;
    for (int i = 0; i < n - 1; i++) {
        ListNode removedNode = p.next;
        p.next = p.next.next;
        removedNode.next = g.next;
        g.next = removedNode;
    }
    return dummNode.next;
}

2.9 反转部分链表节点

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    private ListNode nextNode;
    public ListNode reverseBetween(ListNode head, int left, int right) {
        // 递归思想
        if (left == 1) {
            return reverseFirstNNode(head, right);
        }
        head.next = reverseBetween(head.next, left - 1, right - 1);
        return head;
    }

    private ListNode reverseFirstNNode(ListNode head, int n) {
        if (n == 1) {
            nextNode = head.next;
            return head;
        }
        ListNode last = reverseFirstNNode(head.next, n - 1);
        head.next.next = head;
        head.next = nextNode;
        return last;
    }
    
    public ListNode reverseBetween(ListNode head, int left, int right) {
        // 解法2 双指针 + 头插法
        ListNode dummyHead = new ListNode(head.val);
        dummyHead.next = head;
        ListNode g = dummyHead, p = head;
        for (int i = 0; i < left; i++) { // 将g指针移动到left的前向节点,p指针移动到left节点
            g = g.next;
            p = p.next;
        }
        for (int j = 0; j < right - left; j++) {
            ListNode tmpNode = p.next; // 记录p的后向节点
            p.next = p.next.next; // 移除p的后向节点
            tmpNode.next = g.next; // 从p的头部插入删除的后向节点
            g.next = tmpNode; // p的前向节点指向新插入的节点
        }
        return dummy.next;
    }
}


2.7 链表的深拷贝


/**
 * description
 *
 * @since 2022-03-01
 */
public class ListNodeOperation {
    public ListNode copy(ListNode head) {
        if (head == null) {
            return head;
        }
        ListNode dummyNode = new ListNode(-1);// 创建一个虚拟节点
        ListNode curNode = head;
        while (curNode != null) {
            ListNode tmpNode = new ListNode(curNode.val);
            preNode.next = tmpNode;
            preNode = tmpNode;
            curNode = curNode.next;
        }
        return dummyNode.next;
    }
}

2.8 查询链表的倒数第K个节点


/**
 * description
 *
 * @since 2022-03-01
 */
public class ListNodeOperation {
    // 我们可以先遍历得出链表的长度,再查找顺数第N - K个元素,但是这样的时间复杂度是2*O(N)
    // 查找倒数第K个节点 时间复杂度O(N)
    public ListNode findLastKNode(ListNode head, int k) {
        if (head == null) return head;
        ListNode fast = head, slow = head;
        while (k-- > 0) {
            fast = fast.next;
        }
        while (fast != null) {
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }
}

2.9 查找链表的中点


/**
 * description
 *
 * @since 2022-03-01
 */
public class ListNodeOperation {
    public ListNode findMiddleNode(ListNode head) {
        // 快慢指针实现
        if (head == null) return head;
        ListNode fast = head, slow = head;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        return slow;
    }
}

2.10 合并两个有序链表


/**
 * description
 *
 * @since 2022-03-01
 */
public class ListNodeOperation {
    // 两个链表非空
    public ListNode mergeTwoNodeList(ListNode node1, ListNode node2) {
        // 双指针实现
        if (node1 == null) return node2;
        if (node2 == null) return node1;
        ListNode dummyNode = new ListNode(-1); // 创建一个虚拟头结点
        ListNode cur = dummyNode; // 新链表的移动指针
        ListNode cur1 = node1, cur2 = node2;
        while (cur1 != null && cur2 != null) {
            if (cur1.val < cur2.val) {
                cur.next = cur1;
                cur1 = cur1.next;
            } else {
                cur.next = cur2;
                cur = cur2.next;
            }
            cur = cur.next;
        }
        // 循环终止的条件是cur1、cur2中有一个为null
        if (cur1 != null) {
            cur.next = cur1;
        }
        if (cur2 != null) {
            cur.next = cur2;
        }
        return dummyNode.next;
    }
}

2.11 合并K个有序链表

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        if (lists.length == 0) return null;
        ListNode dummyNode = new ListNode(-1);
        ListNode cur = dummyNode;
        PriorityQueue<ListNode> queue = new PriorityQueue<>(lists.length, ((a, b) -> (a.val - b.val))); // 创建一个优先级队列用于存储lists中的头结点
        for (ListNode head : lists) {
            if (head != null) {
                queue.add(head);
            }
        }
        while (!queue.isEmpty()) {
            ListNode tmp = queue.poll(); // poll出队列中的最小头结点
            cur.next = tmp;
            if (tmp.next != null) {
                queue.add(tmp.next);
            }
            cur = cur.next;
        }
        return dummyNode.next;
    }
}

2.12 判断链表是否包含环

public boolean isHasCycle(ListNode head) {
    if (head == null) return null;
    ListNode fast = head, slow = head;
    while (fast != null && fast.next != null) {
        fast = fast.next.next; // 快指针走两步
        slow = slow.next; // 慢指针走一步
        if (fast == slow) { // 如果两个指针相遇了则证明有环
            return true;
        }
    }
    return false;
}

public boolean isHasCycle(ListNode head) { // 时间复杂度O(N),空间复杂度O(N)
    if (head == null) return null;
    Set<ListNode> set = new HashSet<>();
    ListNode cur = head;
    while (cur != null) {
        if (set.conatains(cur)) {
            return true;
        }
        set.add(cur);
        cur = cur.next;
    }
    return false;
}

2.13 求成环的首个节点

public ListNode findCycleStartNode(ListNode head) {
    if (head == null) return null;
    ListNode fast = head, slow = head;
    while (fast != null && fast.next != null) {
        fast = fast.next.next; // 快指针走两步
        slow = slow.next; // 慢指针走一步
        if (fast == slow) { // 如果两个指针相遇了则证明有环
            break;
        }
    }
    if (fast == null || fast.next == null) return null; // 如果fast遇到null则证明没有成环
    slow = head; // 此时让慢指针重新回到起点
    while (slow != fast) {
        slow = slow.next;
        fast = fast.next;
    }
    return slow;
}

img

2.14 求两条链表的相交节点

public ListNode findConnectedNode(ListNode head1, ListNode head2) {
    // 难点是怎么让两个指针同时走到相交点
    if (head1 == null || head2 == null) return null;
   	ListNode cur1 = head1, cur2 = head2;
    while (cur1 != cur2) {
        // 走到尽头见不到你,于是走过你来时的路,等到相遇时才发现,你也走过我来时的路。
        cur1 = cur1 == null ? head2 : cur1.next;
        cur2 = cur2 == null ? head1 : cur2.next;
    }
    return cur1;
}

img

2.15 K个一组反转链表

2.16 判断回文链表

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public boolean isPalindrome(ListNode head) {
        // 解法一:对称-头尾双指针,时间复杂度O(N),空间复杂度O(N) 
        if (head == null) return false;
        ListNode cur = head;
        List<Integer> res = new ArrayList<>();
        while (cur != null) {
            res.add(cur.val);
            cur = cur.next;
        }
        return isPalindList(res);
    }

    private boolean isPalindList(List<Integer> list) {
        int i = 0;
        int j = list.size() - 1;
        while (i < j) {
            if (!list.get(i).equals(list.get(j))) {
                return false;
            }
            i++;
            j--;
        }
        return true;
    }
    
    public boolean isPalindrome2(ListNode head) {
        // 解法二:先找到链表的中间节点,再对后半部分机型反转与前半部分进行比较,时间复杂度O(N),空间复杂度O(1) 
        if (head == null) return false;
        ListNode rightPartNode = findMiddleNode(head);
        ListNode right = reverse(rightPartNode);
        ListNode left = head;
        while (right != null) {
            if (right.val != left.val) {
                return false;
            }
            right = right.next;
            left = left.next;
        }
        return true;
    }
    
    // 找到待反转的首节点 及中间节点
    private ListNode findMiddleNode(ListNode head) {
        if (head == null) return head;
        ListNode fast = head, slow = head;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        if (fast != null) {
            slow = slow.next;
        }
        return slow;
    }
    
    // 反转以head为起点的链表
    private ListNode reverse(ListNode head) {
        if (head == null) return head;
        ListNode pre = null;
        ListNode cur = head;
        while (cur != null) {
            ListNode next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
}

3.题目

3.1 复杂链表的复制

剑指 Offer 35. 复杂链表的复制

请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。

示例 1:

输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
示例 2:

输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]
示例 3:

输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]
示例 4:

输入:head = []
输出:[]
解释:给定的链表为空(空指针),因此返回 null。

提示:

-10000 <= Node.val <= 10000
Node.random 为空(null)或指向链表中的节点。
节点数目不超过 1000 。

/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/
class Solution {
    /**
     * 本题难点:在复制链表的过程中构建新链表各节点的 random 引用指向。
     */
    public Node copyRandomList1(Node head) {
        // 解题思路:使用hashMap来记录原链表节点和新链表节点
        if (head == null) return head;
        // 1.遍历原链表,创建旧-新节点映射map
        Map<Node, Node> recordMap = new HashMap<>();
        Node cur = head;
        while (cur != null) {
            recordMap.put(cur, new Node(cur.val));
            cur = cur.next;
        }

        // 2.遍历原链表,根据原链表的next、random引用关系创建新节点的引用关系
        cur = head;
        while (cur != null) {
            recordMap.get(cur).next = recordMap.get(cur.next);
            recordMap.get(cur).random = recordMap.get(cur.random);
            cur = cur.next;
        }

        // 3.返回新链表的首节点
        return recordMap.get(head);
    }

    public Node copyRandomList(Node head) {
        // 解题思路:拼接+拆分
        if (head == null) return head;
        // 1.遍历原链表,在原节点后边都加上新节点
        Node cur = head;
        while (cur != null) {
            Node newNode = new Node(cur.val);
            newNode.next = cur.next;
            cur.next = newNode;
            cur = newNode.next;
        }

        // 2.遍历原链表,连上新节点的random引用
        cur = head;
        while (cur != null) {
            if (cur.random != null)
                cur.next.random = cur.random.next;
            cur = cur.next.next;
        }

        // 3.拆分链表
        cur = head.next;
        Node res = head.next;
        Node pre = head;
        while (cur.next != null) {
            pre.next = pre.next.next;
            cur.next = cur.next.next;
            pre = pre.next;
            cur = cur.next;
        }
        pre.next = null;
        return res;
    }
}
  • 链表的拼接与拆分
  • 链表的遍历
posted @ 2022-03-12 17:56  freryc  阅读(38)  评论(0)    收藏  举报