leetcode 回溯題目 golang語言

回溯算法實際上一個相似枚舉的搜索嘗試過程,主要是在搜索嘗試過程當中尋找問題的解,當發現已不知足求解條件時,就 「回溯」 返回,嘗試別的路徑。回溯法是一種選優搜索法,按選優條件向前搜索,以達到目標。但當探索到某一步時,發現原先選擇並不優或達不到目標,就退回一步從新選擇,這種走不通就退回再走的技術爲回溯法,而知足回溯條件的某個狀態的點稱爲 「回溯點」。許多複雜的,規模較大的問題均可以使用回溯法,有「通用解題方法」的美稱。 回溯算法的基本思想是:從一條路往前走,能進則進,不能進則退回來,換一條路再試。python

連接:https://leetcode-cn.com/tag/backtracking/
 來源:力扣(LeetCode)
 著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。
複製代碼

本文主要總結一下回溯算法的一些題目。語言主要是Golang。git

78.子集,不含重複元素

第一種是比較常規的回溯解法。算法

func subsets(nums []int) [][]int {
	result := make([][]int, 0)
	subsetsBT(&result, nums, []int{}, 0)
	return result
}
func subsetsBT(result *[][]int, nums []int, temp []int, start int) {
    //此處深拷貝temp,避免回溯的時候temp被修改後會影響以前保存的結果
	c := make([]int, len(temp))
	copy(c, temp)
	*result = append(*result, c)

	for i := start; i < len(nums); i++ {
		temp = append(temp, nums[i])
		subsetsBT(result, nums, temp, i+1)//不包含重複值
		temp = temp[:len(temp)-1]
	}
}
複製代碼

第二章方法就比較牛逼了,具體解釋參考此處。用二進制位的0,1表示是否選中當前位置的數。bash

func subsets(nums []int) [][]int {
	result := make([][]int, 0)
	n := 1 << uint(len(nums))
	for i := 0; i < n; i++ {
		temp := make([]int, 0)
		for j := 0; j < len(nums); j++ {
			if uint(i)>>uint(j)&1 == 1 {
				temp = append(temp, nums[j])
			}
		}
		result = append(result, temp)
	}

	return result
}
複製代碼

77.組合

常規解法。當temp裏的元素個數等於給定的K時,找到一個知足條件的解。app

func combine(n int, k int) [][]int {
	var result = make([][]int, 0)
	combineBT(n, k, 1, []int{}, &result)
	return result
}
func combineBT(n, k, start int, temp []int, result *[][]int) {
	if len(temp) == k {
		c := make([]int, len(temp))
		copy(c, temp)
		*result = append(*result, c)
		return
	}

	for i := start; i <= n; i++ {
		temp = append(temp, i)
		combineBT(n, k, i+1, temp, result)
		temp = temp[0 : len(temp)-1]
	}
}
複製代碼

39. 組合總和,不包含重複元素,可屢次使用

常規解法,要先排序一下。每次先嚐試減去當前元素,要是減去後還大於0,則表示能夠繼續往下走。而後由於能夠重複使用元素,因此回溯的時候從i開始繼續下一次。直到目標值減到0後,找到一個知足條件的解空間。ui

func combinationSum(candidates []int, target int) [][]int {
	var result = make([][]int, 0)
    sort.Ints(candidates)
	combinationSumBT(&result, candidates, []int{}, target, 0)
	return result
}
func combinationSumBT(result *[][]int, candidates []int, temp []int, target int, start int) {
	if target == 0 {
		c := make([]int, len(temp))
		copy(c, temp)
		*result = append(*result, c)
		return
	}
	for i := start; i < len(candidates); i++ {
		if target-candidates[i] >= 0 {
			target -= candidates[i]
			temp = append(temp, candidates[i])
			combinationSumBT(result, candidates, temp, target, i)//能夠包含已經用過的值,因此從i開始,
			temp = temp[0 : len(temp)-1]//回溯
			target += candidates[i]//得把當前用過的值再加回去。
		} else {
			return
		}
	}
}
複製代碼

40. 組合總和2,只能使用一次且解空間不能包含重複

