文章目录
- 71. 简化路径
- 题目链接
- 标签
- 思路
- 代码
- 86. 分隔链表
- 题目链接
- 标签
- 思路
- 分隔链表
- 构建多个链表
- 合并链表
- 代码
- 117. 填充每个节点的下一个右侧节点指针 II
- 题目链接
- 标签
- 法一:层序遍历
- 思路
- 代码
- 法二:链表
- 思路
- 代码
71. 简化路径
题目链接
71. 简化路径
标签
栈 字符串
思路
本题使用了 双端队列:
- 在存储目录名时,将其当作 栈(从队列的同一端进出)。
- 遇到
".."
时,退一级目录,即移出队列 头部 的元素,对应着 弹出栈顶元素。 - 遇到不为
"."
的非空目录时,进一级目录,即将目录名加入队列 头部,对应着 将元素压入栈顶。
- 遇到
- 在取出目录名时,将其当作 队列(从队列的一端进,另一端出)。
- 由于目录名是从队列 头部 加入的,所以从队列 尾部 取出它。
代码
class Solution {public String simplifyPath(String path) {String[] dirs = path.split("/"); // 用 "/" 将所有目录隔离开来LinkedList<String> deque = new LinkedList<>(); // 储存 目录名 的双端队列for (String dir : dirs) { // 取出每个目录名进行操作if ("..".equals(dir)) { // 如果目录名为 ".."deque.pollFirst(); // 则退一级目录,即移除队列头元素 ( 弹出栈顶元素 )} else if (!dir.isBlank() && !".".equals(dir)) { // 如果目录名不为空,并且不是 "."deque.offerFirst(dir); // 则进一级目录,即将目录名加入队列头部 ( 将元素压入栈顶 )}}if (deque.isEmpty()) { // 如果双端队列为空return "/"; // 则直接返回 "/"}StringBuilder builder = new StringBuilder(); // 用于构建结果路径的字符串拼接器while (!deque.isEmpty()) { // 直到双端队列为空// 将 "/" 和 目录名拼接到路径中,注意此时得从队列尾部开始倒序遍历builder.append('/').append(deque.pollLast());}return builder.toString();}
}
86. 分隔链表
题目链接
86. 分隔链表
标签
链表 双指针
思路
分隔链表
本题要求分隔链表,使得 小于 x
的节点都出现在 大于或等于 x
的节点之前。可以这样思考:分隔链表就是先根据条件构建多个链表,然后再将其合并起来。
构建多个链表
在本题中,可以构建两个链表,第一个链表保存原始链表中所有 小于 x
的节点,第二个链表保存原始链表中所有 大于或等于 x
的节点(即第二个链表保存剩余节点),这样就可以按照题目要求将链表分隔开来。
构建链表有两种方法:
- 使用 头节点 + 当前节点 这个组合。
- 在添加节点时比较麻烦,得判断待添加节点是否是第一个节点,如果是,则需要给头节点赋值。
- 在获取头节点时简单,直接返回头节点即可。
- 使用 哨兵节点 + 当前节点 这个组合。(当前节点的初始值为哨兵节点)
- 在添加节点时简单,无需判断待添加节点是否是第一个节点,直接让当前节点指向待添加节点。
- 在获取头节点时比较麻烦,需要使用
哨兵节点.next
。
我个人比较推荐第二种方法,在本题中,就使用到了第二种方法。
合并链表
构建完链表后,当前节点就是所构建的链表的尾节点;头节点可以通过 哨兵节点.next
来获取。
合并链表分为以下两步:
- 让 大于或等于
x
的节点形成的链表 的尾节点指向null
。 - 让 小于
x
的节点形成的链表的 尾节点 指向 大于或等于x
的节点形成的链表的 头节点,形成新链表。
解释一下为什么需要第一步:将链表构建完毕之后,链表的尾节点可能取自 原先链表的尾节点之外的节点。例如对于链表 1->2->3->4->1
,如果 x = 3
,则最终大于或等于 x
的节点形成的链表 3->4
的尾节点是节点 4
,它指向节点 1
。所以需要让尾节点指向 null
这步操作。
代码
class Solution {public ListNode partition(ListNode head, int x) {ListNode smallS = new ListNode(0); // 小于 x 的节点形成的链表 的哨兵节点ListNode small = smallS; // 小于 x 的节点形成的链表 的当前节点,最后成为尾节点ListNode largeS = new ListNode(0); // 大于或等于 x 的节点形成的链表 的哨兵节点ListNode large = largeS; // 大于或等于 x 的节点形成的链表 的当前节点,最后成为尾节点// 构建链表while (head != null) { // 在原链表上遍历if (head.val < x) { // 如果当前节点小于 xsmall.next = head; // 则将其加入 小于 x 的节点形成的链表small = small.next; // 并 更新 当前节点} else { // 否则当前节点大于或等于 xlarge.next = head; // 则将其加入 大于或等于 x 的节点形成的链表large = large.next; // 并 更新 当前节点}head = head.next; // 更新指向原链表的指针}// 合并链表large.next = null; // 防止 大于或等于 x 的节点形成的链表 的尾节点指向其他节点,让其指向 null// 让 小于 x 的节点形成的链表的尾节点 指向 大于或等于 x 的节点形成的链表的头节点,形成新链表small.next = largeS.next;return smallS.next; // 返回新链表的头节点}
}
117. 填充每个节点的下一个右侧节点指针 II
题目链接
117. 填充每个节点的下一个右侧节点指针 II
标签
树 深度优先搜索 广度优先搜索 链表 二叉树
法一:层序遍历
思路
要将每层节点像链表一样连接起来,说明需要按层遍历二叉树,所以本题考查了 二叉树的层序遍历,而且是 一层一层地遍历,层序遍历使用 广度优先搜索,广度优先搜索使用 队列,处理如下:
- 先处理二叉树的第一层,将根节点放入队列。
- 遍历队列,每次遍历一层节点,直到队列没有节点为止。对这一层节点的遍历的操作如下:
- 从队列中取出当前节点。
- 对当前节点进行操作。
- 如果当前节点的左子节点不为
null
,则将其加入队列,等待下一层遍历。 - 如果当前节点的右子节点不为
null
,则将其加入队列,等待下一层遍历。
除了第二步的第二个操作与题有关之外,其他操作均与题目无关,都是模版。
代码
class Solution {public Node connect(Node root) {if (root == null) { // 如果根节点为 nullreturn null; // 则直接返回 null}LinkedList<Node> queue = new LinkedList<>(); // 存储二叉树每层的节点queue.offer(root); // 先处理二叉树的第一层,将根节点放到队列中while (!queue.isEmpty()) { // 直到队列中没有节点int size = queue.size(); // 获取本层元素的个数Node prev = null; // 当前节点的上一个节点for (int i = 0; i < size; i++) { // 一层一层地遍历Node curr = queue.poll(); // 取出当前节点// 对当前节点进行操作if (i != 0) { // 当 curr 不是第一个节点时prev.next = curr; // 让 prev 指向 curr}prev = curr; // 更新 prev 的值if (curr.left != null) { // 如果当前节点的左子节点不为 nullqueue.offer(curr.left); // 则将其加入队列,等待下一层遍历}if (curr.right != null) { // 如果当前节点的右子节点不为 nullqueue.offer(curr.right); // 则将其加入队列,等待下一层遍历}}}return root;}
}
法二:链表
思路
层序遍历时使用了 队列,它会占用一定的内存,而我们可以使用一种占用内存更少的思路:构建本层的链表可以使用上一层构建好的链表,或者说 从根节点这第一层节点开始,在遍历本层节点时,就可以构建下一层节点形成的链表。
构建链表使用到了 prev
指针,它代表前一个节点。每次在下一层找到新的节点时,让 prev
指向新节点,并更新 prev
的值。此时需要使用 sentinel
哨兵节点,作为 prev
的初始值,从而避免对 每层的头节点 进行特殊处理。
实际上这种解决方案还挺简单的,理解思路之后看代码即可。
代码
class Solution {public Node connect(Node root) {if (root == null) { // 如果根节点为 nullreturn null; // 则直接返回 null}Node head = root; // 当前层的头节点,第一层的头节点是 rootwhile (head != null) {Node sentinel = new Node(0); // 下一层节点的哨兵节点,指向下一层构建的链表的头节点Node prev = sentinel; // 下一层链表中的前一个节点Node curr = head; // 从 head 开始遍历本层节点while (curr != null) {if (curr.left != null) { // 如果左子节点不为 nullprev.next = curr.left; // 则让 prev 指向 左子节点prev = prev.next; // 并更新 prev}if (curr.right != null) { // 如果右子节点不为 nullprev.next = curr.right; // 则让 prev 指向 右子节点prev = prev.next; // 并更新 prev}curr = curr.next; // 更新 curr}head = sentinel.next; // 更新 head 到下一层链表的头节点}return root;}
}