目录
- 394. 字符串解码
- 题目链接
- 标签
- 思路
- 代码
- 61. 旋转链表
- 题目链接
- 标签
- 思路
- 代码
- 100. 相同的树
- 题目链接
- 标签
- 思路
- 代码
- 递归版前序遍历
- 层序遍历
394. 字符串解码
题目链接
394. 字符串解码
标签
栈 递归 字符串
思路
本题可以使用两个栈来解决,一个栈 timesStack
存储字符串重复的次数,另一个栈 stringStack
存储字符串的前缀。针对字符不同,有如下四种操作:
- 如果字符是数字,则将其记录到一个外部变量
times
中,从而计算重复次数。 - 如果字符是小写字母,则将其拼接到一个外部变量
builder
中。 - 如果字符是
'['
,则将times
和builder.toString()
分别放到timesStack
和stringStack
中,为了times
和builder
还能正常使用,将其清空:times
置为 0,builder
换成新的new StringBuilder()
。 - 如果字符是
']'
,则将stringStack
栈顶的字符串 (使用完就没用了,记得弹栈) 作为前缀prefix
、timesStack
栈顶的次数 (使用完就没用了,记得弹栈) 作为重复次数times
、builder.toString()
作为待重复的字符串str
,构成一个字符串,并将其作为构造新StringBuilder
的参数构造一个新的StringBuilder
给builder
。
下图针对 '[', ']'
这两个字符进行了示例:
代码
class Solution {
public String decodeString(String s) {
LinkedList<Integer> timesStack = new LinkedList<>(); // 存储 重复次数 的栈
LinkedList<String> prefixStack = new LinkedList<>(); // 储存 字符串前缀 的栈
int times = 0; // 当前字符串的重复次数
StringBuilder builder = new StringBuilder(); // 拼接当前字符串
for (char ch : s.toCharArray()) {
if (ch >= '0' && ch <= '9') { // ch 是数字
times = times * 10 + (ch - '0'); // 计算多位数
} else if (ch == '[') {
timesStack.push(times);
prefixStack.push(builder.toString());
// 为了能够继续使用 times 和 builder,进行清空操作
times = 0;
builder = new StringBuilder();
} else if (ch == ']') {
String prefix = prefixStack.pop();
String str = builder.toString();
int repeatTimes = timesStack.pop();
builder = new StringBuilder(joint(prefix, str, repeatTimes));
} else { // ch 是小写字母
builder.append(ch);
}
}
return builder.toString();
}
// 以 prefix 为前缀,将 str 重复 times 次,将得到的字符串返回
private String joint(String prefix, String str, int times) {
StringBuilder builder = new StringBuilder(prefix);
while (times-- > 0) {
builder.append(str);
}
return builder.toString();
}
}
61. 旋转链表
题目链接
61. 旋转链表
标签
链表 双指针
思路
本题的做法十分巧妙:将链表向右移动可以分为五步:
- 统计链表的节点个数。
- 计算头节点右移的次数:头节点右移的次数为 链表节点个数 减去
k
,为了减少移动的次数(避免绕环多周),在k
运算之前,先对链表节点个数进行取余。 - 将链表头尾相连,形成 环形链表。
- 在环形链表中将头节点右移指定次数,获取新的头节点。
- 断开 新的尾节点 和 新的头节点 之间的 联系,返回新的头节点。
使用示例1的 head = [1,2,3,4,5], k = 2
进行举例,则会有如下的流程 (此时指定移动 5 - 2 = 3
次):
具体细节请看代码:
代码
class Solution {
public ListNode rotateRight(ListNode head, int k) {
// 如果 不需要移动 或 链表为空 或 链表只有一个节点
if (k == 0 || head == null || head.next == null) {
return head; // 则直接返回 head
}
// 统计链表的节点个数,最终 curr 是链表的尾节点
int n = 1;
ListNode curr = head;
while (curr.next != null) {
curr = curr.next;
n++;
}
// 计算头节点右移的次数
int times = n - (k % n);
if (times == n) { // 如果 头节点右移的次数 与 链表的节点数 相同
return head; // 则直接返回头节点,不需要移动
}
curr.next = head; // 让链表的尾节点指向头节点,从而形成循环链表
while (times-- > 0) { // 右移 times 次
curr = curr.next;
}
// curr 是新链表的尾节点,新的头节点是 curr 的下一个节点
ListNode newHead = curr.next; // 获取新的头节点
curr.next = null; // 将链表从新的 尾节点 和 头节点处 剪断
return newHead;
}
}
100. 相同的树
题目链接
100. 相同的树
标签
树 深度优先搜索 广度优先搜索 二叉树
思路
本题和 101. 对称二叉树 很像,不过本题只是简单考察 二叉树的遍历,并且本题检查两颗二叉树,而101题只检查一颗二叉树,并且稍微难一点。
既然本题考查 二叉树的遍历,那么无论是使用深度优先搜索的三种遍历方式(前序遍历、中序遍历、后序遍历),还是使用广度优先搜索的遍历方式——层序遍历,都可以。
代码
递归版前序遍历
以下代码是递归版的前序遍历:先对本节点的值进行判断,然后遍历左子树,最后遍历右子树。
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
if (p == null && q == null) { // 如果 p, q 都是 null
return true; // 则返回 true
} else if (p == null || q == null) { // 此时 p, q 只有一个是 null
return false; // 则返回 false
} else if (p.val != q.val) { // 如果 p, q 的值不同
return false; // 则返回 false
}
return isSameTree(p.left, q.left) // 判断 p 和 q 的 左子树 是否完全一致
&& isSameTree(p.right, q.right); // 判断 p 和 q 的 右子树 是否完全一致
}
}
层序遍历
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
if (p == null && q == null) { // 如果 p, q 都是 null
return true; // 则返回 true
} else if (p == null || q == null) { // 此时 p, q 只有一个是 null
return false; // 则返回 false
}
LinkedList<TreeNode> queueP = new LinkedList<>(); // 用于遍历二叉树 p
LinkedList<TreeNode> queueQ = new LinkedList<>(); // 用于遍历二叉树 q
queueP.offer(p); // 先将头节点放入队列
queueQ.offer(q); // 先将头节点放入队列
while (!queueP.isEmpty() && !queueQ.isEmpty()) {
TreeNode nodeP = queueP.poll(); // 取出 p 中的一个节点
TreeNode nodeQ = queueQ.poll(); // 取出 q 中的一个节点
if (nodeP.val != nodeQ.val) { // 如果它们的值不同
return false; // 则返回 false
}
TreeNode leftP = nodeP.left, rightP = nodeP.right; // 获取 nodeP 的左右子节点
TreeNode leftQ = nodeQ.left, rightQ = nodeQ.right; // 获取 nodeQ 的左右子节点
// 可以使用 leftP == null ^ leftQ == null 作为判断条件,它与以下条件等价
if ((leftP == null && leftQ != null)
|| (leftP != null && leftQ == null)) { // 如果对应的节点只有一个为null
return false; // 则返回 false
}
// 可以使用 rightP == null ^ rightQ == null 作为判断条件,它与以下条件等价
if ((rightP == null && rightQ != null)
|| (rightP != null && rightQ == null)) { // 如果对应的节点只有一个为null
return false; // 则返回 false
}
// 经过以上判断,leftP 和 leftQ 如果有一个为 null,则都是 null;否则都有值
if (leftP != null) { // 如果有值,则将其添加到队列中等待比较
queueP.offer(leftP);
queueQ.offer(leftQ);
}
// 经过以上判断,rightP 和 rightQ 如果有一个为 null,则都是 null;否则都有值
if (rightP != null) { // 如果有值,则将其添加到队列中等待比较
queueP.offer(rightP);
queueQ.offer(rightQ);
}
}
// 如果有一个队列不为空,则两颗二叉树的节点个数不同,只有两个队列同时为空才代表这两颗二叉树完全一致
return queueP.isEmpty() && queueQ.isEmpty();
}
}
如果只使用 queueP, queueQ
中的一个,就很像对一颗二叉树的层序遍历(一层一层地遍历二叉树)了:
- 创建队列:
LinkedList<TreeNode> queue = new LinkedList<>();
。 - 将根节点提前加入队列:
queue.offer(root);
。 - 在队列不为空的时候进行循环:
while (!queue.isEmpty())
,以下都是循环中的操作:- 获取当前节点:
TreeNode curr = queue.poll();
。 - 对当前节点进行某种操作。
- 获取当前节点的左右子节点:
TreeNode left = curr.left, right = curr.right;
。 - 如果左子节点不为空,则将其加入队列:
if (left != null) { queue.offer(left) }
。 - 如果右子节点不为空,则将其加入队列:
if (right != null) { queue.offer(right) }
。
- 获取当前节点: