目录
563. 二叉树的坡度
https://leetcode-cn.com/problems/binary-tree-tilt/
给定一个二叉树,计算整个树的坡度。一个树的节点的坡度定义即为,该节点左子树的结点之和和右子树结点之和的差的绝对值。空结点的的坡度是0。整个树的坡度就是其所有节点的坡度之和。
提示:任何子树的结点的和不会超过 32 位整数的范围。坡度的值不会超过 32 位整数的范围。
题解
一:遇到一个节点,用其左子树的和减去右子树的和的绝对值累加。关键是遇到一个节点,如何取到他左右子树的和。这边分两个函数,主函数递归节点,dfs函数用来求以某节点为根的子树的和。
class Solution(object):
def __init__(self):
self.res = 0
def findTilt(self, root):
"""
:type root: TreeNode
:rtype: int
"""
if not root or (not root.left and not root.right):
return 0
self.res += abs(self.dfs(root.left) - self.dfs(root.right))
self.findTilt(root.left)
self.findTilt(root.right)
return self.res
def dfs(self, root):
cnt = 0
if not root:
return cnt
cnt += self.dfs(root.left)
cnt += root.val
cnt += self.dfs(root.right)
return cnt
法二:一中会重复计算节点为根的子树的和,这边用一个字典来记录已经算过的以该节点为根的子树的和。
class Solution(object):
def __init__(self):
self.res = 0
self.rec = {}
def findTilt(self, root):
if not root or (not root.left and not root.right):
return 0
self.res += abs(self.dfs(root.left) - self.dfs(root.right))
self.findTilt(root.left)
self.findTilt(root.right)
return self.res
def dfs(self, root):
cnt = 0
if not root:
return cnt
if root in self.rec:
return self.rec[root]
cnt += self.dfs(root.left)
cnt += root.val
cnt += self.dfs(root.right)
self.rec[root] = cnt
return cnt
法三:在求和的过程中计算坡度。getSum函数返回的是以root为根的树的和,左子树的和加右子树的和加当前节点的值,可以看到出现了左子树的和,右子树的和,此时可以顺带求一下坡度。
class Solution(object):
def __init__(self):
self.res = 0
def findTilt(self, root):
def getSum(root):
if not root:
return 0
left_sum = getSum(root.left)
right_sum = getSum(root.right)
self.res += abs(left_sum - right_sum)
return left_sum + right_sum + root.val
if not root or (not root.left and not root.right):
return 0
getSum(root)
return self.res
993. 二叉树的堂兄弟节点
https://leetcode-cn.com/problems/cousins-in-binary-tree/
在二叉树中,根节点位于深度 0 处,每个深度为 k 的节点的子节点位于深度 k+1 处。如果二叉树的两个节点深度相同,但父节点不同,则它们是一对堂兄弟节点。我们给出了具有唯一值的二叉树的根节点 root,以及树中两个不同节点的值 x 和 y。只有与值 x 和 y 对应的节点是堂兄弟节点时,才返回 true。否则,返回 false。
提示:二叉树的节点数介于 2
到 100
之间。每个节点的值都是唯一的、范围为 1
到 100
的整数。
题解
一:层序遍历,同一层的必定满足深度相同,只要再满足父节点不同即可。
from collections import deque
class Solution(object):
def isCousins(self, root, x, y):
"""
:type root: TreeNode
:type x: int
:type y: int
:rtype: bool
"""
if not root or x == y:
return False
q, rec = deque(), None
q.append(root)
while q:
n, rec = len(q), None
for i in range(n):
node = q.popleft()
if node.left:
q.append(node.left)
if node.left.val == x or node.left.val == y:
if rec and rec != node:
return True
rec = node
if node.right:
q.append(node.right)
if node.right.val == x or node.right.val == y:
if rec and rec != node:
return True
rec = node
return False
法二:dfs,深度优先搜索标记每一个节点,对于每一个节点 root,它的父亲为
father
,深度为depth,并记录在字典中。
class Solution(object):
def isCousins(self, root, x, y):
def dfs(root, parient=None):
if not root:
return
depth[root.val] = (depth[parient.val] if parient else 0) + 1
father[root.val] = parient.val if parient else None
dfs(root.left, root)
dfs(root.right, root)
depth, father = {}, {}
dfs(root, None)
return depth.get(x) == depth.get(y) and father.get(x) != father.get(y)
543. 二叉树的直径
给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。
示例 :给定二叉树
返回 3, 它的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。
注意:两结点之间的路径长度是以它们之间边的数目表示。
题解
一:一个节点的直径,是该节点的左子树的最大深度加右子树的最大深度。depth返回一个节点的最大深度。
class Solution(object):
def __init__(self):
self.res = 0
def diameterOfBinaryTree(self, root):
"""
:type root: TreeNode
:rtype: int
"""
def depth(root):
if not root:
return 0
left_depth = depth(root.left)
right_depth = depth(root.right)
self.res = max(self.res, left_depth + right_depth + 1 - 1)
return max(left_depth, right_depth) + 1
depth(root)
return self.res
1161. 最大层内元素和
https://leetcode-cn.com/problems/maximum-level-sum-of-a-binary-tree/
给你一个二叉树的根节点 root。设根节点位于二叉树的第 1 层,而根节点的子节点位于第 2 层,依此类推。请你找出层内元素之和 最大 的那几层(可能只有一层)的层号,并返回其中 最小 的那个。
提示:
- 树中的节点数介于
1
和10^4
之间 -10^5 <= node.val <= 10^5
题解
一:自然而然的想到层序遍历,按层求和,找到最大的那一层。
from collections import deque
class Solution(object):
def maxLevelSum(self, root):
"""
:type root: TreeNode
:rtype: int
"""
q, res = deque(), []
q.append(root)
while q:
n, level_sum = len(q), 0
for i in range(n):
node = q.popleft()
level_sum += node.val
if node.left:
q.append(node.left)
if node.right:
q.append(node.right)
res.append(level_sum)
max_num = max(res)
for i in range(len(res)):
if res[i] == max_num:
return i + 1
二:DFS,递归的时候要加入层信息,并用一个字典来求各层的和。
from collections import defaultdict
class Solution(object):
def maxLevelSum(self, root):
def dfs(root, level):
if not root:
return
level_sum[level] += root.val
dfs(root.left, level + 1)
dfs(root.right, level + 1)
level_sum = defaultdict(int)
dfs(root, 1)
return max(level_sum, key=level_sum.get)
669. 修剪二叉搜索树
https://leetcode-cn.com/problems/trim-a-binary-search-tree/
给定一个二叉搜索树,同时给定最小边界L 和最大边界 R。通过修剪二叉搜索树,使得所有节点的值在[L, R]中 (R>=L) 。你可能需要改变树的根节点,所以结果应当返回修剪好的二叉搜索树的新的根节点。
题解
一:递归,函数trimBST返回新的二叉树的根。
class Solution(object):
def trimBST(self, root, L, R):
"""
:type root: TreeNode
:type L: int
:type R: int
:rtype: TreeNode
"""
if not root:
return None
if L <= root.val <= R:
root.left = self.trimBST(root.left, L, R)
root.right = self.trimBST(root.right, L, R)
elif root.val > R:
root = self.trimBST(root.left, L, R)
elif root.val < L:
root = self.trimBST(root.right, L, R)
return root
606. 根据二叉树创建字符串
https://leetcode-cn.com/problems/construct-string-from-binary-tree/
你需要采用前序遍历的方式,将一个二叉树转换成一个由括号和整数组成的字符串。空节点则用一对空括号 "()" 表示。而且你需要省略所有不影响字符串与原始二叉树之间的一对一映射关系的空括号对。
题解
一:前序遍历,但是要注意,当有右孩子却没有左孩子的时候,需要套上一层()作为左孩子的占位。
class Solution(object):
def tree2str(self, t):
"""
:type t: TreeNode
:rtype: str
"""
s = ""
if not t:
return s
s += str(t.val)
if t.left:
s += "(" + self.tree2str(t.left) + ")"
if t.right:
if not t.left:
s += "()"
s += "(" + self.tree2str(t.right) + ")"
return s
208. 实现 Trie (前缀树)
https://leetcode-cn.com/problems/implement-trie-prefix-tree/
实现一个 Trie (前缀树),包含 insert, search, 和 startsWith 这三个操作。
示例:
Trie trie = new Trie();
trie.insert("apple");
trie.search("apple"); // 返回 true
trie.search("app"); // 返回 false
trie.startsWith("app"); // 返回 true
trie.insert("app");
trie.search("app"); // 返回 true
说明:你可以假设所有的输入都是由小写字母 a-z 构成的。保证所有输入均为非空字符串。
题解
一:每个节点对应26个子节点。Tree中的属性end用来标记是否是某个字符串的尾字母对应的那个点。Trie的根节点是一个虚假的节点,与插入搜索均无关,仅仅表示字典树的起始。
class Tree:
def __init__(self, x):
self.val = x
self.child = [None for _ in range(26)]
self.end = False
class Trie(object):
def __init__(self):
"""
Initialize your data structure here.
"""
self.root = Tree("-1")
self.bias = ord('a')
def insert(self, word):
"""
Inserts a word into the trie.
:type word: str
:rtype: None
"""
node = self.root
for char in word:
idx = ord(char) - self.bias
if node.child[idx] is None:
node.child[idx] = Tree(char)
node = node.child[idx]
node.end = True
def search(self, word):
"""
Returns if the word is in the trie.
:type word: str
:rtype: bool
"""
node = self.root
for char in word:
idx = ord(char) - self.bias
if node.child[idx] is None:
return False
node = node.child[idx]
return node.end
def startsWith(self, prefix):
"""
Returns if there is any word in the trie that starts with the given prefix.
:type prefix: str
:rtype: bool
"""
node = self.root
for char in prefix:
idx = ord(char) - self.bias
if node.child[idx] is None:
return False
node = node.child[idx]
return True
二:与法一不一样的地方,是每个节点最多对应一个子节点。
class Tree:
def __init__(self, x):
self.val = x
self.child = []
self.end = False
class Trie(object):
def __init__(self):
"""
Initialize your data structure here.
"""
self.root = Tree("-1")
def insert(self, word):
"""
Inserts a word into the trie.
:type word: str
:rtype: None
"""
node = self.root
for char in word:
if not node.child:
node.child.append(Tree(char))
node = node.child[0]
continue
flag = False
for child in node.child:
if child.val == char:
node = child
flag = True
break
if not flag:
node.child.append(Tree(char))
node = node.child[-1]
node.end = True
def search(self, word):
"""
Returns if the word is in the trie.
:type word: str
:rtype: bool
"""
node = self.root
for char in word:
flag = False
for child in node.child:
if child.val == char:
node = child
flag = True
break
if not flag:
return False
return node.end
def startsWith(self, prefix):
"""
Returns if there is any word in the trie that starts with the given prefix.
:type prefix: str
:rtype: bool
"""
node = self.root
for char in prefix:
flag = False
for child in node.child:
if child.val == char:
node = child
flag = True
break
if not flag:
return False
return True
307. 区域和检索 - 数组可修改
给定一个整数数组 nums,求出数组从索引 i 到 j (i ≤ j) 范围内元素的总和,包含 i, j 两点。
update(i, val) 函数可以通过将下标为 i 的数值更新为 val,从而对数列进行修改。
示例:
Given nums = [1, 3, 5]
sumRange(0, 2) -> 9
update(1, 2)
sumRange(0, 2) -> 8
说明:
数组仅可以在 update 函数下进行修改。
你可以假设 update 函数与 sumRange 函数的调用次数是均匀分布的。
题解
一:线段树,附上依据b站大神的视频https://www.bilibili.com/video/av47331849?t=1907整理的笔记,
例如,在一个数组中频繁的更新和查找某个范围的值
方法一:在原数组的基础之上直接更新,O(1)的时间复杂度;在原数组上直接求和O(n)的时间复杂度
方法二:创建一个新的数组存放累加和sum[i]表示截止arr[i]的累加和。那么求和是O(1)的时间复杂度,但是更新是O(n)的时间复杂度,例如我们更新arr[i],那么sum[i,...]从i下标之后的都需要更新。
方法三:线段树,将求和和更新的时间复杂读均降为O(lgn)
其中每个节点表示该范围的和,叶节点是对应的下标值,我们先将叶节点的值填上。
其余的节点是其左右孩子的和,这种表示下也是范围和。
例如我们找[2-5]的和,可以看到[0-5]包含[2-5],我们将[2-5]分成两段,在左孩子寻找2节点,对应的值是5,从右边寻找[3-5]对应的值是27,故[2-5]的和是5+27=32
update的操作,例如将9改成6,我们只要找到9对应的节点,将其改成6,再向根节点一步步修改
不看最后一层,是一个满二叉树,所以我们可以用数组来表示这个二叉树
下标约定如下,所有带node的是树的下标,不带的是arr的下标
MAX_LEN = 1000
def bulid_tree(arr, tree, node, start, end):
if start == end:
tree[node] = arr[start]
else:
left_node = 2 * node + 1
right_node = 2 * node + 2
mid = start + (end - start) // 2
bulid_tree(arr, tree, left_node, start, mid)
bulid_tree(arr, tree, right_node, mid + 1, end)
tree[node] = tree[left_node] + tree[right_node]
"""
将idx下标的值改为val,arr[idx] = val
"""
def update_tree(arr, tree, node, start, end, idx, val):
# if start > end:
# return
if start == end:
arr[idx] = val
tree[node] = val
else:
left_node = 2 * node + 1
right_node = 2 * node + 2
mid = start + (end - start) // 2
# if start <= idx <= mid:
# tree[node] = tree[right_node] + update_tree(arr, tree, left_node, start, mid, idx, val)
# elif mid < idx <= end:
# tree[node] = tree[left_node] + update_tree(arr, tree, right_node, mid + 1, end, idx, val)
if start <= idx <= mid:
update_tree(arr, tree, left_node, start, mid, idx, val)
elif mid < idx <= end:
update_tree(arr, tree, right_node, mid + 1, end, idx, val)
tree[node] = tree[left_node] + tree[right_node]
# return tree[node]
def query_tree(arr, tree, node, start, end, l, r):
res = 0
# 不在计算范围内return 0
if l > end or r < start:
return 0
elif start == end:
return tree[node]
elif l <= start and end <= r:
return tree[node]
else:
left_node = 2 * node + 1
right_node = 2 * node + 2
mid = start + (end - start) // 2
sum_left = query_tree(arr, tree, left_node, start, mid, l, r)
sum_right = query_tree(arr, tree, right_node, mid + 1, end, l, r)
return sum_left + sum_right
if __name__ == "__main__":
arr = [1, 3, 5, 7, 9, 11]
size = len(arr)
tree = [0] * MAX_LEN
bulid_tree(arr, tree, 0, 0, size - 1)
update_tree(arr, tree, 0, 0, size - 1, 4, 6)
print(query_tree(arr, tree, 0, 0, size - 1, 2, 5))
该题的解法一样。线段树的容量通常设为原数组容量的4倍。其实一定规模后两倍也可以。
class NumArray(object):
def __init__(self, nums):
"""
:type nums: List[int]
"""
def build_tree(node, start, end):
if start == end:
self.tree[node] = self.nums[start]
return
else:
mid = start + (end - start) // 2
left_node = 2 * node + 1
right_node = 2 * node + 2
build_tree(left_node, start, mid)
build_tree(right_node, mid + 1, end)
self.tree[node] = self.tree[left_node] + self.tree[right_node]
self.nums = nums
if not nums:
return
self.tree = [0] * 4 * len(nums)
self.size = len(nums)
build_tree(0, 0, self.size - 1)
def update(self, i, val):
"""
:type i: int
:type val: int
:rtype: None
"""
def update_tree(node, start, end, idx, val):
if start == end:
self.nums[idx] = val
self.tree[node] = val
else:
mid = start + (end - start) // 2
left_node = 2 * node + 1
right_node = 2 * node + 2
if idx <= mid:
update_tree(left_node, start, mid, idx, val)
else:
update_tree(right_node, mid + 1, end, idx, val)
self.tree[node] = self.tree[left_node] + self.tree[right_node]
if not self.nums:
return
update_tree(0, 0, self.size - 1, i, val)
def sumRange(self, i, j):
"""
:type i: int
:type j: int
:rtype: int
"""
def sum_range_tree(node, start, end, l, r):
if l > end or r < start:
return 0
elif start == end:
return self.tree[node]
elif l <= start and r >= end:
return self.tree[node]
else:
mid = start + (end - start) // 2
left_node = 2 * node + 1
right_node = 2 * node + 2
sum_left = sum_range_tree(left_node, start, mid, l, r)
sum_right = sum_range_tree(right_node, mid + 1, end, l, r)
return sum_left + sum_right
if not self.nums:
return
return sum_range_tree(0, 0, self.size - 1, i, j)
# Your NumArray object will be instantiated and called as such:
# obj = NumArray(nums)
# obj.update(i,val)
# param_2 = obj.sumRange(i,j)
96. 不同的二叉搜索树
https://leetcode-cn.com/problems/unique-binary-search-trees/
给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种?
示例:输入: 3,输出: 5
解释:给定 n = 3, 一共有 5 种不同结构的二叉搜索树:
题解
思路
给定一个有序序列 1⋯n,为了构建出一棵二叉搜索树,我们可以遍历每个数字 i,将该数字作为树根,将 1⋯(i−1) 序列作为左子树,将 (i+1)⋯n 序列作为右子树。接着我们可以按照同样的方式递归构建左子树和右子树。
在上述构建的过程中,由于根的值不同,因此我们能保证每棵二叉搜索树是唯一的。
由此可见,原问题可以分解成规模较小的两个子问题,且子问题的解可以复用。因此,我们可以想到使用动态规划来求解本题。
class Solution(object):
def numTrees(self, n):
"""
:type n: int
:rtype: int
"""
G = [0] * (n + 1)
G[0] = 1
G[1] = 1
for i in range(2, n + 1):
for j in range(1, i + 1):
G[i] += G[j - 1] * G[i - j]
return G[n]