高级农民
- 积分
- 4142
- 大米
- 颗
- 鳄梨
- 个
- 水井
- 尺
- 蓝莓
- 颗
- 萝卜
- 根
- 小米
- 粒
- 学分
- 个
- 注册时间
- 2017-1-22
- 最后登录
- 1970-1-1
|
LC. 2689. Extract Kth Character From The Rope Tree
You are given the root of a binary tree and an integer k. Besides the left and right children, every node of this tree has two other properties, a string node.val containing only lowercase English letters (possibly empty) and a non-negative integer node.len. There are two types of nodes in this tree:
Leaf: These nodes have no children, node.len = 0, and node.val is some non-empty string.
Internal: These nodes have at least one child (also at most two children), node.len > 0, and node.val is an empty string.
The tree described above is called a Rope binary tree. Now we define S[node] recursively as follows:
If node is some leaf node, S[node] = node.val,
Otherwise if node is some internal node, S[node] = concat(S[node.left], S[node.right]) and S[node].length = node.len.
Return k-th character of the string S[root].
Note: If s and p are two strings, concat(s, p) is a string obtained by concatenating p to s. For example, concat("ab", "zz") = "abzz".
Example 1:
Input: root = [10,4,"abcpoe","g","rta"], k = 6
Output: "b"
Explanation: In the picture below, we put an integer on internal nodes that represents node.len, and a string on leaf nodes that represents node.val.
You can see that S[root] = concat(concat("g", "rta"), "abcpoe") = "grtaabcpoe". So S[root][5], which represents 6th character of it, is equal to "b".
Example 2:
Input: root = [12,6,6,"abc","efg","hij","klm"], k = 3
Output: "c"
Explanation: In the picture below, we put an integer on internal nodes that represents node.len, and a string on leaf nodes that represents node.val.
You can see that S[root] = concat(concat("abc", "efg"), concat("hij", "klm")) = "abcefghijklm". So S[root][2], which represents the 3rd character of it, is equal to "c".
Example 3:
Input: root = ["ropetree"], k = 8
Output: "e"
Explanation: In the picture below, we put an integer on internal nodes that represents node.len, and a string on leaf nodes that represents node.val.
You can see that S[root] = "ropetree". So S[root][7], which represents 8th character of it, is equal to "e".
Constraints:
The number of nodes in the tree is in the range [1, 103]
node.val contains only lowercase English letters
0 <= node.val.length <= 50
0 <= node.len <= 104
for leaf nodes, node.len = 0 and node.val is non-empty
for internal nodes, node.len > 0 and node.val is empty
1 <= k <= S[root].length
我的解法- # Definition for a rope tree node.
- # class RopeTreeNode(object):
- # def __init__(self, len=0, val="", left=None, right=None):
- # self.len = len
- # self.val = val
- # self.left = left
- # self.right = right
- class Solution:
- def getKthCharacter(self, root: Optional[object], k: int) -> str:
- """
- :type root: Optional[RopeTreeNode]
- """
- curr = root
- self.ans = ""
- def preOrder(node):
- if node.len == 0:
- self.ans += node.val
-
- if node.left: preOrder(node.left)
- if node.right: preOrder(node.right)
- preOrder(root)
- return self.ans[k-1]
复制代码 你的思路: 前序遍历整棵树,把所有叶子节点的字符串拼接到一起,最后直接通过索引 k-1 取出字符。
存在的问题与隐患:
时间/空间复杂度退化: 你的做法是 $O(N)$,把整棵树展开成了一个完整的长字符串。如果文本长达几个 G,内存直接就爆了。Rope Tree 发明出来的初衷,就是为了避免做全量字符串拼接。
Python 字符串拼接开销: 在 Python 中,字符串是不可变的(Immutable)。频繁使用 self.ans += node.val 会不断创建新字符串,底层开销是 $O(N^2)$。更标准的写法是放进数组里,最后 "".join(arr)。- 2. 核心思路:利用“树上二分”实现 $O(H)$ 查找我们不需要把整棵树拼起来,因为每个内部节点(Internal Node)都记录了 node.len。只要我们知道左子树代表的字符串总长度,我们就能判断第 k 个字符到底在左子树还是右子树。拆解逻辑:假设当前我们在节点 curr,要找当前子树的第 k 个字符。我们先计算出左子树代表的字符串长度 left_len:如果 curr.left 是叶子节点,长度就是 len(curr.left.val)如果 curr.left 是内部节点,长度就是 curr.left.len判断走向:如果 k <= left_len,说明第 k 个字符在左边,我们直接向左走:curr = curr.left。如果 k > left_len,说明第 k 个字符在右边,我们向右走,并且 k 要减去左边的长度:k = k - left_len,curr = curr.right。一直走到叶子节点(node.len == 0),直接返回 curr.val[k-1] 即可。
复制代码- # Definition for a rope tree node.
- # class RopeTreeNode(object):
- # def __init__(self, len=0, val="", left=None, right=None):
- # self.len = len
- # self.val = val
- # self.left = left
- # self.right = right
- class Solution:
- def getKthCharacter(self, root: Optional[object], k: int) -> str:
- """
- :type root: Optional[RopeTreeNode]
- """
-
- """
- 核心思路:利用“树上二分”实现 $O(H)$ 查找我们不需要把整棵树拼起来,因为每个内部节点(Internal Node)都记录了 node.len。只要我们知道左子树代表的字符串总长度,我们就能判断第 k 个字符到底在左子树还是右子树。拆解逻辑:假设当前我们在节点 curr,要找当前子树的第 k 个字符。我们先计算出左子树代表的字符串长度 left_len:如果 curr.left 是叶子节点,长度就是 len(curr.left.val)如果 curr.left 是内部节点,长度就是 curr.left.len判断走向:如果 k <= left_len,说明第 k 个字符在左边,我们直接向左走:curr = curr.left。如果 k > left_len,说明第 k 个字符在右边,我们向右走,并且 k 要减去左边的长度:k = k - left_len,curr = curr.right。一直走到叶子节点(node.len == 0),直接返回 curr.val[k-1] 即可。
- """
- # 辅助函数:快速获取任意子树的字符串总长度
- def get_len(node):
- if not node:
- return 0
- # 根据题目定义:叶子节点 len 为 0,但有实际字符串;内部节点有 len,但字符串为空
- if node.len == 0:
- return len(node.val)
- return node.len
- curr = root
-
- # 只要当前还是内部节点(node.len > 0),就继续往下找
- while curr.len > 0:
- left_len = get_len(curr.left)
-
- if k <= left_len:
- # 目标在左子树
- curr = curr.left
- else:
- # 目标在右子树,减去左子树的长度
- k -= left_len
- curr = curr.right
-
- # 退出循环时,curr 一定是叶子节点
- return curr.val[k - 1]
复制代码 为什么这个解法更好?
时间复杂度: $O(H)$,$H$ 为树的高度。对于平衡的 Rope 树,时间复杂度是 $O(\log N)$。不需要遍历所有节点。
空间复杂度: $O(1)$,不需要额外开辟空间存拼接后的完整字符串,也不需要递归调用栈。 |
|