leetcode 回溯(一)

排列是整个数组遍历,组合是从该下标开始遍历后续数据,去重:先排序,然后有相同元素,若前面的元素未使用则给元素也不用,否则会产生重复解,可以借助used数组记录该下标对应的元素是否使用过了

目录

306. 累加数

17. 电话号码的字母组合

22. 括号生成

93. 复原IP地址

131. 分割回文串

46. 全排列

47. 全排列 II

77. 组合

39. 组合总和

40. 组合总和 II


306. 累加数

力扣

累加数是一个字符串,组成它的数字可以形成累加序列。

一个有效的累加序列必须至少包含 3 个数。除了最开始的两个数以外,字符串中的其他数都等于它之前两个数相加的和。

给定一个只包含数字 '0'-'9' 的字符串,编写一个算法来判断给定输入是否是累加数。

说明: 累加序列里的数不会以 0 开头,所以不会出现 1, 2, 03 或者 1, 02, 3 的情况。

示例 1:输入: "112358",输出: true 
解释: 累加序列为: 1, 1, 2, 3, 5, 8 。1 + 1 = 2, 1 + 2 = 3, 2 + 3 = 5, 3 + 5 = 8
示例 2:输入: "199100199",输出: true 
解释: 累加序列为: 1, 99, 100, 199。1 + 99 = 100, 99 + 100 = 199
进阶:你如何处理一个溢出的过大的整数输入?

题解

方法一:直接遍历,斐波那契数列有一个特点,就是只要确定前两个数,则后面的数均可以确定,最外层的for循环确定第一个数,中间层的for循环确定第二个数,while循环找余下的数,若下一个斐波那契数不在字符串中,则不满足条件退出while循环,若查看了所有的字符且都符合条件,则满足要求,若值大于等于3,则直接返回True,否则继续找。

class Solution(object):
    def isAdditiveNumber(self, num):
        """
        :type num: str
        :rtype: bool
        """
        n = len(num)
        if n <= 2:
            return False 
        
        #[0: i], (i: j] - 1, - 2
        
        for i in range(n):
            if i != 0 and num[0] == '0':
                return False
            for j in range(i + 1, n):
                if j != i + 1 and num[i + 1] == '0':
                    break 
                a = int(num[:i + 1])
                b = int(num[i + 1: j + 1])
                k = j + 1
                count = 2
                while True:
                    c = str(a + b)
                    if k == n:
                        if count >= 3:
                            return True 
                    if len(c) + k > n or c != num[k: len(c) + k]:
                        break
                    count += 1
                    k = k + len(c)
                    a = b
                    b = int(c)               
        return False

17. 电话号码的字母组合

力扣

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

示例:输入:"23",输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
说明:尽管上面的答案是按字典序排列的,但是你可以任意选择答案输出的顺序。

题解

一:搜索问题一般的套路是:先画出递归树,然后思考如何编写代码在这棵递归树上搜索所有可行解。第一层是按顺序遍历digits中的每一个字符,所以也就没必要搞一个for循环,for循环的是每一个字符代表的字符,这一层有点类似排列,所以从0开始的for循环。

class Solution(object):
    def letterCombinations(self, digits):
        """
        :type digits: str
        :rtype: List[str]
        """
        def dfs(x, tmp, ans, digits, n, rec):
            if x == n:
                ans.append(tmp)
                return 
            for c in rec[digits[x]]:
                dfs(x + 1, tmp + c, ans, digits, n, rec)
        
        rec = {'2': 'abc', '3': 'def', '4': 'ghi', '5': 'jkl',
               '6': 'mno', '7': 'pqrs', '8': 'tuv', '9': 'wxyz'}
        ans = [] 
        if not digits:
            return ans 
        n = len(digits)
        dfs(0, "", ans, digits, n, rec)
        return ans

22. 括号生成

力扣

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

示例:

输入:n = 3
输出:[
       "((()))",
       "(()())",
       "(())()",
       "()(())",
       "()()()"
     ]

题解

一:其中,cnt表示两两抵消后的左括号"("数,n表示剩下的括号数。这一段,是保证括号的合法性,即左右括号数亩必须相等,当然还要配合下面的if以保证左括号一定在右括号的前面。

if cnt == 0:
    self.res.append(tmp_res)

这一段是进行一些剪枝,提升运行效率,其中if后面的cnt<=0,这一块也是合法性的保证,保证左括号一定在右括号的前面,避免出现这种")("。

if cnt <= 0:
    self.DFS(cnt + 1, tmp_res + "(", n - 1)
elif cnt > n:
    self.DFS(cnt - 1, tmp_res + ")", n - 1)
class Solution(object):
    def __init__(self):
        self.res = []

    def generateParenthesis(self, n):
        """
        :type n: int
        :rtype: List[str]
        """
        if n <= 0:
            return []
        self.DFS(0, "", 2 * n)
        return self.res
        
    def DFS(self, cnt, tmp_res, n):
        if n == 0:
            if cnt == 0:
                self.res.append(tmp_res)
            return 

        if cnt <= 0:
            self.DFS(cnt + 1, tmp_res + "(", n - 1)
        elif cnt > n:
            self.DFS(cnt - 1, tmp_res + ")", n - 1)
        else:
            self.DFS(cnt - 1, tmp_res + ")", n - 1)
            self.DFS(cnt + 1, tmp_res + "(", n - 1)

93. 复原IP地址

力扣

给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。有效的 IP 地址正好由四个整数(每个整数位于 0 到 255 之间组成),整数之间用 '.' 分隔。

示例:输入: "25525511135",输出: ["255.255.11.135", "255.255.111.35"]

题解

一:其实这边主要是看三个'.'插在何处,并且还要符合条件。其中before表示的是该段起始的字符下标,idx表示的是目前正在考虑的字符下标,cnt记录目前为止的'.'的个数。

终止条件

  • 到达最后一个字符串
    • 如果符合条件,前面已将添加过3个'.',且最后一段也符合IP地址的格式,在[0,255]范围,且第一个字符为0只能是最后一段为0,且只有一个字符,添加进res
    • 如果不符合条件,直接返回
  • 不符合条件,已经添加了不止3个'.',范围不再[0,255],或者第一个字符为'0',但是这一段却不为0,或者这一段为0,字符数却不止一个。
class Solution(object):
    def __init__(self):
        self.res = []

    def restoreIpAddresses(self, s):
        """
        :type s: str
        :rtype: List[str]
        """
        if not s or len(s) > 12:
            return self.res

        self.helper(0, 0, 0, "", s)
        return self.res

    # before - 分隔符起始位置
    def helper(self, before, idx, cnt, tmp_res, s):
        if cnt > 3:
            return 
        if idx == len(s) - 1:
            num = int(s[before:]) 
            if ((0 < num <= 255 and s[before] != '0') or (num == 0 and before == idx)) and cnt == 3:
                self.res.append(tmp_res + s[-1])
            return 
        num = int(s[before: idx + 1]) 
        if (before - idx + 1 > 3 or not (0 <= num <= 255) 
            or (num ==0 and before != idx) or (num != 0 and s[before] == '0')):
            return
        self.helper(before, idx + 1, cnt, tmp_res + s[idx], s)
        self.helper(idx + 1, idx + 1, cnt + 1, tmp_res + s[idx] +'.', s)

131. 分割回文串

力扣

给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。返回 s 所有可能的分割方案。

示例:

输入: "aab"
输出:
[["aa","b"],
  ["a","a","b"]]

题解

一:与上一题思路近乎一样,上题考虑的是在哪放置3个'.',该题考虑的是在哪进行分隔,该题也要验证是否符合回文串的要求。其中before表示的是该段起始的字符下标,idx表示的是目前正在考虑的字符下标。

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

    def partition(self, s):
        """
        :type s: str
        :rtype: List[List[str]]
        """
        if not s:
            return self.res
        self.helper(0, 0, [], s)
        return self.res

    def helper(self, before, idx, tmp_res, s):
        if idx >= len(s):
            return 
        if idx == len(s) - 1:
            if self.isValid(s[before:]):
                tmp_res.append(s[before:])
                self.res.append(tmp_res[:])
                tmp_res.pop()
                return 
    
        if self.isValid(s[before: idx + 1]):
            tmp_res.append(s[before: idx + 1])
            self.helper(idx + 1, idx + 1, tmp_res, s)
            tmp_res.pop()
        self.helper(before, idx + 1, tmp_res, s)
            

    def isValid(self, s):
        l, r = 0, len(s) - 1
        while l < r:
            if s[l] != s[r]:
                return False
            l += 1
            r -= 1
        return True

二:这边对时间进行优化,用空间换时间。法一,每次都要使用对撞指针来判断是否是回文串,这边借助动态规划事先存储每组下标起始结束的是否回文串的标记。

class Solution(object):
    def __init__(self):
        self.res = []
        self.dp = []

    def partition(self, s):
        if not s:
            return self.res
        self.dp = [[False] * len(s) for _ in range(len(s))]
        for right in xrange(len(s)):
            for left in xrange(right + 1):
                if (s[left] == s[right] and (right - left <= 2 or self.dp[left + 1][right - 1])):
                    self.dp[left][right] = True

        self.helper(0, 0, [], s)
        return self.res

    def helper(self, before, idx, tmp_res, s):
        if idx >= len(s):
            return 
        if idx == len(s) - 1:
            if self.dp[before][idx]:
                tmp_res.append(s[before:])
                self.res.append(tmp_res[:])
                tmp_res.pop()
                return 

        if self.dp[before][idx]:
            tmp_res.append(s[before: idx + 1])
            self.helper(idx + 1, idx + 1, tmp_res, s)
            tmp_res.pop()
        self.helper(before, idx + 1, tmp_res, s)

46. 全排列

力扣

给定一个 没有重复 数字的序列,返回其所有可能的全排列。

示例:

输入: [1,2,3]
输出:
[ [1,2,3],
  [1,3,2],
  [2,1,3],
  [2,3,1],
  [3,1,2],
  [3,2,1]]

题解

一:经典排列题。其中,used数组记录访问过哪些数组。这是经典排列题,故有一层for循环,从0开始循环,将值填入tmp_res,不能重复取某个下标值,故多一个used数组,记录哪些已经被填入。回溯,保留现场,当退回去时,我们要将这个下标的数值退出数组,并恢复used数组,即置False,表示该下标未使用。