和第一個很像,可是每一個數字只能用一次且解空間不能包含重複解。spa

func combinationSum2(candidates []int, target int) [][]int {
	sort.Ints(candidates)
	var result = make([][]int, 0)
	combinationSumBT2(&result, candidates, []int{}, target, 0)
	return result
}
func combinationSumBT2(result *[][]int, candidates []int, temp []int, target int, start int) {
	if target == 0 {
		c := make([]int, len(temp))
		copy(c, temp)
		*result = append(*result, c)
		return
	}
	for i := start; i < len(candidates); i++ {
		if target-candidates[i] >= 0 {
		//好比[10,1,2,7,6,1,5], target = 8
		//排好序後[1,1,2,5,6,7,10]
		//在第一個for循環裏,先遍歷到第一個1,通過一系列操做,獲得解集[1,7]
		//而後仍是第一個for循環裏,又遍歷到後面的1,如今是不須要[第二個1,7]這個解集了,因此跳過。
			if i != start && candidates[i] == candidates[i-1] { //由於解空間不能有重複
				continue
			}
			target -= candidates[i]
			temp = append(temp, candidates[i])
			combinationSumBT2(result, candidates, temp, target, i+1)//由於不能重複使用,因此從i+1開始
			temp = temp[0 : len(temp)-1]
			target += candidates[i]
		} else {
			return
		}
	}
}

複製代碼

216. 組合總和3,只有1-9,且每一個組合中不能有重複且最後的解空間不能包含重複

func combinationSum3(k int, n int) [][]int {
	var result = make([][]int, 0)
	combinationSumBT3(&result, []int{}, k, n, 1)
	return result
}
func combinationSumBT3(result *[][]int, temp []int, k int, target int, start int) {
    //和第一個很像,在target的基礎上增長了一個k的限制。
	if target == 0 && k == 0 {
		c := make([]int, len(temp))
		copy(c, temp)
		*result = append(*result, c)
		return
	}
	for i := start; i <= 9; i++ {
		if target-i >= 0 {
			target -= i
			k--
			temp = append(temp, i)
			combinationSumBT3(result, temp, k, target, i+1)//每一個組合不能有重複
			temp = temp[0 : len(temp)-1]
			target += i
			k++
		} else {
			return
		}
	}
}

複製代碼

17.電話號碼的字母組合

方法一是常規的回溯。code

var wordsMap = map[int]string{2: "abc", 3: "def", 4: "ghi", 5: "jkl", 6: "mno", 7: "pqrs", 8: "tuv", 9: "wxyz"}
func letterCombinations(digits string) []string {
    if len(digits) == 0 {
		return []string{}
	}
	answer := make([]string, 0)
	letterCombinationsBT(&answer, digits, "", 0)
	return answer
}
func letterCombinationsBT(answer *[]string, digits string, temp string, index int) {
    if len(temp) == len(digits) {
		*answer = append(*answer, temp)
		return
	}

	char := digits[index] - '0'
	letter := wordsMap[int(char)]
	//fmt.Println(int(char), letter)
	for i := 0; i < len(letter); i++ {
		letterCombinationsBT(answer, digits, temp+string(letter[i]), index+1)
	}

	return
}
複製代碼

方法二就比較牛逼了,把按的數字對應的字母依次放到隊列中,而後和下一個數字的字母挨個拼,拼完再扔到隊尾。 好比我按了 "23" 對應 abc 和 def 我先在隊列[從左到右表示隊首到隊尾]初始化一個空字符串。
" "
而後遍歷第一個數字 2 ,對應的字母是 abc,而後用隊列頭部的空字符串 "" 依次和abc作拼接,獲得 "a", "b", "c", 而後依次從隊尾扔到隊列,如今隊列是
a b c
遍歷完2對應的字母再繼續遍歷3的。3對應def。取出隊首的"a",依次和後面的def拼接,獲得 "ad", "ae", "af",而後扔到隊尾,如今隊列裏是
b c ad ae af
繼續重複這個操做便可完成最後的遍歷,很方便。排序

c ad ae af bd be bf隊列

ad ae af bd be bf cd ce cf

