mirror of
https://github.com/youngyangyang04/leetcode-master.git
synced 2025-07-08 00:43:04 +08:00
Merge branch 'youngyangyang04:master' into master
This commit is contained in:
@ -120,7 +120,7 @@ for (int i = 0; i < letters.size(); i++) {
|
||||
|
||||
**注意这里for循环,可不像是在[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)和[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)中从startIndex开始遍历的**。
|
||||
|
||||
**因为本题每一个数字代表的是不同集合,也就是求不同集合之间的组合,而[77. 组合](https://programmercarl.com/0077.组合.html)和[216.组合总和III](https://programmercarl.com/0216.组合总和III.html)都是是求同一个集合中的组合!**
|
||||
**因为本题每一个数字代表的是不同集合,也就是求不同集合之间的组合,而[77. 组合](https://programmercarl.com/0077.组合.html)和[216.组合总和III](https://programmercarl.com/0216.组合总和III.html)都是求同一个集合中的组合!**
|
||||
|
||||
|
||||
注意:输入1 * #按键等等异常情况
|
||||
@ -356,38 +356,32 @@ class Solution:
|
||||
主要在于递归中传递下一个数字
|
||||
|
||||
```go
|
||||
var (
|
||||
m []string
|
||||
path []byte
|
||||
res []string
|
||||
)
|
||||
func letterCombinations(digits string) []string {
|
||||
lenth:=len(digits)
|
||||
if lenth==0 ||lenth>4{
|
||||
return nil
|
||||
m = []string{"abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"}
|
||||
path, res = make([]byte, 0), make([]string, 0)
|
||||
if digits == "" {
|
||||
return res
|
||||
}
|
||||
digitsMap:= [10]string{
|
||||
"", // 0
|
||||
"", // 1
|
||||
"abc", // 2
|
||||
"def", // 3
|
||||
"ghi", // 4
|
||||
"jkl", // 5
|
||||
"mno", // 6
|
||||
"pqrs", // 7
|
||||
"tuv", // 8
|
||||
"wxyz", // 9
|
||||
}
|
||||
res:=make([]string,0)
|
||||
recursion("",digits,0,digitsMap,&res)
|
||||
return res
|
||||
dfs(digits, 0)
|
||||
return res
|
||||
}
|
||||
func recursion(tempString ,digits string, Index int,digitsMap [10]string, res *[]string) {//index表示第几个数字
|
||||
if len(tempString)==len(digits){//终止条件,字符串长度等于digits的长度
|
||||
*res=append(*res,tempString)
|
||||
func dfs(digits string, start int) {
|
||||
if len(path) == len(digits) { //终止条件,字符串长度等于digits的长度
|
||||
tmp := string(path)
|
||||
res = append(res, tmp)
|
||||
return
|
||||
}
|
||||
tmpK:=digits[Index]-'0' // 将index指向的数字转为int(确定下一个数字)
|
||||
letter:=digitsMap[tmpK]// 取数字对应的字符集
|
||||
for i:=0;i<len(letter);i++{
|
||||
tempString=tempString+string(letter[i])//拼接结果
|
||||
recursion(tempString,digits,Index+1,digitsMap,res)
|
||||
tempString=tempString[:len(tempString)-1]//回溯
|
||||
digit := int(digits[start] - '0') // 将index指向的数字转为int(确定下一个数字)
|
||||
str := m[digit-2] // 取数字对应的字符集(注意和map中的对应)
|
||||
for j := 0; j < len(str); j++ {
|
||||
path = append(path, str[j])
|
||||
dfs(digits, start+1)
|
||||
path = path[:len(path)-1]
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -415,7 +409,6 @@ var letterCombinations = function(digits) {
|
||||
backtracking(n, k, a + 1);
|
||||
path.pop();
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
```
|
||||
|
@ -77,7 +77,7 @@ public:
|
||||
|
||||
双指针法(快慢指针法): **通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。**
|
||||
|
||||
定义快慢指针
|
||||
定义快慢指针
|
||||
|
||||
* 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
|
||||
* 慢指针:指向更新 新数组下标的位置
|
||||
@ -196,21 +196,26 @@ class Solution {
|
||||
|
||||
Python:
|
||||
|
||||
```python3
|
||||
|
||||
``` python 3
|
||||
class Solution:
|
||||
def removeElement(self, nums: List[int], val: int) -> int:
|
||||
# 快指针遍历元素
|
||||
fast = 0
|
||||
# 慢指针记录位置
|
||||
slow = 0
|
||||
for fast in range(len(nums)):
|
||||
# 快慢指针
|
||||
fast = 0 # 快指针
|
||||
slow = 0 # 慢指针
|
||||
size = len(nums)
|
||||
while fast < size: # 不加等于是因为,a = size 时,nums[a] 会越界
|
||||
# slow 用来收集不等于 val 的值,如果 fast 对应值不等于 val,则把它与 slow 替换
|
||||
if nums[fast] != val:
|
||||
nums[slow] = nums[fast]
|
||||
slow += 1
|
||||
fast += 1
|
||||
return slow
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
Go:
|
||||
```go
|
||||
func removeElement(nums []int, val int) int {
|
||||
@ -262,7 +267,7 @@ Ruby:
|
||||
```ruby
|
||||
def remove_element(nums, val)
|
||||
i = 0
|
||||
nums.each_index do |j|
|
||||
nums.each_index do |j|
|
||||
if nums[j] != val
|
||||
nums[i] = nums[j]
|
||||
i+=1
|
||||
@ -336,7 +341,7 @@ int removeElement(int* nums, int numsSize, int val){
|
||||
if(nums[fast] != val) {
|
||||
//将其挪到慢指针指向的位置,慢指针+1
|
||||
nums[slow++] = nums[fast];
|
||||
}
|
||||
}
|
||||
}
|
||||
//最后慢指针的大小就是新的数组的大小
|
||||
return slow;
|
||||
|
@ -42,7 +42,7 @@ candidates 中的数字可以无限制重复被选取。
|
||||
|
||||
题目中的**无限制重复被选取,吓得我赶紧想想 出现0 可咋办**,然后看到下面提示:1 <= candidates[i] <= 200,我就放心了。
|
||||
|
||||
本题和[77.组合](https://programmercarl.com/0077.组合.html),[216.组合总和III](https://programmercarl.com/0216.组合总和III.html)和区别是:本题没有数量要求,可以无限重复,但是有总和的限制,所以间接的也是有个数的限制。
|
||||
本题和[77.组合](https://programmercarl.com/0077.组合.html),[216.组合总和III](https://programmercarl.com/0216.组合总和III.html)的区别是:本题没有数量要求,可以无限重复,但是有总和的限制,所以间接的也是有个数的限制。
|
||||
|
||||
本题搜索的过程抽象成树形结构如下:
|
||||
|
||||
@ -335,33 +335,32 @@ class Solution:
|
||||
主要在于递归中传递下一个数字
|
||||
|
||||
```go
|
||||
var (
|
||||
res [][]int
|
||||
path []int
|
||||
)
|
||||
func combinationSum(candidates []int, target int) [][]int {
|
||||
var trcak []int
|
||||
var res [][]int
|
||||
backtracking(0,0,target,candidates,trcak,&res)
|
||||
res, path = make([][]int, 0), make([]int, 0, len(candidates))
|
||||
sort.Ints(candidates) // 排序,为剪枝做准备
|
||||
dfs(candidates, 0, target)
|
||||
return res
|
||||
}
|
||||
func backtracking(startIndex,sum,target int,candidates,trcak []int,res *[][]int){
|
||||
//终止条件
|
||||
if sum==target{
|
||||
tmp:=make([]int,len(trcak))
|
||||
copy(tmp,trcak)//拷贝
|
||||
*res=append(*res,tmp)//放入结果集
|
||||
|
||||
func dfs(candidates []int, start int, target int) {
|
||||
if target == 0 { // target 不断减小,如果为0说明达到了目标值
|
||||
tmp := make([]int, len(path))
|
||||
copy(tmp, path)
|
||||
res = append(res, tmp)
|
||||
return
|
||||
}
|
||||
if sum>target{return}
|
||||
//回溯
|
||||
for i:=startIndex;i<len(candidates);i++{
|
||||
//更新路径集合和sum
|
||||
trcak=append(trcak,candidates[i])
|
||||
sum+=candidates[i]
|
||||
//递归
|
||||
backtracking(i,sum,target,candidates,trcak,res)
|
||||
//回溯
|
||||
trcak=trcak[:len(trcak)-1]
|
||||
sum-=candidates[i]
|
||||
for i := start; i < len(candidates); i++ {
|
||||
if candidates[i] > target { // 剪枝,提前返回
|
||||
break
|
||||
}
|
||||
path = append(path, candidates[i])
|
||||
dfs(candidates, i, target - candidates[i])
|
||||
path = path[:len(path) - 1]
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -110,13 +110,13 @@ if (sum == target) {
|
||||
}
|
||||
```
|
||||
|
||||
`sum > target` 这个条件其实可以省略,因为和在递归单层遍历的时候,会有剪枝的操作,下面会介绍到。
|
||||
`sum > target` 这个条件其实可以省略,因为在递归单层遍历的时候,会有剪枝的操作,下面会介绍到。
|
||||
|
||||
* **单层搜索的逻辑**
|
||||
|
||||
这里与[39.组合总和](https://programmercarl.com/0039.组合总和.html)最大的不同就是要去重了。
|
||||
|
||||
前面我们提到:要去重的是“同一树层上的使用过”,如果判断同一树层上元素(相同的元素)是否使用过了呢。
|
||||
前面我们提到:要去重的是“同一树层上的使用过”,如何判断同一树层上元素(相同的元素)是否使用过了呢。
|
||||
|
||||
**如果`candidates[i] == candidates[i - 1]` 并且 `used[i - 1] == false`,就说明:前一个树枝,使用了candidates[i - 1],也就是说同一树层使用过candidates[i - 1]**。
|
||||
|
||||
@ -438,76 +438,74 @@ class Solution:
|
||||
|
||||
**使用used数组**
|
||||
```go
|
||||
var (
|
||||
res [][]int
|
||||
path []int
|
||||
used []bool
|
||||
)
|
||||
func combinationSum2(candidates []int, target int) [][]int {
|
||||
var trcak []int
|
||||
var res [][]int
|
||||
var history map[int]bool
|
||||
history=make(map[int]bool)
|
||||
sort.Ints(candidates)
|
||||
backtracking(0,0,target,candidates,trcak,&res,history)
|
||||
res, path = make([][]int, 0), make([]int, 0, len(candidates))
|
||||
used = make([]bool, len(candidates))
|
||||
sort.Ints(candidates) // 排序,为剪枝做准备
|
||||
dfs(candidates, 0, target)
|
||||
return res
|
||||
}
|
||||
func backtracking(startIndex,sum,target int,candidates,trcak []int,res *[][]int,history map[int]bool){
|
||||
//终止条件
|
||||
if sum==target{
|
||||
tmp:=make([]int,len(trcak))
|
||||
copy(tmp,trcak)//拷贝
|
||||
*res=append(*res,tmp)//放入结果集
|
||||
|
||||
func dfs(candidates []int, start int, target int) {
|
||||
if target == 0 { // target 不断减小,如果为0说明达到了目标值
|
||||
tmp := make([]int, len(path))
|
||||
copy(tmp, path)
|
||||
res = append(res, tmp)
|
||||
return
|
||||
}
|
||||
if sum>target{return}
|
||||
//回溯
|
||||
// used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
|
||||
// used[i - 1] == false,说明同一树层candidates[i - 1]使用过
|
||||
for i:=startIndex;i<len(candidates);i++{
|
||||
if i>0&&candidates[i]==candidates[i-1]&&history[i-1]==false{
|
||||
continue
|
||||
for i := start; i < len(candidates); i++ {
|
||||
if candidates[i] > target { // 剪枝,提前返回
|
||||
break
|
||||
}
|
||||
//更新路径集合和sum
|
||||
trcak=append(trcak,candidates[i])
|
||||
sum+=candidates[i]
|
||||
history[i]=true
|
||||
//递归
|
||||
backtracking(i+1,sum,target,candidates,trcak,res,history)
|
||||
//回溯
|
||||
trcak=trcak[:len(trcak)-1]
|
||||
sum-=candidates[i]
|
||||
history[i]=false
|
||||
// used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
|
||||
// used[i - 1] == false,说明同一树层candidates[i - 1]使用过
|
||||
if i > 0 && candidates[i] == candidates[i-1] && used[i-1] == false {
|
||||
continue
|
||||
}
|
||||
path = append(path, candidates[i])
|
||||
used[i] = true
|
||||
dfs(candidates, i+1, target - candidates[i])
|
||||
used[i] = false
|
||||
path = path[:len(path) - 1]
|
||||
}
|
||||
}
|
||||
```
|
||||
**不使用used数组**
|
||||
```go
|
||||
var (
|
||||
res [][]int
|
||||
path []int
|
||||
)
|
||||
func combinationSum2(candidates []int, target int) [][]int {
|
||||
var trcak []int
|
||||
var res [][]int
|
||||
sort.Ints(candidates)
|
||||
backtracking(0,0,target,candidates,trcak,&res)
|
||||
res, path = make([][]int, 0), make([]int, 0, len(candidates))
|
||||
sort.Ints(candidates) // 排序,为剪枝做准备
|
||||
dfs(candidates, 0, target)
|
||||
return res
|
||||
}
|
||||
func backtracking(startIndex,sum,target int,candidates,trcak []int,res *[][]int){
|
||||
//终止条件
|
||||
if sum==target{
|
||||
tmp:=make([]int,len(trcak))
|
||||
//拷贝
|
||||
copy(tmp,trcak)
|
||||
//放入结果集
|
||||
*res=append(*res,tmp)
|
||||
|
||||
func dfs(candidates []int, start int, target int) {
|
||||
if target == 0 { // target 不断减小,如果为0说明达到了目标值
|
||||
tmp := make([]int, len(path))
|
||||
copy(tmp, path)
|
||||
res = append(res, tmp)
|
||||
return
|
||||
}
|
||||
//回溯
|
||||
for i:=startIndex;i<len(candidates) && sum+candidates[i]<=target;i++{
|
||||
// 若当前树层有使用过相同的元素,则跳过
|
||||
if i>startIndex&&candidates[i]==candidates[i-1]{
|
||||
continue
|
||||
for i := start; i < len(candidates); i++ {
|
||||
if candidates[i] > target { // 剪枝,提前返回
|
||||
break
|
||||
}
|
||||
//更新路径集合和sum
|
||||
trcak=append(trcak,candidates[i])
|
||||
sum+=candidates[i]
|
||||
backtracking(i+1,sum,target,candidates,trcak,res)
|
||||
//回溯
|
||||
trcak=trcak[:len(trcak)-1]
|
||||
sum-=candidates[i]
|
||||
// i != start 限制了这不对深度遍历到达的此值去重
|
||||
if i != start && candidates[i] == candidates[i-1] { // 去重
|
||||
continue
|
||||
}
|
||||
path = append(path, candidates[i])
|
||||
dfs(candidates, i+1, target - candidates[i])
|
||||
path = path[:len(path) - 1]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
# 52. N皇后II
|
||||
|
||||
题目链接:https://leetcode.cn/problems/n-queens-ii/
|
||||
题目链接:[https://leetcode.cn/problems/n-queens-ii/](https://leetcode.cn/problems/n-queens-ii/)
|
||||
|
||||
n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
|
||||
|
||||
@ -308,3 +308,4 @@ class Solution {
|
||||
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
|
||||
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>
|
||||
</a>
|
||||
|
||||
|
@ -28,7 +28,7 @@
|
||||
* 1 阶 + 2 阶
|
||||
* 2 阶 + 1 阶
|
||||
|
||||
# 视频讲解
|
||||
# 视频讲解
|
||||
|
||||
**《代码随想录》算法视频公开课:[带你学透动态规划-爬楼梯|LeetCode:70.爬楼梯)](https://www.bilibili.com/video/BV17h411h7UH),相信结合视频在看本篇题解,更有助于大家对本题的理解**。
|
||||
|
||||
@ -216,7 +216,7 @@ public:
|
||||
## 其他语言版本
|
||||
|
||||
|
||||
### Java
|
||||
### Java
|
||||
|
||||
```java
|
||||
// 常规方式
|
||||
@ -237,7 +237,7 @@ class Solution {
|
||||
public int climbStairs(int n) {
|
||||
if(n <= 2) return n;
|
||||
int a = 1, b = 2, sum = 0;
|
||||
|
||||
|
||||
for(int i = 3; i <= n; i++){
|
||||
sum = a + b; // f(i - 1) + f(i - 2)
|
||||
a = b; // 记录f(i - 1),即下一轮的f(i - 2)
|
||||
@ -261,7 +261,7 @@ class Solution:
|
||||
for i in range(2,n+1):
|
||||
dp[i]=dp[i-1]+dp[i-2]
|
||||
return dp[n]
|
||||
|
||||
|
||||
# 空间复杂度为O(1)版本
|
||||
class Solution:
|
||||
def climbStairs(self, n: int) -> int:
|
||||
@ -275,7 +275,7 @@ class Solution:
|
||||
return dp[1]
|
||||
```
|
||||
|
||||
### Go
|
||||
### Go
|
||||
```Go
|
||||
func climbStairs(n int) int {
|
||||
if n==1{
|
||||
@ -303,7 +303,7 @@ var climbStairs = function(n) {
|
||||
};
|
||||
```
|
||||
|
||||
TypeScript
|
||||
### TypeScript
|
||||
|
||||
> 爬2阶
|
||||
|
||||
@ -447,7 +447,26 @@ public class Solution {
|
||||
}
|
||||
```
|
||||
|
||||
### Rust
|
||||
|
||||
```rust
|
||||
impl Solution {
|
||||
pub fn climb_stairs(n: i32) -> i32 {
|
||||
if n <= 2 {
|
||||
return n;
|
||||
}
|
||||
let mut a = 1;
|
||||
let mut b = 2;
|
||||
let mut f = 0;
|
||||
for i in 2..n {
|
||||
f = a + b;
|
||||
a = b;
|
||||
b = f;
|
||||
}
|
||||
return f;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<p align="center">
|
||||
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
|
||||
|
@ -34,7 +34,7 @@
|
||||
# 思路
|
||||
|
||||
|
||||
本题这是回溯法的经典题目。
|
||||
本题是回溯法的经典题目。
|
||||
|
||||
直接的解法当然是使用for循环,例如示例中k为2,很容易想到 用两个for循环,这样就可以输出 和示例中一样的结果。
|
||||
|
||||
@ -82,13 +82,13 @@ for (int i = 1; i <= n; i++) {
|
||||
|
||||
如果脑洞模拟回溯搜索的过程,绝对可以让人窒息,所以需要抽象图形结构来进一步理解。
|
||||
|
||||
**我们在[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)中说道回溯法解决的问题都可以抽象为树形结构(N叉树),用树形结构来理解回溯就容易多了**。
|
||||
**我们在[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)中说到回溯法解决的问题都可以抽象为树形结构(N叉树),用树形结构来理解回溯就容易多了**。
|
||||
|
||||
那么我把组合问题抽象为如下树形结构:
|
||||
|
||||

|
||||
|
||||
可以看出这个棵树,一开始集合是 1,2,3,4, 从左向右取数,取过的数,不在重复取。
|
||||
可以看出这棵树,一开始集合是 1,2,3,4, 从左向右取数,取过的数,不再重复取。
|
||||
|
||||
第一次取1,集合变为2,3,4 ,因为k为2,我们只需要再取一个数就可以了,分别取2,3,4,得到集合[1,2] [1,3] [1,4],以此类推。
|
||||
|
||||
@ -120,7 +120,7 @@ vector<int> path; // 用来存放符合条件结果
|
||||
|
||||
其实不定义这两个全局变量也是可以的,把这两个变量放进递归函数的参数里,但函数里参数太多影响可读性,所以我定义全局变量了。
|
||||
|
||||
函数里一定有两个参数,既然是集合n里面取k的数,那么n和k是两个int型的参数。
|
||||
函数里一定有两个参数,既然是集合n里面取k个数,那么n和k是两个int型的参数。
|
||||
|
||||
然后还需要一个参数,为int型变量startIndex,这个参数用来记录本层递归的中,集合从哪里开始遍历(集合就是[1,...,n] )。
|
||||
|
||||
@ -389,9 +389,8 @@ class Solution(object):
|
||||
|
||||
# 剪枝, 最后k - len(path)个节点直接构造结果,无需递归
|
||||
last_startidx = n - (k - len(path)) + 1
|
||||
result.append(path + [idx for idx in range(last_startidx, n + 1)])
|
||||
|
||||
for x in range(startidx, last_startidx):
|
||||
for x in range(startidx, last_startidx + 1):
|
||||
path.append(x)
|
||||
backtracking(n, k, x + 1) # 递归
|
||||
path.pop() # 回溯
|
||||
@ -435,6 +434,36 @@ class Solution:
|
||||
return res
|
||||
```
|
||||
|
||||
### Go
|
||||
```Go
|
||||
var (
|
||||
path []int
|
||||
res [][]int
|
||||
)
|
||||
|
||||
func combine(n int, k int) [][]int {
|
||||
path, res = make([]int, 0, k), make([][]int, 0)
|
||||
dfs(n, k, 1)
|
||||
return res
|
||||
}
|
||||
|
||||
func dfs(n int, k int, start int) {
|
||||
if len(path) == k { // 说明已经满足了k个数的要求
|
||||
tmp := make([]int, k)
|
||||
copy(tmp, path)
|
||||
res = append(res, tmp)
|
||||
return
|
||||
}
|
||||
for i := start; i <= n; i++ { // 从start开始,不往回走,避免出现重复组合
|
||||
if n - i + 1 < k - len(path) { // 剪枝
|
||||
break
|
||||
}
|
||||
path = append(path, i)
|
||||
dfs(n, k, i+1)
|
||||
path = path[:len(path)-1]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### javascript
|
||||
|
||||
@ -481,63 +510,6 @@ function combine(n: number, k: number): number[][] {
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Go
|
||||
```Go
|
||||
var res [][]int
|
||||
func combine(n int, k int) [][]int {
|
||||
res=[][]int{}
|
||||
if n <= 0 || k <= 0 || k > n {
|
||||
return res
|
||||
}
|
||||
backtrack(n, k, 1, []int{})
|
||||
return res
|
||||
}
|
||||
func backtrack(n,k,start int,track []int){
|
||||
if len(track)==k{
|
||||
temp:=make([]int,k)
|
||||
copy(temp,track)
|
||||
res=append(res,temp)
|
||||
}
|
||||
if len(track)+n-start+1 < k {
|
||||
return
|
||||
}
|
||||
for i:=start;i<=n;i++{
|
||||
track=append(track,i)
|
||||
backtrack(n,k,i+1,track)
|
||||
track=track[:len(track)-1]
|
||||
}
|
||||
}
|
||||
```
|
||||
剪枝:
|
||||
```Go
|
||||
var res [][]int
|
||||
func combine(n int, k int) [][]int {
|
||||
res=[][]int{}
|
||||
if n <= 0 || k <= 0 || k > n {
|
||||
return res
|
||||
}
|
||||
backtrack(n, k, 1, []int{})
|
||||
return res
|
||||
}
|
||||
func backtrack(n,k,start int,track []int){
|
||||
if len(track)==k{
|
||||
temp:=make([]int,k)
|
||||
copy(temp,track)
|
||||
res=append(res,temp)
|
||||
}
|
||||
if len(track)+n-start+1 < k {
|
||||
return
|
||||
}
|
||||
for i:=start;i<=n;i++{
|
||||
track=append(track,i)
|
||||
backtrack(n,k,i+1,track)
|
||||
track=track[:len(track)-1]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Rust
|
||||
|
||||
```Rust
|
||||
|
@ -133,7 +133,7 @@ public:
|
||||
|
||||
# 总结
|
||||
|
||||
本篇我们准对求组合问题的回溯法代码做了剪枝优化,这个优化如果不画图的话,其实不好理解,也不好讲清楚。
|
||||
本篇我们针对求组合问题的回溯法代码做了剪枝优化,这个优化如果不画图的话,其实不好理解,也不好讲清楚。
|
||||
|
||||
所以我依然是把整个回溯过程抽象为一棵树形结构,然后可以直观的看出,剪枝究竟是剪的哪里。
|
||||
|
||||
@ -194,28 +194,28 @@ class Solution:
|
||||
```
|
||||
Go:
|
||||
```Go
|
||||
var res [][]int
|
||||
var (
|
||||
path []int
|
||||
res [][]int
|
||||
)
|
||||
|
||||
func combine(n int, k int) [][]int {
|
||||
res=[][]int{}
|
||||
if n <= 0 || k <= 0 || k > n {
|
||||
return res
|
||||
}
|
||||
backtrack(n, k, 1, []int{})
|
||||
return res
|
||||
path, res = make([]int, 0, k), make([][]int, 0)
|
||||
dfs(n, k, 1)
|
||||
return res
|
||||
}
|
||||
func backtrack(n,k,start int,track []int){
|
||||
if len(track)==k{
|
||||
temp:=make([]int,k)
|
||||
copy(temp,track)
|
||||
res=append(res,temp)
|
||||
|
||||
func dfs(n int, k int, start int) {
|
||||
if len(path) == k {
|
||||
tmp := make([]int, k)
|
||||
copy(tmp, path)
|
||||
res = append(res, tmp)
|
||||
return
|
||||
}
|
||||
if len(track)+n-start+1 < k {
|
||||
return
|
||||
}
|
||||
for i:=start;i<=n;i++{
|
||||
track=append(track,i)
|
||||
backtrack(n,k,i+1,track)
|
||||
track=track[:len(track)-1]
|
||||
for i := start; i <= n - (k-len(path)) + 1; i++ {
|
||||
path = append(path, i)
|
||||
dfs(n, k, i+1)
|
||||
path = path[:len(path)-1]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -227,25 +227,25 @@ class Solution:
|
||||
|
||||
## Go
|
||||
```Go
|
||||
var res [][]int
|
||||
func subset(nums []int) [][]int {
|
||||
res = make([][]int, 0)
|
||||
sort.Ints(nums)
|
||||
Dfs([]int{}, nums, 0)
|
||||
return res
|
||||
var (
|
||||
path []int
|
||||
res [][]int
|
||||
)
|
||||
func subsets(nums []int) [][]int {
|
||||
res, path = make([][]int, 0), make([]int, 0, len(nums))
|
||||
dfs(nums, 0)
|
||||
return res
|
||||
}
|
||||
func Dfs(temp, nums []int, start int){
|
||||
tmp := make([]int, len(temp))
|
||||
copy(tmp, temp)
|
||||
res = append(res, tmp)
|
||||
for i := start; i < len(nums); i++{
|
||||
//if i>start&&nums[i]==nums[i-1]{
|
||||
// continue
|
||||
//}
|
||||
temp = append(temp, nums[i])
|
||||
Dfs(temp, nums, i+1)
|
||||
temp = temp[:len(temp)-1]
|
||||
}
|
||||
func dfs(nums []int, start int) {
|
||||
tmp := make([]int, len(path))
|
||||
copy(tmp, path)
|
||||
res = append(res, tmp)
|
||||
|
||||
for i := start; i < len(nums); i++ {
|
||||
path = append(path, nums[i])
|
||||
dfs(nums, i+1)
|
||||
path = path[:len(path)-1]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -424,6 +424,42 @@ class Solution:
|
||||
return True
|
||||
```
|
||||
|
||||
## Go
|
||||
|
||||
```go
|
||||
var (
|
||||
path []string
|
||||
res []string
|
||||
)
|
||||
func restoreIpAddresses(s string) []string {
|
||||
path, res = make([]string, 0, len(s)), make([]string, 0)
|
||||
dfs(s, 0)
|
||||
return res
|
||||
}
|
||||
func dfs(s string, start int) {
|
||||
if len(path) == 4 { // 够四段后就不再继续往下递归
|
||||
if start == len(s) {
|
||||
str := strings.Join(path, ".")
|
||||
res = append(res, str)
|
||||
}
|
||||
return
|
||||
}
|
||||
for i := start; i < len(s); i++ {
|
||||
if i != start && s[start] == '0' { // 含有前导 0,无效
|
||||
break
|
||||
}
|
||||
str := s[start : i+1]
|
||||
num, _ := strconv.Atoi(str)
|
||||
if num >= 0 && num <= 255 {
|
||||
path = append(path, str) // 符合条件的就进入下一层
|
||||
dfs(s, i+1)
|
||||
path = path[:len(path) - 1]
|
||||
} else { // 如果不满足条件,再往后也不可能满足条件,直接退出
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## JavaScript
|
||||
|
||||
@ -494,48 +530,6 @@ function restoreIpAddresses(s: string): string[] {
|
||||
};
|
||||
```
|
||||
|
||||
## Go
|
||||
|
||||
回溯(对于前导 0的IP(特别注意s[startIndex]=='0'的判断,不应该写成s[startIndex]==0,因为s截取出来不是数字))
|
||||
|
||||
```go
|
||||
func restoreIpAddresses(s string) []string {
|
||||
var res,path []string
|
||||
backTracking(s,path,0,&res)
|
||||
return res
|
||||
}
|
||||
func backTracking(s string,path []string,startIndex int,res *[]string){
|
||||
//终止条件
|
||||
if startIndex==len(s)&&len(path)==4{
|
||||
tmpIpString:=path[0]+"."+path[1]+"."+path[2]+"."+path[3]
|
||||
*res=append(*res,tmpIpString)
|
||||
}
|
||||
for i:=startIndex;i<len(s);i++{
|
||||
//处理
|
||||
path:=append(path,s[startIndex:i+1])
|
||||
if i-startIndex+1<=3&&len(path)<=4&&isNormalIp(s,startIndex,i){
|
||||
//递归
|
||||
backTracking(s,path,i+1,res)
|
||||
}else {//如果首尾超过了3个,或路径多余4个,或前导为0,或大于255,直接回退
|
||||
return
|
||||
}
|
||||
//回溯
|
||||
path=path[:len(path)-1]
|
||||
}
|
||||
}
|
||||
func isNormalIp(s string,startIndex,end int)bool{
|
||||
checkInt,_:=strconv.Atoi(s[startIndex:end+1])
|
||||
if end-startIndex+1>1&&s[startIndex]=='0'{//对于前导 0的IP(特别注意s[startIndex]=='0'的判断,不应该写成s[startIndex]==0,因为s截取出来不是数字)
|
||||
return false
|
||||
}
|
||||
if checkInt>255{
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Rust
|
||||
|
||||
```Rust
|
||||
|
@ -437,8 +437,6 @@ class Solution:
|
||||
## Go
|
||||
|
||||
```Go
|
||||
import "math"
|
||||
|
||||
func isValidBST(root *TreeNode) bool {
|
||||
// 二叉搜索树也可以是空树
|
||||
if root == nil {
|
||||
|
@ -59,7 +59,7 @@ int getdepth(treenode* node)
|
||||
if (node == NULL) return 0;
|
||||
```
|
||||
|
||||
3. 确定单层递归的逻辑:先求它的左子树的深度,再求的右子树的深度,最后取左右深度最大的数值 再+1 (加1是因为算上当前中间节点)就是目前节点为根节点的树的深度。
|
||||
3. 确定单层递归的逻辑:先求它的左子树的深度,再求右子树的深度,最后取左右深度最大的数值 再+1 (加1是因为算上当前中间节点)就是目前节点为根节点的树的深度。
|
||||
|
||||
代码如下:
|
||||
|
||||
@ -591,15 +591,15 @@ var maxdepth = function(root) {
|
||||
var maxdepth = function(root) {
|
||||
//使用递归的方法 递归三部曲
|
||||
//1. 确定递归函数的参数和返回值
|
||||
const getdepth=function(node){
|
||||
const getdepth = function(node) {
|
||||
//2. 确定终止条件
|
||||
if(node===null){
|
||||
if(node === null) {
|
||||
return 0;
|
||||
}
|
||||
//3. 确定单层逻辑
|
||||
let leftdepth=getdepth(node.left);
|
||||
let rightdepth=getdepth(node.right);
|
||||
let depth=1+Math.max(leftdepth,rightdepth);
|
||||
let leftdepth = getdepth(node.left);
|
||||
let rightdepth = getdepth(node.right);
|
||||
let depth = 1 + Math.max(leftdepth, rightdepth);
|
||||
return depth;
|
||||
}
|
||||
return getdepth(root);
|
||||
|
@ -34,7 +34,7 @@
|
||||
|
||||
## 思路
|
||||
|
||||
首先回忆一下如何根据两个顺序构造一个唯一的二叉树,相信理论知识大家应该都清楚,就是以 后序数组的最后一个元素为切割点,先切中序数组,根据中序数组,反过来在切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素。
|
||||
首先回忆一下如何根据两个顺序构造一个唯一的二叉树,相信理论知识大家应该都清楚,就是以 后序数组的最后一个元素为切割点,先切中序数组,根据中序数组,反过来再切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素。
|
||||
|
||||
如果让我们肉眼看两个序列,画一棵二叉树的话,应该分分钟都可以画出来。
|
||||
|
||||
@ -236,7 +236,7 @@ private:
|
||||
vector<int> leftPostorder(postorder.begin(), postorder.begin() + leftInorder.size());
|
||||
vector<int> rightPostorder(postorder.begin() + leftInorder.size(), postorder.end());
|
||||
|
||||
// 一下为日志
|
||||
// 以下为日志
|
||||
cout << "----------" << endl;
|
||||
|
||||
cout << "leftInorder :";
|
||||
@ -275,7 +275,7 @@ public:
|
||||
};
|
||||
```
|
||||
|
||||
**此时应该发现了,如上的代码性能并不好,应为每层递归定定义了新的vector(就是数组),既耗时又耗空间,但上面的代码是最好理解的,为了方便读者理解,所以用如上的代码来讲解。**
|
||||
**此时应该发现了,如上的代码性能并不好,因为每层递归定义了新的vector(就是数组),既耗时又耗空间,但上面的代码是最好理解的,为了方便读者理解,所以用如上的代码来讲解。**
|
||||
|
||||
下面给出用下标索引写出的代码版本:(思路是一样的,只不过不用重复定义vector了,每次用下标索引来分割)
|
||||
|
||||
@ -569,7 +569,7 @@ tree2 的前序遍历是[1 2 3], 后序遍历是[3 2 1]。
|
||||
|
||||
之前我们讲的二叉树题目都是各种遍历二叉树,这次开始构造二叉树了,思路其实比较简单,但是真正代码实现出来并不容易。
|
||||
|
||||
所以要避免眼高手低,踏实的把代码写出来。
|
||||
所以要避免眼高手低,踏实地把代码写出来。
|
||||
|
||||
我同时给出了添加日志的代码版本,因为这种题目是不太容易写出来调一调就能过的,所以一定要把流程日志打出来,看看符不符合自己的思路。
|
||||
|
||||
@ -728,25 +728,33 @@ class Solution:
|
||||
* Right *TreeNode
|
||||
* }
|
||||
*/
|
||||
var (
|
||||
hash map[int]int
|
||||
)
|
||||
func buildTree(inorder []int, postorder []int) *TreeNode {
|
||||
if len(inorder)<1||len(postorder)<1{return nil}
|
||||
//先找到根节点(后续遍历的最后一个就是根节点)
|
||||
nodeValue:=postorder[len(postorder)-1]
|
||||
//从中序遍历中找到一分为二的点,左边为左子树,右边为右子树
|
||||
left:=findRootIndex(inorder,nodeValue)
|
||||
//构造root
|
||||
root:=&TreeNode{Val: nodeValue,
|
||||
Left: buildTree(inorder[:left],postorder[:left]),//将后续遍历一分为二,左边为左子树,右边为右子树
|
||||
Right: buildTree(inorder[left+1:],postorder[left:len(postorder)-1])}
|
||||
hash = make(map[int]int)
|
||||
for i, v := range inorder { // 用map保存中序序列的数值对应位置
|
||||
hash[v] = i
|
||||
}
|
||||
// 以左闭右闭的原则进行切分
|
||||
root := rebuild(inorder, postorder, len(postorder)-1, 0, len(inorder)-1)
|
||||
return root
|
||||
}
|
||||
func findRootIndex(inorder []int,target int) (index int){
|
||||
for i:=0;i<len(inorder);i++{
|
||||
if target==inorder[i]{
|
||||
return i
|
||||
}
|
||||
// rootIdx表示根节点在后序数组中的索引,l, r 表示在中序数组中的前后切分点
|
||||
func rebuild(inorder []int, postorder []int, rootIdx int, l, r int) *TreeNode {
|
||||
if l > r { // 说明没有元素,返回空树
|
||||
return nil
|
||||
}
|
||||
return -1
|
||||
if l == r { // 只剩唯一一个元素,直接返回
|
||||
return &TreeNode{Val : inorder[l]}
|
||||
}
|
||||
rootV := postorder[rootIdx] // 根据后序数组找到根节点的值
|
||||
rootIn := hash[rootV] // 找到根节点在对应的中序数组中的位置
|
||||
root := &TreeNode{Val : rootV} // 构造根节点
|
||||
// 重建左节点和右节点
|
||||
root.Left = rebuild(inorder, postorder, rootIdx-(r-rootIn)-1, l, rootIn-1)
|
||||
root.Right = rebuild(inorder, postorder, rootIdx-1, rootIn+1, r)
|
||||
return root
|
||||
}
|
||||
```
|
||||
|
||||
@ -761,22 +769,27 @@ func findRootIndex(inorder []int,target int) (index int){
|
||||
* Right *TreeNode
|
||||
* }
|
||||
*/
|
||||
var (
|
||||
hash map[int]int
|
||||
)
|
||||
func buildTree(preorder []int, inorder []int) *TreeNode {
|
||||
if len(preorder)<1||len(inorder)<1{return nil}
|
||||
left:=findRootIndex(preorder[0],inorder)
|
||||
root:=&TreeNode{
|
||||
Val: preorder[0],
|
||||
Left: buildTree(preorder[1:left+1],inorder[:left]),
|
||||
Right: buildTree(preorder[left+1:],inorder[left+1:])}
|
||||
hash = make(map[int]int, len(inorder))
|
||||
for i, v := range inorder {
|
||||
hash[v] = i
|
||||
}
|
||||
root := build(preorder, inorder, 0, 0, len(inorder)-1) // l, r 表示构造的树在中序遍历数组中的范围
|
||||
return root
|
||||
}
|
||||
func findRootIndex(target int,inorder []int) int{
|
||||
for i:=0;i<len(inorder);i++{
|
||||
if target==inorder[i]{
|
||||
return i
|
||||
}
|
||||
func build(pre []int, in []int, root int, l, r int) *TreeNode {
|
||||
if l > r {
|
||||
return nil
|
||||
}
|
||||
return -1
|
||||
rootVal := pre[root] // 找到本次构造的树的根节点
|
||||
index := hash[rootVal] // 根节点在中序数组中的位置
|
||||
node := &TreeNode {Val: rootVal}
|
||||
node.Left = build(pre, in, root + 1, l, index-1)
|
||||
node.Right = build(pre, in, root + (index-l) + 1, index+1, r)
|
||||
return node
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -35,7 +35,7 @@
|
||||
|
||||
因为只要给我们一个有序数组,如果强调平衡,都可以以线性结构来构造二叉搜索树。
|
||||
|
||||
例如 有序数组[-10,-3,0,5,9] 可以就可以构造成这样的二叉搜索树,如图。
|
||||
例如 有序数组[-10,-3,0,5,9] 就可以构造成这样的二叉搜索树,如图。
|
||||
|
||||

|
||||
|
||||
@ -147,7 +147,7 @@ public:
|
||||
};
|
||||
```
|
||||
|
||||
**注意:在调用traversal的时候为什么传入的left和right为什么是0和nums.size() - 1,因为定义的区间为左闭右闭**。
|
||||
**注意:在调用traversal的时候传入的left和right为什么是0和nums.size() - 1,因为定义的区间为左闭右闭**。
|
||||
|
||||
|
||||
## 迭代法
|
||||
@ -354,10 +354,15 @@ class Solution:
|
||||
|
||||
```go
|
||||
func sortedArrayToBST(nums []int) *TreeNode {
|
||||
if len(nums)==0{return nil}//终止条件,最后数组为空则可以返回
|
||||
root:=&TreeNode{nums[len(nums)/2],nil,nil}//按照BSL的特点,从中间构造节点
|
||||
root.Left=sortedArrayToBST(nums[:len(nums)/2])//数组的左边为左子树
|
||||
root.Right=sortedArrayToBST(nums[len(nums)/2+1:])//数字的右边为右子树
|
||||
if len(nums) == 0 { //终止条件,最后数组为空则可以返回
|
||||
return nil
|
||||
}
|
||||
idx := len(nums)/2
|
||||
root := &TreeNode{Val: nums[idx]}
|
||||
|
||||
root.Left = sortedArrayToBST(nums[:idx])
|
||||
root.Right = sortedArrayToBST(nums[idx+1:])
|
||||
|
||||
return root
|
||||
}
|
||||
```
|
||||
@ -384,33 +389,33 @@ var sortedArrayToBST = function (nums) {
|
||||
迭代
|
||||
```JavaScript
|
||||
var sortedArrayToBST = function(nums) {
|
||||
if(nums.length===0){
|
||||
if(nums.length===0) {
|
||||
return null;
|
||||
}
|
||||
let root=new TreeNode(0); //初始根节点
|
||||
let nodeQue=[root]; //放遍历的节点,并初始化
|
||||
let leftQue=[0]; //放左区间的下标,初始化
|
||||
let rightQue=[nums.length-1]; // 放右区间的下标
|
||||
let root = new TreeNode(0); //初始根节点
|
||||
let nodeQue = [root]; //放遍历的节点,并初始化
|
||||
let leftQue = [0]; //放左区间的下标,初始化
|
||||
let rightQue = [nums.length-1]; // 放右区间的下标
|
||||
|
||||
while(nodeQue.length){
|
||||
let curNode=nodeQue.pop();
|
||||
let left=leftQue.pop();
|
||||
let right=rightQue.pop();
|
||||
let mid=left+Math.floor((right-left)/2);
|
||||
while(nodeQue.length) {
|
||||
let curNode = nodeQue.pop();
|
||||
let left = leftQue.pop();
|
||||
let right = rightQue.pop();
|
||||
let mid = left + Math.floor((right-left)/2);
|
||||
|
||||
curNode.val=nums[mid]; //将下标为mid的元素给中间节点
|
||||
curNode.val = nums[mid]; //将下标为mid的元素给中间节点
|
||||
|
||||
// 处理左区间
|
||||
if(left<=mid-1){
|
||||
curNode.left=new TreeNode(0);
|
||||
if(left <= mid-1) {
|
||||
curNode.left = new TreeNode(0);
|
||||
nodeQue.push(curNode.left);
|
||||
leftQue.push(left);
|
||||
rightQue.push(mid-1);
|
||||
}
|
||||
|
||||
// 处理右区间
|
||||
if(right>=mid+1){
|
||||
curNode.right=new TreeNode(0);
|
||||
if(right >= mid+1) {
|
||||
curNode.right = new TreeNode(0);
|
||||
nodeQue.push(curNode.right);
|
||||
leftQue.push(mid+1);
|
||||
rightQue.push(right);
|
||||
|
@ -158,7 +158,7 @@ if (node == NULL) {
|
||||
|
||||
如何判断以当前传入节点为根节点的二叉树是否是平衡二叉树呢?当然是其左子树高度和其右子树高度的差值。
|
||||
|
||||
分别求出其左右子树的高度,然后如果差值小于等于1,则返回当前二叉树的高度,否则则返回-1,表示已经不是二叉平衡树了。
|
||||
分别求出其左右子树的高度,然后如果差值小于等于1,则返回当前二叉树的高度,否则返回-1,表示已经不是二叉平衡树了。
|
||||
|
||||
代码如下:
|
||||
|
||||
@ -342,7 +342,7 @@ public:
|
||||
|
||||
**例如:都知道回溯法其实就是递归,但是很少人用迭代的方式去实现回溯算法!**
|
||||
|
||||
因为对于回溯算法已经是非常复杂的递归了,如果在用迭代的话,就是自己给自己找麻烦,效率也并不一定高。
|
||||
因为对于回溯算法已经是非常复杂的递归了,如果再用迭代的话,就是自己给自己找麻烦,效率也并不一定高。
|
||||
|
||||
## 总结
|
||||
|
||||
@ -559,37 +559,32 @@ class Solution:
|
||||
### Go
|
||||
```Go
|
||||
func isBalanced(root *TreeNode) bool {
|
||||
if root==nil{
|
||||
return true
|
||||
}
|
||||
if !isBalanced(root.Left) || !isBalanced(root.Right){
|
||||
return false
|
||||
}
|
||||
LeftH:=maxdepth(root.Left)+1
|
||||
RightH:=maxdepth(root.Right)+1
|
||||
if abs(LeftH-RightH)>1{
|
||||
h := getHeight(root)
|
||||
if h == -1 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
func maxdepth(root *TreeNode)int{
|
||||
if root==nil{
|
||||
// 返回以该节点为根节点的二叉树的高度,如果不是平衡二叉树了则返回-1
|
||||
func getHeight(root *TreeNode) int {
|
||||
if root == nil {
|
||||
return 0
|
||||
}
|
||||
return max(maxdepth(root.Left),maxdepth(root.Right))+1
|
||||
l, r := getHeight(root.Left), getHeight(root.Right)
|
||||
if l == -1 || r == -1 {
|
||||
return -1
|
||||
}
|
||||
if l - r > 1 || r - l > 1 {
|
||||
return -1
|
||||
}
|
||||
return max(l, r) + 1
|
||||
}
|
||||
func max(a,b int)int{
|
||||
if a>b{
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
func abs(a int)int{
|
||||
if a<0{
|
||||
return -a
|
||||
}
|
||||
return a
|
||||
}
|
||||
```
|
||||
|
||||
### JavaScript
|
||||
@ -810,6 +805,38 @@ func getHeight(_ root: TreeNode?) -> Int {
|
||||
}
|
||||
```
|
||||
|
||||
### rust
|
||||
|
||||
递归
|
||||
|
||||
```rust
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
impl Solution {
|
||||
pub fn is_balanced(root: Option<Rc<RefCell<TreeNode>>>) -> bool {
|
||||
Self::get_depth(root) != -1
|
||||
}
|
||||
pub fn get_depth(root: Option<Rc<RefCell<TreeNode>>>) -> i32 {
|
||||
if root.is_none() {
|
||||
return 0;
|
||||
}
|
||||
let right = Self::get_depth(root.as_ref().unwrap().borrow().left.clone());
|
||||
let left = Self::get_depth(root.unwrap().borrow().right.clone());
|
||||
if right == -1 {
|
||||
return -1;
|
||||
}
|
||||
if left == -1 {
|
||||
return -1;
|
||||
}
|
||||
if (right - left).abs() > 1 {
|
||||
return -1;
|
||||
}
|
||||
|
||||
1 + right.max(left)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<p align="center">
|
||||
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
|
||||
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>
|
||||
|
@ -39,7 +39,7 @@
|
||||
* 二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数或者节点数(取决于深度从0开始还是从1开始)
|
||||
* 二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数后者节点数(取决于高度从0开始还是从1开始)
|
||||
|
||||
那么使用后序遍历,其实求的是根节点到叶子节点的最小距离,就是求高度的过程,这不过这个最小距离 也同样是最小深度。
|
||||
那么使用后序遍历,其实求的是根节点到叶子节点的最小距离,就是求高度的过程,不过这个最小距离 也同样是最小深度。
|
||||
|
||||
以下讲解中遍历顺序上依然采用后序遍历(因为要比较递归返回之后的结果,本文我也给出前序遍历的写法)。
|
||||
|
||||
@ -199,7 +199,7 @@ public:
|
||||
|
||||
如果对层序遍历还不清楚的话,可以看这篇:[二叉树:层序遍历登场!](https://programmercarl.com/0102.二叉树的层序遍历.html)
|
||||
|
||||
**需要注意的是,只有当左右孩子都为空的时候,才说明遍历的最低点了。如果其中一个孩子为空则不是最低点**
|
||||
**需要注意的是,只有当左右孩子都为空的时候,才说明遍历到最低点了。如果其中一个孩子不为空则不是最低点**
|
||||
|
||||
代码如下:(详细注释)
|
||||
|
||||
|
@ -33,7 +33,7 @@
|
||||
* 112.路径总和
|
||||
* 113.路径总和ii
|
||||
|
||||
这道题我们要遍历从根节点到叶子节点的的路径看看总和是不是目标和。
|
||||
这道题我们要遍历从根节点到叶子节点的路径看看总和是不是目标和。
|
||||
|
||||
### 递归
|
||||
|
||||
@ -167,7 +167,7 @@ public:
|
||||
};
|
||||
```
|
||||
|
||||
**是不是发现精简之后的代码,已经完全看不出分析的过程了,所以我们要把题目分析清楚之后,在追求代码精简。** 这一点我已经强调很多次了!
|
||||
**是不是发现精简之后的代码,已经完全看不出分析的过程了,所以我们要把题目分析清楚之后,再追求代码精简。** 这一点我已经强调很多次了!
|
||||
|
||||
|
||||
### 迭代
|
||||
@ -316,13 +316,13 @@ class solution {
|
||||
}
|
||||
if (root.left != null) {
|
||||
boolean left = haspathsum(root.left, targetsum);
|
||||
if (left) {// 已经找到
|
||||
if (left) { // 已经找到
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (root.right != null) {
|
||||
boolean right = haspathsum(root.right, targetsum);
|
||||
if (right) {// 已经找到
|
||||
if (right) { // 已经找到
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -348,31 +348,37 @@ class solution {
|
||||
```java
|
||||
class solution {
|
||||
public boolean haspathsum(treenode root, int targetsum) {
|
||||
if(root==null)return false;
|
||||
if(root == null) return false;
|
||||
stack<treenode> stack1 = new stack<>();
|
||||
stack<integer> stack2 = new stack<>();
|
||||
stack1.push(root);stack2.push(root.val);
|
||||
while(!stack1.isempty()){
|
||||
stack1.push(root);
|
||||
stack2.push(root.val);
|
||||
while(!stack1.isempty()) {
|
||||
int size = stack1.size();
|
||||
for(int i=0;i<size;i++){
|
||||
treenode node = stack1.pop();int sum=stack2.pop();
|
||||
|
||||
for(int i = 0; i < size; i++) {
|
||||
treenode node = stack1.pop();
|
||||
int sum = stack2.pop();
|
||||
|
||||
// 如果该节点是叶子节点了,同时该节点的路径数值等于sum,那么就返回true
|
||||
if(node.left==null && node.right==null && sum==targetsum)return true;
|
||||
if(node.left == null && node.right == null && sum == targetsum) {
|
||||
return true;
|
||||
}
|
||||
// 右节点,压进去一个节点的时候,将该节点的路径数值也记录下来
|
||||
if(node.right!=null){
|
||||
stack1.push(node.right);stack2.push(sum+node.right.val);
|
||||
if(node.right != null){
|
||||
stack1.push(node.right);
|
||||
stack2.push(sum + node.right.val);
|
||||
}
|
||||
// 左节点,压进去一个节点的时候,将该节点的路径数值也记录下来
|
||||
if(node.left!=null){
|
||||
stack1.push(node.left);stack2.push(sum+node.left.val);
|
||||
if(node.left != null) {
|
||||
stack1.push(node.left);
|
||||
stack2.push(sum + node.left.val);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### 0113.路径总和-ii
|
||||
@ -715,21 +721,21 @@ var pathsum = function(root, targetsum) {
|
||||
//递归方法
|
||||
let respath = [],curpath = [];
|
||||
// 1. 确定递归函数参数
|
||||
const traveltree = function(node,count){
|
||||
const traveltree = function(node,count) {
|
||||
curpath.push(node.val);
|
||||
count-=node.val;
|
||||
if(node.left===null&&node.right===null&&count===0){
|
||||
count -= node.val;
|
||||
if(node.left === null && node.right === null && count === 0) {
|
||||
respath.push([...curpath]);
|
||||
}
|
||||
node.left&&traveltree(node.left,count);
|
||||
node.right&&traveltree(node.right,count);
|
||||
node.left && traveltree(node.left, count);
|
||||
node.right && traveltree(node.right, count);
|
||||
let cur = curpath.pop();
|
||||
count-=cur;
|
||||
count -= cur;
|
||||
}
|
||||
if(root===null){
|
||||
if(root === null) {
|
||||
return respath;
|
||||
}
|
||||
travelTree(root,targetSum);
|
||||
travelTree(root, targetSum);
|
||||
return resPath;
|
||||
};
|
||||
```
|
||||
@ -1213,6 +1219,111 @@ object Solution {
|
||||
}
|
||||
```
|
||||
|
||||
## rust
|
||||
|
||||
### 112.路径总和.md
|
||||
|
||||
递归:
|
||||
|
||||
```rust
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
impl Solution {
|
||||
pub fn has_path_sum(root: Option<Rc<RefCell<TreeNode>>>, target_sum: i32) -> bool {
|
||||
if root.is_none() {
|
||||
return false;
|
||||
}
|
||||
let node = root.unwrap();
|
||||
if node.borrow().left.is_none() && node.borrow().right.is_none() {
|
||||
return node.borrow().val == target_sum;
|
||||
}
|
||||
return Self::has_path_sum(node.borrow().left.clone(), target_sum - node.borrow().val)
|
||||
|| Self::has_path_sum(node.borrow().right.clone(), target_sum - node.borrow().val);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
迭代:
|
||||
|
||||
```rust
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
impl Solution {
|
||||
pub fn has_path_sum(root: Option<Rc<RefCell<TreeNode>>>, target_sum: i32) -> bool {
|
||||
let mut stack = vec![];
|
||||
if let Some(node) = root {
|
||||
stack.push((node.borrow().val, node.to_owned()));
|
||||
}
|
||||
while !stack.is_empty() {
|
||||
let (value, node) = stack.pop().unwrap();
|
||||
if node.borrow().left.is_none() && node.borrow().right.is_none() && value == target_sum
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if node.borrow().left.is_some() {
|
||||
if let Some(r) = node.borrow().left.as_ref() {
|
||||
stack.push((r.borrow().val + value, r.to_owned()));
|
||||
}
|
||||
}
|
||||
if node.borrow().right.is_some() {
|
||||
if let Some(r) = node.borrow().right.as_ref() {
|
||||
stack.push((r.borrow().val + value, r.to_owned()));
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
```
|
||||
|
||||
### 113.路径总和-ii
|
||||
|
||||
```rust
|
||||
impl Solution {
|
||||
pub fn path_sum(root: Option<Rc<RefCell<TreeNode>>>, target_sum: i32) -> Vec<Vec<i32>> {
|
||||
let mut res = vec![];
|
||||
let mut route = vec![];
|
||||
if root.is_none() {
|
||||
return res;
|
||||
} else {
|
||||
route.push(root.as_ref().unwrap().borrow().val);
|
||||
}
|
||||
Self::recur(
|
||||
&root,
|
||||
target_sum - root.as_ref().unwrap().borrow().val,
|
||||
&mut res,
|
||||
&mut route,
|
||||
);
|
||||
res
|
||||
}
|
||||
|
||||
pub fn recur(
|
||||
root: &Option<Rc<RefCell<TreeNode>>>,
|
||||
sum: i32,
|
||||
res: &mut Vec<Vec<i32>>,
|
||||
route: &mut Vec<i32>,
|
||||
) {
|
||||
let node = root.as_ref().unwrap().borrow();
|
||||
if node.left.is_none() && node.right.is_none() && sum == 0 {
|
||||
res.push(route.to_vec());
|
||||
return;
|
||||
}
|
||||
if node.left.is_some() {
|
||||
let left = node.left.as_ref().unwrap();
|
||||
route.push(left.borrow().val);
|
||||
Self::recur(&node.left, sum - left.borrow().val, res, route);
|
||||
route.pop();
|
||||
}
|
||||
if node.right.is_some() {
|
||||
let right = node.right.as_ref().unwrap();
|
||||
route.push(right.borrow().val);
|
||||
Self::recur(&node.right, sum - right.borrow().val, res, route);
|
||||
route.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
<p align="center">
|
||||
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
|
||||
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>
|
||||
|
@ -43,8 +43,8 @@
|
||||
|
||||
例如对于字符串abcdef:
|
||||
|
||||
* 组合问题:选取一个a之后,在bcdef中再去选取第二个,选取b之后在cdef中在选组第三个.....。
|
||||
* 切割问题:切割一个a之后,在bcdef中再去切割第二段,切割b之后在cdef中在切割第三段.....。
|
||||
* 组合问题:选取一个a之后,在bcdef中再去选取第二个,选取b之后在cdef中再选取第三个.....。
|
||||
* 切割问题:切割一个a之后,在bcdef中再去切割第二段,切割b之后在cdef中再切割第三段.....。
|
||||
|
||||
感受出来了不?
|
||||
|
||||
@ -78,7 +78,7 @@ void backtracking (const string& s, int startIndex) {
|
||||
|
||||

|
||||
|
||||
从树形结构的图中可以看出:切割线切到了字符串最后面,说明找到了一种切割方法,此时就是本层递归的终止终止条件。
|
||||
从树形结构的图中可以看出:切割线切到了字符串最后面,说明找到了一种切割方法,此时就是本层递归的终止条件。
|
||||
|
||||
**那么在代码里什么是切割线呢?**
|
||||
|
||||
@ -98,7 +98,7 @@ void backtracking (const string& s, int startIndex) {
|
||||
|
||||
* 单层搜索的逻辑
|
||||
|
||||
**来看看在递归循环,中如何截取子串呢?**
|
||||
**来看看在递归循环中如何截取子串呢?**
|
||||
|
||||
在`for (int i = startIndex; i < s.size(); i++)`循环中,我们 定义了起始位置startIndex,那么 [startIndex, i] 就是要截取的子串。
|
||||
|
||||
@ -126,7 +126,7 @@ for (int i = startIndex; i < s.size(); i++) {
|
||||
|
||||
最后我们看一下回文子串要如何判断了,判断一个字符串是否是回文。
|
||||
|
||||
可以使用双指针法,一个指针从前向后,一个指针从后先前,如果前后指针所指向的元素是相等的,就是回文字符串了。
|
||||
可以使用双指针法,一个指针从前向后,一个指针从后向前,如果前后指针所指向的元素是相等的,就是回文字符串了。
|
||||
|
||||
那么判断回文的C++代码如下:
|
||||
|
||||
@ -295,7 +295,7 @@ public:
|
||||
|
||||
除了这些难点,**本题还有细节,例如:切割过的地方不能重复切割所以递归函数需要传入i + 1**。
|
||||
|
||||
所以本题应该是一个道hard题目了。
|
||||
所以本题应该是一道hard题目了。
|
||||
|
||||
**可能刷过这道题目的录友都没感受到自己原来克服了这么多难点,就把这道题目AC了**,这应该叫做无招胜有招,人码合一,哈哈哈。
|
||||
|
||||
@ -432,45 +432,39 @@ class Solution:
|
||||
```
|
||||
|
||||
## Go
|
||||
**注意切片(go切片是披着值类型外衣的引用类型)**
|
||||
```go
|
||||
var (
|
||||
path []string // 放已经回文的子串
|
||||
res [][]string
|
||||
)
|
||||
func partition(s string) [][]string {
|
||||
var tmpString []string//切割字符串集合
|
||||
var res [][]string//结果集合
|
||||
backTracking(s,tmpString,0,&res)
|
||||
path, res = make([]string, 0), make([][]string, 0)
|
||||
dfs(s, 0)
|
||||
return res
|
||||
}
|
||||
func backTracking(s string,tmpString []string,startIndex int,res *[][]string){
|
||||
if startIndex==len(s){//到达字符串末尾了
|
||||
//进行一次切片拷贝,怕之后的操作影响tmpString切片内的值
|
||||
t := make([]string, len(tmpString))
|
||||
copy(t, tmpString)
|
||||
*res=append(*res,t)
|
||||
|
||||
func dfs(s string, start int) {
|
||||
if start == len(s) { // 如果起始位置等于s的大小,说明已经找到了一组分割方案了
|
||||
tmp := make([]string, len(path))
|
||||
copy(tmp, path)
|
||||
res = append(res, tmp)
|
||||
return
|
||||
}
|
||||
for i:=startIndex;i<len(s);i++{
|
||||
//处理(首先通过startIndex和i判断切割的区间,进而判断该区间的字符串是否为回文,若为回文,则加入到tmpString,否则继续后移,找到回文区间)(这里为一层处理)
|
||||
if isPartition(s,startIndex,i){
|
||||
tmpString=append(tmpString,s[startIndex:i+1])
|
||||
}else{
|
||||
continue
|
||||
for i := start; i < len(s); i++ {
|
||||
str := s[start : i+1]
|
||||
if isPalindrome(str) { // 是回文子串
|
||||
path = append(path, str)
|
||||
dfs(s, i+1) // 寻找i+1为起始位置的子串
|
||||
path = path[:len(path)-1] // 回溯过程,弹出本次已经填在的子串
|
||||
}
|
||||
//递归
|
||||
backTracking(s,tmpString,i+1,res)
|
||||
//回溯
|
||||
tmpString=tmpString[:len(tmpString)-1]
|
||||
}
|
||||
}
|
||||
//判断是否为回文
|
||||
func isPartition(s string,startIndex,end int)bool{
|
||||
left:=startIndex
|
||||
right:=end
|
||||
for ;left<right;{
|
||||
if s[left]!=s[right]{
|
||||
|
||||
func isPalindrome(s string) bool {
|
||||
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
|
||||
if s[i] != s[j] {
|
||||
return false
|
||||
}
|
||||
//移动左右指针
|
||||
left++
|
||||
right--
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
@ -36,7 +36,7 @@
|
||||
|
||||
想到这一点了,做过[77. 组合](https://programmercarl.com/0077.组合.html)之后,本题是简单一些了。
|
||||
|
||||
本题k相当于了树的深度,9(因为整个集合就是9个数)就是树的宽度。
|
||||
本题k相当于树的深度,9(因为整个集合就是9个数)就是树的宽度。
|
||||
|
||||
例如 k = 2,n = 4的话,就是在集合[1,2,3,4,5,6,7,8,9]中求 k(个数) = 2, n(和) = 4的组合。
|
||||
|
||||
@ -380,29 +380,32 @@ class Solution:
|
||||
回溯+减枝
|
||||
|
||||
```go
|
||||
var (
|
||||
res [][]int
|
||||
path []int
|
||||
)
|
||||
func combinationSum3(k int, n int) [][]int {
|
||||
var track []int// 遍历路径
|
||||
var result [][]int// 存放结果集
|
||||
backTree(n,k,1,&track,&result)
|
||||
return result
|
||||
res, path = make([][]int, 0), make([]int, 0, k)
|
||||
dfs(k, n, 1, 0)
|
||||
return res
|
||||
}
|
||||
func backTree(n,k,startIndex int,track *[]int,result *[][]int){
|
||||
if len(*track)==k{
|
||||
var sum int
|
||||
tmp:=make([]int,k)
|
||||
for k,v:=range *track{
|
||||
sum+=v
|
||||
tmp[k]=v
|
||||
}
|
||||
if sum==n{
|
||||
*result=append(*result,tmp)
|
||||
|
||||
func dfs(k, n int, start int, sum int) {
|
||||
if len(path) == k {
|
||||
if sum == n {
|
||||
tmp := make([]int, k)
|
||||
copy(tmp, path)
|
||||
res = append(res, tmp)
|
||||
}
|
||||
return
|
||||
}
|
||||
for i:=startIndex;i<=9-(k-len(*track))+1;i++{//减枝(k-len(*track)表示还剩多少个可填充的元素)
|
||||
*track=append(*track,i)//记录路径
|
||||
backTree(n,k,i+1,track,result)//递归
|
||||
*track=(*track)[:len(*track)-1]//回溯
|
||||
for i := start; i <= 9; i++ {
|
||||
if sum + i > n || 9-i+1 < k-len(path) {
|
||||
break
|
||||
}
|
||||
path = append(path, i)
|
||||
dfs(k, n, i+1, sum+i)
|
||||
path = path[:len(path)-1]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -63,7 +63,7 @@ int getNodesNum(TreeNode* cur) {
|
||||
if (cur == NULL) return 0;
|
||||
```
|
||||
|
||||
3. 确定单层递归的逻辑:先求它的左子树的节点数量,再求的右子树的节点数量,最后取总和再加一 (加1是因为算上当前中间节点)就是目前节点为根节点的节点数量。
|
||||
3. 确定单层递归的逻辑:先求它的左子树的节点数量,再求右子树的节点数量,最后取总和再加一 (加1是因为算上当前中间节点)就是目前节点为根节点的节点数量。
|
||||
|
||||
代码如下:
|
||||
|
||||
@ -168,7 +168,7 @@ public:
|
||||
|
||||
可以看出如果整个树不是满二叉树,就递归其左右孩子,直到遇到满二叉树为止,用公式计算这个子树(满二叉树)的节点数量。
|
||||
|
||||
这里关键在于如果去判断一个左子树或者右子树是不是满二叉树呢?
|
||||
这里关键在于如何去判断一个左子树或者右子树是不是满二叉树呢?
|
||||
|
||||
在完全二叉树中,如果递归向左遍历的深度等于递归向右遍历的深度,那说明就是满二叉树。如图:
|
||||
|
||||
@ -178,13 +178,13 @@ public:
|
||||
|
||||

|
||||
|
||||
哪有录友说了,这种情况,递归向左遍历的深度等于递归向右遍历的深度,但也不是满二叉树,如题:
|
||||
那有录友说了,这种情况,递归向左遍历的深度等于递归向右遍历的深度,但也不是满二叉树,如题:
|
||||
|
||||

|
||||
|
||||
如果这么想,大家就是对 完全二叉树理解有误区了,**以上这棵二叉树,它根本就不是一个完全二叉树**!
|
||||
|
||||
判断其子树岂不是满二叉树,如果是则利用用公式计算这个子树(满二叉树)的节点数量,如果不是则继续递归,那么 在递归三部曲中,第二部:终止条件的写法应该是这样的:
|
||||
判断其子树是不是满二叉树,如果是则利用公式计算这个子树(满二叉树)的节点数量,如果不是则继续递归,那么 在递归三部曲中,第二部:终止条件的写法应该是这样的:
|
||||
|
||||
```CPP
|
||||
if (root == nullptr) return 0;
|
||||
@ -292,26 +292,22 @@ class Solution {
|
||||
* 满二叉树的结点数为:2^depth - 1
|
||||
*/
|
||||
public int countNodes(TreeNode root) {
|
||||
if(root == null) {
|
||||
return 0;
|
||||
if (root == null) return 0;
|
||||
TreeNode left = root.left;
|
||||
TreeNode right = root.right;
|
||||
int leftDepth = 0, rightDepth = 0; // 这里初始为0是有目的的,为了下面求指数方便
|
||||
while (left != null) { // 求左子树深度
|
||||
left = left.left;
|
||||
leftDepth++;
|
||||
}
|
||||
int leftDepth = getDepth(root.left);
|
||||
int rightDepth = getDepth(root.right);
|
||||
if (leftDepth == rightDepth) {// 左子树是满二叉树
|
||||
// 2^leftDepth其实是 (2^leftDepth - 1) + 1 ,左子树 + 根结点
|
||||
return (1 << leftDepth) + countNodes(root.right);
|
||||
} else {// 右子树是满二叉树
|
||||
return (1 << rightDepth) + countNodes(root.left);
|
||||
while (right != null) { // 求右子树深度
|
||||
right = right.right;
|
||||
rightDepth++;
|
||||
}
|
||||
}
|
||||
|
||||
private int getDepth(TreeNode root) {
|
||||
int depth = 0;
|
||||
while (root != null) {
|
||||
root = root.left;
|
||||
depth++;
|
||||
if (leftDepth == rightDepth) {
|
||||
return (2 << leftDepth) - 1; // 注意(2<<1) 相当于2^2,所以leftDepth初始为0
|
||||
}
|
||||
return depth;
|
||||
return countNodes(root.left) + countNodes(root.right) + 1;
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -397,7 +393,7 @@ class Solution:
|
||||
* Right *TreeNode
|
||||
* }
|
||||
*/
|
||||
//本题直接就是求有多少个节点,无脑存进数组算长度就行了。
|
||||
//本题直接就是求有多少个节点,无脑存进结果变量就行了。
|
||||
func countNodes(root *TreeNode) int {
|
||||
if root == nil {
|
||||
return 0
|
||||
@ -473,15 +469,15 @@ func countNodes(root *TreeNode) int {
|
||||
var countNodes = function(root) {
|
||||
//递归法计算二叉树节点数
|
||||
// 1. 确定递归函数参数
|
||||
const getNodeSum=function(node){
|
||||
const getNodeSum = function(node) {
|
||||
//2. 确定终止条件
|
||||
if(node===null){
|
||||
if(node === null) {
|
||||
return 0;
|
||||
}
|
||||
//3. 确定单层递归逻辑
|
||||
let leftNum=getNodeSum(node.left);
|
||||
let rightNum=getNodeSum(node.right);
|
||||
return leftNum+rightNum+1;
|
||||
let leftNum = getNodeSum(node.left);
|
||||
let rightNum = getNodeSum(node.right);
|
||||
return leftNum + rightNum + 1;
|
||||
}
|
||||
return getNodeSum(root);
|
||||
};
|
||||
@ -491,19 +487,19 @@ var countNodes = function(root) {
|
||||
```javascript
|
||||
var countNodes = function(root) {
|
||||
//层序遍历
|
||||
let queue=[];
|
||||
if(root===null){
|
||||
let queue = [];
|
||||
if(root === null) {
|
||||
return 0;
|
||||
}
|
||||
queue.push(root);
|
||||
let nodeNums=0;
|
||||
while(queue.length){
|
||||
let length=queue.length;
|
||||
while(length--){
|
||||
let node=queue.shift();
|
||||
let nodeNums = 0;
|
||||
while(queue.length) {
|
||||
let length = queue.length;
|
||||
while(length--) {
|
||||
let node = queue.shift();
|
||||
nodeNums++;
|
||||
node.left&&queue.push(node.left);
|
||||
node.right&&queue.push(node.right);
|
||||
node.left && queue.push(node.left);
|
||||
node.right && queue.push(node.right);
|
||||
}
|
||||
}
|
||||
return nodeNums;
|
||||
@ -514,24 +510,24 @@ var countNodes = function(root) {
|
||||
```javascript
|
||||
var countNodes = function(root) {
|
||||
//利用完全二叉树的特点
|
||||
if(root===null){
|
||||
if(root === null) {
|
||||
return 0;
|
||||
}
|
||||
let left=root.left;
|
||||
let right=root.right;
|
||||
let leftDepth=0,rightDepth=0;
|
||||
while(left){
|
||||
left=left.left;
|
||||
let left = root.left;
|
||||
let right = root.right;
|
||||
let leftDepth = 0, rightDepth = 0;
|
||||
while(left) {
|
||||
left = left.left;
|
||||
leftDepth++;
|
||||
}
|
||||
while(right){
|
||||
right=right.right;
|
||||
while(right) {
|
||||
right = right.right;
|
||||
rightDepth++;
|
||||
}
|
||||
if(leftDepth==rightDepth){
|
||||
return Math.pow(2,leftDepth+1)-1;
|
||||
if(leftDepth == rightDepth) {
|
||||
return Math.pow(2, leftDepth+1) - 1;
|
||||
}
|
||||
return countNodes(root.left)+countNodes(root.right)+1;
|
||||
return countNodes(root.left) + countNodes(root.right) + 1;
|
||||
};
|
||||
```
|
||||
|
||||
|
@ -60,12 +60,12 @@
|
||||
|
||||
而递归遍历顺序,本题就不涉及到 前中后序了(这里没有中节点的处理逻辑,遍历顺序无所谓了)。
|
||||
|
||||
如图所示:p为节点3,q为节点5
|
||||
如图所示:p为节点6,q为节点9
|
||||
|
||||

|
||||
|
||||
|
||||
可以看出直接按照指定的方向,就可以找到节点4,为最近公共祖先,而且不需要遍历整棵树,找到结果直接返回!
|
||||
可以看出直接按照指定的方向,就可以找到节点8,为最近公共祖先,而且不需要遍历整棵树,找到结果直接返回!
|
||||
|
||||
## 递归法
|
||||
|
||||
@ -303,14 +303,22 @@ class Solution:
|
||||
|
||||
递归法:
|
||||
```go
|
||||
//利用BSL的性质(前序遍历有序)
|
||||
func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
|
||||
if root==nil{return nil}
|
||||
if root.Val>p.Val&&root.Val>q.Val{//当前节点的值大于给定的值,则说明满足条件的在左边
|
||||
return lowestCommonAncestor(root.Left,p,q)
|
||||
}else if root.Val<p.Val&&root.Val<q.Val{//当前节点的值小于各点的值,则说明满足条件的在右边
|
||||
return lowestCommonAncestor(root.Right,p,q)
|
||||
}else {return root}//当前节点的值在给定值的中间(或者等于),即为最深的祖先
|
||||
if root == nil {
|
||||
return nil
|
||||
}
|
||||
for {
|
||||
if root.Val > p.Val && root.Val > q.Val {
|
||||
root = root.Left
|
||||
}
|
||||
if root.Val < p.Val && root.Val < q.Val {
|
||||
root = root.Right
|
||||
}
|
||||
if (root.Val - p.Val) * (root.Val - q.Val) <= 0 {
|
||||
return root
|
||||
}
|
||||
}
|
||||
return root
|
||||
}
|
||||
```
|
||||
|
||||
@ -326,11 +334,11 @@ var lowestCommonAncestor = function(root, p, q) {
|
||||
if(root === null) {
|
||||
return root;
|
||||
}
|
||||
if(root.val>p.val&&root.val>q.val) {
|
||||
if(root.val > p.val && root.val > q.val) {
|
||||
// 向左子树查询
|
||||
return root.left = lowestCommonAncestor(root.left,p,q);
|
||||
}
|
||||
if(root.val<p.val&&root.val<q.val) {
|
||||
if(root.val < p.val && root.val < q.val) {
|
||||
// 向右子树查询
|
||||
return root.right = lowestCommonAncestor(root.right,p,q);
|
||||
}
|
||||
@ -343,9 +351,9 @@ var lowestCommonAncestor = function(root, p, q) {
|
||||
var lowestCommonAncestor = function(root, p, q) {
|
||||
// 使用迭代的方法
|
||||
while(root) {
|
||||
if(root.val>p.val&&root.val>q.val) {
|
||||
if(root.val > p.val && root.val > q.val) {
|
||||
root = root.left;
|
||||
}else if(root.val<p.val&&root.val<q.val) {
|
||||
}else if(root.val < p.val && root.val < q.val) {
|
||||
root = root.right;
|
||||
}else {
|
||||
return root;
|
||||
|
@ -48,7 +48,7 @@
|
||||
|
||||
后序遍历(左右中)就是天然的回溯过程,可以根据左右子树的返回值,来处理中节点的逻辑。
|
||||
|
||||
接下来就看如何判断一个节点是节点q和节点p的公共公共祖先呢。
|
||||
接下来就看如何判断一个节点是节点q和节点p的公共祖先呢。
|
||||
|
||||
**首先最容易想到的一个情况:如果找到一个节点,发现左子树出现结点p,右子树出现节点q,或者 左子树出现结点q,右子树出现节点p,那么该节点就是节点p和q的最近公共祖先。** 即情况一:
|
||||
|
||||
@ -66,9 +66,9 @@
|
||||
|
||||
其实情况一 和 情况二 代码实现过程都是一样的,也可以说,实现情况一的逻辑,顺便包含了情况二。
|
||||
|
||||
因为遇到 q 或者 p 就返回,这样也包含了 q 或者 p 本省就是 公共祖先的情况。
|
||||
因为遇到 q 或者 p 就返回,这样也包含了 q 或者 p 本身就是 公共祖先的情况。
|
||||
|
||||
这一点是很多录友容易忽略的,在下面的代码讲解中,可以在去体会。
|
||||
这一点是很多录友容易忽略的,在下面的代码讲解中,可以再去体会。
|
||||
|
||||
递归三部曲:
|
||||
|
||||
@ -86,9 +86,9 @@ TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q)
|
||||
|
||||
* 确定终止条件
|
||||
|
||||
遇到空的话,然后然后空,因为树都是空了,所以返回空。
|
||||
遇到空的话,因为树都是空了,所以返回空。
|
||||
|
||||
那么我们来说一说,如果 root == q,或者 root == p,说明找到 q p ,则将其返回,这个返回值,后面在中节点的处理过程中会用到,那么中节点处理逻辑,后下面讲解。
|
||||
那么我们来说一说,如果 root == q,或者 root == p,说明找到 q p ,则将其返回,这个返回值,后面在中节点的处理过程中会用到,那么中节点的处理逻辑,下面讲解。
|
||||
|
||||
代码如下:
|
||||
|
||||
@ -167,7 +167,7 @@ TreeNode* right = lowestCommonAncestor(root->right, p, q);
|
||||
|
||||
图中节点10的左子树返回null,右子树返回目标值7,那么此时节点10的处理逻辑就是把右子树的返回值(最近公共祖先7)返回上去!
|
||||
|
||||
这里点也很重要,可能刷过这道题目的同学,都不清楚结果究竟是如何从底层一层一层传到头结点的。
|
||||
这里也很重要,可能刷过这道题目的同学,都不清楚结果究竟是如何从底层一层一层传到头结点的。
|
||||
|
||||
那么如果left和right都为空,则返回left或者right都是可以的,也就是返回空。
|
||||
|
||||
@ -231,7 +231,7 @@ public:
|
||||
|
||||
**那么我给大家归纳如下三点**:
|
||||
|
||||
1. 求最小公共祖先,需要从底向上遍历,那么二叉树,只能通过后序遍历(即:回溯)实现从低向上的遍历方式。
|
||||
1. 求最小公共祖先,需要从底向上遍历,那么二叉树,只能通过后序遍历(即:回溯)实现从底向上的遍历方式。
|
||||
|
||||
2. 在回溯的过程中,必然要遍历整棵二叉树,即使已经找到结果了,依然要把其他节点遍历完,因为要使用递归函数的返回值(也就是代码中的left和right)做逻辑判断。
|
||||
|
||||
@ -332,16 +332,16 @@ var lowestCommonAncestor = function(root, p, q) {
|
||||
// 1. 确定递归的函数
|
||||
const travelTree = function(root,p,q) {
|
||||
// 2. 确定递归终止条件
|
||||
if(root === null || root === p||root === q) {
|
||||
if(root === null || root === p || root === q) {
|
||||
return root;
|
||||
}
|
||||
// 3. 确定递归单层逻辑
|
||||
let left = travelTree(root.left,p,q);
|
||||
let right = travelTree(root.right,p,q);
|
||||
if(left !== null&&right !== null) {
|
||||
if(left !== null && right !== null) {
|
||||
return root;
|
||||
}
|
||||
if(left ===null) {
|
||||
if(left === null) {
|
||||
return right;
|
||||
}
|
||||
return left;
|
||||
|
@ -24,7 +24,7 @@
|
||||
|
||||
这道题目要求从根节点到叶子的路径,所以需要前序遍历,这样才方便让父节点指向孩子节点,找到对应的路径。
|
||||
|
||||
在这道题目中将第一次涉及到回溯,因为我们要把路径记录下来,需要回溯来回退一一个路径在进入另一个路径。
|
||||
在这道题目中将第一次涉及到回溯,因为我们要把路径记录下来,需要回溯来回退一个路径再进入另一个路径。
|
||||
|
||||
前序遍历以及回溯的过程如图:
|
||||
|
||||
@ -44,7 +44,7 @@ void traversal(TreeNode* cur, vector<int>& path, vector<string>& result)
|
||||
|
||||
2. 确定递归终止条件
|
||||
|
||||
再写递归的时候都习惯了这么写:
|
||||
在写递归的时候都习惯了这么写:
|
||||
|
||||
```
|
||||
if (cur == NULL) {
|
||||
@ -67,7 +67,7 @@ if (cur->left == NULL && cur->right == NULL) {
|
||||
|
||||
再来看一下终止处理的逻辑。
|
||||
|
||||
这里使用vector<int> 结构path来记录路径,所以要把vector<int> 结构的path转为string格式,在把这个string 放进 result里。
|
||||
这里使用vector<int> 结构path来记录路径,所以要把vector<int> 结构的path转为string格式,再把这个string 放进 result里。
|
||||
|
||||
**那么为什么使用了vector<int> 结构来记录路径呢?** 因为在下面处理单层递归逻辑的时候,要做回溯,使用vector方便来做回溯。
|
||||
|
||||
@ -123,7 +123,7 @@ if (cur->right) {
|
||||
path.pop_back();
|
||||
```
|
||||
|
||||
这个回溯就要很大的问题,我们知道,**回溯和递归是一一对应的,有一个递归,就要有一个回溯**,这么写的话相当于把递归和回溯拆开了, 一个在花括号里,一个在花括号外。
|
||||
这个回溯就有很大的问题,我们知道,**回溯和递归是一一对应的,有一个递归,就要有一个回溯**,这么写的话相当于把递归和回溯拆开了, 一个在花括号里,一个在花括号外。
|
||||
|
||||
**所以回溯要和递归永远在一起,世界上最遥远的距离是你在花括号里,而我在花括号外!**
|
||||
|
||||
@ -300,16 +300,16 @@ public:
|
||||
|
||||
```
|
||||
|
||||
**大家应该可以感受出来,如果把 `path + "->"`作为函数参数就是可以的,因为并有没有改变path的数值,执行完递归函数之后,path依然是之前的数值(相当于回溯了)**
|
||||
**大家应该可以感受出来,如果把 `path + "->"`作为函数参数就是可以的,因为并没有改变path的数值,执行完递归函数之后,path依然是之前的数值(相当于回溯了)**
|
||||
|
||||
|
||||
**综合以上,第二种递归的代码虽然精简但把很多重要的点隐藏在了代码细节里,第一种递归写法虽然代码多一些,但是把每一个逻辑处理都完整的展现了出来了。**
|
||||
**综合以上,第二种递归的代码虽然精简但把很多重要的点隐藏在了代码细节里,第一种递归写法虽然代码多一些,但是把每一个逻辑处理都完整的展现出来了。**
|
||||
|
||||
## 拓展
|
||||
|
||||
这里讲解本题解的写法逻辑以及一些更具体的细节,下面的讲解中,涉及到C++语法特性,如果不是C++的录友,就可以不看了,避免越看越晕。
|
||||
|
||||
如果是C++的录友,建议本题独立刷过两遍,在看下面的讲解,同样避免越看越晕,造成不必要的负担。
|
||||
如果是C++的录友,建议本题独立刷过两遍,再看下面的讲解,同样避免越看越晕,造成不必要的负担。
|
||||
|
||||
在第二版本的代码中,其实仅仅是回溯了 `->` 部分(调用两次pop_back,一个pop`>` 一次pop`-`),大家应该疑惑那么 `path += to_string(cur->val);` 这一步为什么没有回溯呢? 一条路径能持续加节点 不做回溯吗?
|
||||
|
||||
@ -378,7 +378,7 @@ public:
|
||||
|
||||
最后我依然给出了迭代法。
|
||||
|
||||
对于本地充分了解递归与回溯的过程之后,有精力的同学可以在去实现迭代法。
|
||||
对于本题充分了解递归与回溯的过程之后,有精力的同学可以再去实现迭代法。
|
||||
|
||||
|
||||
|
||||
@ -386,7 +386,7 @@ public:
|
||||
|
||||
# 其他语言版本
|
||||
|
||||
Java:
|
||||
## Java:
|
||||
|
||||
```Java
|
||||
//解法一
|
||||
@ -466,7 +466,7 @@ class Solution {
|
||||
}
|
||||
```
|
||||
---
|
||||
Python:
|
||||
## Python:
|
||||
递归法+隐形回溯
|
||||
```Python3
|
||||
# Definition for a binary tree node.
|
||||
@ -529,7 +529,7 @@ class Solution:
|
||||
|
||||
---
|
||||
|
||||
Go:
|
||||
## Go:
|
||||
|
||||
递归法:
|
||||
|
||||
@ -591,28 +591,28 @@ func binaryTreePaths(root *TreeNode) []string {
|
||||
```
|
||||
|
||||
---
|
||||
JavaScript:
|
||||
## JavaScript:
|
||||
|
||||
递归法:
|
||||
|
||||
```javascript
|
||||
var binaryTreePaths = function(root) {
|
||||
//递归遍历+递归三部曲
|
||||
let res=[];
|
||||
let res = [];
|
||||
//1. 确定递归函数 函数参数
|
||||
const getPath=function(node,curPath){
|
||||
const getPath = function(node,curPath) {
|
||||
//2. 确定终止条件,到叶子节点就终止
|
||||
if(node.left===null&&node.right===null){
|
||||
curPath+=node.val;
|
||||
if(node.left === null && node.right === null) {
|
||||
curPath += node.val;
|
||||
res.push(curPath);
|
||||
return ;
|
||||
return;
|
||||
}
|
||||
//3. 确定单层递归逻辑
|
||||
curPath+=node.val+'->';
|
||||
node.left&&getPath(node.left,curPath);
|
||||
node.right&&getPath(node.right,curPath);
|
||||
curPath += node.val + '->';
|
||||
node.left && getPath(node.left, curPath);
|
||||
node.right && getPath(node.right, curPath);
|
||||
}
|
||||
getPath(root,'');
|
||||
getPath(root, '');
|
||||
return res;
|
||||
};
|
||||
```
|
||||
@ -644,7 +644,7 @@ var binaryTreePaths = function(root) {
|
||||
};
|
||||
```
|
||||
|
||||
TypeScript:
|
||||
## TypeScript:
|
||||
|
||||
> 递归法
|
||||
|
||||
@ -698,7 +698,7 @@ function binaryTreePaths(root: TreeNode | null): string[] {
|
||||
};
|
||||
```
|
||||
|
||||
Swift:
|
||||
## Swift:
|
||||
|
||||
> 递归/回溯
|
||||
```swift
|
||||
@ -765,7 +765,7 @@ func binaryTreePaths(_ root: TreeNode?) -> [String] {
|
||||
}
|
||||
```
|
||||
|
||||
Scala:
|
||||
## Scala:
|
||||
|
||||
递归:
|
||||
```scala
|
||||
@ -794,6 +794,33 @@ object Solution {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
rust:
|
||||
|
||||
```rust
|
||||
// 递归
|
||||
impl Solution {
|
||||
pub fn binary_tree_paths(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<String> {
|
||||
let mut res = vec![];
|
||||
Self::recur(&root, String::from(""), &mut res);
|
||||
res
|
||||
}
|
||||
pub fn recur(node: &Option<Rc<RefCell<TreeNode>>>, mut path: String, res: &mut Vec<String>) {
|
||||
let r = node.as_ref().unwrap().borrow();
|
||||
path += format!("{}", r.val).as_str();
|
||||
if r.left.is_none() && r.right.is_none() {
|
||||
res.push(path.to_string());
|
||||
return;
|
||||
}
|
||||
if r.left.is_some() {
|
||||
Self::recur(&r.left, path.clone() + "->", res);
|
||||
}
|
||||
if r.right.is_some() {
|
||||
Self::recur(&r.right, path + "->", res);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<p align="center">
|
||||
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
|
||||
|
@ -35,7 +35,7 @@
|
||||
|
||||

|
||||
|
||||
相信通过这两个图,大家可以最左叶子的定义有明确理解了。
|
||||
相信通过这两个图,大家对最左叶子的定义有明确理解了。
|
||||
|
||||
那么**判断当前节点是不是左叶子是无法判断的,必须要通过节点的父节点来判断其左孩子是不是左叶子。**
|
||||
|
||||
@ -298,48 +298,49 @@ class Solution:
|
||||
|
||||
```go
|
||||
func sumOfLeftLeaves(root *TreeNode) int {
|
||||
var res int
|
||||
findLeft(root,&res)
|
||||
return res
|
||||
}
|
||||
func findLeft(root *TreeNode,res *int){
|
||||
//左节点
|
||||
if root.Left!=nil&&root.Left.Left==nil&&root.Left.Right==nil{
|
||||
*res=*res+root.Left.Val
|
||||
if root == nil {
|
||||
return 0
|
||||
}
|
||||
if root.Left!=nil{
|
||||
findLeft(root.Left,res)
|
||||
}
|
||||
if root.Right!=nil{
|
||||
findLeft(root.Right,res)
|
||||
leftValue := sumOfLeftLeaves(root.Left) // 左
|
||||
|
||||
if root.Left != nil && root.Left.Left == nil && root.Left.Right == nil {
|
||||
leftValue = root.Left.Val // 中
|
||||
}
|
||||
|
||||
rightValue := sumOfLeftLeaves(root.Right) // 右
|
||||
|
||||
return leftValue + rightValue
|
||||
}
|
||||
```
|
||||
|
||||
**迭代法**
|
||||
**迭代法(前序遍历)**
|
||||
|
||||
```go
|
||||
func sumOfLeftLeaves(root *TreeNode) int {
|
||||
var res int
|
||||
queue:=list.New()
|
||||
queue.PushBack(root)
|
||||
for queue.Len()>0{
|
||||
length:=queue.Len()
|
||||
for i:=0;i<length;i++{
|
||||
node:=queue.Remove(queue.Front()).(*TreeNode)
|
||||
if node.Left!=nil&&node.Left.Left==nil&&node.Left.Right==nil{
|
||||
res=res+node.Left.Val
|
||||
}
|
||||
if node.Left!=nil{
|
||||
queue.PushBack(node.Left)
|
||||
}
|
||||
if node.Right!=nil{
|
||||
queue.PushBack(node.Right)
|
||||
}
|
||||
st := make([]*TreeNode, 0)
|
||||
if root == nil {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
return res
|
||||
st = append(st, root)
|
||||
result := 0
|
||||
|
||||
for len(st) != 0 {
|
||||
node := st[len(st)-1]
|
||||
st = st[:len(st)-1]
|
||||
if node.Left != nil && node.Left.Left == nil && node.Left.Right == nil {
|
||||
result += node.Left.Val
|
||||
}
|
||||
if node.Right != nil {
|
||||
st = append(st, node.Right)
|
||||
}
|
||||
if node.Left != nil {
|
||||
st = append(st, node.Left)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
@ -351,16 +352,16 @@ func sumOfLeftLeaves(root *TreeNode) int {
|
||||
var sumOfLeftLeaves = function(root) {
|
||||
//采用后序遍历 递归遍历
|
||||
// 1. 确定递归函数参数
|
||||
const nodesSum = function(node){
|
||||
const nodesSum = function(node) {
|
||||
// 2. 确定终止条件
|
||||
if(node===null){
|
||||
if(node === null) {
|
||||
return 0;
|
||||
}
|
||||
let leftValue = nodesSum(node.left);
|
||||
let rightValue = nodesSum(node.right);
|
||||
// 3. 单层递归逻辑
|
||||
let midValue = 0;
|
||||
if(node.left&&node.left.left===null&&node.left.right===null){
|
||||
if(node.left && node.left.left === null && node.left.right === null) {
|
||||
midValue = node.left.val;
|
||||
}
|
||||
let sum = midValue + leftValue + rightValue;
|
||||
@ -374,19 +375,19 @@ var sumOfLeftLeaves = function(root) {
|
||||
```javascript
|
||||
var sumOfLeftLeaves = function(root) {
|
||||
//采用层序遍历
|
||||
if(root===null){
|
||||
if(root === null) {
|
||||
return null;
|
||||
}
|
||||
let queue = [];
|
||||
let sum = 0;
|
||||
queue.push(root);
|
||||
while(queue.length){
|
||||
while(queue.length) {
|
||||
let node = queue.shift();
|
||||
if(node.left!==null&&node.left.left===null&&node.left.right===null){
|
||||
if(node.left !== null && node.left.left === null && node.left.right === null) {
|
||||
sum+=node.left.val;
|
||||
}
|
||||
node.left&&queue.push(node.left);
|
||||
node.right&&queue.push(node.right);
|
||||
node.left && queue.push(node.left);
|
||||
node.right && queue.push(node.right);
|
||||
}
|
||||
return sum;
|
||||
};
|
||||
@ -575,6 +576,58 @@ object Solution {
|
||||
}
|
||||
```
|
||||
|
||||
### Rust
|
||||
|
||||
**递归**
|
||||
|
||||
```rust
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
impl Solution {
|
||||
pub fn sum_of_left_leaves(root: Option<Rc<RefCell<TreeNode>>>) -> i32 {
|
||||
let mut res = 0;
|
||||
if let Some(node) = root {
|
||||
if let Some(left) = &node.borrow().left {
|
||||
if left.borrow().right.is_none() && left.borrow().right.is_none() {
|
||||
res += left.borrow().val;
|
||||
}
|
||||
}
|
||||
res + Self::sum_of_left_leaves(node.borrow().left.clone())
|
||||
+ Self::sum_of_left_leaves(node.borrow().right.clone())
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**迭代:**
|
||||
|
||||
```rust
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
impl Solution {
|
||||
pub fn sum_of_left_leaves(root: Option<Rc<RefCell<TreeNode>>>) -> i32 {
|
||||
let mut res = 0;
|
||||
let mut stack = vec![root];
|
||||
while !stack.is_empty() {
|
||||
if let Some(node) = stack.pop().unwrap() {
|
||||
if let Some(left) = &node.borrow().left {
|
||||
if left.borrow().left.is_none() && left.borrow().right.is_none() {
|
||||
res += left.borrow().val;
|
||||
}
|
||||
stack.push(Some(left.to_owned()));
|
||||
}
|
||||
if let Some(right) = &node.borrow().right {
|
||||
stack.push(Some(right.to_owned()));
|
||||
}
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<p align="center">
|
||||
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
|
||||
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>
|
||||
|
@ -25,7 +25,7 @@
|
||||
|
||||
# 思路
|
||||
|
||||
搜索树的节点删除要比节点增加复杂的多,有很多情况需要考虑,做好心里准备。
|
||||
搜索树的节点删除要比节点增加复杂的多,有很多情况需要考虑,做好心理准备。
|
||||
|
||||
## 递归
|
||||
|
||||
@ -33,7 +33,7 @@
|
||||
|
||||
* 确定递归函数参数以及返回值
|
||||
|
||||
说道递归函数的返回值,在[二叉树:搜索树中的插入操作](https://programmercarl.com/0701.二叉搜索树中的插入操作.html)中通过递归返回值来加入新节点, 这里也可以通过递归返回值删除节点。
|
||||
说到递归函数的返回值,在[二叉树:搜索树中的插入操作](https://programmercarl.com/0701.二叉搜索树中的插入操作.html)中通过递归返回值来加入新节点, 这里也可以通过递归返回值删除节点。
|
||||
|
||||
代码如下:
|
||||
|
||||
@ -66,7 +66,7 @@ if (root == nullptr) return root;
|
||||
|
||||

|
||||
|
||||
动画中棵二叉搜索树中,删除元素7, 那么删除节点(元素7)的左孩子就是5,删除节点(元素7)的右子树的最左面节点是元素8。
|
||||
动画中的二叉搜索树中,删除元素7, 那么删除节点(元素7)的左孩子就是5,删除节点(元素7)的右子树的最左面节点是元素8。
|
||||
|
||||
将删除节点(元素7)的左孩子放到删除节点(元素7)的右子树的最左面节点(元素8)的左孩子上,就是把5为根节点的子树移到了8的左孩子的位置。
|
||||
|
||||
@ -251,7 +251,7 @@ public:
|
||||
|
||||
**这里最关键的逻辑就是第五种情况(删除一个左右孩子都不为空的节点),这种情况一定要想清楚**。
|
||||
|
||||
而且就算想清楚了,对应的代码也未必可以写出来,所以**这道题目即考察思维逻辑,也考察代码能力**。
|
||||
而且就算想清楚了,对应的代码也未必可以写出来,所以**这道题目既考察思维逻辑,也考察代码能力**。
|
||||
|
||||
递归中我给出了两种写法,推荐大家学会第一种(利用搜索树的特性)就可以了,第二种递归写法其实是比较绕的。
|
||||
|
||||
@ -390,39 +390,39 @@ class Solution:
|
||||
```Go
|
||||
// 递归版本
|
||||
func deleteNode(root *TreeNode, key int) *TreeNode {
|
||||
if root==nil{
|
||||
if root == nil {
|
||||
return nil
|
||||
}
|
||||
if key<root.Val{
|
||||
root.Left=deleteNode(root.Left,key)
|
||||
if key < root.Val {
|
||||
root.Left = deleteNode(root.Left, key)
|
||||
return root
|
||||
}
|
||||
if key>root.Val{
|
||||
root.Right=deleteNode(root.Right,key)
|
||||
if key > root.Val {
|
||||
root.Right = deleteNode(root.Right, key)
|
||||
return root
|
||||
}
|
||||
if root.Right==nil{
|
||||
if root.Right == nil {
|
||||
return root.Left
|
||||
}
|
||||
if root.Left==nil{
|
||||
if root.Left == nil{
|
||||
return root.Right
|
||||
}
|
||||
minnode:=root.Right
|
||||
for minnode.Left!=nil{
|
||||
minnode=minnode.Left
|
||||
minnode := root.Right
|
||||
for minnode.Left != nil {
|
||||
minnode = minnode.Left
|
||||
}
|
||||
root.Val=minnode.Val
|
||||
root.Right=deleteNode1(root.Right)
|
||||
root.Val = minnode.Val
|
||||
root.Right = deleteNode1(root.Right)
|
||||
return root
|
||||
}
|
||||
|
||||
func deleteNode1(root *TreeNode)*TreeNode{
|
||||
if root.Left==nil{
|
||||
pRight:=root.Right
|
||||
root.Right=nil
|
||||
func deleteNode1(root *TreeNode)*TreeNode {
|
||||
if root.Left == nil {
|
||||
pRight := root.Right
|
||||
root.Right = nil
|
||||
return pRight
|
||||
}
|
||||
root.Left=deleteNode1(root.Left)
|
||||
root.Left = deleteNode1(root.Left)
|
||||
return root
|
||||
}
|
||||
// 迭代版本
|
||||
|
@ -54,7 +54,7 @@
|
||||
|
||||
1. 这个树都遍历了,用map统计频率
|
||||
|
||||
至于用前中后序那种遍历也不重要,因为就是要全遍历一遍,怎么个遍历法都行,层序遍历都没毛病!
|
||||
至于用前中后序哪种遍历也不重要,因为就是要全遍历一遍,怎么个遍历法都行,层序遍历都没毛病!
|
||||
|
||||
这里采用前序遍历,代码如下:
|
||||
|
||||
@ -354,7 +354,7 @@ public:
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public int[] findMode(FindModeInBinarySearchTree.TreeNode root) {
|
||||
public int[] findMode(TreeNode root) {
|
||||
Map<Integer, Integer> map = new HashMap<>();
|
||||
List<Integer> list = new ArrayList<>();
|
||||
if (root == null) return list.stream().mapToInt(Integer::intValue).toArray();
|
||||
@ -375,7 +375,7 @@ class Solution {
|
||||
return list.stream().mapToInt(Integer::intValue).toArray();
|
||||
}
|
||||
|
||||
void searchBST(FindModeInBinarySearchTree.TreeNode curr, Map<Integer, Integer> map) {
|
||||
void searchBST(TreeNode curr, Map<Integer, Integer> map) {
|
||||
if (curr == null) return;
|
||||
map.put(curr.val, map.getOrDefault(curr.val, 0) + 1);
|
||||
searchBST(curr.left, map);
|
||||
@ -556,46 +556,7 @@ class Solution:
|
||||
```
|
||||
## Go
|
||||
|
||||
暴力法(非BSL)
|
||||
|
||||
```go
|
||||
func findMode(root *TreeNode) []int {
|
||||
var history map[int]int
|
||||
var maxValue int
|
||||
var maxIndex int
|
||||
var result []int
|
||||
history=make(map[int]int)
|
||||
traversal(root,history)
|
||||
for k,value:=range history{
|
||||
if value>maxValue{
|
||||
maxValue=value
|
||||
maxIndex=k
|
||||
}
|
||||
}
|
||||
for k,value:=range history{
|
||||
if value==history[maxIndex]{
|
||||
result=append(result,k)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
func traversal(root *TreeNode,history map[int]int){
|
||||
if root.Left!=nil{
|
||||
traversal(root.Left,history)
|
||||
}
|
||||
if value,ok:=history[root.Val];ok{
|
||||
history[root.Val]=value+1
|
||||
}else{
|
||||
history[root.Val]=1
|
||||
}
|
||||
if root.Right!=nil{
|
||||
traversal(root.Right,history)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
计数法,不使用额外空间,利用二叉树性质,中序遍历
|
||||
|
||||
```go
|
||||
func findMode(root *TreeNode) []int {
|
||||
res := make([]int, 0)
|
||||
|
@ -26,7 +26,7 @@
|
||||
|
||||
## 思路
|
||||
|
||||
本地要找出树的最后一行找到最左边的值。此时大家应该想起用层序遍历是非常简单的了,反而用递归的话会比较难一点。
|
||||
本题要找出树的最后一行的最左边的值。此时大家应该想起用层序遍历是非常简单的了,反而用递归的话会比较难一点。
|
||||
|
||||
我们依然还是先介绍递归法。
|
||||
|
||||
@ -46,7 +46,7 @@
|
||||
|
||||
所以要找深度最大的叶子节点。
|
||||
|
||||
那么如果找最左边的呢?可以使用前序遍历(当然中序,后序都可以,因为本题没有 中间节点的处理逻辑,只要左优先就行),保证优先左边搜索,然后记录深度最大的叶子节点,此时就是树的最后一行最左边的值。
|
||||
那么如何找最左边的呢?可以使用前序遍历(当然中序,后序都可以,因为本题没有 中间节点的处理逻辑,只要左优先就行),保证优先左边搜索,然后记录深度最大的叶子节点,此时就是树的最后一行最左边的值。
|
||||
|
||||
递归三部曲:
|
||||
|
||||
@ -169,7 +169,7 @@ public:
|
||||
|
||||
### 迭代法
|
||||
|
||||
本题使用层序遍历再合适不过了,比递归要好理解的多!
|
||||
本题使用层序遍历再合适不过了,比递归要好理解得多!
|
||||
|
||||
只需要记录最后一行第一个节点的数值就可以了。
|
||||
|
||||
@ -323,34 +323,25 @@ class Solution:
|
||||
递归法:
|
||||
|
||||
```go
|
||||
var maxDeep int // 全局变量 深度
|
||||
var value int //全局变量 最终值
|
||||
var depth int // 全局变量 最大深度
|
||||
var res int // 记录最终结果
|
||||
func findBottomLeftValue(root *TreeNode) int {
|
||||
if root.Left==nil&&root.Right==nil{//需要提前判断一下(不要这个if的话提交结果会出错,但执行代码不会。防止这种情况出现,故先判断是否只有一个节点)
|
||||
return root.Val
|
||||
}
|
||||
findLeftValue (root,maxDeep)
|
||||
return value
|
||||
depth, res = 0, 0 // 初始化
|
||||
dfs(root, 1)
|
||||
return res
|
||||
}
|
||||
func findLeftValue (root *TreeNode,deep int){
|
||||
//最左边的值在左边
|
||||
if root.Left==nil&&root.Right==nil{
|
||||
if deep>maxDeep{
|
||||
value=root.Val
|
||||
maxDeep=deep
|
||||
}
|
||||
}
|
||||
//递归
|
||||
if root.Left!=nil{
|
||||
deep++
|
||||
findLeftValue(root.Left,deep)
|
||||
deep--//回溯
|
||||
|
||||
func dfs(root *TreeNode, d int) {
|
||||
if root == nil {
|
||||
return
|
||||
}
|
||||
if root.Right!=nil{
|
||||
deep++
|
||||
findLeftValue(root.Right,deep)
|
||||
deep--//回溯
|
||||
// 因为先遍历左边,所以左边如果有值,右边的同层不会更新结果
|
||||
if root.Left == nil && root.Right == nil && depth < d {
|
||||
depth = d
|
||||
res = root.Val
|
||||
}
|
||||
dfs(root.Left, d+1) // 隐藏回溯
|
||||
dfs(root.Right, d+1)
|
||||
}
|
||||
```
|
||||
|
||||
@ -358,18 +349,21 @@ func findLeftValue (root *TreeNode,deep int){
|
||||
|
||||
```go
|
||||
func findBottomLeftValue(root *TreeNode) int {
|
||||
queue:=list.New()
|
||||
var gradation int
|
||||
queue := list.New()
|
||||
|
||||
queue.PushBack(root)
|
||||
for queue.Len()>0{
|
||||
length:=queue.Len()
|
||||
for i:=0;i<length;i++{
|
||||
node:=queue.Remove(queue.Front()).(*TreeNode)
|
||||
if i==0{gradation=node.Val}
|
||||
if node.Left!=nil{
|
||||
for queue.Len() > 0 {
|
||||
length := queue.Len()
|
||||
for i := 0; i < length; i++ {
|
||||
node := queue.Remove(queue.Front()).(*TreeNode)
|
||||
if i == 0 {
|
||||
gradation = node.Val
|
||||
}
|
||||
if node.Left != nil {
|
||||
queue.PushBack(node.Left)
|
||||
}
|
||||
if node.Right!=nil{
|
||||
if node.Right != nil {
|
||||
queue.PushBack(node.Right)
|
||||
}
|
||||
}
|
||||
@ -385,19 +379,18 @@ func findBottomLeftValue(root *TreeNode) int {
|
||||
```javascript
|
||||
var findBottomLeftValue = function(root) {
|
||||
//首先考虑递归遍历 前序遍历 找到最大深度的叶子节点即可
|
||||
let maxPath = 0,resNode = null;
|
||||
let maxPath = 0, resNode = null;
|
||||
// 1. 确定递归函数的函数参数
|
||||
const dfsTree = function(node,curPath){
|
||||
const dfsTree = function(node, curPath) {
|
||||
// 2. 确定递归函数终止条件
|
||||
if(node.left===null&&node.right===null){
|
||||
if(curPath>maxPath){
|
||||
if(node.left === null && node.right === null) {
|
||||
if(curPath > maxPath) {
|
||||
maxPath = curPath;
|
||||
resNode = node.val;
|
||||
}
|
||||
// return ;
|
||||
}
|
||||
node.left&&dfsTree(node.left,curPath+1);
|
||||
node.right&&dfsTree(node.right,curPath+1);
|
||||
node.left && dfsTree(node.left, curPath+1);
|
||||
node.right && dfsTree(node.right, curPath+1);
|
||||
}
|
||||
dfsTree(root,1);
|
||||
return resNode;
|
||||
@ -409,20 +402,20 @@ var findBottomLeftValue = function(root) {
|
||||
var findBottomLeftValue = function(root) {
|
||||
//考虑层序遍历 记录最后一行的第一个节点
|
||||
let queue = [];
|
||||
if(root===null){
|
||||
if(root === null) {
|
||||
return null;
|
||||
}
|
||||
queue.push(root);
|
||||
let resNode;
|
||||
while(queue.length){
|
||||
let length = queue.length;
|
||||
for(let i=0; i<length; i++){
|
||||
while(queue.length) {
|
||||
let length = queue.length;
|
||||
for(let i = 0; i < length; i++) {
|
||||
let node = queue.shift();
|
||||
if(i===0){
|
||||
if(i === 0) {
|
||||
resNode = node.val;
|
||||
}
|
||||
node.left&&queue.push(node.left);
|
||||
node.right&&queue.push(node.right);
|
||||
node.left && queue.push(node.left);
|
||||
node.right && queue.push(node.right);
|
||||
}
|
||||
}
|
||||
return resNode;
|
||||
@ -587,6 +580,77 @@ object Solution {
|
||||
}
|
||||
```
|
||||
|
||||
### rust
|
||||
|
||||
**层序遍历**
|
||||
|
||||
```rust
|
||||
use std::cell::RefCell;
|
||||
use std::collections::VecDeque;
|
||||
use std::rc::Rc;
|
||||
impl Solution {
|
||||
pub fn find_bottom_left_value(root: Option<Rc<RefCell<TreeNode>>>) -> i32 {
|
||||
let mut queue = VecDeque::new();
|
||||
let mut res = 0;
|
||||
if root.is_some() {
|
||||
queue.push_back(root);
|
||||
}
|
||||
while !queue.is_empty() {
|
||||
for i in 0..queue.len() {
|
||||
let node = queue.pop_front().unwrap().unwrap();
|
||||
if i == 0 {
|
||||
res = node.borrow().val;
|
||||
}
|
||||
if node.borrow().left.is_some() {
|
||||
queue.push_back(node.borrow().left.clone());
|
||||
}
|
||||
if node.borrow().right.is_some() {
|
||||
queue.push_back(node.borrow().right.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**递归**
|
||||
|
||||
```rust
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
impl Solution {
|
||||
//*递归*/
|
||||
pub fn find_bottom_left_value(root: Option<Rc<RefCell<TreeNode>>>) -> i32 {
|
||||
let mut res = 0;
|
||||
let mut max_depth = i32::MIN;
|
||||
Self::traversal(root, 0, &mut max_depth, &mut res);
|
||||
res
|
||||
}
|
||||
fn traversal(
|
||||
root: Option<Rc<RefCell<TreeNode>>>,
|
||||
depth: i32,
|
||||
max_depth: &mut i32,
|
||||
res: &mut i32,
|
||||
) {
|
||||
let node = root.unwrap();
|
||||
if node.borrow().left.is_none() && node.borrow().right.is_none() {
|
||||
if depth > *max_depth {
|
||||
*max_depth = depth;
|
||||
*res = node.borrow().val;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if node.borrow().left.is_some() {
|
||||
Self::traversal(node.borrow().left.clone(), depth + 1, max_depth, res);
|
||||
}
|
||||
if node.borrow().right.is_some() {
|
||||
Self::traversal(node.borrow().right.clone(), depth + 1, max_depth, res);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<p align="center">
|
||||
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
|
||||
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>
|
||||
|
@ -248,28 +248,6 @@ class Solution:
|
||||
## Go:
|
||||
|
||||
中序遍历,然后计算最小差值
|
||||
|
||||
```go
|
||||
func getMinimumDifference(root *TreeNode) int {
|
||||
var res []int
|
||||
findMIn(root,&res)
|
||||
min:=1000000//一个比较大的值
|
||||
for i:=1;i<len(res);i++{
|
||||
tempValue:=res[i]-res[i-1]
|
||||
if tempValue<min{
|
||||
min=tempValue
|
||||
}
|
||||
}
|
||||
return min
|
||||
}
|
||||
//中序遍历
|
||||
func findMIn(root *TreeNode,res *[]int){
|
||||
if root==nil{return}
|
||||
findMIn(root.Left,res)
|
||||
*res=append(*res,root.Val)
|
||||
findMIn(root.Right,res)
|
||||
}
|
||||
```
|
||||
```go
|
||||
// 中序遍历的同时计算最小值
|
||||
func getMinimumDifference(root *TreeNode) int {
|
||||
|
@ -45,11 +45,11 @@
|
||||
|
||||
# 思路
|
||||
|
||||
一看到累加树,相信很多小伙伴都会疑惑:如何累加?遇到一个节点,然后在遍历其他节点累加?怎么一想这么麻烦呢。
|
||||
一看到累加树,相信很多小伙伴都会疑惑:如何累加?遇到一个节点,然后再遍历其他节点累加?怎么一想这么麻烦呢。
|
||||
|
||||
然后再发现这是一棵二叉搜索树,二叉搜索树啊,这是有序的啊。
|
||||
|
||||
那么有序的元素如果求累加呢?
|
||||
那么有序的元素如何求累加呢?
|
||||
|
||||
**其实这就是一棵树,大家可能看起来有点别扭,换一个角度来看,这就是一个有序数组[2, 5, 13],求从后到前的累加数组,也就是[20, 18, 13],是不是感觉这就简单了。**
|
||||
|
||||
@ -233,23 +233,23 @@ class Solution:
|
||||
## Go
|
||||
|
||||
弄一个sum暂存其和值
|
||||
|
||||
```go
|
||||
//右中左
|
||||
func bstToGst(root *TreeNode) *TreeNode {
|
||||
var sum int
|
||||
RightMLeft(root,&sum)
|
||||
return root
|
||||
}
|
||||
func RightMLeft(root *TreeNode,sum *int) *TreeNode {
|
||||
if root==nil{return nil}//终止条件,遇到空节点就返回
|
||||
RightMLeft(root.Right,sum)//先遍历右边
|
||||
temp:=*sum//暂存总和值
|
||||
*sum+=root.Val//将总和值变更
|
||||
root.Val+=temp//更新节点值
|
||||
RightMLeft(root.Left,sum)//遍历左节点
|
||||
var pre int
|
||||
func convertBST(root *TreeNode) *TreeNode {
|
||||
pre = 0
|
||||
traversal(root)
|
||||
return root
|
||||
}
|
||||
|
||||
func traversal(cur *TreeNode) {
|
||||
if cur == nil {
|
||||
return
|
||||
}
|
||||
traversal(cur.Right)
|
||||
cur.Val += pre
|
||||
pre = cur.Val
|
||||
traversal(cur.Left)
|
||||
}
|
||||
```
|
||||
|
||||
## JavaScript
|
||||
|
@ -46,7 +46,7 @@
|
||||
|
||||
1. **确定递归函数的参数和返回值:**
|
||||
|
||||
首先那么要合入两个二叉树,那么参数至少是要传入两个二叉树的根节点,返回值就是合并之后二叉树的根节点。
|
||||
首先要合入两个二叉树,那么参数至少是要传入两个二叉树的根节点,返回值就是合并之后二叉树的根节点。
|
||||
|
||||
代码如下:
|
||||
|
||||
@ -56,7 +56,7 @@ TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
|
||||
|
||||
2. **确定终止条件:**
|
||||
|
||||
因为是传入了两个树,那么就有两个树遍历的节点t1 和 t2,如果t1 == NULL 了,两个树合并就应该是 t2 了啊(如果t2也为NULL也无所谓,合并之后就是NULL)。
|
||||
因为是传入了两个树,那么就有两个树遍历的节点t1 和 t2,如果t1 == NULL 了,两个树合并就应该是 t2 了(如果t2也为NULL也无所谓,合并之后就是NULL)。
|
||||
|
||||
反过来如果t2 == NULL,那么两个数合并就是t1(如果t1也为NULL也无所谓,合并之后就是NULL)。
|
||||
|
||||
@ -70,7 +70,7 @@ if (t2 == NULL) return t1; // 如果t2为空,合并之后就应该是t1
|
||||
|
||||
3. **确定单层递归的逻辑:**
|
||||
|
||||
单层递归的逻辑就比较好些了,这里我们用重复利用一下t1这个树,t1就是合并之后树的根节点(就是修改了原来树的结构)。
|
||||
单层递归的逻辑就比较好写了,这里我们重复利用一下t1这个树,t1就是合并之后树的根节点(就是修改了原来树的结构)。
|
||||
|
||||
那么单层递归中,就要把两棵树的元素加到一起。
|
||||
```
|
||||
@ -144,7 +144,7 @@ public:
|
||||
|
||||
**但是前序遍历是最好理解的,我建议大家用前序遍历来做就OK。**
|
||||
|
||||
如上的方法修改了t1的结构,当然也可以不修改t1和t2的结构,重新定一个树。
|
||||
如上的方法修改了t1的结构,当然也可以不修改t1和t2的结构,重新定义一个树。
|
||||
|
||||
不修改输入树的结构,前序遍历,代码如下:
|
||||
|
||||
@ -214,7 +214,7 @@ public:
|
||||
|
||||
## 拓展
|
||||
|
||||
当然也可以秀一波指针的操作,这是我写的野路子,大家就随便看看就行了,以防带跑遍了。
|
||||
当然也可以秀一波指针的操作,这是我写的野路子,大家就随便看看就行了,以防带跑偏了。
|
||||
|
||||
如下代码中,想要更改二叉树的值,应该传入指向指针的指针。
|
||||
|
||||
@ -252,7 +252,7 @@ public:
|
||||
|
||||
迭代法中,一般一起操作两个树都是使用队列模拟类似层序遍历,同时处理两个树的节点,这种方式最好理解,如果用模拟递归的思路的话,要复杂一些。
|
||||
|
||||
最后拓展中,我给了一个操作指针的野路子,大家随便看看就行了,如果学习C++的话,可以在去研究研究。
|
||||
最后拓展中,我给了一个操作指针的野路子,大家随便看看就行了,如果学习C++的话,可以再去研究研究。
|
||||
|
||||
|
||||
## 其他语言版本
|
||||
@ -417,43 +417,7 @@ class Solution:
|
||||
### Go
|
||||
|
||||
```go
|
||||
/**
|
||||
* Definition for a binary tree node.
|
||||
* type TreeNode struct {
|
||||
* Val int
|
||||
* Left *TreeNode
|
||||
* Right *TreeNode
|
||||
* }
|
||||
*/
|
||||
//前序遍历(递归遍历,跟105 106差不多的思路)
|
||||
func mergeTrees(t1 *TreeNode, t2 *TreeNode) *TreeNode {
|
||||
var value int
|
||||
var nullNode *TreeNode//空node,便于遍历
|
||||
nullNode=&TreeNode{
|
||||
Val:0,
|
||||
Left:nil,
|
||||
Right:nil}
|
||||
switch {
|
||||
case t1==nil&&t2==nil: return nil//终止条件
|
||||
default : //如果其中一个节点为空,则将该节点置为nullNode,方便下次遍历
|
||||
if t1==nil{
|
||||
value=t2.Val
|
||||
t1=nullNode
|
||||
}else if t2==nil{
|
||||
value=t1.Val
|
||||
t2=nullNode
|
||||
}else {
|
||||
value=t1.Val+t2.Val
|
||||
}
|
||||
}
|
||||
root:=&TreeNode{//构造新的二叉树
|
||||
Val: value,
|
||||
Left: mergeTrees(t1.Left,t2.Left),
|
||||
Right: mergeTrees(t1.Right,t2.Right)}
|
||||
return root
|
||||
}
|
||||
|
||||
// 前序遍历简洁版
|
||||
// 前序遍历
|
||||
func mergeTrees(root1 *TreeNode, root2 *TreeNode) *TreeNode {
|
||||
if root1 == nil {
|
||||
return root2
|
||||
@ -479,28 +443,28 @@ func mergeTrees(root1 *TreeNode, root2 *TreeNode) *TreeNode {
|
||||
queue = append(queue,root1)
|
||||
queue = append(queue,root2)
|
||||
|
||||
for size:=len(queue);size>0;size=len(queue){
|
||||
for size := len(queue); size>0; size=len(queue) {
|
||||
node1 := queue[0]
|
||||
queue = queue[1:]
|
||||
node2 := queue[0]
|
||||
queue = queue[1:]
|
||||
node1.Val += node2.Val
|
||||
// 左子树都不为空
|
||||
if node1.Left != nil && node2.Left != nil{
|
||||
if node1.Left != nil && node2.Left != nil {
|
||||
queue = append(queue,node1.Left)
|
||||
queue = append(queue,node2.Left)
|
||||
}
|
||||
// 右子树都不为空
|
||||
if node1.Right !=nil && node2.Right !=nil{
|
||||
queue = append(queue,node1.Right)
|
||||
queue = append(queue,node2.Right)
|
||||
if node1.Right !=nil && node2.Right !=nil {
|
||||
queue = append(queue, node1.Right)
|
||||
queue = append(queue, node2.Right)
|
||||
}
|
||||
// 树 1 的左子树为 nil,直接接上树 2 的左子树
|
||||
if node1.Left == nil{
|
||||
if node1.Left == nil {
|
||||
node1.Left = node2.Left
|
||||
}
|
||||
// 树 1 的右子树为 nil,直接接上树 2 的右子树
|
||||
if node1.Right == nil{
|
||||
if node1.Right == nil {
|
||||
node1.Right = node2.Right
|
||||
}
|
||||
}
|
||||
@ -691,6 +655,78 @@ object Solution {
|
||||
}
|
||||
```
|
||||
|
||||
### rust
|
||||
|
||||
递归:
|
||||
|
||||
```rust
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
impl Solution {
|
||||
pub fn merge_trees(
|
||||
root1: Option<Rc<RefCell<TreeNode>>>,
|
||||
root2: Option<Rc<RefCell<TreeNode>>>,
|
||||
) -> Option<Rc<RefCell<TreeNode>>> {
|
||||
if root1.is_none() {
|
||||
return root2;
|
||||
}
|
||||
if root2.is_none() {
|
||||
return root1;
|
||||
}
|
||||
let binding = root1.clone();
|
||||
let mut node1 = binding.as_ref().unwrap().borrow_mut();
|
||||
let node2 = root2.as_ref().unwrap().borrow_mut();
|
||||
node1.left = Self::merge_trees(node1.left.clone(), node2.left.clone());
|
||||
node1.right = Self::merge_trees(node1.right.clone(), node2.right.clone());
|
||||
node1.val += node2.val;
|
||||
|
||||
root1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
迭代:
|
||||
|
||||
```rust
|
||||
impl Solution {
|
||||
pub fn merge_trees(
|
||||
root1: Option<Rc<RefCell<TreeNode>>>,
|
||||
root2: Option<Rc<RefCell<TreeNode>>>,
|
||||
) -> Option<Rc<RefCell<TreeNode>>> {
|
||||
if root1.is_none() {
|
||||
return root2;
|
||||
}
|
||||
if root2.is_none() {
|
||||
return root1;
|
||||
}
|
||||
let mut stack = vec![];
|
||||
stack.push(root2);
|
||||
stack.push(root1.clone());
|
||||
while !stack.is_empty() {
|
||||
let node1 = stack.pop().unwrap().unwrap();
|
||||
let node2 = stack.pop().unwrap().unwrap();
|
||||
let mut node1 = node1.borrow_mut();
|
||||
let node2 = node2.borrow();
|
||||
node1.val += node2.val;
|
||||
if node1.left.is_some() && node2.left.is_some() {
|
||||
stack.push(node2.left.clone());
|
||||
stack.push(node1.left.clone());
|
||||
}
|
||||
if node1.right.is_some() && node2.right.is_some() {
|
||||
stack.push(node2.right.clone());
|
||||
stack.push(node1.right.clone());
|
||||
}
|
||||
if node1.left.is_none() && node2.left.is_some() {
|
||||
node1.left = node2.left.clone();
|
||||
}
|
||||
if node1.right.is_none() && node2.right.is_some() {
|
||||
node1.right = node2.right.clone();
|
||||
}
|
||||
}
|
||||
root1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<p align="center">
|
||||
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
|
||||
|
@ -40,7 +40,7 @@
|
||||
|
||||
* 确定递归函数的参数和返回值
|
||||
|
||||
参数就是传入的是存放元素的数组,返回该数组构造的二叉树的头结点,返回类型是指向节点的指针。
|
||||
参数传入的是存放元素的数组,返回该数组构造的二叉树的头结点,返回类型是指向节点的指针。
|
||||
|
||||
代码如下:
|
||||
|
||||
@ -309,30 +309,24 @@ class Solution:
|
||||
|
||||
|
||||
```go
|
||||
/**
|
||||
* Definition for a binary tree node.
|
||||
* type TreeNode struct {
|
||||
* Val int
|
||||
* Left *TreeNode
|
||||
* Right *TreeNode
|
||||
* }
|
||||
*/
|
||||
func constructMaximumBinaryTree(nums []int) *TreeNode {
|
||||
if len(nums)<1{return nil}
|
||||
//首选找到最大值
|
||||
index:=findMax(nums)
|
||||
//其次构造二叉树
|
||||
root:=&TreeNode{
|
||||
if len(nums) == 0 {
|
||||
return nil
|
||||
}
|
||||
// 找到最大值
|
||||
index := findMax(nums)
|
||||
// 构造二叉树
|
||||
root := &TreeNode {
|
||||
Val: nums[index],
|
||||
Left:constructMaximumBinaryTree(nums[:index]),//左半边
|
||||
Right:constructMaximumBinaryTree(nums[index+1:]),//右半边
|
||||
Left: constructMaximumBinaryTree(nums[:index]), //左半边
|
||||
Right: constructMaximumBinaryTree(nums[index+1:]),//右半边
|
||||
}
|
||||
return root
|
||||
}
|
||||
func findMax(nums []int) (index int){
|
||||
for i:=0;i<len(nums);i++{
|
||||
if nums[i]>nums[index]{
|
||||
index=i
|
||||
func findMax(nums []int) (index int) {
|
||||
for i, v := range nums {
|
||||
if nums[index] < v {
|
||||
index = i
|
||||
}
|
||||
}
|
||||
return
|
||||
|
@ -150,6 +150,37 @@ var judgeCircle = function(moves) {
|
||||
```
|
||||
|
||||
|
||||
## TypeScript
|
||||
|
||||
```ts
|
||||
var judgeCircle = function (moves) {
|
||||
let x = 0
|
||||
let y = 0
|
||||
for (let i = 0; i < moves.length; i++) {
|
||||
switch (moves[i]) {
|
||||
case 'L': {
|
||||
x--
|
||||
break
|
||||
}
|
||||
case 'R': {
|
||||
x++
|
||||
break
|
||||
}
|
||||
case 'U': {
|
||||
y--
|
||||
break
|
||||
}
|
||||
case 'D': {
|
||||
y++
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return x === 0 && y === 0
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
<p align="center">
|
||||
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
|
||||
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>
|
||||
|
@ -59,7 +59,7 @@ public:
|
||||

|
||||
|
||||
|
||||
理解了最关键部分了我们在递归三部曲:
|
||||
理解了最关键部分了我们再递归三部曲:
|
||||
|
||||
* 确定递归函数的参数以及返回值
|
||||
|
||||
@ -179,7 +179,7 @@ public:
|
||||
};
|
||||
```
|
||||
|
||||
只看代码,其实不太好理解节点是符合移除的,这一块大家可以自己在模拟模拟!
|
||||
只看代码,其实不太好理解节点是如何移除的,这一块大家可以自己再模拟模拟!
|
||||
|
||||
## 迭代法
|
||||
|
||||
@ -301,19 +301,19 @@ class Solution:
|
||||
|
||||
// 递归
|
||||
func trimBST(root *TreeNode, low int, high int) *TreeNode {
|
||||
if root==nil{
|
||||
if root == nil {
|
||||
return nil
|
||||
}
|
||||
if root.Val<low{//如果该节点值小于最小值,则该节点更换为该节点的右节点值,继续遍历
|
||||
right:=trimBST(root.Right,low,high)
|
||||
if root.Val < low { //如果该节点值小于最小值,则该节点更换为该节点的右节点值,继续遍历
|
||||
right := trimBST(root.Right, low, high)
|
||||
return right
|
||||
}
|
||||
if root.Val>high{//如果该节点的值大于最大值,则该节点更换为该节点的左节点值,继续遍历
|
||||
left:=trimBST(root.Left,low,high)
|
||||
if root.Val > high { //如果该节点的值大于最大值,则该节点更换为该节点的左节点值,继续遍历
|
||||
left := trimBST(root.Left, low, high)
|
||||
return left
|
||||
}
|
||||
root.Left=trimBST(root.Left,low,high)
|
||||
root.Right=trimBST(root.Right,low,high)
|
||||
root.Left = trimBST(root.Left, low, high)
|
||||
root.Right = trimBST(root.Right, low, high)
|
||||
return root
|
||||
}
|
||||
|
||||
@ -323,25 +323,25 @@ func trimBST(root *TreeNode, low int, high int) *TreeNode {
|
||||
return nil
|
||||
}
|
||||
// 处理 root,让 root 移动到[low, high] 范围内,注意是左闭右闭
|
||||
for root != nil && (root.Val<low||root.Val>high){
|
||||
if root.Val < low{
|
||||
for root != nil && (root.Val < low || root.Val > high) {
|
||||
if root.Val < low {
|
||||
root = root.Right
|
||||
}else{
|
||||
} else {
|
||||
root = root.Left
|
||||
}
|
||||
}
|
||||
cur := root
|
||||
// 此时 root 已经在[low, high] 范围内,处理左孩子元素小于 low 的情况(左节点是一定小于 root.Val,因此天然小于 high)
|
||||
for cur != nil{
|
||||
for cur.Left!=nil && cur.Left.Val < low{
|
||||
for cur != nil {
|
||||
for cur.Left != nil && cur.Left.Val < low {
|
||||
cur.Left = cur.Left.Right
|
||||
}
|
||||
cur = cur.Left
|
||||
}
|
||||
cur = root
|
||||
// 此时 root 已经在[low, high] 范围内,处理右孩子大于 high 的情况
|
||||
for cur != nil{
|
||||
for cur.Right!=nil && cur.Right.Val > high{
|
||||
for cur != nil {
|
||||
for cur.Right != nil && cur.Right.Val > high {
|
||||
cur.Right = cur.Right.Left
|
||||
}
|
||||
cur = cur.Right
|
||||
@ -359,24 +359,24 @@ var trimBST = function(root, low, high) {
|
||||
if(root === null) {
|
||||
return null;
|
||||
}
|
||||
while(root !==null &&(root.val<low||root.val>high)) {
|
||||
if(root.val<low) {
|
||||
while(root !== null && (root.val < low || root.val > high)) {
|
||||
if(root.val < low) {
|
||||
root = root.right;
|
||||
}else {
|
||||
root = root.left;
|
||||
}
|
||||
}
|
||||
let cur = root;
|
||||
while(cur!==null) {
|
||||
while(cur.left&&cur.left.val<low) {
|
||||
while(cur !== null) {
|
||||
while(cur.left && cur.left.val < low) {
|
||||
cur.left = cur.left.right;
|
||||
}
|
||||
cur = cur.left;
|
||||
}
|
||||
cur = root;
|
||||
//判断右子树大于high的情况
|
||||
while(cur!==null) {
|
||||
while(cur.right&&cur.right.val>high) {
|
||||
while(cur !== null) {
|
||||
while(cur.right && cur.right.val > high) {
|
||||
cur.right = cur.right.left;
|
||||
}
|
||||
cur = cur.right;
|
||||
@ -391,16 +391,16 @@ var trimBST = function (root,low,high) {
|
||||
if(root === null) {
|
||||
return null;
|
||||
}
|
||||
if(root.val<low) {
|
||||
let right = trimBST(root.right,low,high);
|
||||
if(root.val < low) {
|
||||
let right = trimBST(root.right, low, high);
|
||||
return right;
|
||||
}
|
||||
if(root.val>high) {
|
||||
let left = trimBST(root.left,low,high);
|
||||
if(root.val > high) {
|
||||
let left = trimBST(root.left, low, high);
|
||||
return left;
|
||||
}
|
||||
root.left = trimBST(root.left,low,high);
|
||||
root.right = trimBST(root.right,low,high);
|
||||
root.left = trimBST(root.left, low, high);
|
||||
root.right = trimBST(root.right, low, high);
|
||||
return root;
|
||||
}
|
||||
```
|
||||
|
@ -197,7 +197,6 @@ class Solution:
|
||||
if not visited[i][j] and grid[i][j] == 1:
|
||||
# 每一个新岛屿
|
||||
self.count = 0
|
||||
print(f'{self.count}')
|
||||
self.bfs(grid, visited, i, j)
|
||||
result = max(result, self.count)
|
||||
|
||||
|
@ -24,7 +24,7 @@
|
||||
|
||||
## 思路
|
||||
|
||||
之前我们讲了都是普通二叉树,那么接下来看看二叉搜索树。
|
||||
之前我们讲的都是普通二叉树,那么接下来看看二叉搜索树。
|
||||
|
||||
在[关于二叉树,你该了解这些!](https://programmercarl.com/二叉树理论基础.html)中,我们已经讲过了二叉搜索树。
|
||||
|
||||
@ -269,13 +269,13 @@ class Solution:
|
||||
```go
|
||||
//递归法
|
||||
func searchBST(root *TreeNode, val int) *TreeNode {
|
||||
if root==nil||root.Val==val{
|
||||
if root == nil || root.Val == val {
|
||||
return root
|
||||
}
|
||||
if root.Val>val{
|
||||
return searchBST(root.Left,val)
|
||||
if root.Val > val {
|
||||
return searchBST(root.Left, val)
|
||||
}
|
||||
return searchBST(root.Right,val)
|
||||
return searchBST(root.Right, val)
|
||||
}
|
||||
```
|
||||
|
||||
@ -284,13 +284,13 @@ func searchBST(root *TreeNode, val int) *TreeNode {
|
||||
```go
|
||||
//迭代法
|
||||
func searchBST(root *TreeNode, val int) *TreeNode {
|
||||
for root!=nil{
|
||||
if root.Val>val{
|
||||
root=root.Left
|
||||
}else if root.Val<val{
|
||||
root=root.Right
|
||||
}else{
|
||||
break
|
||||
for root != nil {
|
||||
if root.Val > val {
|
||||
root = root.Left
|
||||
} else if root.Val < val {
|
||||
root = root.Right
|
||||
} else {
|
||||
return root
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@ -414,6 +414,56 @@ object Solution {
|
||||
}
|
||||
```
|
||||
|
||||
### rust
|
||||
|
||||
递归:
|
||||
|
||||
```rust
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
impl Solution {
|
||||
pub fn search_bst(
|
||||
root: Option<Rc<RefCell<TreeNode>>>,
|
||||
val: i32,
|
||||
) -> Option<Rc<RefCell<TreeNode>>> {
|
||||
if root.is_none() || root.as_ref().unwrap().borrow().val == val {
|
||||
return root;
|
||||
}
|
||||
let node_val = root.as_ref().unwrap().borrow().val;
|
||||
if node_val > val {
|
||||
return Self::search_bst(root.as_ref().unwrap().borrow().left.clone(), val);
|
||||
}
|
||||
if node_val < val {
|
||||
return Self::search_bst(root.unwrap().borrow().right.clone(), val);
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
迭代:
|
||||
|
||||
```rust
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::cmp;
|
||||
impl Solution {
|
||||
pub fn search_bst(
|
||||
mut root: Option<Rc<RefCell<TreeNode>>>,
|
||||
val: i32,
|
||||
) -> Option<Rc<RefCell<TreeNode>>> {
|
||||
while let Some(ref node) = root.clone() {
|
||||
match val.cmp(&node.borrow().val) {
|
||||
cmp::Ordering::Less => root = node.borrow().left.clone(),
|
||||
cmp::Ordering::Equal => return root,
|
||||
cmp::Ordering::Greater => root = node.borrow().right.clone(),
|
||||
};
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<p align="center">
|
||||
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
|
||||
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>
|
||||
|
@ -24,7 +24,7 @@
|
||||
|
||||
# 思路
|
||||
|
||||
其实这道题目其实是一道简单题目,**但是题目中的提示:有多种有效的插入方式,还可以重构二叉搜索树,一下子吓退了不少人**,瞬间感觉题目复杂了很多。
|
||||
这道题目其实是一道简单题目,**但是题目中的提示:有多种有效的插入方式,还可以重构二叉搜索树,一下子吓退了不少人**,瞬间感觉题目复杂了很多。
|
||||
|
||||
其实**可以不考虑题目中提示所说的改变树的结构的插入方式。**
|
||||
|
||||
@ -157,7 +157,7 @@ public:
|
||||
|
||||
我之所以举这个例子,是想说明通过递归函数的返回值完成父子节点的赋值是可以带来便利的。
|
||||
|
||||
**网上千变一律的代码,可能会误导大家认为通过递归函数返回节点 这样的写法是天经地义,其实这里是有优化的!**
|
||||
**网上千篇一律的代码,可能会误导大家认为通过递归函数返回节点 这样的写法是天经地义,其实这里是有优化的!**
|
||||
|
||||
|
||||
## 迭代
|
||||
@ -197,7 +197,7 @@ public:
|
||||
|
||||
首先在二叉搜索树中的插入操作,大家不用恐惧其重构搜索树,其实根本不用重构。
|
||||
|
||||
然后在递归中,我们重点讲了如果通过递归函数的返回值完成新加入节点和其父节点的赋值操作,并强调了搜索树的有序性。
|
||||
然后在递归中,我们重点讲了如何通过递归函数的返回值完成新加入节点和其父节点的赋值操作,并强调了搜索树的有序性。
|
||||
|
||||
最后依然给出了迭代的方法,迭代的方法就需要记录当前遍历节点的父节点了,这个和没有返回值的递归函数实现的代码逻辑是一样的。
|
||||
|
||||
|
@ -106,14 +106,11 @@ public:
|
||||
// 在第index个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
|
||||
// 如果index 等于链表的长度,则说明是新插入的节点为链表的尾结点
|
||||
// 如果index大于链表的长度,则返回空
|
||||
// 如果index小于0,则置为0,作为链表的新头节点。
|
||||
// 如果index小于0,则在头部插入节点
|
||||
void addAtIndex(int index, int val) {
|
||||
if (index > _size) {
|
||||
return;
|
||||
}
|
||||
if (index < 0) {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
if(index > _size) return;
|
||||
if(index < 0) index = 0;
|
||||
LinkedNode* newNode = new LinkedNode(val);
|
||||
LinkedNode* cur = _dummyHead;
|
||||
while(index--) {
|
||||
|
@ -617,6 +617,75 @@ func _binaryTreePaths3(_ root: TreeNode, res: inout [String], paths: inout [Int]
|
||||
}
|
||||
```
|
||||
|
||||
### Rust
|
||||
|
||||
> 100.相同的树
|
||||
|
||||
```rsut
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
impl Solution {
|
||||
pub fn is_same_tree(
|
||||
p: Option<Rc<RefCell<TreeNode>>>,
|
||||
q: Option<Rc<RefCell<TreeNode>>>,
|
||||
) -> bool {
|
||||
match (p, q) {
|
||||
(None, None) => true,
|
||||
(None, Some(_)) => false,
|
||||
(Some(_), None) => false,
|
||||
(Some(n1), Some(n2)) => {
|
||||
if n1.borrow().val == n2.borrow().val {
|
||||
let right =
|
||||
Self::is_same_tree(n1.borrow().left.clone(), n2.borrow().left.clone());
|
||||
let left =
|
||||
Self::is_same_tree(n1.borrow().right.clone(), n2.borrow().right.clone());
|
||||
right && left
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> 257.二叉树的不同路径
|
||||
|
||||
```rust
|
||||
// 递归
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
impl Solution {
|
||||
pub fn binary_tree_paths(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<String> {
|
||||
let mut res = vec![];
|
||||
let mut path = vec![];
|
||||
Self::recur(&root, &mut path, &mut res);
|
||||
res
|
||||
}
|
||||
|
||||
pub fn recur(
|
||||
root: &Option<Rc<RefCell<TreeNode>>>,
|
||||
path: &mut Vec<String>,
|
||||
res: &mut Vec<String>,
|
||||
) {
|
||||
let node = root.as_ref().unwrap().borrow();
|
||||
path.push(node.val.to_string());
|
||||
if node.left.is_none() && node.right.is_none() {
|
||||
res.push(path.join("->"));
|
||||
return;
|
||||
}
|
||||
if node.left.is_some() {
|
||||
Self::recur(&node.left, path, res);
|
||||
path.pop(); //回溯
|
||||
}
|
||||
if node.right.is_some() {
|
||||
Self::recur(&node.right, path, res);
|
||||
path.pop(); //回溯
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
<p align="center">
|
||||
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
|
||||
|
Reference in New Issue
Block a user