0
点赞
收藏
分享

微信扫一扫

算法-回溯

西街小学的王 2022-03-13 阅读 108

1.组合

给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。

class Solution:
    def combine(self,n,k):
        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

2.组合总和III

找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。

说明:

  • 所有数字都是正整数。
  • 解集不能包含重复的组合。
class Solution:
    def combine(self,n,k):
        res = []
        path = []
        self.sum_now = 0
        def backtracking(n,k,start_num):
            if self.sum_now > n:
                return
            if len(path) == k:
                if self.sum_now == n:
                    res.append(path[:])
                return
            for i in range(start_num,9 - (k - len(path)) + 2):
                path.append(i)
                self.sum_now += i
                backtracking(n,k,i + 1)
                path.pop()
                self.sum_now -= i
        backtracking(n,k,1)
        return res

3.电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。

class Solution:
    def __init__(self):
        self.answers = []
        self.answer = ''
        self.letter_map = {
            '2':'abc',
            '3':'def',
            '4':'ghi',
            '5':'jkl',
            '6':'mno',
            '7':'pqrs',
            '8':'tuv',
            '9':'wxyz'
        }
    def letterCombinations(self,digits):
        self.answers.clear()
        if not digits:return []
        self.backstracking(digits,0)
        return self.answers
    def backstracking(self,digits,index):
        if index == len(digits):# 当遍历穷尽后的下一层
            self.answers.append(self.answer)
            return
        # 单层递归逻辑
        letters = self.letter_map[digits[index]]
        for letter in letters:
            self.answer += letter
            self.backstracking(digits,index + 1)# 递归至下一层
            self.answer = self.answer[:-1]# 回溯

4.组合总和

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的数字可以无限制重复被选取。

class Solution:
    def __init__(self):
        self.path = []
        self.paths = []

    def combinationSum(self, candidates, target):
        '''
        因为本题没有组合数量限制,所以只要元素总和大于target就算结束
        '''
        self.path.clear()
        self.paths.clear()
        # 为了剪枝需要提前进行排序
        candidates.sort()
        self.backtracking(candidates, target, 0, 0)
        return self.paths

    def backtracking(self,candidates, target, sum, startIndex):
        if sum > target:
            return
        if sum == target:
            self.paths.append(self.path[:])# 因为是shallow copy,所以不能直接传入self.path
            return

        #单层递归逻辑
        # 如果本层 sum + condidates[i] > target,就提前结束遍历,剪枝
        for i in range(startIndex, len(candidates)):
            if sum + candidates[i] > target:
                return
            self.path.append(candidates[i])
            sum += candidates[i]
            self.backtracking(candidates, target, sum, i)# 因为无限制重复选取,所以不是i-1
            self.path.pop()# 回溯
            sum -= candidates[i]# 回溯

5.组合总和II

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用一次。

说明: 所有数字(包括目标数)都是正整数。 解集不能包含重复的组合。

class Solution:
    def __init__(self):
        self.path = []
        self.paths = []

    def combinationSum(self, candidates, target):
        '''
        因为本题没有组合数量限制,所以只要元素总和大于target就算结束
        '''
        self.path.clear()
        self.paths.clear()
        # 为了剪枝需要提前进行排序
        candidates.sort()
        self.backtracking(candidates, target, 0, 0)
        return self.paths

    def backtracking(self,candidates, target, sum, startIndex):
        if sum == target:
            self.paths.append(self.path[:])# 因为是shallow copy,所以不能直接传入self.path
            return

        #单层递归逻辑
        # 如果本层 sum + condidates[i] > target,就提前结束遍历,剪枝
        for i in range(startIndex, len(candidates)):
            if sum + candidates[i] > target:
                return
            # 跳过同一树层使用过的元素
            if i > startIndex and candidates[i] == candidates[i - 1]:
                continue
            self.path.append(candidates[i])
            sum += candidates[i]
            self.backtracking(candidates, target, sum, i + 1)
            self.path.pop()# 回溯
            sum -= candidates[i]# 回溯

6.分割回文串

给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。

返回 s 所有可能的分割方案。

class Solution:
    def __init__(self):
        self.path = []
        self.paths = []

    def partition(self, s):
        '''
        递归用于纵向遍历
        for循环用于横向遍历
        当切割线迭代至字符串末尾,说明找到一种方法
        类似组合问题,为了不重复切割同一位置,需要start_index来做标记下一轮递归的起始位置(切割线)
        '''
        self.path.clear()
        self.paths.clear()
        self.backtracking(s, 0)
        return self.paths

    def backtracking(self,s, startIndex):
        if startIndex >= len(s):
            self.paths.append(self.path[:])
            return
        # 单层递归逻辑
        for i in range(startIndex, len(s)):
            # 此次比其他组合题目多了一步判断:
            # 判断被截取的这一段子串([start_index, i])是否为回文串
            tmp = s[startIndex: i + 1]
            if tmp == tmp[::-1]:# 若反序和正序相同,意味着这是回文串
                self.path.append(tmp)
                self.backtracking(s,i + 1)# 递归纵向遍历:从下一处进行切割,判断其余是否仍为回文串
                self.path.pop()
            else:
                continue

7.复原IP地址

给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。

有效的 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 '.' 分隔。

class Solution:
    def __init__(self):
        self.paths = []

    def partition(self, s):
        '''
        本质切割问题使用回溯搜索法,本题只能切割三次,所以纵向递归总共四层
        因为不能重复分割,所以需要start_index来记录下一层递归分割的起始位置
        添加变量point_num来记录逗号的数量[0,3]
        '''
        self.paths.clear()
        if len(s) > 12:return []
        self.backtracking(s, 0, 0)
        return self.paths

    def backtracking(self,s, startIndex, point_num):
        if point_num == 3:
            if self.isValid(s, startIndex, len(s) - 1):
                self.paths.append(s[:])
            return
        # 单层递归逻辑
        for i in range(startIndex, len(s)):
            # [start_index, i]就是被截取的子串
            if self.isValid(s, startIndex, 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或者大于三位数,直接结束本层循环
                continue

    def isValid(self,s,start,end):
        if start > end:return False
        if s[start] == '0' and start != end:
            return False
        if not (0 <= int(s[start: end + 1]) <= 255):
            return False
        return True

8.子集

给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

class Solution:
    def __init__(self):
        self.paths = []
        self.path = []

    def partition(self, nums):
        self.path.clear()
        self.paths.clear()
        self.backtracking(nums, 0)
        return self.paths

    def backtracking(self, nums, startIndex):
        # 收集子集,要先于终止判断
        self.paths.append(self.path[:])
        if startIndex == len(nums):
            return
        # 单层递归逻辑
        for i in range(startIndex, len(nums)):
            self.path.append(nums[i])
            self.backtracking(nums, i + 1)
            self.path.pop()
举报

相关推荐

0 条评论