链表其他问题(快慢指针)

简介: 链表其他问题(快慢指针)

1.回文链表(234-中)



问题描述:判断一个链表是否回文,要求: O(n) 时间复杂度和 O(1) 空间复杂度


案例:

输入: 1->2
输出: false


思路:最简单的是使用栈,全部元素入栈,出栈比较一半元素,见代码。


如果不使用额外空间。思路如下:


  • 快慢指针找到链表的中间位置(左中点);
  • 反转链表的前半部分;
  • 依次比较判断回文。slow节点记录后半部分,pre记录反转后的前半部分


注意:这里我们采用一边反转链表,一边找中间那位置。slow节点相当于记录了反转链表的next节点。


代码:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    // 利用栈(链表入栈,然后比较一半,ps:比较的是节点值)
    public boolean isPalindrome(ListNode head) {
        if (head == null || head.next == null) {
            return true;
        }
        Deque<ListNode> stack = new LinkedList<>();
        int len = 0;
        ListNode cur = head;
        while (cur != null) {
            stack.push(cur);
            len++;
            cur = cur.next;
        }
        len >>= 1;
        while (len-- >= 0) {
            if (stack.pop().val != head.val) {
                return false;
            }
            head = head.next;
        }
        return true;
    }
    // 快慢指针找中点(左中点) + 反转前一半链表
    public boolean isPalindrome(ListNode head) {
        ListNode slow = head, fast = head;
        ListNode pre = null, cur = head;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
            cur.next = pre;
            pre = cur;
            cur = slow;
        }
        // 链表长度为奇数时
        if (fast != null) {
            slow = slow.next;
        }
        while (slow != null) {
            if (pre.val != slow.val) {
                return false;
            }
            pre = pre.next;
            slow = slow.next;
        }
        return true;
    }
}


2.复制带随机指针的链表(138-中)



问题描述:给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。


构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。返回复制链表的头节点。你的代码 只 接受原链表的头节点 head 作为传入参数。


用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:


  • val:一个表示 Node.val 的整数。
  • random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为  null 。


案例:

image.png

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


思路:解法1:可以直接使用哈希表进行映射<原节点,原节点的副本>,再通过映射去创建(复制)新节点和新节点的next和random指针(注意空指针)。


解法2比较巧妙:对于每个链表的next节点创建一个副本,连接副本的random指针,分离两个链表,注意两点:


  • 虚拟头节点的技巧
  • 恢复原链表(一起连接两个链表的next节点)

image.png

代码:

/*
// 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 {
    // hash映射<原节点,原节点的副本>
    public Node copyRandomList(Node head) {
        if (head == null) {
            return null;
        }
        HashMap<Node, Node> map = new HashMap<>();
        Node p = head;
        while (p != null) {
            Node copyNode = new Node(p.val);
            map.put(p, copyNode);
            p = p.next;
        }
        p = head;
        while (p != null) {
            Node newNode = map.get(p);
            if (p.random != null) {
                newNode.random = map.get(p.random);
            }
            if (p.next != null) {
                newNode.next = map.get(p.next);
            }
            p = p.next;
        }
        return map.get(head);
    } 
    // 原链表创建副本,然后分离
    public Node copyRandomList(Node head) {
        if (head == null) {
            return null;
        }
        Node p = head;
        while (p != null) {
            Node newNode = new Node(p.val);
            newNode.next = p.next;
            p.next = newNode;
            p = newNode.next;
        }
        p = head;
        while (p != null) {
            if (p.random != null) {
                p.next.random = p.random.next;
            }
            p = p.next.next;
        }
        p = head;
        Node dummyHead = new Node(-1);
        Node cur = dummyHead;
        while (p != null) {
            cur.next = p.next;
            cur = cur.next;
            // ps:原链表恢复
            p.next = cur.next;
            p = p.next;
        }
        return dummyHead.next;
    }
}


3.链表的中间节点(876-易)



题目描述:给定一个头结点为 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。


示例:

输入:[1,2,3,4,5,6]
输出:此列表中的结点 4 (序列化形式:[4,5,6])
由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。


思路:快慢指针解法,注意偶数节点情况,这里提供返回第一和第二个中间节点的代码。不同点:快指针的初始位置!


代码:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    // 返回第一个中间节点
    public ListNode middleNode(ListNode head) {
        ListNode slow = head, fast = head;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }
    // 返回第二个中间节点
    public ListNode middleNode(ListNode head) {
        ListNode slow = head, fast = head.next;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }
}


4.有序链表转二叉搜索树(109-中)



题目描述:给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树。


本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。


示例:

给定的有序链表: [-10, -3, 0, 5, 9],
一个可能的答案是:[0, -3, 9, -10, null, 5], 它可以表示下面这个高度平衡二叉搜索树:
      0
     / \
   -3   9
   /   /
 -10  5


思路:给定条件:链表升序!下面都是使用分治的思想:


  • 常规解法:寻找链表的中间节点(快慢指针),注意:要断链表
  • 终止条件:没有元素和只有一个元素(创建二叉树节点)
  • 断链表,创建根节点,递归构建左右子树
  • 另解:先递归的构建树(需要知道构建树的左右边界),当遍历到该位置再填值。


代码:

/**
 * 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; }
 * }
 */
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    // 找中间节点(的前一个节点) + 分治思想
    public TreeNode sortedListToBST(ListNode head) {
        if (head == null) {
            return null;
        }
        if (head.next == null) {
            return new TreeNode(head.val);
        }
        ListNode pre = preMid(head);
        ListNode mid = pre.next;
        // 断链表
        pre.next = null;
        TreeNode root = new TreeNode(mid.val);
        root.left = sortedListToBST(head);
        root.right = sortedListToBST(mid.next);
        return root;
    }
    private ListNode preMid(ListNode head) {
        ListNode slow = head, fast = head;
        ListNode pre = null;
        while (fast != null && fast.next != null) {
            pre = slow;
            slow = slow.next;
            fast = fast.next.next;
        }
        return pre;
    }
    // 先建树(分治),后填值!
    private ListNode globalNode;
    public TreeNode sortedListToBST(ListNode head) {
        if (head == null) {
            return null;
        }
        ListNode cur = head;
        int len = 0;
        while (cur != null) {
            len++;
            cur = cur.next;
        }
        globalNode = head;
        return buildTree(0, len - 1);
    }
    private TreeNode buildTree(int left, int right) {
        if (left > right) {
            return null;
        }
        int mid = left + (right - left) / 2;
        TreeNode root = new TreeNode();
        root.left = buildTree(left, mid - 1);
        root.val = globalNode.val;
        globalNode = globalNode.next;
        root.right = buildTree(mid + 1, right);
        return root;
    }
}


相关文章
|
3月前
链表指针的传参,传值和传地址
本文讨论了链表操作中指针传参的问题,特别是指针的传值与传地址的区别,并提供了修正代码,以确保链表插入操作能正确地修改指针指向的地址。
22 1
链表指针的传参,传值和传地址
|
2月前
|
算法 索引
单链表题+数组题(快慢指针和左右指针)
单链表题+数组题(快慢指针和左右指针)
43 1
|
8月前
|
存储 C语言
用指针处理链表
用指针处理链表
77 3
|
3月前
|
C语言
无头链表二级指针方式实现(C语言描述)
本文介绍了如何在C语言中使用二级指针实现无头链表,并提供了创建节点、插入、删除、查找、销毁链表等操作的函数实现,以及一个示例程序来演示这些操作。
47 0
|
6月前
【数据结构OJ题】复制带随机指针的链表
力扣题目——复制带随机指针的链表
59 1
【数据结构OJ题】复制带随机指针的链表
|
7月前
|
算法
【经典LeetCode算法题目专栏分类】【第7期】快慢指针与链表
【经典LeetCode算法题目专栏分类】【第7期】快慢指针与链表
|
8月前
|
存储 C语言
链表—初始化指针变和创建新的节点------区别应用分析
链表—初始化指针变和创建新的节点------区别应用分析
|
8月前
|
存储 缓存 搜索推荐
指针链表
指针链表
57 0
|
8月前
|
算法 C语言 索引
环形链表(快慢指针)
环形链表(快慢指针)
|
8月前
数据结构--链表刷题(一)快慢指针(下)
数据结构--链表刷题(一)快慢指针
56 0