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 。
案例:
输入: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节点)
代码:
/* // 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; } }