mirror of
https://github.com/youngyangyang04/leetcode-master.git
synced 2025-07-08 00:43:04 +08:00
Merge pull request #1810 from juguagua/leetcode-modify-the-code-of-the-backtracking
更新回溯部分:主要修改错字 和 替换 go 代码(原代码风格较差)
This commit is contained in:
@ -42,7 +42,7 @@ candidates 中的数字可以无限制重复被选取。
|
|||||||
|
|
||||||
题目中的**无限制重复被选取,吓得我赶紧想想 出现0 可咋办**,然后看到下面提示:1 <= candidates[i] <= 200,我就放心了。
|
题目中的**无限制重复被选取,吓得我赶紧想想 出现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
|
```go
|
||||||
|
var (
|
||||||
|
res [][]int
|
||||||
|
path []int
|
||||||
|
)
|
||||||
func combinationSum(candidates []int, target int) [][]int {
|
func combinationSum(candidates []int, target int) [][]int {
|
||||||
var trcak []int
|
res, path = make([][]int, 0), make([]int, 0, len(candidates))
|
||||||
var res [][]int
|
sort.Ints(candidates) // 排序,为剪枝做准备
|
||||||
backtracking(0,0,target,candidates,trcak,&res)
|
dfs(candidates, 0, target)
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
func backtracking(startIndex,sum,target int,candidates,trcak []int,res *[][]int){
|
|
||||||
//终止条件
|
func dfs(candidates []int, start int, target int) {
|
||||||
if sum==target{
|
if target == 0 { // target 不断减小,如果为0说明达到了目标值
|
||||||
tmp:=make([]int,len(trcak))
|
tmp := make([]int, len(path))
|
||||||
copy(tmp,trcak)//拷贝
|
copy(tmp, path)
|
||||||
*res=append(*res,tmp)//放入结果集
|
res = append(res, tmp)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if sum>target{return}
|
for i := start; i < len(candidates); i++ {
|
||||||
//回溯
|
if candidates[i] > target { // 剪枝,提前返回
|
||||||
for i:=startIndex;i<len(candidates);i++{
|
break
|
||||||
//更新路径集合和sum
|
}
|
||||||
trcak=append(trcak,candidates[i])
|
path = append(path, candidates[i])
|
||||||
sum+=candidates[i]
|
dfs(candidates, i, target - candidates[i])
|
||||||
//递归
|
path = path[:len(path) - 1]
|
||||||
backtracking(i,sum,target,candidates,trcak,res)
|
|
||||||
//回溯
|
|
||||||
trcak=trcak[:len(trcak)-1]
|
|
||||||
sum-=candidates[i]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -110,13 +110,13 @@ if (sum == target) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`sum > target` 这个条件其实可以省略,因为和在递归单层遍历的时候,会有剪枝的操作,下面会介绍到。
|
`sum > target` 这个条件其实可以省略,因为在递归单层遍历的时候,会有剪枝的操作,下面会介绍到。
|
||||||
|
|
||||||
* **单层搜索的逻辑**
|
* **单层搜索的逻辑**
|
||||||
|
|
||||||
这里与[39.组合总和](https://programmercarl.com/0039.组合总和.html)最大的不同就是要去重了。
|
这里与[39.组合总和](https://programmercarl.com/0039.组合总和.html)最大的不同就是要去重了。
|
||||||
|
|
||||||
前面我们提到:要去重的是“同一树层上的使用过”,如果判断同一树层上元素(相同的元素)是否使用过了呢。
|
前面我们提到:要去重的是“同一树层上的使用过”,如何判断同一树层上元素(相同的元素)是否使用过了呢。
|
||||||
|
|
||||||
**如果`candidates[i] == candidates[i - 1]` 并且 `used[i - 1] == false`,就说明:前一个树枝,使用了candidates[i - 1],也就是说同一树层使用过candidates[i - 1]**。
|
**如果`candidates[i] == candidates[i - 1]` 并且 `used[i - 1] == false`,就说明:前一个树枝,使用了candidates[i - 1],也就是说同一树层使用过candidates[i - 1]**。
|
||||||
|
|
||||||
@ -438,76 +438,74 @@ class Solution:
|
|||||||
|
|
||||||
**使用used数组**
|
**使用used数组**
|
||||||
```go
|
```go
|
||||||
|
var (
|
||||||
|
res [][]int
|
||||||
|
path []int
|
||||||
|
used []bool
|
||||||
|
)
|
||||||
func combinationSum2(candidates []int, target int) [][]int {
|
func combinationSum2(candidates []int, target int) [][]int {
|
||||||
var trcak []int
|
res, path = make([][]int, 0), make([]int, 0, len(candidates))
|
||||||
var res [][]int
|
used = make([]bool, len(candidates))
|
||||||
var history map[int]bool
|
sort.Ints(candidates) // 排序,为剪枝做准备
|
||||||
history=make(map[int]bool)
|
dfs(candidates, 0, target)
|
||||||
sort.Ints(candidates)
|
|
||||||
backtracking(0,0,target,candidates,trcak,&res,history)
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
func backtracking(startIndex,sum,target int,candidates,trcak []int,res *[][]int,history map[int]bool){
|
|
||||||
//终止条件
|
func dfs(candidates []int, start int, target int) {
|
||||||
if sum==target{
|
if target == 0 { // target 不断减小,如果为0说明达到了目标值
|
||||||
tmp:=make([]int,len(trcak))
|
tmp := make([]int, len(path))
|
||||||
copy(tmp,trcak)//拷贝
|
copy(tmp, path)
|
||||||
*res=append(*res,tmp)//放入结果集
|
res = append(res, tmp)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if sum>target{return}
|
for i := start; i < len(candidates); i++ {
|
||||||
//回溯
|
if candidates[i] > target { // 剪枝,提前返回
|
||||||
|
break
|
||||||
|
}
|
||||||
// used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
|
// used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
|
||||||
// used[i - 1] == false,说明同一树层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] && used[i-1] == false {
|
||||||
if i>0&&candidates[i]==candidates[i-1]&&history[i-1]==false{
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
//更新路径集合和sum
|
path = append(path, candidates[i])
|
||||||
trcak=append(trcak,candidates[i])
|
used[i] = true
|
||||||
sum+=candidates[i]
|
dfs(candidates, i+1, target - candidates[i])
|
||||||
history[i]=true
|
used[i] = false
|
||||||
//递归
|
path = path[:len(path) - 1]
|
||||||
backtracking(i+1,sum,target,candidates,trcak,res,history)
|
|
||||||
//回溯
|
|
||||||
trcak=trcak[:len(trcak)-1]
|
|
||||||
sum-=candidates[i]
|
|
||||||
history[i]=false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
**不使用used数组**
|
**不使用used数组**
|
||||||
```go
|
```go
|
||||||
|
var (
|
||||||
|
res [][]int
|
||||||
|
path []int
|
||||||
|
)
|
||||||
func combinationSum2(candidates []int, target int) [][]int {
|
func combinationSum2(candidates []int, target int) [][]int {
|
||||||
var trcak []int
|
res, path = make([][]int, 0), make([]int, 0, len(candidates))
|
||||||
var res [][]int
|
sort.Ints(candidates) // 排序,为剪枝做准备
|
||||||
sort.Ints(candidates)
|
dfs(candidates, 0, target)
|
||||||
backtracking(0,0,target,candidates,trcak,&res)
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
func backtracking(startIndex,sum,target int,candidates,trcak []int,res *[][]int){
|
|
||||||
//终止条件
|
func dfs(candidates []int, start int, target int) {
|
||||||
if sum==target{
|
if target == 0 { // target 不断减小,如果为0说明达到了目标值
|
||||||
tmp:=make([]int,len(trcak))
|
tmp := make([]int, len(path))
|
||||||
//拷贝
|
copy(tmp, path)
|
||||||
copy(tmp,trcak)
|
res = append(res, tmp)
|
||||||
//放入结果集
|
|
||||||
*res=append(*res,tmp)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//回溯
|
for i := start; i < len(candidates); i++ {
|
||||||
for i:=startIndex;i<len(candidates) && sum+candidates[i]<=target;i++{
|
if candidates[i] > target { // 剪枝,提前返回
|
||||||
// 若当前树层有使用过相同的元素,则跳过
|
break
|
||||||
if i>startIndex&&candidates[i]==candidates[i-1]{
|
}
|
||||||
|
// i != start 限制了这不对深度遍历到达的此值去重
|
||||||
|
if i != start && candidates[i] == candidates[i-1] { // 去重
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
//更新路径集合和sum
|
path = append(path, candidates[i])
|
||||||
trcak=append(trcak,candidates[i])
|
dfs(candidates, i+1, target - candidates[i])
|
||||||
sum+=candidates[i]
|
path = path[:len(path) - 1]
|
||||||
backtracking(i+1,sum,target,candidates,trcak,res)
|
|
||||||
//回溯
|
|
||||||
trcak=trcak[:len(trcak)-1]
|
|
||||||
sum-=candidates[i]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -227,24 +227,24 @@ class Solution:
|
|||||||
|
|
||||||
## Go
|
## Go
|
||||||
```Go
|
```Go
|
||||||
var res [][]int
|
var (
|
||||||
func subset(nums []int) [][]int {
|
path []int
|
||||||
res = make([][]int, 0)
|
res [][]int
|
||||||
sort.Ints(nums)
|
)
|
||||||
Dfs([]int{}, nums, 0)
|
func subsets(nums []int) [][]int {
|
||||||
|
res, path = make([][]int, 0), make([]int, 0, len(nums))
|
||||||
|
dfs(nums, 0)
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
func Dfs(temp, nums []int, start int){
|
func dfs(nums []int, start int) {
|
||||||
tmp := make([]int, len(temp))
|
tmp := make([]int, len(path))
|
||||||
copy(tmp, temp)
|
copy(tmp, path)
|
||||||
res = append(res, tmp)
|
res = append(res, tmp)
|
||||||
|
|
||||||
for i := start; i < len(nums); i++ {
|
for i := start; i < len(nums); i++ {
|
||||||
//if i>start&&nums[i]==nums[i-1]{
|
path = append(path, nums[i])
|
||||||
// continue
|
dfs(nums, i+1)
|
||||||
//}
|
path = path[:len(path)-1]
|
||||||
temp = append(temp, nums[i])
|
|
||||||
Dfs(temp, nums, i+1)
|
|
||||||
temp = temp[:len(temp)-1]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -424,6 +424,42 @@ class Solution:
|
|||||||
return True
|
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
|
## 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
|
||||||
|
|
||||||
```Rust
|
```Rust
|
||||||
|
@ -43,8 +43,8 @@
|
|||||||
|
|
||||||
例如对于字符串abcdef:
|
例如对于字符串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] 就是要截取的子串。
|
在`for (int i = startIndex; i < s.size(); i++)`循环中,我们 定义了起始位置startIndex,那么 [startIndex, i] 就是要截取的子串。
|
||||||
|
|
||||||
@ -126,7 +126,7 @@ for (int i = startIndex; i < s.size(); i++) {
|
|||||||
|
|
||||||
最后我们看一下回文子串要如何判断了,判断一个字符串是否是回文。
|
最后我们看一下回文子串要如何判断了,判断一个字符串是否是回文。
|
||||||
|
|
||||||
可以使用双指针法,一个指针从前向后,一个指针从后先前,如果前后指针所指向的元素是相等的,就是回文字符串了。
|
可以使用双指针法,一个指针从前向后,一个指针从后向前,如果前后指针所指向的元素是相等的,就是回文字符串了。
|
||||||
|
|
||||||
那么判断回文的C++代码如下:
|
那么判断回文的C++代码如下:
|
||||||
|
|
||||||
@ -295,7 +295,7 @@ public:
|
|||||||
|
|
||||||
除了这些难点,**本题还有细节,例如:切割过的地方不能重复切割所以递归函数需要传入i + 1**。
|
除了这些难点,**本题还有细节,例如:切割过的地方不能重复切割所以递归函数需要传入i + 1**。
|
||||||
|
|
||||||
所以本题应该是一个道hard题目了。
|
所以本题应该是一道hard题目了。
|
||||||
|
|
||||||
**可能刷过这道题目的录友都没感受到自己原来克服了这么多难点,就把这道题目AC了**,这应该叫做无招胜有招,人码合一,哈哈哈。
|
**可能刷过这道题目的录友都没感受到自己原来克服了这么多难点,就把这道题目AC了**,这应该叫做无招胜有招,人码合一,哈哈哈。
|
||||||
|
|
||||||
@ -432,45 +432,39 @@ class Solution:
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Go
|
## Go
|
||||||
**注意切片(go切片是披着值类型外衣的引用类型)**
|
|
||||||
```go
|
```go
|
||||||
|
var (
|
||||||
|
path []string // 放已经回文的子串
|
||||||
|
res [][]string
|
||||||
|
)
|
||||||
func partition(s string) [][]string {
|
func partition(s string) [][]string {
|
||||||
var tmpString []string//切割字符串集合
|
path, res = make([]string, 0), make([][]string, 0)
|
||||||
var res [][]string//结果集合
|
dfs(s, 0)
|
||||||
backTracking(s,tmpString,0,&res)
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
func backTracking(s string,tmpString []string,startIndex int,res *[][]string){
|
|
||||||
if startIndex==len(s){//到达字符串末尾了
|
func dfs(s string, start int) {
|
||||||
//进行一次切片拷贝,怕之后的操作影响tmpString切片内的值
|
if start == len(s) { // 如果起始位置等于s的大小,说明已经找到了一组分割方案了
|
||||||
t := make([]string, len(tmpString))
|
tmp := make([]string, len(path))
|
||||||
copy(t, tmpString)
|
copy(tmp, path)
|
||||||
*res=append(*res,t)
|
res = append(res, tmp)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
for i:=startIndex;i<len(s);i++{
|
for i := start; i < len(s); i++ {
|
||||||
//处理(首先通过startIndex和i判断切割的区间,进而判断该区间的字符串是否为回文,若为回文,则加入到tmpString,否则继续后移,找到回文区间)(这里为一层处理)
|
str := s[start : i+1]
|
||||||
if isPartition(s,startIndex,i){
|
if isPalindrome(str) { // 是回文子串
|
||||||
tmpString=append(tmpString,s[startIndex:i+1])
|
path = append(path, str)
|
||||||
}else{
|
dfs(s, i+1) // 寻找i+1为起始位置的子串
|
||||||
continue
|
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
|
func isPalindrome(s string) bool {
|
||||||
right:=end
|
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
|
||||||
for ;left<right;{
|
if s[i] != s[j] {
|
||||||
if s[left]!=s[right]{
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
//移动左右指针
|
|
||||||
left++
|
|
||||||
right--
|
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user