Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

[pull] main from itcharge:main #83

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
pull merged 3 commits into AlgorithmAndLeetCode:main from itcharge:main
Apr 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 45 additions & 25 deletions Solutions/0124. 二叉树中的最大路径和.md
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@

## 题目大意

**描述**:给定一个二叉树的根节点 `root`
**描述**:给定一个二叉树的根节点 $root$

**要求**:返回其最大路径和。

**说明**:

- **路径**:从树中的任意节点出发,沿父节点——子节点连接,到达任意节点的序列。同一个节点在一条路径序列中至多出现一次。该路径至少包含一个节点,且不一定经过根节点。
- **路径**:被定义为一条节点序列,序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中至多出现一次。该路径至少包含一个节点,且不一定经过根节点。
- **路径和**:路径中各节点值的总和。
- 树中节点数目范围是 $[1, 3 * 10^4]$。
- $-1000 \le Node.val \le 1000$。
Expand Down Expand Up @@ -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:复杂度分析
Expand Down
61 changes: 42 additions & 19 deletions Solutions/0543. 二叉树的直径.md
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@

## 题目大意

**描述**:给一个二叉树的根节点 `root`
**描述**:给一个二叉树的根节点 $root$

**要求**:计算该二叉树的直径长度。

**说明**:

- **二叉树的直径长度**:二叉树中任意两个节点路径长度中的最大值。
- 两节点之间的路径长度是以它们之间边的数目表示。
- 这条路径可能穿过也可能不穿过根节点。

**示例**:

Expand All @@ -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:复杂度分析
Expand Down
101 changes: 101 additions & 0 deletions Solutions/2246. 相邻字符不同的最长路径.md
View file Open in desktop
Original file line number Diff line number Diff line change
@@ -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 によって変換されたページ (->オリジナル) /