文章目录
- 题目
- 标题和出处
- 难度
- 题目描述
- 要求
- 示例
- 数据范围
- 解法
- 思路和算法
- 代码
- 复杂度分析
题目
标题和出处
标题:二叉树着色游戏
出处:1145. 二叉树着色游戏
难度
6 级
题目描述
要求
两位玩家参与二叉树着色游戏。给定二叉树的根结点 root \texttt{root} root,以及树中的结点数目 n \texttt{n} n, n \texttt{n} n 是奇数且每个结点的值从 1 \texttt{1} 1 到 n \texttt{n} n 各不相同。
初始时,第一位玩家选择一个值 x \texttt{x} x,满足 1 ≤ x ≤ n \texttt{1} \le \texttt{x} \le \texttt{n} 1≤x≤n,第二位玩家选择一个值 y \texttt{y} y,满足 1 ≤ y ≤ n \texttt{1} \le \texttt{y} \le \texttt{n} 1≤y≤n 且 y ≠ x \texttt{y} \ne \texttt{x} y=x。第一位玩家将值为 x \texttt{x} x 的结点涂上红色,第二位玩家将值为 y \texttt{y} y 的结点涂上蓝色。
之后从第一位玩家开始,两位玩家轮流操作。每一轮,玩家选择自己颜色的结点(第一位玩家选择红色,第二位玩家选择蓝色),将所选结点的一个未涂色的相邻结点(左子结点、右子结点或者父结点)涂上颜色。
当且仅当一位玩家无法按照该规则选择结点时,该玩家必须跳过当前轮次。如果两位玩家都跳过当前轮次,游戏结束,涂色结点更多的玩家是获胜方。
你是第二位玩家。如果可能选择一个 y \texttt{y} y 确保你赢得游戏,返回 true \texttt{true} true。如果不可能,返回 false \texttt{false} false。
示例
示例 1:
输入:
root
=
[1,2,3,4,5,6,7,8,9,10,11],
n
=
11,
x
=
3
\texttt{root = [1,2,3,4,5,6,7,8,9,10,11], n = 11, x = 3}
root = [1,2,3,4,5,6,7,8,9,10,11], n = 11, x = 3
输出:
true
\texttt{true}
true
解释:第二位玩家可以选择值为
2
\texttt{2}
2 的结点。
示例 2:
输入:
root
=
[1,2,3],
n
=
3,
x
=
1
\texttt{root = [1,2,3], n = 3, x = 1}
root = [1,2,3], n = 3, x = 1
输出:
false
\texttt{false}
false
数据范围
- 树中结点数目是 n \texttt{n} n
- 1 ≤ x ≤ n ≤ 100 \texttt{1} \le \texttt{x} \le \texttt{n} \le \texttt{100} 1≤x≤n≤100
- n \texttt{n} n 是奇数
- 1 ≤ Node.val ≤ n \texttt{1} \le \texttt{Node.val} \le \texttt{n} 1≤Node.val≤n
- 树中的所有值各不相同
解法
思路和算法
由于二叉树中的结点数目是 n n n,每个结点的值从 1 1 1 到 n n n 各不相同, 1 ≤ x ≤ n 1 \le x \le n 1≤x≤n,因此二叉树中存在唯一的值为 x x x 的结点,该结点称为结点 x x x。
首先使用深度优先搜索在二叉树中定位到结点 x x x,结点 x x x 将二叉树中的其余 n − 1 n - 1 n−1 个结点分成三个部分:结点 x x x 的左子树、结点 x x x 的右子树和其余结点,每个部分各为一个连通区域,不同部分之间不连通,每个部分的结点数最少为 0 0 0,最多为 n − 1 n - 1 n−1,三个部分的结点数总和为 n − 1 n - 1 n−1。
根据游戏规则,每一位玩家在分别选择第一个涂色的结点之后,每次选择涂色的结点都必须和己方已经涂色的结点相邻,因此同一位玩家涂色的结点一定总是相连的。由于结点 x x x 被第一位玩家涂色,因此第二位玩家选择的结点 y y y 一定属于三个部分之一,且当第二位玩家选择结点 y y y 之后,只能在同一个部分继续涂色。
第二位玩家为了赢得游戏,选择的结点 y y y 应该使得己方可以涂色的结点数最多,因此结点 y y y 应该位于结点数最多的一个部分。只要结点 y y y 和结点 x x x 相邻,第二位玩家即可确保结点 y y y 所在的部分的所有结点都可以由第二位玩家涂色。为了使得结点 y y y 和结点 x x x 相邻,结点 y y y 应为结点 x x x 的左子结点、右子结点或者父结点,由于结点 y y y 所在的部分的结点数一定大于 0 0 0,因此结点 y y y 不为空。
除了结点 y y y 所在的部分以外,其余结点都由第一位玩家涂色,因此判断第二位玩家是否可以赢得游戏的依据是结点 y y y 所在的部分的结点数是否大于其余结点数总和。由于二叉树中的结点数目 n n n 是奇数,因此第二位玩家可以赢得游戏等价于结点 y y y 所在的部分的结点数至少为 n + 1 2 \dfrac{n + 1}{2} 2n+1。
由于第二位玩家选择的结点 y y y 应该位于结点数最多的一个部分,因此只需要判断三个部分的结点数中的最大值是否至少为 n + 1 2 \dfrac{n + 1}{2} 2n+1,即可知道第二位玩家是否可以赢得游戏。
计算三个部分的结点数的方法为:对结点 x x x 的左子树和右子树分别使用深度优先搜索计算结点数,左子树和右子树的结点数分别记为 leftCount \textit{leftCount} leftCount 和 rightCount \textit{rightCount} rightCount,剩下的一个部分的结点数为 n − 1 − leftCount − rightCount n - 1 - \textit{leftCount} - \textit{rightCount} n−1−leftCount−rightCount。只要有一个部分的结点数至少为 n + 1 2 \dfrac{n + 1}{2} 2n+1,则返回 true \text{true} true,否则返回 false \text{false} false。
代码
class Solution {
TreeNode xNode;
public boolean btreeGameWinningMove(TreeNode root, int n, int x) {
search(root, x);
int threshold = (n + 1) / 2;
int leftCount = countSubtreeNodes(xNode.left);
if (leftCount >= threshold) {
return true;
}
int rightCount = countSubtreeNodes(xNode.right);
if (rightCount >= threshold) {
return true;
}
int remain = n - 1 - leftCount - rightCount;
return remain >= threshold;
}
public void search(TreeNode node, int x) {
if (xNode != null || node == null) {
return;
}
if (node.val == x) {
xNode = node;
return;
}
search(node.left, x);
search(node.right, x);
}
public int countSubtreeNodes(TreeNode node) {
if (node == null) {
return 0;
}
return 1 + countSubtreeNodes(node.left) + countSubtreeNodes(node.right);
}
}
复杂度分析
-
时间复杂度: O ( n ) O(n) O(n),其中 n n n 是二叉树的结点数。定位到结点 x x x 和计算二叉树每个部分的结点数的过程中,每个结点最多被访问一次。
-
空间复杂度: O ( n ) O(n) O(n),其中 n n n 是二叉树的结点数。空间复杂度主要是递归调用的栈空间,取决于二叉树的高度,最坏情况下二叉树的高度是 O ( n ) O(n) O(n)。