什么问题
- 组合问题:N个数里面按一定规则找出k个数的集合
- 切割问题:一个字符串按一定规则有几种切割方式
- 子集问题:一个N个数的集合里有多少符合条件的子集
- 排列问题:N个数按一定规则全排列,有几种排列方式
- 棋盘问题:N皇后,解数独等等
模板-三部曲
- 返回值 参数
- 终止条件
- 回溯搜索的遍历过程
for循环可以理解是横向遍历,backtracking(递归)就是纵向遍历,
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
组合问题
给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。
回溯剪枝
剪枝:如果for循环选择的起始位置之后的元素个数 已经不足 我们需要的元素个数了,那么就没有必要搜索了。
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)
return
}
for i:=start;i<=n-(k-len(track))+1;i++{
track = append(track,i)
backtrack(n,k,i+1,track)
track = track[:len(track)-1]
}
}
组合总和3
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
- 本题k相当于了树的深度,9(因为整个集合就是9个数)就是树的宽度。
func combinationSum31(k int, n int) [][]int {
var res [][]int
var path []int
sum := 0
var backTrack func(int)
backTrack = func(start int){
// 剪枝,如果当前路径和已经大于n或者路径长度已经大于k,后续遍历就没有意义了
if sum > n || len(path) > k{
return
}
// 递归终止条件
// 如果当前路径长度为k,且路径和等于n,便找到了一条满足要求的路径
if len(path) == k && sum == n{
temp := make([]int, k)
copy(temp, path)
res = append(res, temp)
return
}
// for循环遍历(剪枝)
for i:=start;i<=9-(k-len(path))+1;i++{
// 处理每一个节点
sum += i
path = append(path, i)
// 递归
backTrack(i+1)
// 回溯
sum -= i
path = path[:len(path)-1]
}
}
backTrack(1)
return res
}
电话号码的数字组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
- 数字和字母如何映射
- 两个字母就两个for循环,三个字符我就三个for循环,以此类推,然后发现代码根本写不出来
- 输入1 * #按键等等异常情况
func letterCombinations(digits string) []string {
length := len(digits)
if length==0 || length>4{
return nil
}
digitsMap := [10]string{
"",
"",
"abc",
"def",
"ghi",
"jkl",
"mno",
"pqrs",
"tuv",
"wxyz",
}
res := []string{}
var tempstring string
var backtrack func(index int)
backtrack = func(index int){
if len(tempstring) == len(digits){
var copystring string
copystring = tempstring
res = append(res,copystring)
return
}
tmpk := digits[index]-'0'
letter := digitsMap[tmpk]
for i:=0;i<len(letter);i++{
tempstring = tempstring+string(letter[i])
backtrack(index+1)
tempstring = tempstring[:len(tempstring)-1]
}
}
backtrack(0)
return res
}
组合总和--可以重复选取
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
func combinationSum1(candidates []int, target int) [][]int {
sum := 0
temp := []int{}
res := [][]int{}
var backtrack func(start int)
backtrack = func(start int){
if sum > target{
return
}
if sum == target{
copytemp := make([]int,len(temp))
copy(copytemp,temp)
res = append(res,copytemp)
return
}
for i:= start;i<len(candidates);i++{
temp = append(temp,candidates[i])
sum += candidates[i]
backtrack(i)
sum-=candidates[i]
temp = temp[:len(temp)-1]
}
}
backtrack(0)
return res
}
组合总和2---只能使用一次。
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
先排序
两点:
- 本层取过就不能取了
- 树枝上要传入i+1
//两个维度去重复,横向for去重,先排序;纵向通过start
func combinationSum2(candidates []int, target int) [][]int {
sum := 0
track := []int{}
res := [][]int{}
sort.Ints(candidates)
var backtrack func(start int)
backtrack = func(start int){
if sum >target{
return
}
if sum == target{
temp := make([]int,len(track))
copy(temp,track)
res =append(res,temp)
return
}
for i:=start;i<len(candidates);i++{
if i > start && candidates[i]==candidates[i-1]{
continue
}
track = append(track,candidates[i])
sum += candidates[i]
backtrack(i+1)
sum-=candidates[i]
track = track[:len(track)-1]
}
}
backtrack(0)
return res
}
分割回文串
给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
返回 s 所有可能的分割方案。
两点:
- 切割问题,有不同的切割方式
- 判断回文
func isPartition(s string,start,end int) bool {
left := start
right := end
for left < right{
if s[left] == s[right]{
left++
right--
}else{
return false
}
}
return true
}
func partition(s string) [][]string {
res := make([][]string,0)
var temp []string
var backtrack func(start int)
backtrack = func(start int){
if start == len(s){
t := make([]string,len(temp))
copy(t,temp)
res = append(res,t)
}
for i:=start;i<len(s);i++{
if isPartition(s,start,i){
temp = append(temp,s[start:i+1])
}else{
continue
}
backtrack(i+1)
temp = temp[:len(temp)-1]
}
}
backtrack(0)
return res
}
复原ip地址
给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。
有效的 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 '.'
- 判断字符串符不符合ip条件
终止条件 start = len(s)
- 另外注意是先加入path,再去怕判断长度是不是超了,如果超了那就直接返回上一层,注意上一层的path是没有加入新元素的,也就是说还是len=4的path,然后更新path len=3
func isNormalIP(s string,start,end int)bool {
if end-start+1 > 1 && s[start]=='0'{
return false
}
checkInt,_ := strconv.Atoi(s[start:end+1])
if checkInt > 255 {
return false
}
for i :=start;i<=end;i++{
if s[i] > '9' || s[i] <'0' {
return false
}
}
return true
}
func restoreIpAddresses(s string)[]string {
var res,path []string
var backTracking2 func(start int,path []string)
backTracking2 = func(start int,path []string){
if start == len(s) && len(path) == 4{
copypath := make([]string,len(path))
copy(copypath,path)
res = append(res ,string(strings.Join(copypath,".")) )
}
for i:=start;i<len(s);i++{
path = append(path,s[start:i+1])
if i-start+1<=3 && isNormalIP(s,start,i)&&len(path)<=4{
backTracking2(i+1,path)
}else {
//如果首尾超过了3个,或路径多余4个,或前导为0,或大于255,直接回退
return
}
path = path[:len(path)-1]
}
}
backTracking2(0,path)
return res
}
子集
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例: 输入: nums = [1,2,3] 输出: [ [3], [1], [2], [1,2,3], [1,3], [2,3], [1,2], [] ]
- 如果把 子集问题、组合问题、分割问题都抽象为一棵树的话,那么组合问题和分割问题都是收集树的叶子节点,而子集问题是找树的所有节点!
///先放一个空集进去
func subsets(nums []int) [][]int {
res := make([][]int,0)
temp := []int{}
var backtrack func(start int)
backtrack = func(start int){
tmp := make([]int,len(temp))
copy(tmp,temp)
res = append(res,tmp)
for i:=start;i<len(nums);i++{
temp =append(temp,nums[i])
backtrack(i+1)
temp = temp[:len(temp)-1]
}
}
sort.Ints(nums)
backtrack(0)
return res
}
子集2-集合里有重复元素,求取的子集要去重
给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
-排序
func subsetsWithDup(nums []int) [][]int {
var temp []int
res := make([][]int,0)
var backtrack func(start int)
backtrack = func(start int){
tmp := make([]int,len(temp))
copy(tmp,temp)
res = append(res,tmp)
if start == len(nums){
return
}
for i:=start;i<len(nums);i++{
if i> start &&nums[i]==nums[i-1]{
continue
}
temp = append(temp,nums[i])
backtrack(i+1)
temp = temp[:len(temp)-1]
}
}
sort.Ints(nums)
backtrack(0)
return res
}
递增子序列 -- 不可以排序
给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是2。
不能有相同的递增子序列。
func findSubsequences(nums []int) [][]int {
var res [][]int
backTring(0,nums,[]int{},&res)
return res
}
func backTring(start int,nums,subRes []int,res *[][]int) {
if len(subRes)>1 {
tmp := make([]int, len(subRes))
copy(tmp, subRes)
*res = append(*res, tmp)
}
//注意她在每一层都要开辟一个history去记录这一层的信息,有没有重复
//history := [201]int{}
history := make(map[int]bool)
for i :=start;i<len(nums);i++{
//分两种情况判断:一,当前取的元素小于子集的最后一个元素,则继续寻找下一个适合的元素
//二,当前取的元素在本层已经出现过了,所以跳过该元素,继续寻找
if len(subRes)>0 && nums[i]<subRes[len(subRes)-1] || history[nums[i]] {
continue
}
history[nums[i]] = true
subRes = append(subRes,nums[i])
backTring(i+1,nums,subRes,res)
subRes = subRes[:len(subRes)-1]
}
}
全排列
给定一个 没有重复 数字的序列,返回其所有可能的全排列。
- 输入: [1,2,3]
- 输出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]
func permute1(nums []int) [][]int {
visited := make(map[int]bool)
res := [][]int{}
var backtrack func(path []int)
backtrack = func(path []int){
if len(nums) == len(path){
p := make([]int,len(path))
copy(p,path)
res = append(res,p)
return
}
for i:=0;i<len(nums);i++{
if visited[nums[i]]{
continue
}
cur := nums[i]
path = append(path,cur)
visited[nums[i]] = true
backtrack(path)
visited[nums[i]] = false
path = path[:len(path)-1]
}
}
backtrack([]int{})
return res
}
全排列2 --包含重复数字---去重复排序
给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有包含重复数字。
-
还要强调的是去重一定要对元素进行排序,这样我们才方便通过相邻的节点来判断是否重复使用了。
-
i从0开始,所以nums[i] == nums[i-1]可能是同一层也可能是同一树枝上 // 同树枝的时候会满足nums[i] == nums[i-1],但是如果visied[i-1]=true就证明是同树,是可以的
func permuteUnique(nums []int) [][]int {
visited := make(map[int]bool)
res := [][]int{}
var backtrack func(path []int)
backtrack = func(path []int){
if len(nums) == len(path){
p := make([]int,len(path))
copy(p,path)
res = append(res,p)
return
}
//i从0开始,所以nums[i] == nums[i-1]可能是同一层也可能是同一树枝上
// 同树枝的时候会满足nums[i] == nums[i-1],但是如果visied[i-1]=true就证明是同树,是可以的
// 同层的话如果visied[i-1]==false,那么就要continue
//去重去的是层
for i:=0;i<len(nums);i++{
if i>0 && nums[i] == nums[i-1]&& !visited[i-1]{
continue
}
if visited[i]{
continue
}
cur := nums[i]
path = append(path,cur)
visited[i] = true
backtrack(path)
visited[i] = false
path = path[:len(path)-1]
}
}
sort.Ints(nums)
backtrack([]int{})
return res
}
n皇后
皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。
首先来看一下皇后们的约束条件:
- 不能同行
- 不能同列
- 不能同斜线
func solveNQueens(n int)[][]string{
bd := make([][]string,n)
for i:=0;i<len(bd);i++{
bd[i] = make([]string,n)
for j :=0;j<len(bd[i]);j++{
bd[i][j] = "."
}
}
res := [][]string{}
//r 代表着行
var helper func(r int)
helper = func(r int){
if r== n{
//tmp存的是字符串切片,每一位代表一行
tmp := make([]string,n)
for i:=0;i<len(dp);i+{
tmp[i] = strings.Join(bs[i],"")
}
res = append(res,tmp)
}
for col:=0;col<n;col++{
if !isVakid(bd,r,col){
continue
}
bd[r][col] = "Q"
helper1(r+1)
bd[r][col] = "."
}
}
helper(0)
return res
}
func isVakid(bd [][]string,row,col int)bool{
//lie
for i:=0;i<row;i++{
if bd[row][col]=="Q"{
return false
}
}
for i,j:=row-1,col-1;i>=0&&j>=0;i,j = i-1,j-1{
if bd[i][j] = "Q"{
return false
}
}
for i,j := row-1,col+1;i>=0&&j<len(bd);i,j = i-1,j+1{
if bd[i][j] = "Q"{
return false
}
}
}
解数独
编写一个程序,通过填充空格来解决数独问题。
数字 1-9 在每一行只能出现一次。 数字 1-9 在每一列只能出现一次。 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。 空白格用 '.' 表示。
func solveSudoku(board [][]byte) {
full(board)
}
//两个for循环去遍历棋盘
//不用设置终止条件,应为只有一个解,不符合的直接返回false回溯
func full(board [][]byte)bool {
for i :=0;i<9;i++{
for j:=0;j<9;j++{
//判断此位置是否合适填数字
if board[i][j] != '.'{
continue
}
//尝试1-9
for k:='1';k<='9';k++{
if isvalid(i,j ,byte(k),board)==true{
board[i][j] = byte(k)
if full(board) == true{
return true
}
board[i][j] = '.'
}
}
return false
}
}
return true
}
func isvalid(row,col int,k byte,board [][]byte)bool {
for i:=0;i<9;i++{//行
if board[row][i]==k{
return false
}
}
for i:=0;i<9;i++{//列
if board[i][col]==k{
return false
}
}
//方格 计算方格左上起点(row/3)*3(col/3)*3
startrow:=(row/3)*3
startcol:=(col/3)*3
for i:=startrow;i<startrow+3;i++{
for j:=startcol;j<startcol+3;j++{
if board[i][j]==k{
return false
}
}
}
return true
}