func letterCombinations(digits string) []string {
	if len(digits) == 0 {
		return []string{}
	}
	var words = [8]string{"abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"}
	queue := make([]string, 0)
	queue = append(queue, "")
	for i := 0; i < len(digits); i++ {
		n := digits[i] - '2'
		size := len(queue)
		for j := 0; j < size; j++ {
			st := queue[0]
			queue = queue[1:]
			for _, ch := range words[n] {
				temp := st + string(ch)
				queue = append(queue, temp)
			}
		}
	}
	return queue
}
複製代碼

79. 單詞搜索,一個格子裏的單詞不準重複使用

func exist(board [][]byte, word string) bool {
	if len(word) == 0 {
		return false
	}
	for i := 0; i < len(board); i++ {
		for j := 0; j < len(board[0]); j++ {
			if existWordsBT(board, word, i, j, 0) {
				return true
			}
		}
	}
	return false
}

var direction = [][]int{{-1, 0}, {0, -1}, {0, 1}, {1, 0}}

func existWordsBT(board [][]byte, word string, i, j, index int) bool {
    //遍歷到最後一個單詞的時候,要是等於就OK
	if index == len(word)-1 {
		return word[index] == board[i][j]
	}
	//fmt.Println(index, string(board[i][j]), visited[i][j])
	if board[i][j] == word[index] {
		temp := board[i][j]
		board[i][j] = '%' //標記當前字母被使用過了
		//visited[i][j] = 1
		for k := 0; k < 4; k++ { //套路,四個方向
			newX := i + direction[k][0]
			newY := j + direction[k][1]
			//四個新方向在格子內且沒被用過就能夠繼續下去了
			if newX >= 0 && newX < len(board) && newY >= 0 && newY < len(board[0]) && board[newX][newY] != '%' {
				if existWordsBT(board, word, newX, newY, index+1) {
					return true
				}
			}
		}
		//visited[i][j] = 0
		board[i][j] = temp //回溯到沒用過當前單詞
	}

	return false
}
複製代碼

695. 島嶼最大面積

func maxAreaOfIsland(grid [][]int) int {
	row, col := len(grid), len(grid[0])
	ret := 0
	for i := 0; i < row; i++ {
		for j := 0; j < col; j++ {
			if grid[i][j] == 1 {
				ret = int(math.Max(float64(dfs(grid, i, j, 0)), float64(ret)))
			}
		}
	}
	return ret
}

func dfs(grid [][]int, i int, j int, sum int) int {
	row, col := len(grid), len(grid[0])
	if i < 0 || j < 0 || i >= row || j >= col || grid[i][j] != 1 {
		return 0
	}

	sum++
	grid[i][j] = 9
	sum += dfs(grid, i-1, j, 0)
	sum += dfs(grid, i+1, j, 0)
	sum += dfs(grid, i, j-1, 0)
	sum += dfs(grid, i, j+1, 0)

	return sum
}
複製代碼

下面這種應該也是能夠的。並且從結果來看比第一張方法還快一些。

func maxAreaOfIsland2(grid [][]int) int {
	row, col := len(grid), len(grid[0])
	ret := 0
	for i := 0; i < row; i++ {
		for j := 0; j < col; j++ {
			ret = myMax(ret, maxAreaBT(grid, i, j, 0))
		}
	}
	return ret
}

func maxAreaBT(grid [][]int, i int, j int, sum int) int {
	row, col := len(grid), len(grid[0])
	if i < 0 || j < 0 || i >= row || j >= col || grid[i][j] != 1 {
		return 0
	}
	if grid[i][j] == 1 {
		grid[i][j] = 9
		sum++
		fmt.Println(i, j, sum)
		for k := 0; k < 4; k++ {
			newX := i + direction[k][0]
			newY := j + direction[k][1]
			if newX >= 0 && newY >= 0 && newX < row && newY < col && grid[newX][newY] == 1 {
				fmt.Println(99, newX, newY, sum, grid)
				sum = maxAreaBT(grid, newX, newY, sum)
			}
		}
		//grid[i][j] = 1
		return sum
	} else {
		return 0
	}

}

複製代碼
相關文章
相關標籤/搜索