diff --git a/problems/0017.电话号码的字母组合.md b/problems/0017.电话号码的字母组合.md index d506bb88..cf5e4520 100644 --- a/problems/0017.电话号码的字母组合.md +++ b/problems/0017.电话号码的字母组合.md @@ -287,98 +287,153 @@ class Solution { ``` ## Python -**回溯** +回溯 ```python class Solution: def __init__(self): - self.answers: List[str] = [] - self.answer: str = '' - self.letter_map = { - '2': 'abc', - '3': 'def', - '4': 'ghi', - '5': 'jkl', - '6': 'mno', - '7': 'pqrs', - '8': 'tuv', - '9': 'wxyz' - } - - def letterCombinations(self, digits: str) -> List[str]: - self.answers.clear() - if not digits: return [] + self.letterMap = [ + "", # 0 + "", # 1 + "abc", # 2 + "def", # 3 + "ghi", # 4 + "jkl", # 5 + "mno", # 6 + "pqrs", # 7 + "tuv", # 8 + "wxyz" # 9 + ] + self.result = [] + self.s = "" + + def backtracking(self, digits, index): + if index == len(digits): + self.result.append(self.s) + return + digit = int(digits[index]) # 将索引处的数字转换为整数 + letters = self.letterMap[digit] # 获取对应的字符集 + for i in range(len(letters)): + self.s += letters[i] # 处理字符 + self.backtracking(digits, index + 1) # 递归调用,注意索引加1,处理下一个数字 + self.s = self.s[:-1] # 回溯,删除最后添加的字符 + + def letterCombinations(self, digits): + if len(digits) == 0: + return self.result self.backtracking(digits, 0) - return self.answers - - def backtracking(self, digits: str, index: int) -> None: - # 回溯函数没有返回值 - # Base Case - if index == len(digits): # 当遍历穷尽后的下一层时 - self.answers.append(self.answer) - return - # 单层递归逻辑 - letters: str = self.letter_map[digits[index]] - for letter in letters: - self.answer += letter # 处理 - self.backtracking(digits, index + 1) # 递归至下一层 - self.answer = self.answer[:-1] # 回溯 + return self.result + ``` -**回溯简化** +回溯精简(版本一) ```python class Solution: def __init__(self): - self.answers: List[str] = [] - self.letter_map = { - '2': 'abc', - '3': 'def', - '4': 'ghi', - '5': 'jkl', - '6': 'mno', - '7': 'pqrs', - '8': 'tuv', - '9': 'wxyz' - } - - def letterCombinations(self, digits: str) -> List[str]: - self.answers.clear() - if not digits: return [] - self.backtracking(digits, 0, '') - return self.answers + self.letterMap = [ + "", # 0 + "", # 1 + "abc", # 2 + "def", # 3 + "ghi", # 4 + "jkl", # 5 + "mno", # 6 + "pqrs", # 7 + "tuv", # 8 + "wxyz" # 9 + ] + self.result = [] - def backtracking(self, digits: str, index: int, answer: str) -> None: - # 回溯函数没有返回值 - # Base Case - if index == len(digits): # 当遍历穷尽后的下一层时 - self.answers.append(answer) - return - # 单层递归逻辑 - letters: str = self.letter_map[digits[index]] + def getCombinations(self, digits, index, s): + if index == len(digits): + self.result.append(s) + return + digit = int(digits[index]) + letters = self.letterMap[digit] for letter in letters: - self.backtracking(digits, index + 1, answer + letter) # 递归至下一层 + 回溯 + self.getCombinations(digits, index + 1, s + letter) + + def letterCombinations(self, digits): + if len(digits) == 0: + return self.result + self.getCombinations(digits, 0, "") + return self.result + ``` -**使用itertools** +回溯精简(版本二) ```python class Solution: - def letterCombinations(self, digits: str) -> List[str]: - import itertools - if not digits: - return list() - - phoneMap = { - "2": "abc", - "3": "def", - "4": "ghi", - "5": "jkl", - "6": "mno", - "7": "pqrs", - "8": "tuv", - "9": "wxyz", - } + def __init__(self): + self.letterMap = [ + "", # 0 + "", # 1 + "abc", # 2 + "def", # 3 + "ghi", # 4 + "jkl", # 5 + "mno", # 6 + "pqrs", # 7 + "tuv", # 8 + "wxyz" # 9 + ] + + def getCombinations(self, digits, index, s, result): + if index == len(digits): + result.append(s) + return + digit = int(digits[index]) + letters = self.letterMap[digit] + for letter in letters: + self.getCombinations(digits, index + 1, s + letter, result) + + def letterCombinations(self, digits): + result = [] + if len(digits) == 0: + return result + self.getCombinations(digits, 0, "", result) + return result + - groups = (phoneMap[digit] for digit in digits) - return ["".join(combination) for combination in itertools.product(*groups)] ``` +回溯优化使用列表 +```python +class Solution: + def __init__(self): + self.letterMap = [ + "", # 0 + "", # 1 + "abc", # 2 + "def", # 3 + "ghi", # 4 + "jkl", # 5 + "mno", # 6 + "pqrs", # 7 + "tuv", # 8 + "wxyz" # 9 + ] + + def getCombinations(self, digits, index, path, result): + if index == len(digits): + result.append(''.join(path)) + return + digit = int(digits[index]) + letters = self.letterMap[digit] + for letter in letters: + path.append(letter) + self.getCombinations(digits, index + 1, path, result) + path.pop() + + def letterCombinations(self, digits): + result = [] + if len(digits) == 0: + return result + self.getCombinations(digits, 0, [], result) + return result + + + +``` + + ## Go diff --git a/problems/0039.组合总和.md b/problems/0039.组合总和.md index e1e51923..4d9466c3 100644 --- a/problems/0039.组合总和.md +++ b/problems/0039.组合总和.md @@ -273,75 +273,101 @@ class Solution { ## Python -**回溯** +回溯(版本一) ```python class Solution: - def __init__(self): - self.path = [] - self.paths = [] - def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: - ''' - 因为本题没有组合数量限制,所以只要元素总和大于target就算结束 - ''' - self.path.clear() - self.paths.clear() - self.backtracking(candidates, target, 0, 0) - return self.paths - - def backtracking(self, candidates: List[int], target: int, sum_: int, start_index: int) -> None: - # Base Case - if sum_ == target: - self.paths.append(self.path[:]) # 因为是shallow copy,所以不能直接传入self.path + def backtracking(self, candidates, target, total, startIndex, path, result): + if total > target: return - if sum_ > target: + if total == target: + result.append(path[:]) return - # 单层递归逻辑 - for i in range(start_index, len(candidates)): - sum_ += candidates[i] - self.path.append(candidates[i]) - self.backtracking(candidates, target, sum_, i) # 因为无限制重复选取,所以不是i+1 - sum_ -= candidates[i] # 回溯 - self.path.pop() # 回溯 + for i in range(startIndex, len(candidates)): + total += candidates[i] + path.append(candidates[i]) + self.backtracking(candidates, target, total, i, path, result) # 不用i+1了,表示可以重复读取当前的数 + total -= candidates[i] + path.pop() + + def combinationSum(self, candidates, target): + result = [] + self.backtracking(candidates, target, 0, 0, [], result) + return result + ``` -**剪枝回溯** +回溯剪枝(版本一) ```python class Solution: - def __init__(self): - self.path = [] - self.paths = [] - def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: - ''' - 因为本题没有组合数量限制,所以只要元素总和大于target就算结束 - ''' - self.path.clear() - self.paths.clear() - - # 为了剪枝需要提前进行排序 - candidates.sort() - self.backtracking(candidates, target, 0, 0) - return self.paths - - def backtracking(self, candidates: List[int], target: int, sum_: int, start_index: int) -> None: - # Base Case - if sum_ == target: - self.paths.append(self.path[:]) # 因为是shallow copy,所以不能直接传入self.path + def backtracking(self, candidates, target, total, startIndex, path, result): + if total == target: + result.append(path[:]) return - # 单层递归逻辑 - # 如果本层 sum + condidates[i] > target,就提前结束遍历,剪枝 - for i in range(start_index, len(candidates)): - if sum_ + candidates[i] > target: - return - sum_ += candidates[i] - self.path.append(candidates[i]) - self.backtracking(candidates, target, sum_, i) # 因为无限制重复选取,所以不是i-1 - sum_ -= candidates[i] # 回溯 - self.path.pop() # 回溯 + + for i in range(startIndex, len(candidates)): + if total + candidates[i] > target: + break + total += candidates[i] + path.append(candidates[i]) + self.backtracking(candidates, target, total, i, path, result) + total -= candidates[i] + path.pop() + + def combinationSum(self, candidates, target): + result = [] + candidates.sort() # 需要排序 + self.backtracking(candidates, target, 0, 0, [], result) + return result + +``` + +回溯(版本二) + +```python +class Solution: + def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: + result =[] + self.backtracking(candidates, target, 0, [], result) + return result + def backtracking(self, candidates, target, startIndex, path, result): + if target == 0: + result.append(path[:]) + return + if target < 0: + return + for i in range(startIndex, len(candidates)): + path.append(candidates[i]) + self.backtracking(candidates, target - candidates[i], i, path, result) + path.pop() + +``` + +回溯剪枝(版本二) + +```python +class Solution: + def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: + result =[] + candidates.sort() + self.backtracking(candidates, target, 0, [], result) + return result + def backtracking(self, candidates, target, startIndex, path, result): + if target == 0: + result.append(path[:]) + return + + for i in range(startIndex, len(candidates)): + if target - candidates[i] < 0: + break + path.append(candidates[i]) + self.backtracking(candidates, target - candidates[i], i, path, result) + path.pop() + ``` ## Go diff --git a/problems/0040.组合总和II.md b/problems/0040.组合总和II.md index b708650a..9094020e 100644 --- a/problems/0040.组合总和II.md +++ b/problems/0040.组合总和II.md @@ -356,93 +356,92 @@ class Solution { ``` ## Python -**回溯+巧妙去重(省去使用used** +回溯 ```python class Solution: - def __init__(self): - self.paths = [] - self.path = [] - def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]: - ''' - 类似于求三数之和,求四数之和,为了避免重复组合,需要提前进行数组排序 - ''' - self.paths.clear() - self.path.clear() - # 必须提前进行数组排序,避免重复 - candidates.sort() - self.backtracking(candidates, target, 0, 0) - return self.paths - def backtracking(self, candidates: List[int], target: int, sum_: int, start_index: int) -> None: - # Base Case - if sum_ == target: - self.paths.append(self.path[:]) + def backtracking(self, candidates, target, total, startIndex, path, result): + if total == target: + result.append(path[:]) return - - # 单层递归逻辑 - for i in range(start_index, len(candidates)): - # 剪枝,同39.组合总和 - if sum_ + candidates[i] > target: - return - - # 跳过同一树层使用过的元素 - if i > start_index and candidates[i] == candidates[i-1]: + + for i in range(startIndex, len(candidates)): + if i > startIndex and candidates[i] == candidates[i - 1]: continue - - sum_ += candidates[i] - self.path.append(candidates[i]) - self.backtracking(candidates, target, sum_, i+1) - self.path.pop() # 回溯,为了下一轮for loop - sum_ -= candidates[i] # 回溯,为了下一轮for loop + + if total + candidates[i] > target: + break + + total += candidates[i] + path.append(candidates[i]) + self.backtracking(candidates, target, total, i + 1, path, result) + total -= candidates[i] + path.pop() + + def combinationSum2(self, candidates, target): + result = [] + candidates.sort() + self.backtracking(candidates, target, 0, 0, [], result) + return result + ``` -**回溯+去重(使用used)** +回溯 使用used ```python class Solution: - def __init__(self): - self.paths = [] - self.path = [] - self.used = [] - def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]: - ''' - 类似于求三数之和,求四数之和,为了避免重复组合,需要提前进行数组排序 - 本题需要使用used,用来标记区别同一树层的元素使用重复情况:注意区分递归纵向遍历遇到的重复元素,和for循环遇到的重复元素,这两者的区别 - ''' - self.paths.clear() - self.path.clear() - self.usage_list = [False] * len(candidates) - # 必须提前进行数组排序,避免重复 - candidates.sort() - self.backtracking(candidates, target, 0, 0) - return self.paths - def backtracking(self, candidates: List[int], target: int, sum_: int, start_index: int) -> None: - # Base Case - if sum_ == target: - self.paths.append(self.path[:]) + def backtracking(self, candidates, target, total, startIndex, used, path, result): + if total == target: + result.append(path[:]) return - - # 单层递归逻辑 - for i in range(start_index, len(candidates)): - # 剪枝,同39.组合总和 - if sum_ + candidates[i] > target: - return - - # 检查同一树层是否出现曾经使用过的相同元素 - # 若数组中前后元素值相同,但前者却未被使用(used == False),说明是for loop中的同一树层的相同元素情况 - if i > 0 and candidates[i] == candidates[i-1] and self.usage_list[i-1] == False: + + for i in range(startIndex, len(candidates)): + # 对于相同的数字,只选择第一个未被使用的数字,跳过其他相同数字 + if i > startIndex and candidates[i] == candidates[i - 1] and not used[i - 1]: continue - sum_ += candidates[i] - self.path.append(candidates[i]) - self.usage_list[i] = True - self.backtracking(candidates, target, sum_, i+1) - self.usage_list[i] = False # 回溯,为了下一轮for loop - self.path.pop() # 回溯,为了下一轮for loop - sum_ -= candidates[i] # 回溯,为了下一轮for loop -``` + if total + candidates[i] > target: + break + total += candidates[i] + path.append(candidates[i]) + used[i] = True + self.backtracking(candidates, target, total, i + 1, used, path, result) + used[i] = False + total -= candidates[i] + path.pop() + + def combinationSum2(self, candidates, target): + used = [False] * len(candidates) + result = [] + candidates.sort() + self.backtracking(candidates, target, 0, 0, used, [], result) + return result + +``` +回溯优化 +```python +class Solution: + def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]: + candidates.sort() + results = [] + self.combinationSumHelper(candidates, target, 0, [], results) + return results + + def combinationSumHelper(self, candidates, target, index, path, results): + if target == 0: + results.append(path[:]) + return + for i in range(index, len(candidates)): + if i > index and candidates[i] == candidates[i - 1]: + continue + if candidates[i] > target: + break + path.append(candidates[i]) + self.combinationSumHelper(candidates, target - candidates[i], i + 1, path, results) + path.pop() +``` ## Go 主要在于如何在回溯中去重 diff --git a/problems/0046.全排列.md b/problems/0046.全排列.md index fb70be41..de1af642 100644 --- a/problems/0046.全排列.md +++ b/problems/0046.全排列.md @@ -215,68 +215,27 @@ class Solution { ``` ### Python -**回溯** +回溯 使用used ```python class Solution: - def __init__(self): - self.path = [] - self.paths = [] + def permute(self, nums): + result = [] + self.backtracking(nums, [], [False] * len(nums), result) + return result - def permute(self, nums: List[int]) -> List[List[int]]: - ''' - 因为本题排列是有序的,这意味着同一层的元素可以重复使用,但同一树枝上不能重复使用(usage_list) - 所以处理排列问题每层都需要从头搜索,故不再使用start_index - ''' - usage_list = [False] * len(nums) - self.backtracking(nums, usage_list) - return self.paths - - def backtracking(self, nums: List[int], usage_list: List[bool]) -> None: - # Base Case本题求叶子节点 - if len(self.path) == len(nums): - self.paths.append(self.path[:]) + def backtracking(self, nums, path, used, result): + if len(path) == len(nums): + result.append(path[:]) return - - # 单层递归逻辑 - for i in range(0, len(nums)): # 从头开始搜索 - # 若遇到self.path里已收录的元素,跳过 - if usage_list[i] == True: + for i in range(len(nums)): + if used[i]: continue - usage_list[i] = True - self.path.append(nums[i]) - self.backtracking(nums, usage_list) # 纵向传递使用信息,去重 - self.path.pop() - usage_list[i] = False -``` -**回溯+丢掉usage_list** -```python -class Solution: - def __init__(self): - self.path = [] - self.paths = [] + used[i] = True + path.append(nums[i]) + self.backtracking(nums, path, used, result) + path.pop() + used[i] = False - def permute(self, nums: List[int]) -> List[List[int]]: - ''' - 因为本题排列是有序的,这意味着同一层的元素可以重复使用,但同一树枝上不能重复使用 - 所以处理排列问题每层都需要从头搜索,故不再使用start_index - ''' - self.backtracking(nums) - return self.paths - - def backtracking(self, nums: List[int]) -> None: - # Base Case本题求叶子节点 - if len(self.path) == len(nums): - self.paths.append(self.path[:]) - return - - # 单层递归逻辑 - for i in range(0, len(nums)): # 从头开始搜索 - # 若遇到self.path里已收录的元素,跳过 - if nums[i] in self.path: - continue - self.path.append(nums[i]) - self.backtracking(nums) - self.path.pop() ``` ### Go diff --git a/problems/0047.全排列II.md b/problems/0047.全排列II.md index 3ff3fb8f..53ea62ba 100644 --- a/problems/0047.全排列II.md +++ b/problems/0047.全排列II.md @@ -223,28 +223,25 @@ class Solution { ```python class Solution: - def permuteUnique(self, nums: List[int]) -> List[List[int]]: - # res用来存放结果 - if not nums: return [] - res = [] - used = [0] * len(nums) - def backtracking(nums, used, path): - # 终止条件 - if len(path) == len(nums): - res.append(path.copy()) - return - for i in range(len(nums)): - if not used[i]: - if i>0 and nums[i] == nums[i-1] and not used[i-1]: - continue - used[i] = 1 - path.append(nums[i]) - backtracking(nums, used, path) - path.pop() - used[i] = 0 - # 记得给nums排序 - backtracking(sorted(nums),used,[]) - return res + def permuteUnique(self, nums): + nums.sort() # 排序 + result = [] + self.backtracking(nums, [], [False] * len(nums), result) + return result + + def backtracking(self, nums, path, used, result): + if len(path) == len(nums): + result.append(path[:]) + return + for i in range(len(nums)): + if (i > 0 and nums[i] == nums[i - 1] and not used[i - 1]) or used[i]: + continue + used[i] = True + path.append(nums[i]) + self.backtracking(nums, path, used, result) + path.pop() + used[i] = False + ``` ### Go diff --git a/problems/0051.N皇后.md b/problems/0051.N皇后.md index 54580cf7..13cdafb8 100644 --- a/problems/0051.N皇后.md +++ b/problems/0051.N皇后.md @@ -351,48 +351,47 @@ class Solution { ```python class Solution: def solveNQueens(self, n: int) -> List[List[str]]: - if not n: return [] - board = [['.'] * n for _ in range(n)] - res = [] - def isVaild(board,row, col): - #判断同一列是否冲突 - for i in range(len(board)): - if board[i][col] == 'Q': - return False - # 判断左上角是否冲突 - i = row -1 - j = col -1 - while i>=0 and j>=0: - if board[i][j] == 'Q': - return False - i -= 1 - j -= 1 - # 判断右上角是否冲突 - i = row - 1 - j = col + 1 - while i>=0 and j < len(board): - if board[i][j] == 'Q': - return False - i -= 1 - j += 1 - return True + result = [] # 存储最终结果的二维字符串数组 + + chessboard = ['.' * n for _ in range(n)] # 初始化棋盘 + self.backtracking(n, 0, chessboard, result) # 回溯求解 + return [[''.join(row) for row in solution] for solution in result] # 返回结果集 + + def backtracking(self, n: int, row: int, chessboard: List[str], result: List[List[str]]) -> None: + if row == n: + result.append(chessboard[:]) # 棋盘填满,将当前解加入结果集 + return + + for col in range(n): + if self.isValid(row, col, chessboard): + chessboard[row] = chessboard[row][:col] + 'Q' + chessboard[row][col+1:] # 放置皇后 + self.backtracking(n, row + 1, chessboard, result) # 递归到下一行 + chessboard[row] = chessboard[row][:col] + '.' + chessboard[row][col+1:] # 回溯,撤销当前位置的皇后 + + def isValid(self, row: int, col: int, chessboard: List[str]) -> bool: + # 检查列 + for i in range(row): + if chessboard[i][col] == 'Q': + return False # 当前列已经存在皇后,不合法 + + # 检查 45 度角是否有皇后 + i, j = row - 1, col - 1 + while i >= 0 and j >= 0: + if chessboard[i][j] == 'Q': + return False # 左上方向已经存在皇后,不合法 + i -= 1 + j -= 1 + + # 检查 135 度角是否有皇后 + i, j = row - 1, col + 1 + while i >= 0 and j < len(chessboard): + if chessboard[i][j] == 'Q': + return False # 右上方向已经存在皇后,不合法 + i -= 1 + j += 1 + + return True # 当前位置合法 - def backtracking(board, row, n): - # 如果走到最后一行,说明已经找到一个解 - if row == n: - temp_res = [] - for temp in board: - temp_str = "".join(temp) - temp_res.append(temp_str) - res.append(temp_res) - for col in range(n): - if not isVaild(board, row, col): - continue - board[row][col] = 'Q' - backtracking(board, row+1, n) - board[row][col] = '.' - backtracking(board, 0, n) - return res ``` diff --git a/problems/0077.组合.md b/problems/0077.组合.md index 3f222a17..444e15ce 100644 --- a/problems/0077.组合.md +++ b/problems/0077.组合.md @@ -381,68 +381,42 @@ class Solution { ``` ### Python - +未剪枝优化 ```python -class Solution(object): - def combine(self, n, k): - """ - :type n: int - :type k: int - :rtype: List[List[int]] - """ - result = [] - path = [] - def backtracking(n, k, startidx): - if len(path) == k: - result.append(path[:]) - return - - # 剪枝, 最后k - len(path)个节点直接构造结果,无需递归 - last_startidx = n - (k - len(path)) + 1 - - for x in range(startidx, last_startidx + 1): - path.append(x) - backtracking(n, k, x + 1) # 递归 - path.pop() # 回溯 - - backtracking(n, k, 1) +class Solution: + def combine(self, n: int, k: int) -> List[List[int]]: + result = [] # 存放结果集 + self.backtracking(n, k, 1, [], result) return result + def backtracking(self, n, k, startIndex, path, result): + if len(path) == k: + result.append(path[:]) + return + for i in range(startIndex, n + 1): # 需要优化的地方 + path.append(i) # 处理节点 + self.backtracking(n, k, i + 1, path, result) + path.pop() # 回溯,撤销处理的节点 + ``` + +剪枝优化: + ```python class Solution: def combine(self, n: int, k: int) -> List[List[int]]: - res = [] - path = [] - def backtrack(n, k, StartIndex): - if len(path) == k: - res.append(path[:]) - return - for i in range(StartIndex, n + 1): - path.append(i) - backtrack(n, k, i+1) - path.pop() - backtrack(n, k, 1) - return res -``` + result = [] # 存放结果集 + self.backtracking(n, k, 1, [], result) + return result + def backtracking(self, n, k, startIndex, path, result): + if len(path) == k: + result.append(path[:]) + return + for i in range(startIndex, n - (k - len(path)) + 2): # 优化的地方 + path.append(i) # 处理节点 + self.backtracking(n, k, i + 1, path, result) + path.pop() # 回溯,撤销处理的节点 -剪枝: - -```python -class Solution: - def combine(self, n: int, k: int) -> List[List[int]]: - res=[] #存放符合条件结果的集合 - path=[] #用来存放符合条件结果 - def backtrack(n,k,startIndex): - if len(path) == k: - res.append(path[:]) - return - for i in range(startIndex,n-(k-len(path))+2): #优化的地方 - path.append(i) #处理节点 - backtrack(n,k,i+1) #递归 - path.pop() #回溯,撤销处理的节点 - backtrack(n,k,1) - return res ``` ### Go diff --git a/problems/0077.组合优化.md b/problems/0077.组合优化.md index 0c816bc1..3926d006 100644 --- a/problems/0077.组合优化.md +++ b/problems/0077.组合优化.md @@ -183,18 +183,21 @@ Python: ```python class Solution: def combine(self, n: int, k: int) -> List[List[int]]: - res=[] #存放符合条件结果的集合 - path=[] #用来存放符合条件结果 - def backtrack(n,k,startIndex): - if len(path) == k: - res.append(path[:]) - return - for i in range(startIndex,n-(k-len(path))+2): #优化的地方 - path.append(i) #处理节点 - backtrack(n,k,i+1) #递归 - path.pop() #回溯,撤销处理的节点 - backtrack(n,k,1) - return res + result = [] # 存放结果集 + self.backtracking(n, k, 1, [], result) + return result + def backtracking(self, n, k, startIndex, path, result): + if len(path) == k: + result.append(path[:]) + return + for i in range(startIndex, n - (k - len(path)) + 2): # 优化的地方 + path.append(i) # 处理节点 + self.backtracking(n, k, i + 1, path, result) + path.pop() # 回溯,撤销处理的节点 + + + + ``` Go: ```Go diff --git a/problems/0078.子集.md b/problems/0078.子集.md index 07418fbc..21009f6a 100644 --- a/problems/0078.子集.md +++ b/problems/0078.子集.md @@ -208,28 +208,20 @@ class Solution { ## Python ```python class Solution: - def __init__(self): - self.path: List[int] = [] - self.paths: List[List[int]] = [] + def subsets(self, nums): + result = [] + path = [] + self.backtracking(nums, 0, path, result) + return result - def subsets(self, nums: List[int]) -> List[List[int]]: - self.paths.clear() - self.path.clear() - self.backtracking(nums, 0) - return self.paths - - def backtracking(self, nums: List[int], start_index: int) -> None: - # 收集子集,要先于终止判断 - self.paths.append(self.path[:]) - # Base Case - if start_index == len(nums): - return - - # 单层递归逻辑 - for i in range(start_index, len(nums)): - self.path.append(nums[i]) - self.backtracking(nums, i+1) - self.path.pop() # 回溯 + def backtracking(self, nums, startIndex, path, result): + result.append(path[:]) # 收集子集,要放在终止添加的上面,否则会漏掉自己 + # if startIndex >= len(nums): # 终止条件可以不加 + # return + for i in range(startIndex, len(nums)): + path.append(nums[i]) + self.backtracking(nums, i + 1, path, result) + path.pop() ``` ## Go diff --git a/problems/0090.子集II.md b/problems/0090.子集II.md index 63f75d29..3238ee52 100644 --- a/problems/0090.子集II.md +++ b/problems/0090.子集II.md @@ -238,86 +238,84 @@ class Solution { } ``` -### Python -```python -class Solution: - def __init__(self): - self.paths = [] - self.path = [] - def subsetsWithDup(self, nums: List[int]) -> List[List[int]]: - nums.sort() - self.backtracking(nums, 0) - return self.paths - - def backtracking(self, nums: List[int], start_index: int) -> None: - # ps.空集合仍符合要求 - self.paths.append(self.path[:]) - # Base Case - if start_index == len(nums): - return - - # 单层递归逻辑 - for i in range(start_index, len(nums)): - if i > start_index and nums[i] == nums[i-1]: - # 当前后元素值相同时,跳入下一个循环,去重 - continue - self.path.append(nums[i]) - self.backtracking(nums, i+1) - self.path.pop() -``` #### Python3 -不使用used数组 +回溯 利用used数组去重 ```python class Solution: - def subsetsWithDup(self, nums: List[int]) -> List[List[int]]: - res = [] - path = [] - nums.sort() # 去重需要先对数组进行排序 - - def backtracking(nums, startIndex): - # 终止条件 - res.append(path[:]) - if startIndex == len(nums): - return - - # for循环 - for i in range(startIndex, len(nums)): - # 数层去重 - if i > startIndex and nums[i] == nums[i-1]: # 去重 - continue - path.append(nums[i]) - backtracking(nums, i+1) - path.pop() - - backtracking(nums, 0) - return res -``` - -使用used数组 -```python -class Solution: - def subsetsWithDup(self, nums: List[int]) -> List[List[int]]: + def subsetsWithDup(self, nums): result = [] path = [] - nums.sort() - used = [0] * len(nums) - def backtrack(nums, startIdx): - result.append(path[:]) - for i in range(startIdx, len(nums)): - if i > startIdx and nums[i] == nums[i-1] and used[i-1] == 0: - continue - used[i] = 1 - path.append(nums[i]) - backtrack(nums, i+1) - path.pop() - used[i] = 0 - backtrack(nums, 0) + used = [False] * len(nums) + nums.sort() # 去重需要排序 + self.backtracking(nums, 0, used, path, result) return result + + def backtracking(self, nums, startIndex, used, path, result): + result.append(path[:]) # 收集子集 + for i in range(startIndex, len(nums)): + # used[i - 1] == True,说明同一树枝 nums[i - 1] 使用过 + # used[i - 1] == False,说明同一树层 nums[i - 1] 使用过 + # 而我们要对同一树层使用过的元素进行跳过 + if i > 0 and nums[i] == nums[i - 1] and not used[i - 1]: + continue + path.append(nums[i]) + used[i] = True + self.backtracking(nums, i + 1, used, path, result) + used[i] = False + path.pop() + ``` +回溯 利用集合去重 + +```python +class Solution: + def subsetsWithDup(self, nums): + result = [] + path = [] + nums.sort() # 去重需要排序 + self.backtracking(nums, 0, path, result) + return result + + def backtracking(self, nums, startIndex, path, result): + result.append(path[:]) # 收集子集 + uset = set() + for i in range(startIndex, len(nums)): + if nums[i] in uset: + continue + uset.add(nums[i]) + path.append(nums[i]) + self.backtracking(nums, i + 1, path, result) + path.pop() + +``` + +回溯 利用递归的时候下一个startIndex是i+1而不是0去重 + +```python +class Solution: + def subsetsWithDup(self, nums): + result = [] + path = [] + nums.sort() # 去重需要排序 + self.backtracking(nums, 0, path, result) + return result + + def backtracking(self, nums, startIndex, path, result): + result.append(path[:]) # 收集子集 + for i in range(startIndex, len(nums)): + # 而我们要对同一树层使用过的元素进行跳过 + if i > startIndex and nums[i] == nums[i - 1]: + continue + path.append(nums[i]) + self.backtracking(nums, i + 1, path, result) + path.pop() + + +``` ### Go ```Go diff --git a/problems/0093.复原IP地址.md b/problems/0093.复原IP地址.md index 161fb96e..3701420f 100644 --- a/problems/0093.复原IP地址.md +++ b/problems/0093.复原IP地址.md @@ -362,104 +362,82 @@ class Solution { ## python -python2: -```python -class Solution(object): - def restoreIpAddresses(self, s): - """ - :type s: str - :rtype: List[str] - """ - ans = [] - path = [] - def backtrack(path, startIndex): - if len(s) > 12: return [] - if len(path) == 4: - if startIndex == len(s): - ans.append(".".join(path[:])) - return - for i in range(startIndex+1, min(startIndex+4, len(s)+1)): # 剪枝 - string = s[startIndex:i] - if not 0 <= int(string) <= 255: - continue - if not string == "0" and not string.lstrip('0') == string: - continue - path.append(string) - backtrack(path, i) - path.pop() - - backtrack([], 0) - return ans -``` - -python3: +回溯(版本一) ```python class Solution: - def __init__(self): - self.result = [] - def restoreIpAddresses(self, s: str) -> List[str]: - ''' - 本质切割问题使用回溯搜索法,本题只能切割三次,所以纵向递归总共四层 - 因为不能重复分割,所以需要start_index来记录下一层递归分割的起始位置 - 添加变量point_num来记录逗号的数量[0,3] - ''' - self.result.clear() - if len(s) > 12: return [] - self.backtracking(s, 0, 0) - return self.result + result = [] + self.backtracking(s, 0, 0, "", result) + return result - def backtracking(self, s: str, start_index: int, point_num: int) -> None: - # Base Case - if point_num == 3: - if self.is_valid(s, start_index, len(s)-1): - self.result.append(s[:]) + def backtracking(self, s, start_index, point_num, current, result): + if point_num == 3: # 逗点数量为3时,分隔结束 + if self.is_valid(s, start_index, len(s) - 1): # 判断第四段子字符串是否合法 + current += s[start_index:] # 添加最后一段子字符串 + result.append(current) return - # 单层递归逻辑 - for i in range(start_index, len(s)): - # [start_index, i]就是被截取的子串 - if self.is_valid(s, start_index, i): - s = s[:i+1] + '.' + s[i+1:] - self.backtracking(s, i+2, point_num+1) # 在填入.后,下一子串起始后移2位 - s = s[:i+1] + s[i+2:] # 回溯 - else: - # 若当前被截取的子串大于255或者大于三位数,直接结束本层循环 - break - - def is_valid(self, s: str, start: int, end: int) -> bool: - if start > end: return False - # 若数字是0开头,不合法 - if s[start] == '0' and start != end: - return False - if not 0 <= int(s[start:end+1]) <= 255: - return False - return True -``` -python3; 简单拼接版本(类似Leetcode131写法): + for i in range(start_index, len(s)): + if self.is_valid(s, start_index, i): # 判断 [start_index, i] 这个区间的子串是否合法 + sub = s[start_index:i + 1] + self.backtracking(s, i + 1, point_num + 1, current + sub + '.', result) + else: + break + + def is_valid(self, s, start, end): + if start > end: + return False + if s[start] == '0' and start != end: # 0开头的数字不合法 + return False + num = 0 + for i in range(start, end + 1): + if not s[i].isdigit(): # 遇到非数字字符不合法 + return False + num = num * 10 + int(s[i]) + if num > 255: # 如果大于255了不合法 + return False + return True + +``` +回溯(版本二) + ```python -class Solution: +class Solution: def restoreIpAddresses(self, s: str) -> List[str]: - global results, path results = [] - path = [] - self.backtracking(s,0) + self.backtracking(s, 0, [], results) return results - def backtracking(self,s,index): - global results,path - if index == len(s) and len(path)==4: - results.append('.'.join(path)) # 在连接时需要中间间隔符号的话就在''中间写上对应的间隔符 + def backtracking(self, s, index, path, results): + if index == len(s) and len(path) == 4: + results.append('.'.join(path)) return - for i in range(index,len(s)): - if len(path)>3: break # 剪枝 - temp = s[index:i+1] - if (int(temp)<256 and int(temp)>0 and temp[0]!='0') or (temp=='0'): - path.append(temp) - self.backtracking(s,i+1) - path.pop() + + if len(path) > 4: # 剪枝 + return + + for i in range(index, min(index + 3, len(s))): + if self.is_valid(s, index, i): + sub = s[index:i+1] + path.append(sub) + self.backtracking(s, i+1, path, results) + path.pop() + + def is_valid(self, s, start, end): + if start > end: + return False + if s[start] == '0' and start != end: # 0开头的数字不合法 + return False + num = int(s[start:end+1]) + return 0 <= num <= 255 + + + + ``` + + ## Go ```go diff --git a/problems/0131.分割回文串.md b/problems/0131.分割回文串.md index dfec7853..636cf59c 100644 --- a/problems/0131.分割回文串.md +++ b/problems/0131.分割回文串.md @@ -352,12 +352,9 @@ class Solution { ``` ## Python -**回溯+正反序判断回文串** +回溯 基本版 ```python class Solution: - def __init__(self): - self.paths = [] - self.path = [] def partition(self, s: str) -> List[List[str]]: ''' @@ -366,52 +363,14 @@ class Solution: 当切割线迭代至字符串末尾,说明找到一种方法 类似组合问题,为了不重复切割同一位置,需要start_index来做标记下一轮递归的起始位置(切割线) ''' - self.path.clear() - self.paths.clear() - self.backtracking(s, 0) - return self.paths + result = [] + self.backtracking(s, 0, [], result) + return result - def backtracking(self, s: str, start_index: int) -> None: + def backtracking(self, s, start_index, path, result ): # Base Case - if start_index >= len(s): - self.paths.append(self.path[:]) - return - - # 单层递归逻辑 - for i in range(start_index, len(s)): - # 此次比其他组合题目多了一步判断: - # 判断被截取的这一段子串([start_index, i])是否为回文串 - temp = s[start_index:i+1] - if temp == temp[::-1]: # 若反序和正序相同,意味着这是回文串 - self.path.append(temp) - self.backtracking(s, i+1) # 递归纵向遍历:从下一处进行切割,判断其余是否仍为回文串 - self.path.pop() - else: - continue -``` -**回溯+函数判断回文串** -```python -class Solution: - def __init__(self): - self.paths = [] - self.path = [] - - def partition(self, s: str) -> List[List[str]]: - ''' - 递归用于纵向遍历 - for循环用于横向遍历 - 当切割线迭代至字符串末尾,说明找到一种方法 - 类似组合问题,为了不重复切割同一位置,需要start_index来做标记下一轮递归的起始位置(切割线) - ''' - self.path.clear() - self.paths.clear() - self.backtracking(s, 0) - return self.paths - - def backtracking(self, s: str, start_index: int) -> None: - # Base Case - if start_index >= len(s): - self.paths.append(self.path[:]) + if start_index == len(s): + result.append(path[:]) return # 单层递归逻辑 @@ -419,11 +378,10 @@ class Solution: # 此次比其他组合题目多了一步判断: # 判断被截取的这一段子串([start_index, i])是否为回文串 if self.is_palindrome(s, start_index, i): - self.path.append(s[start_index:i+1]) - self.backtracking(s, i+1) # 递归纵向遍历:从下一处进行切割,判断其余是否仍为回文串 - self.path.pop() # 回溯 - else: - continue + path.append(s[start_index:i+1]) + self.backtracking(s, i+1, path, result) # 递归纵向遍历:从下一处进行切割,判断其余是否仍为回文串 + path.pop() # 回溯 + def is_palindrome(self, s: str, start: int, end: int) -> bool: i: int = start @@ -433,9 +391,88 @@ class Solution: return False i += 1 j -= 1 - return True + return True ``` +回溯+优化判定回文函数 +```python +class Solution: + def partition(self, s: str) -> List[List[str]]: + result = [] + self.backtracking(s, 0, [], result) + return result + + def backtracking(self, s, start_index, path, result ): + # Base Case + if start_index == len(s): + result.append(path[:]) + return + + # 单层递归逻辑 + for i in range(start_index, len(s)): + # 若反序和正序相同,意味着这是回文串 + if s[start_index: i + 1] == s[start_index: i + 1][::-1]: + path.append(s[start_index:i+1]) + self.backtracking(s, i+1, path, result) # 递归纵向遍历:从下一处进行切割,判断其余是否仍为回文串 + path.pop() # 回溯 + +``` +回溯+高效判断回文子串 +```python +class Solution: + def partition(self, s: str) -> List[List[str]]: + result = [] + isPalindrome = [[False] * len(s) for _ in range(len(s))] # 初始化isPalindrome矩阵 + self.computePalindrome(s, isPalindrome) + self.backtracking(s, 0, [], result, isPalindrome) + return result + + def backtracking(self, s, startIndex, path, result, isPalindrome): + if startIndex >= len(s): + result.append(path[:]) + return + + for i in range(startIndex, len(s)): + if isPalindrome[startIndex][i]: # 是回文子串 + substring = s[startIndex:i + 1] + path.append(substring) + self.backtracking(s, i + 1, path, result, isPalindrome) # 寻找i+1为起始位置的子串 + path.pop() # 回溯过程,弹出本次已经填在的子串 + + def computePalindrome(self, s, isPalindrome): + for i in range(len(s) - 1, -1, -1): # 需要倒序计算,保证在i行时,i+1行已经计算好了 + for j in range(i, len(s)): + if j == i: + isPalindrome[i][j] = True + elif j - i == 1: + isPalindrome[i][j] = (s[i] == s[j]) + else: + isPalindrome[i][j] = (s[i] == s[j] and isPalindrome[i+1][j-1]) +``` +回溯+使用all函数判断回文子串 +```python +class Solution: + def partition(self, s: str) -> List[List[str]]: + result = [] + self.partition_helper(s, 0, [], result) + return result + + def partition_helper(self, s, start_index, path, result): + if start_index == len(s): + result.append(path[:]) + return + + for i in range(start_index + 1, len(s) + 1): + sub = s[start_index:i] + if self.isPalindrome(sub): + path.append(sub) + self.partition_helper(s, i, path, result) + path.pop() + + def isPalindrome(self, s): + return all(s[i] == s[len(s) - 1 - i] for i in range(len(s) // 2)) + +``` ## Go ```go var ( diff --git a/problems/0216.组合总和III.md b/problems/0216.组合总和III.md index f08d77ea..319b2eba 100644 --- a/problems/0216.组合总和III.md +++ b/problems/0216.组合总和III.md @@ -362,28 +362,25 @@ class Solution { ```py class Solution: - def __init__(self): - self.res = [] - self.sum_now = 0 - self.path = [] + def combinationSum3(self, k: int, n: int) -> List[List[int]]: + result = [] # 存放结果集 + self.backtracking(n, k, 0, 1, [], result) + return result - def combinationSum3(self, k: int, n: int) -> [[int]]: - self.backtracking(k, n, 1) - return self.res + def backtracking(self, targetSum, k, currentSum, startIndex, path, result): + if currentSum > targetSum: # 剪枝操作 + return # 如果path的长度等于k但currentSum不等于targetSum,则直接返回 + if len(path) == k: + if currentSum == targetSum: + result.append(path[:]) + return + for i in range(startIndex, 9 - (k - len(path)) + 2): # 剪枝 + currentSum += i # 处理 + path.append(i) # 处理 + self.backtracking(targetSum, k, currentSum, i + 1, path, result) # 注意i+1调整startIndex + currentSum -= i # 回溯 + path.pop() # 回溯 - def backtracking(self, k: int, n: int, start_num: int): - if self.sum_now > n: # 剪枝 - return - if len(self.path) == k: # len(path)==k时不管sum是否等于n都会返回 - if self.sum_now == n: - self.res.append(self.path[:]) - return - for i in range(start_num, 10 - (k - len(self.path)) + 1): - self.path.append(i) - self.sum_now += i - self.backtracking(k, n, i + 1) - self.path.pop() - self.sum_now -= i ``` ## Go diff --git a/problems/0332.重新安排行程.md b/problems/0332.重新安排行程.md index 95e7d3ed..fa6414e9 100644 --- a/problems/0332.重新安排行程.md +++ b/problems/0332.重新安排行程.md @@ -347,64 +347,88 @@ class Solution { ``` ### python +回溯 使用used数组 ```python class Solution: def findItinerary(self, tickets: List[List[str]]) -> List[str]: - # defaultdic(list) 是为了方便直接append - tickets_dict = defaultdict(list) - for item in tickets: - tickets_dict[item[0]].append(item[1]) - # 给每一个机场的到达机场排序,小的在前面,在回溯里首先被pop(0)出去 - # 这样最先找的的path就是排序最小的答案,直接返回 - for airport in tickets_dict: tickets_dict[airport].sort() - ''' - tickets_dict里面的内容是这样的 - {'JFK': ['ATL', 'SFO'], 'SFO': ['ATL'], 'ATL': ['JFK', 'SFO']}) - ''' - path = ["JFK"] - def backtracking(start_point): - # 终止条件 - if len(path) == len(tickets) + 1: - return True - for _ in tickets_dict[start_point]: - #必须及时删除,避免出现死循环 - end_point = tickets_dict[start_point].pop(0) - path.append(end_point) - # 只要找到一个就可以返回了 - if backtracking(end_point): - return True - path.pop() - tickets_dict[start_point].append(end_point) - - backtracking("JFK") - return path -``` - -python - 使用used数组 - 神似之前几题写法 - -```python -class Solution: - def findItinerary(self, tickets: List[List[str]]) -> List[str]: - global used,path,results - used = [0]*len(tickets) + tickets.sort() # 先排序,这样一旦找到第一个可行路径,一定是字母排序最小的 + used = [0] * len(tickets) path = ['JFK'] results = [] - tickets.sort() # 先排序,这样一旦找到第一个可行路径,一定是字母排序最小的 - self.backtracking(tickets,'JFK') + self.backtracking(tickets, used, path, 'JFK', results) return results[0] - def backtracking(self,tickets,cur): - if sum(used) == len(tickets): - results.append(path[:]) - return True # 只要找到就返回 - for i in range(len(tickets)): - if tickets[i][0]==cur and used[i]==0: - used[i]=1 - path.append(tickets[i][1]) - state = self.backtracking(tickets,tickets[i][1]) - path.pop() - used[i]=0 - if state: return True # 只要找到就返回,不继续搜索了 + + def backtracking(self, tickets, used, path, cur, results): + if len(path) == len(tickets) + 1: # 终止条件:路径长度等于机票数量+1 + results.append(path[:]) # 将当前路径添加到结果列表 + return True + + for i, ticket in enumerate(tickets): # 遍历机票列表 + if ticket[0] == cur and used[i] == 0: # 找到起始机场为cur且未使用过的机票 + used[i] = 1 # 标记该机票为已使用 + path.append(ticket[1]) # 将到达机场添加到路径中 + state = self.backtracking(tickets, used, path, ticket[1], results) # 递归搜索 + path.pop() # 回溯,移除最后添加的到达机场 + used[i] = 0 # 标记该机票为未使用 + if state: + return True # 只要找到一个可行路径就返回,不继续搜索 + +``` +回溯 使用字典 +```python +from collections import defaultdict + +class Solution: + def findItinerary(self, tickets: List[List[str]]) -> List[str]: + targets = defaultdict(list) # 构建机场字典 + for ticket in tickets: + targets[ticket[0]].append(ticket[1]) + for airport in targets: + targets[airport].sort() # 对目的地列表进行排序 + + path = ["JFK"] # 起始机场为"JFK" + self.backtracking(targets, path, len(tickets)) + return path + + def backtracking(self, targets, path, ticketNum): + if len(path) == ticketNum + 1: + return True # 找到有效行程 + + airport = path[-1] # 当前机场 + destinations = targets[airport] # 当前机场可以到达的目的地列表 + for i, dest in enumerate(destinations): + targets[airport].pop(i) # 标记已使用的机票 + path.append(dest) # 添加目的地到路径 + if self.backtracking(targets, path, ticketNum): + return True # 找到有效行程 + targets[airport].insert(i, dest) # 回溯,恢复机票 + path.pop() # 移除目的地 + return False # 没有找到有效行程 + +``` +回溯 使用字典 逆序 +```python +from collections import defaultdict + +class Solution: + def findItinerary(self, tickets): + targets = defaultdict(list) # 创建默认字典,用于存储机场映射关系 + for ticket in tickets: + targets[ticket[0]].append(ticket[1]) # 将机票输入到字典中 + + for key in targets: + targets[key].sort(reverse=True) # 对到达机场列表进行字母逆序排序 + + result = [] + self.backtracking("JFK", targets, result) # 调用回溯函数开始搜索路径 + return result[::-1] # 返回逆序的行程路径 + + def backtracking(self, airport, targets, result): + while targets[airport]: # 当机场还有可到达的机场时 + next_airport = targets[airport].pop() # 弹出下一个机场 + self.backtracking(next_airport, targets, result) # 递归调用回溯函数进行深度优先搜索 + result.append(airport) # 将当前机场添加到行程路径中 ``` ### GO diff --git a/problems/0376.摆动序列.md b/problems/0376.摆动序列.md index ff388b55..285a0280 100644 --- a/problems/0376.摆动序列.md +++ b/problems/0376.摆动序列.md @@ -305,21 +305,43 @@ class Solution { ### Python -**贪心** +贪心(版本一) + +```python +class Solution: + def wiggleMaxLength(self, nums): + if len(nums) <= 1: + return len(nums) # 如果数组长度为0或1,则返回数组长度 + curDiff = 0 # 当前一对元素的差值 + preDiff = 0 # 前一对元素的差值 + result = 1 # 记录峰值的个数,初始为1(默认最右边的元素被视为峰值) + for i in range(len(nums) - 1): + curDiff = nums[i + 1] - nums[i] # 计算下一个元素与当前元素的差值 + # 如果遇到一个峰值 + if (preDiff <= 0 and curDiff > 0) or (preDiff >= 0 and curDiff < 0): + result += 1 # 峰值个数加1 + preDiff = curDiff # 注意这里,只在摆动变化的时候更新preDiff + return result # 返回最长摆动子序列的长度 + +``` +贪心(版本二) ```python class Solution: def wiggleMaxLength(self, nums: List[int]) -> int: - preC,curC,res = 0,0,1 #题目里nums长度大于等于1,当长度为1时,其实到不了for循环里去,所以不用考虑nums长度 + if len(nums) <= 1: + return len(nums) # 如果数组长度为0或1,则返回数组长度 + preDiff,curDiff ,result = 0,0,1 #题目里nums长度大于等于1,当长度为1时,其实到不了for循环里去,所以不用考虑nums长度 for i in range(len(nums) - 1): - curC = nums[i + 1] - nums[i] - if curC * preC <= 0 and curC !=0: #差值为0时,不算摆动 - res += 1 - preC = curC #如果当前差值和上一个差值为一正一负时,才需要用当前差值替代上一个差值 - return res + curDiff = nums[i + 1] - nums[i] + if curDiff * preDiff <= 0 and curDiff !=0: #差值为0时,不算摆动 + result += 1 + preDiff = curDiff #如果当前差值和上一个差值为一正一负时,才需要用当前差值替代上一个差值 + return result + ``` -**动态规划** +动态规划(版本一) ```python class Solution: @@ -341,25 +363,44 @@ class Solution: return max(dp[-1][0], dp[-1][1]) ``` +动态规划(版本二) + ```python class Solution: - def wiggleMaxLength(self, nums: List[int]) -> int: - # up i作为波峰最长的序列长度 - # down i作为波谷最长的序列长度 - n = len(nums) - # 长度为0和1的直接返回长度 - if n<2: return n - for i in range(1,n): - if nums[i]>nums[i-1]: - # nums[i] 为波峰,1. 前面是波峰,up值不变,2. 前面是波谷,down值加1 - # 目前up值取两者的较大值(其实down+1即可,可以推理前一步down和up最多相差1,所以down+1>=up) - up = max(up, down+1) - elif nums[i] nums[i]: + dp[i][1] = max(dp[i][1], dp[j][0] + 1) # 如果前一个数比当前数大,可以形成一个上升峰值,更新dp[i][1] + for j in range(i): + if nums[j] < nums[i]: + dp[i][0] = max(dp[i][0], dp[j][1] + 1) # 如果前一个数比当前数小,可以形成一个下降峰值,更新dp[i][0] + return max(dp[-1][0], dp[-1][1]) # 返回最大的摆动序列长度 + ``` +动态规划(版本三)优化 + +```python +class Solution: + def wiggleMaxLength(self, nums): + if len(nums) <= 1: + return len(nums) # 如果数组长度为0或1,则返回数组长度 + + up = down = 1 # 记录上升和下降摆动序列的最大长度 + for i in range(1, len(nums)): + if nums[i] > nums[i-1]: + up = down + 1 # 如果当前数比前一个数大,则可以形成一个上升峰值 + elif nums[i] < nums[i-1]: + down = up + 1 # 如果当前数比前一个数小,则可以形成一个下降峰值 + + return max(up, down) # 返回上升和下降摆动序列的最大长度 + + +``` ### Go **贪心** diff --git a/problems/0455.分发饼干.md b/problems/0455.分发饼干.md index c4f81035..ce1987ef 100644 --- a/problems/0455.分发饼干.md +++ b/problems/0455.分发饼干.md @@ -177,32 +177,33 @@ class Solution { ``` ### Python - +贪心 大饼干优先 ```python class Solution: - # 思路1:优先考虑小胃口 - def findContentChildren(self, g: List[int], s: List[int]) -> int: - g.sort() - s.sort() - res = 0 - for i in range(len(s)): - if res = g[res]: #小饼干先喂饱小胃口 - res += 1 - return res + def findContentChildren(self, g, s): + g.sort() # 将孩子的贪心因子排序 + s.sort() # 将饼干的尺寸排序 + index = len(s) - 1 # 饼干数组的下标,从最后一个饼干开始 + result = 0 # 满足孩子的数量 + for i in range(len(g)-1, -1, -1): # 遍历胃口,从最后一个孩子开始 + if index >= 0 and s[index] >= g[i]: # 遍历饼干 + result += 1 + index -= 1 + return result + ``` - +贪心 小饼干优先 ```python class Solution: - # 思路2:优先考虑大胃口 - def findContentChildren(self, g: List[int], s: List[int]) -> int: - g.sort() - s.sort() - start, count = len(s) - 1, 0 - for index in range(len(g) - 1, -1, -1): # 先喂饱大胃口 - if start >= 0 and g[index] <= s[start]: - start -= 1 - count += 1 - return count + def findContentChildren(self, g, s): + g.sort() # 将孩子的贪心因子排序 + s.sort() # 将饼干的尺寸排序 + index = 0 + for i in range(len(s)): # 遍历饼干 + if index < len(g) and g[index] <= s[i]: # 如果当前孩子的贪心因子小于等于当前饼干尺寸 + index += 1 # 满足一个孩子,指向下一个孩子 + return index # 返回满足的孩子数目 + ``` ### Go diff --git a/problems/0491.递增子序列.md b/problems/0491.递增子序列.md index d6c6b9c9..130b5666 100644 --- a/problems/0491.递增子序列.md +++ b/problems/0491.递增子序列.md @@ -268,80 +268,56 @@ class Solution { ### Python -python3 -**回溯** + +回溯 利用set去重 ```python class Solution: - def __init__(self): - self.paths = [] - self.path = [] - - def findSubsequences(self, nums: List[int]) -> List[List[int]]: - ''' - 本题求自增子序列,所以不能改变原数组顺序 - ''' - self.backtracking(nums, 0) - return self.paths - - def backtracking(self, nums: List[int], start_index: int): - # 收集结果,同78.子集,仍要置于终止条件之前 - if len(self.path) >= 2: - # 本题要求所有的节点 - self.paths.append(self.path[:]) + def findSubsequences(self, nums): + result = [] + path = [] + self.backtracking(nums, 0, path, result) + return result + + def backtracking(self, nums, startIndex, path, result): + if len(path) > 1: + result.append(path[:]) # 注意要使用切片将当前路径的副本加入结果集 + # 注意这里不要加return,要取树上的节点 - # Base Case(可忽略) - if start_index == len(nums): - return - - # 单层递归逻辑 - # 深度遍历中每一层都会有一个全新的usage_list用于记录本层元素是否重复使用 - usage_list = set() - # 同层横向遍历 - for i in range(start_index, len(nums)): - # 若当前元素值小于前一个时(非递增)或者曾用过,跳入下一循环 - if (self.path and nums[i] < self.path[-1]) or nums[i] in usage_list: + uset = set() # 使用集合对本层元素进行去重 + for i in range(startIndex, len(nums)): + if (path and nums[i] < path[-1]) or nums[i] in uset: continue - usage_list.add(nums[i]) - self.path.append(nums[i]) - self.backtracking(nums, i+1) - self.path.pop() + + uset.add(nums[i]) # 记录这个元素在本层用过了,本层后面不能再用了 + path.append(nums[i]) + self.backtracking(nums, i + 1, path, result) + path.pop() + ``` -**回溯+哈希表去重** +回溯 利用哈希表去重 ```python class Solution: - def __init__(self): - self.paths = [] - self.path = [] + def findSubsequences(self, nums): + result = [] + path = [] + self.backtracking(nums, 0, path, result) + return result - def findSubsequences(self, nums: List[int]) -> List[List[int]]: - ''' - 本题求自增子序列,所以不能改变原数组顺序 - ''' - self.backtracking(nums, 0) - return self.paths - - def backtracking(self, nums: List[int], start_index: int): - # 收集结果,同78.子集,仍要置于终止条件之前 - if len(self.path) >= 2: - # 本题要求所有的节点 - self.paths.append(self.path[:]) + def backtracking(self, nums, startIndex, path, result): + if len(path) > 1: + result.append(path[:]) # 注意要使用切片将当前路径的副本加入结果集 - # Base Case(可忽略) - if start_index == len(nums): - return + used = [0] * 201 # 使用数组来进行去重操作,题目说数值范围[-100, 100] + for i in range(startIndex, len(nums)): + if (path and nums[i] < path[-1]) or used[nums[i] + 100] == 1: + continue # 如果当前元素小于上一个元素,或者已经使用过当前元素,则跳过当前元素 + + used[nums[i] + 100] = 1 # 标记当前元素已经使用过 + path.append(nums[i]) # 将当前元素加入当前递增子序列 + self.backtracking(nums, i + 1, path, result) + path.pop() + - # 单层递归逻辑 - # 深度遍历中每一层都会有一个全新的usage_list用于记录本层元素是否重复使用 - usage_list = [False] * 201 # 使用列表去重,题中取值范围[-100, 100] - # 同层横向遍历 - for i in range(start_index, len(nums)): - # 若当前元素值小于前一个时(非递增)或者曾用过,跳入下一循环 - if (self.path and nums[i] < self.path[-1]) or usage_list[nums[i]+100] == True: - continue - usage_list[nums[i]+100] = True - self.path.append(nums[i]) - self.backtracking(nums, i+1) - self.path.pop() ``` ### Go diff --git a/problems/0701.二叉搜索树中的插入操作.md b/problems/0701.二叉搜索树中的插入操作.md index 1ba7461f..fc4351ba 100644 --- a/problems/0701.二叉搜索树中的插入操作.md +++ b/problems/0701.二叉搜索树中的插入操作.md @@ -326,7 +326,22 @@ class Solution: return root ``` +递归法(版本四) +```python +class Solution: + def insertIntoBST(self, root, val): + if root is None: + node = TreeNode(val) + return node + if root.val > val: + root.left = self.insertIntoBST(root.left, val) + if root.val < val: + root.right = self.insertIntoBST(root.right, val) + + return root + +``` 迭代法 ```python diff --git a/problems/回溯算法去重问题的另一种写法.md b/problems/回溯算法去重问题的另一种写法.md index f39c3c74..4e09b925 100644 --- a/problems/回溯算法去重问题的另一种写法.md +++ b/problems/回溯算法去重问题的另一种写法.md @@ -356,76 +356,80 @@ Python: ```python class Solution: - def subsetsWithDup(self, nums: List[int]) -> List[List[int]]: - res = [] - nums.sort() - def backtracking(start, path): - res.append(path) - uset = set() - for i in range(start, len(nums)): - if nums[i] not in uset: - backtracking(i + 1, path + [nums[i]]) - uset.add(nums[i]) + def subsetsWithDup(self, nums): + nums.sort() # 去重需要排序 + result = [] + self.backtracking(nums, 0, [], result) + return result + + def backtracking(self, nums, startIndex, path, result): + result.append(path[:]) + used = set() + for i in range(startIndex, len(nums)): + if nums[i] in used: + continue + used.add(nums[i]) + path.append(nums[i]) + self.backtracking(nums, i + 1, path, result) + path.pop() - backtracking(0, []) - return res ``` **40. 组合总和 II** ```python class Solution: - def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]: - - res = [] + def combinationSum2(self, candidates, target): candidates.sort() + result = [] + self.backtracking(candidates, target, 0, 0, [], result) + return result - def backtracking(start, path): - if sum(path) == target: - res.append(path) - elif sum(path) < target: - used = set() - for i in range(start, len(candidates)): - if candidates[i] in used: - continue - else: - used.add(candidates[i]) - backtracking(i + 1, path + [candidates[i]]) - - backtracking(0, []) + def backtracking(self, candidates, target, sum, startIndex, path, result): + if sum == target: + result.append(path[:]) + return + used = set() + for i in range(startIndex, len(candidates)): + if sum + candidates[i] > target: + break + if candidates[i] in used: + continue + used.add(candidates[i]) + sum += candidates[i] + path.append(candidates[i]) + self.backtracking(candidates, target, sum, i + 1, path, result) + sum -= candidates[i] + path.pop() - return res ``` **47. 全排列 II** ```python class Solution: - def permuteUnique(self, nums: List[int]) -> List[List[int]]: - path = [] - res = [] - used = [False]*len(nums) + def permuteUnique(self, nums): + nums.sort() # 排序 + result = [] + self.backtracking(nums, [False] * len(nums), [], result) + return result - def backtracking(): - if len(path) == len(nums): - res.append(path.copy()) - - deduplicate = set() - for i, num in enumerate(nums): - if used[i] == True: - continue - if num in deduplicate: - continue + def backtracking(self, nums, used, path, result): + if len(path) == len(nums): + result.append(path[:]) + return + used_set = set() + for i in range(len(nums)): + if nums[i] in used_set: + continue + if not used[i]: + used_set.add(nums[i]) used[i] = True path.append(nums[i]) - backtracking() - used[i] = False + self.backtracking(nums, used, path, result) path.pop() - deduplicate.add(num) - - backtracking() + used[i] = False - return res ``` JavaScript: