今日题目:
今日总结
- 复原IP地址是分割回文串的升级版,小细节比较多。区分使用continue和break剪枝的区别。
- 子集问题需要遍历整棵树,而不像组合问题只需要收获叶子节点。所以没有确定的终止条件,并且每层递归时都需要存储path。
93. 复原IP地址
要点
- 分割回文串的升级版,掌握流程了也不算难
- 记住string.Join 和 strconv.Atoi函数的用法
- 在终止条件中要确保start扫描到s的尾部,否则结果中会有长度小于s的IP地址
- 在剪枝时使用continue也可以,但是最好使用break。因为对于当前分割的IP地址,比如01,我们就不用考虑更长的IP的地址了比如010.
代码:
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'{
break //不用continue是因为这一层后续已经不用考虑了
}
str := s[start:i+1]
num, _ := strconv.Atoi(str)
if num > 255 || num < 0{
break
}
path = append(path, str)
dfs(s, i+1)
path = path[:len(path)-1]
}
}
78. 子集
要点:
- 与组合问题的不同点在于子集需要储存路径上的所有节点而不只是叶子节点。
- 这导致每次循环都需要存储path,并且没有特定的递归终止条件,即需要遍历整棵树。
var (
path []int
res [][]int
)
func subsets(nums []int) [][]int {
path, res = make([]int, 0), make([][]int, 0)
dfs(nums, 0)
return res
}
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]
}
}
90. 子集 II
要点:
- 子集问题与去重的结合。这里我们仍然采用排序去重的方式。
var (
path []int
res [][]int
)
func subsetsWithDup(nums []int) [][]int {
sort.Ints(nums)
path, res = make([]int, 0), make([][]int, 0)
dfs(nums, 0)
return res
}
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++{
if i > start && nums[i] == nums[i-1]{ //这里i > start 很关键
continue
}
path = append(path, nums[i])
dfs(nums, i+1)
path = path[:len(path)-1]
}
}