From 68198299777006206628a2f090bb9f3cdab7728c Mon Sep 17 00:00:00 2001 From: ITCharge Date: 2023年4月27日 17:45:51 +0800 Subject: [PATCH 1/3] =?UTF-8?q?Update=200124.=20=E4=BA=8C=E5=8F=89?= =?UTF-8?q?=E6=A0=91=E4=B8=AD=E7=9A=84=E6=9C=80=E5=A4=A7=E8=B7=AF=E5=BE=84?= =?UTF-8?q?=E5=92=8C.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...47350円267円257円345円276円204円345円222円214円.md" | 70 ++++++++++++------- 1 file changed, 45 insertions(+), 25 deletions(-) diff --git "a/Solutions/0124. 344円272円214円345円217円211円346円240円221円344円270円255円347円232円204円346円234円200円345円244円247円350円267円257円345円276円204円345円222円214円.md" "b/Solutions/0124. 344円272円214円345円217円211円346円240円221円344円270円255円347円232円204円346円234円200円345円244円247円350円267円257円345円276円204円345円222円214円.md" index 7a24a763..c0a8db4a 100644 --- "a/Solutions/0124. 344円272円214円345円217円211円346円240円221円344円270円255円347円232円204円346円234円200円345円244円247円350円267円257円345円276円204円345円222円214円.md" +++ "b/Solutions/0124. 344円272円214円345円217円211円346円240円221円344円270円255円347円232円204円346円234円200円345円244円247円350円267円257円345円276円204円345円222円214円.md" @@ -5,13 +5,13 @@ ## 题目大意 -**描述**:给定一个二叉树的根节点 `root`。 +**描述**:给定一个二叉树的根节点 $root$。 **要求**:返回其最大路径和。 **说明**: -- **路径**:从树中的任意节点出发,沿父节点——子节点连接,到达任意节点的序列。同一个节点在一条路径序列中至多出现一次。该路径至少包含一个节点,且不一定经过根节点。 +- **路径**:被定义为一条节点序列,序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中至多出现一次。该路径至少包含一个节点,且不一定经过根节点。 - **路径和**:路径中各节点值的总和。 - 树中节点数目范围是 $[1, 3 * 10^4]$。 - $-1000 \le Node.val \le 1000$。 @@ -40,44 +40,64 @@ ## 解题思路 -### 思路 1:深度优先搜索 +### 思路 1:树形 DP + 深度优先搜索 -使用深度优先搜索递归遍历二叉树。递归遍历的同时,维护一个最大路径和变量 `self.max_sum`。定义函数 `dfs(self, root)` 计算二叉树中以该节点为根节点,并且经过该节点的最大贡献值。 +根据最大路径和中对应路径是否穿过根节点,我们可以将二叉树分为两种: -计算的结果可能的情况有 2ドル$ 种: +1. 最大路径和中对应路径穿过根节点。 +2. 最大路径和中对应路径不穿过根节点。 + +如果最大路径和中对应路径穿过根节点,则:$该二叉树的最大路径和 = 左子树中最大贡献值 + 右子树中最大贡献值 + 当前节点值$。 + +而如果最大路径和中对应路径不穿过根节点,则:$该二叉树的最大路径和 = 所有子树中最大路径和$。 + +即:$该二叉树的最大路径和 = max(左子树中最大贡献值 + 右子树中最大贡献值 + 当前节点值, \quad 所有子树中最大路径和)$。 + +对此我们可以使用深度优先搜索递归遍历二叉树,并在递归遍历的同时,维护一个最大路径和变量 $ans$。 -1. 经过空节点的最大贡献值等于 `0`。 -2. 经过非空节点的最大贡献值等于「当前节点值」+「左右子节点的最大贡献值中较大的一个」。 +然后定义函数 ` def dfs(self, node):` 计算二叉树中以该节点为根节点,并且经过该节点的最大贡献值。 -在递归时,我们先计算左右子节点的最大贡献值,再更新维护当前最大路径和变量。最终 `self.max_sum` 即为答案。 +计算的结果可能的情况有 2ドル$ 种: + +1. 经过空节点的最大贡献值等于 0ドル$。 +2. 经过非空节点的最大贡献值等于 $当前节点值 + 左右子节点提供的最大贡献值中较大的一个$。如果该贡献值为负数,可以考虑舍弃,即最大贡献值为 0ドル$。 -具体步骤如下: +在递归时,我们先计算左右子节点的最大贡献值,再更新维护当前最大路径和变量。最终 $ans$ 即为答案。具体步骤如下: -1. 如果根节点 `root` 为空,则返回 `0`。 -2. 递归计算左子树的最大贡献值为 `left_max`。 -3. 递归计算右子树的最大贡献值为 `right_max`。 -4. 更新维护最大路径和变量,即 `self.max_sum = max(self.max_sum, root.val + left_max + right_max)`。 -5. 返回以当前节点为根节点,并且经过该节点的最大贡献值。即返回「当前节点值」+「左右子节点的最大贡献值中较大的一个」。 -6. 最终 `self.max_sum` 即为答案。 +1. 如果根节点 $root$ 为空,则返回 0ドル$。 +2. 递归计算左子树的最大贡献值为 $left\underline{}max$。 +3. 递归计算右子树的最大贡献值为 $right\underline{}max$。 +4. 更新维护最大路径和变量,即 $self.ans = max \lbrace self.ans, \quad left\underline{}max + right\underline{}max + node.val \rbrace$。 +5. 返回以当前节点为根节点,并且经过该节点的最大贡献值。即返回 $当前节点值 + 左右子节点提供的最大贡献值中较大的一个$。 +6. 最终 $self.ans$ 即为答案。 ### 思路 1:代码 ```Python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, val=0, left=None, right=None): +# self.val = val +# self.left = left +# self.right = right class Solution: def __init__(self): - self.max_sum = float('-inf') - - def dfs(self, root): - if not root: + self.ans = float('-inf') + + def dfs(self, node): + if not node: return 0 - left_max = max(self.dfs(root.left), 0) - right_max = max(self.dfs(root.right), 0) - self.max_sum = max(self.max_sum, root.val + left_max + right_max) - return root.val + max(left_max, right_max) + left_max = max(self.dfs(node.left), 0) # 左子树提供的最大贡献值 + right_max = max(self.dfs(node.right), 0) # 右子树提供的最大贡献值 + + cur_max = left_max + right_max + node.val # 包含当前节点和左右子树的最大路径和 + self.ans = max(self.ans, cur_max) # 更新所有路径中的最大路径和 + + return max(left_max, right_max) + node.val # 返回包含当前节点的子树的最大贡献值 - def maxPathSum(self, root: TreeNode) -> int: + def maxPathSum(self, root: Optional[TreeNode]) -> int: self.dfs(root) - return self.max_sum + return self.ans ``` ### 思路 1:复杂度分析 From de485bc1cd8da39bb64beebdc4fae6487838d1eb Mon Sep 17 00:00:00 2001 From: ITCharge Date: 2023年4月27日 17:45:53 +0800 Subject: [PATCH 2/3] =?UTF-8?q?Update=200543.=20=E4=BA=8C=E5=8F=89?= =?UTF-8?q?=E6=A0=91=E7=9A=84=E7=9B=B4=E5=BE=84.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...21347円232円204円347円233円264円345円276円204円.md" | 61 +++++++++++++------ 1 file changed, 42 insertions(+), 19 deletions(-) diff --git "a/Solutions/0543. 344円272円214円345円217円211円346円240円221円347円232円204円347円233円264円345円276円204円.md" "b/Solutions/0543. 344円272円214円345円217円211円346円240円221円347円232円204円347円233円264円345円276円204円.md" index f81d6deb..cbb4aa41 100644 --- "a/Solutions/0543. 344円272円214円345円217円211円346円240円221円347円232円204円347円233円264円345円276円204円.md" +++ "b/Solutions/0543. 344円272円214円345円217円211円346円240円221円347円232円204円347円233円264円345円276円204円.md" @@ -5,7 +5,7 @@ ## 题目大意 -**描述**:给一个二叉树的根节点 `root`。 +**描述**:给一个二叉树的根节点 $root$。 **要求**:计算该二叉树的直径长度。 @@ -13,6 +13,7 @@ - **二叉树的直径长度**:二叉树中任意两个节点路径长度中的最大值。 - 两节点之间的路径长度是以它们之间边的数目表示。 +- 这条路径可能穿过也可能不穿过根节点。 **示例**: @@ -31,36 +32,58 @@ ## 解题思路 -### 思路 1:深度优先搜索 +### 思路 1:树形 DP + 深度优先搜索 -这道题的重点是理解直径长度的定义。这里的直径并不是简单的「左子树高度」+「右子树高度」。 +这道题重点是理解直径长度的定义。「二叉树的直径长度」的定义为:二叉树中任意两个节点路径长度中的最大值。并且这条路径可能穿过也可能不穿过根节点。 -而是 `当前节点的直径 = max{左子树高度+右子树高度,所有子树中最大直径}`。 +对于根为 $root$ 的二叉树来说,其直径长度并不简单等于「左子树高度」加上「右子树高度」。 -也就是说当前节点的直径可能来自于 「左子树高度」+「右子树高度」,也可能来自于「子树中的最大直径」。 +根据路径是否穿过根节点,我们可以将二叉树分为两种: -这就需要在递归求解子树高度的时候维护一个 `maxDiameter` 变量。每次递归都要去判断 当前「左子树高度」+「右子树的高度」是否大于 `self.maxDiameter`,如果大于,则更新最大值。 +1. 直径长度所对应的路径穿过根节点。 +2. 直径长度所对应的路径不穿过根节点。 + +我们来看下图中的两个例子。 + +![](https://qcdn.itcharge.cn/images/20230427111005.png) + +如图所示,左侧这棵二叉树就是一棵常见的平衡二叉树,其直径长度所对应的路径是穿过根节点的($D\rightarrow B \rightarrow A \rightarrow C$)。这种情况下:$二叉树的直径 = 左子树高度 + 右子树高度$。 + +而右侧这棵特殊的二叉树,其直径长度所对应的路径是没有穿过根节点的($F \rightarrow D \rightarrow B \rightarrow E \rightarrow G$)。这种情况下:$二叉树的直径 = 所有子树中最大直径长度$。 + +也就是说根为 $root$ 的二叉树的直径长度可能来自于 $左子树高度 + 右子树高度,ドル也可能来自于 $子树中的最大直径,ドル即 $二叉树的直径 = max(左子树高度 + 右子树高度, \quad 所有子树中最大直径长度)$。 + +那么现在问题就变成为如何求「子树的高度」和「子树中的最大直径」。 + +1. 子树的高度:我们可以利用深度优先搜索方法,递归遍历左右子树,并分别返回左右子树的高度。 +2. 子树中的最大直径:我们可以在递归求解子树高度的时候维护一个 $ans$ 变量,用于记录所有 $左子树高度 + 右子树高度$ 中的最大值。 + +最终 $ans$ 就是我们所求的该二叉树的最大直径,将其返回即可。 ### 思路 1:代码 ```Python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, val=0, left=None, right=None): +# self.val = val +# self.left = left +# self.right = right class Solution: def __init__(self): - # 保存当前最大直径 - self.maxDiameter = 0 + self.ans = 0 - def diameterOfBinaryTree(self, root: TreeNode) -> int: - self.height(root) - return self.maxDiameter - - def height(self, root): - if root == None: + def dfs(self, node): + if not node: return 0 - leftHeight = self.height(root.left) - rightHeight = self.height(root.right) - self.maxDiameter = max(self.maxDiameter, leftHeight + rightHeight) - - return max(leftHeight, rightHeight) + 1 + left_height = self.dfs(node.left) # 左子树高度 + right_height = self.dfs(node.right) # 右子树高度 + self.ans = max(self.ans, left_height + right_height) # 维护所有路径中的最大直径 + return max(left_height, right_height) + 1 # 返回该节点的高度 = 左右子树最大高度 + 1 + + def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int: + self.dfs(root) + return self.ans ``` ### 思路 1:复杂度分析 From 2e06107666d4cdba5f4f161c81d631c37aa95f39 Mon Sep 17 00:00:00 2001 From: ITCharge Date: 2023年4月27日 17:45:55 +0800 Subject: [PATCH 3/3] =?UTF-8?q?Create=202246.=20=E7=9B=B8=E9=82=BB?= =?UTF-8?q?=E5=AD=97=E7=AC=A6=E4=B8=8D=E5=90=8C=E7=9A=84=E6=9C=80=E9=95=BF?= =?UTF-8?q?=E8=B7=AF=E5=BE=84.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...00351円225円277円350円267円257円345円276円204円.md" | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 "Solutions/2246. 347円233円270円351円202円273円345円255円227円347円254円246円344円270円215円345円220円214円347円232円204円346円234円200円351円225円277円350円267円257円345円276円204円.md" diff --git "a/Solutions/2246. 347円233円270円351円202円273円345円255円227円347円254円246円344円270円215円345円220円214円347円232円204円346円234円200円351円225円277円350円267円257円345円276円204円.md" "b/Solutions/2246. 347円233円270円351円202円273円345円255円227円347円254円246円344円270円215円345円220円214円347円232円204円346円234円200円351円225円277円350円267円257円345円276円204円.md" new file mode 100644 index 00000000..5e555a73 --- /dev/null +++ "b/Solutions/2246. 347円233円270円351円202円273円345円255円227円347円254円246円344円270円215円345円220円214円347円232円204円346円234円200円351円225円277円350円267円257円345円276円204円.md" @@ -0,0 +1,101 @@ +# [2246. 相邻字符不同的最长路径](https://leetcode.cn/problems/longest-path-with-different-adjacent-characters/) + +- 标签:树、深度优先搜索、图、拓扑排序、数组、字符串 +- 难度:困难 + +## 题目大意 + +**描述**:给定一个长度为 $n$ 的数组 $parent$ 来表示一棵树(即一个连通、无向、无环图)。该树的节点编号为 0ドル \sim n - 1,ドル共 $n$ 个节点,其中根节点的编号为 0ドル$。其中 $parent[i]$ 表示节点 $i$ 的父节点,由于节点 0ドル$ 是根节点,所以 $parent[0] == -1$。再给定一个长度为 $n$ 的字符串,其中 $s[i]$ 表示分配给节点 $i$ 的字符。 + +**要求**:找出路径上任意一对相邻节点都没有分配到相同字符的最长路径,并返回该路径的长度。 + +**说明**: + +- $n == parent.length == s.length$。 +- 1ドル \le n \le 10^5$。 +- 对所有 $i \ge 1$ ,0ドル \le parent[i] \le n - 1$ 均成立。 +- $parent[0] == -1$。 +- $parent$ 表示一棵有效的树。 +- $s$ 仅由小写英文字母组成。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2022/03/25/testingdrawio.png) + +```Python +输入:parent = [-1,0,0,1,1,2], s = "abacbe" +输出:3 +解释:任意一对相邻节点字符都不同的最长路径是:0 -> 1 -> 3 。该路径的长度是 3 ,所以返回 3。 +可以证明不存在满足上述条件且比 3 更长的路径。 +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2022/03/25/graph2drawio.png) + +```Python +输入:parent = [-1,0,0,0], s = "aabc" +输出:3 +解释:任意一对相邻节点字符都不同的最长路径是:2 -> 0 -> 3 。该路径的长度为 3 ,所以返回 3。 +``` + +## 解题思路 + +### 思路 1:树形 DP + 深度优先搜索 + +因为题目给定的是表示父子节点的 $parent$ 数组,为了方便递归遍历相邻节点,我们可以根据 $partent$ 数组,建立一个由父节点指向子节点的有向图 $graph$。 + +如果不考虑相邻节点是否为相同字符这一条件,那么这道题就是在求树的直径(树的最长路径长度)中的节点个数。 + +对于根节点为 $u$ 的树来说: + +1. 如果其最长路径经过根节点 $u,ドル则 $最长路径长度 = 某子树中的最长路径长度 + 另一子树中的最长路径长度 + 1$。 +2. 如果其最长路径不经过根节点 $u,ドル则 $最长路径长度 = 某个子树中的最长路径长度$。 + +即:$最长路径长度 = max(某子树中的最长路径长度 + 另一子树中的最长路径长度 + 1, \quad 某个子树中的最长路径长度)$。 + +对此,我们可以使用深度优先搜索递归遍历 $u$ 的所有相邻节点 $v,ドル并在递归遍历的同时,维护一个全局最大路径和变量 $ans,ドル以及当前节点 $u$ 的最大路径长度变量 $u\underline{}len$。 + +1. 先计算出从相邻节点 $v$ 出发的最长路径长度 $v\underline{}len$。 +2. 更新维护全局最长路径长度为 $self.ans = max(self.ans, \quad u\underline{}len + v\underline{}len + 1)$。 +3. 更新维护当前节点 $u$ 的最长路径长度为 $u\underline{}len = max(u\underline{}len, \quad v\underline{}len + 1)$。 + +因为题目限定了「相邻节点字符不同」,所以在更新全局最长路径长度和当前节点 $u$ 的最长路径长度时,我们需要判断一下节点 $u$ 与相邻节点 $v$ 的字符是否相同,只有在字符不同的条件下,才能够更新维护。 + +最后,因为题目要求的是树的直径(树的最长路径长度)中的节点个数,而:$路径的节点 = 路径长度 + 1,ドル所以最后我们返回 $self.ans + 1$ 作为答案。 + +### 思路 1:代码 + +```Python +class Solution: + def __init__(self): + self.ans = 0 + + def dfs(self, graph, s, u): + u_len = 0 # u 节点的最大路径长度 + for v in graph[u]: # 遍历 u 节点的相邻节点 + v_len = self.dfs(graph, s, v) # 相邻节点的最大路径长度 + if s[u] != s[v]: # 相邻节点字符不同 + self.ans = max(self.ans, u_len + v_len + 1) # 维护最大路径长度 + u_len = max(u_len, v_len + 1) # 更新 u 节点的最大路径长度 + return u_len # 返回 u 节点的最大路径长度 + + def longestPath(self, parent: List[int], s: str) -> int: + size = len(parent) + + # 根据 parent 数组,建立有向图 + graph = [[] for _ in range(size)] + for i in range(1, size): + graph[parent[i]].append(i) + + self.dfs(graph, s, 0) + + return self.ans + 1 +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n),ドル其中 $n$ 是树的节点数目。 +- **空间复杂度**:$O(n)$

AltStyle によって変換されたページ (->オリジナル) /