LeetCode 1145. 二叉树着色游戏
题目描述



题意分析
红方先选定值为
x的节点,蓝方再选一个不同节点作为起点。之后红方先手,双方轮流把与自己已染色节点相邻的未染色节点占为己有;相邻包括父节点和左右孩子,不能穿过或改染对方节点。无法继续染色的一方跳过回合,双方都不能操作时比较占有节点数。题目保证总数
n为奇数,判断蓝方是否存在一个起点,可以确保最终占据严格超过半数的节点,而不是模拟某一组任意走法。
解法:统计三块区域大小
核心思路
[!blue]
树上两个节点之间只有一条简单路径。把已归红方的节点
x当作不可通过的屏障,其他节点就分成至多三块:它的左子树、右子树,以及父节点方向的剩余部分。分别记大小为L、R、P,其中P = n - L - R - 1,减去的一对应红方已经占据的x。蓝方的起点必然落在其中一块,而且永远无法经过红色的
x进入其他块,所以无论如何行动,蓝方能占有的节点数都不会超过这块的大小。这给出了胜利的必要条件:至少有一块严格大于全树的一半。这个条件也足够。蓝方直接占据该区域与
x相邻的入口:选择左孩子、右孩子或父节点。红方进入该区域的唯一路径就被这个蓝色入口挡住,无法争夺其中任何节点;蓝方则可以沿区域内的连接逐步染满整块。即使红方先手,也不能越过已经占据的入口,因此蓝方能保证得到整个区域。所以不需要搜索每一回合,只要比较三块大小。用后序遍历让每个节点返回自身子树的节点数,在遇到
x时顺手保存左右子树大小,父方向再通过总数补集求出。若三块最大值大于n / 2,蓝方可获胜,否则任何起点都不够。
解题步骤
- 后序遍历二叉树,空节点返回零,非空节点返回左右子树大小之和再加一。
- 遍历到值为
x的节点时,记录已经算好的左右子树大小。- 计算父方向区域大小
n - L - R - 1。- 取三块中的最大值,与整除后的
n / 2比较,严格大于才返回true。
代码实现
class Solution {
public boolean btreeGameWinningMove(TreeNode root, int n, int x) {
int[] sizes = new int[2];
count(root, x, sizes);
// 父亲方向用总数倒推,省掉向上遍历。
int parent = n - sizes[0] - sizes[1] - 1;
int max = Math.max(parent, Math.max(sizes[0], sizes[1]));
// 蓝方必须拿到严格超过半数的节点。
return max > n / 2;
}
// 返回以 node 为根的子树节点数;后序回溯时顺手记下 x 的左右子树大小。
private int count(TreeNode node, int x, int[] sizes) {
if (node == null) {
return 0;
}
int left = count(node.left, x, sizes);
int right = count(node.right, x, sizes);
// 左右子树已统计完,此时记录红方节点分出的两块区域。
if (node.val == x) {
sizes[0] = left;
sizes[1] = right;
}
return left + right + 1;
}
}
func btreeGameWinningMove(root *TreeNode, n int, x int) bool {
leftSize, rightSize := 0, 0
// 返回以 node 为根的子树节点数;后序回溯时顺手记下 x 的左右子树大小。
var count func(node *TreeNode) int
count = func(node *TreeNode) int {
if node == nil {
return 0
}
left := count(node.Left)
right := count(node.Right)
// 左右子树已统计完,此时记录红方节点分出的两块区域。
if node.Val == x {
leftSize = left
rightSize = right
}
return left + right + 1
}
count(root)
// 父亲方向用总数倒推,省掉向上遍历。
parent := n - leftSize - rightSize - 1
maxPart := parent
if leftSize > maxPart {
maxPart = leftSize
}
if rightSize > maxPart {
maxPart = rightSize
}
// 蓝方必须拿到严格超过半数的节点。
return maxPart > n/2
}
复杂度分析
- 时间复杂度:$O(n)$,每个节点在后序遍历中只处理一次。
- 空间复杂度:$O(h)$,
h为树高,来自递归调用栈;另外只保存左右区域大小。
关键点总结
[!green]
- 红色起点把树切成三块,蓝方无法跨越这条屏障。
- 选择紧邻
x的区域入口,才能保证独占整块,而不是只在区域内部与红方竞争。- 父方向是左右子树和
x之外的全部节点,不需要真正向上遍历。- 三块最大值既是蓝方可能得到的上界,也是通过正确起点能达到的值。
易错点总结
[!yellow]
- 只比较左右子树,会漏掉最大的区域位于父节点方向的情况。
- 计算补集时没有减去
x自身,会把已属于红方的节点也计入蓝方领地。- 使用大于等于整除后的
n / 2,可能把不到半数的区域误判为足够获胜。- 在大区域内随意选一个节点,却声称一定得到整块,没有阻断入口时红方仍可能进入该区域。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 2049. 统计最高分的节点数目 | 中等 | 同样删除一个节点后按子树及父方向计算分量大小,原题将大小相乘,本题判断是否存在超过一半的可控制区域。 |
| 1110. 删点成林 | 中等 | 删掉对手初始节点后可从分出的连通区域理解可争夺范围,原题实际返回删除后的森林。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!