class Solution(object):
 
    def permute(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        def helper(tmp_res, used, nums):
            if len(tmp_res) == len(nums):
                ans.append(tmp_res[:])
                return 
            for i in range(len(nums)):
                if not used[i]:
                    tmp_res.append(nums[i])
                    used[i] = True
                    helper(tmp_res, used, nums)
                    tmp_res.pop()
                    used[i] = False
        ans = []
        if not nums:
            return ans
        used = [False] * len(nums)
        helper([], used, nums)
        return ans

47. 全排列 II

力扣

给定一个可包含重复数字的序列,返回所有不重复的全排列。

示例:

输入: [1,1,2]
输出:
[[1,1,2],
  [1,2,1],
  [2,1,1]]

题解

一:其实和上题思路完全一样,不过这题包含重复数字,我们得返回不重复的排列,举个例子,例如[1,1,2],用上题方法,会出现两个[1,1,2],分别是下标(0,1,2)和下标(1,0,2),还有其他的也会重复。这也是经典题,应对着这种情况,我们可以将nums数组排序,然后只要遍历的数值与前面一个数值相同,但若前面的未访问过跳过就好,其余情况依旧将该值填入tmp_res。

class Solution(object):
    def __init__(self):
        self.res = []
        
    def permuteUnique(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        if not nums:
            return self.res
        nums = sorted(nums)
        used = [False] * len(nums)
        self.helper([], used, nums)
        return self.res
    
    def helper(self, tmp_res, used, nums):
        if len(tmp_res) == len(nums):
            self.res.append(tmp_res[:])
            return 
        
        for i in range(len(nums)):
            if i != 0 and nums[i] == nums[i - 1] and not used[i - 1]:
                continue
            if not used[i]:
                tmp_res.append(nums[i])
                used[i] = True
                self.helper(tmp_res, used, nums)
                tmp_res.pop()
                used[i] = False

77. 组合

力扣

输入: n = 4, k = 2 输出: [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4],]

题解

一:经典的组合题,从该下标开始遍历后续数据,由于已经遍历过的不取,所以每次都要对start+1,同时由于都是从该下标开始遍历后续,不会遍历到之前遍历过的数据,无需向排列一样搞个used。

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

    def combine(self, n, k):
        """
        :type n: int
        :type k: int
        :rtype: List[List[int]]
        """
        if n <= 0 or k > n:
            return self.res
        self.DFS([], 0, n, k)
        return self.res

    def DFS(self, tmp_res, start, n, k):
        if len(tmp_res) == k:
            self.res.append(tmp_res[:])
            return 
        start += 1
        if start > n:
            return 

        for i in xrange(start, n + 1):
            tmp_res.append(i)
            self.DFS(tmp_res, i, n, k)
            tmp_res.pop()

39. 组合总和

力扣

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

说明:所有数字(包括 target)都是正整数。解集不能包含重复的组合。 
示例 1:输入: candidates = [2,3,6,7], target = 7,
所求解集为:
[[7],
  [2,2,3]]
示例 2:输入: candidates = [2,3,5], target = 8,
所求解集为:
[[2,2,2,2],
  [2,3,3],
  [3,5]]

题解

一:这亦是一道经典的组合题,故从当前下标向后遍历,与77不同的是,本题可以重复取值,所以此处的start不用加1,因为次下标亦可取。

class Solution(object):
    def combinationSum(self, candidates, target):
        """
        :type candidates: List[int]
        :type target: int
        :rtype: List[List[int]]
        """
        def DFS(start, tmp_res, target):
            if target < 0:
                return 
            if target == 0:
                res.append(tmp_res[:])
                return 
            for i in xrange(start, len(candidates)):
                residue = target - candidates[i]
                if residue < 0:
                    continue
                tmp_res.append(candidates[i])
                DFS(i, tmp_res, residue)
                tmp_res.pop()
            
        if not candidates or target <= 0:
            return self.res
        res = []
        DFS(0, [], target)
        return res

40. 组合总和 II

力扣

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

说明:所有数字(包括目标数)都是正整数。解集不能包含重复的组合。 
示例 1:输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[[1, 7],
  [1, 2, 5],
  [2, 6],
  [1, 1, 6]]
示例 2:输入: candidates = [2,5,2,1,2], target = 5,
所求解集为:
[[1,2,2],
  [5]]

题解

一:这边感觉还是和上题一样的思路,借助排序来剪枝(去掉重复解,用used数组标记该下标的数值是否被使用过,若与前面的数值相同,而前面的未被使用过,该下标也不被使用),只能用一次,就需要start+1。

class Solution(object):
    def combinationSum2(self, candidates, target):
        """
        :type candidates: List[int]
        :type target: int
        :rtype: List[List[int]]
        """
        def DFS(start, tmp_res, target):
            if target < 0:
                return 
            if target == 0:
                res.append(tmp_res[:])
                return 
            for i in xrange(start + 1, len(candidates)):
                residue = target - candidates[i]
                if residue < 0:
                    break
                if i >= 1 and candidates[i] == candidates[i - 1] and not used[i - 1]:
                    continue
                tmp_res.append(candidates[i])
                used[i] = True 
                DFS(i, tmp_res, residue)
                tmp_res.pop()
                used[i] = False 

        candidates = sorted(candidates)
        used = [False] * len(candidates)
        res = []
        DFS(-1, [], target)
        return res

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值