leetcode 回溯(一)

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

目录

306. 累加数

17. 电话号码的字母组合

22. 括号生成

93. 复原IP地址

131. 分割回文串

46. 全排列

47. 全排列 II

77. 组合

39. 组合总和

40. 组合总和 II


306. 累加数

https://leetcode-cn.com/problems/additive-number/

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

一个有效的累加序列必须至少包含 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, flag = len(num), False
        #num[:i + 1], num[i + 1: j + 1], num[j+1:.]
        for i in range((n + 1) // 2):
            if num[0] == '0' and i > 0:
                break        
            for j in range(i + 1, n):
                if num[i + 1] == '0' and j > i + 1:
                    break
                a_2, a_1 = int(num[: i + 1]), int(num[i + 1 : j + 1])
                start, local_ans = j, 2
                while True:
                    if start >= n - 1:
                        break
                    a = a_1 + a_2
                    
                    # print(i, j, a_2, a_1, a)
                    str_a = str(a)
                    m = len(str_a)
                    end = start + m
                    cur = int(num[start + 1: min(n, end + 1)])
                    if cur != a:
                        break
                    local_ans += 1
                    a_2 = a_1
                    a_1 = a 
                    a = a_1 + a_2
                    start = end
                    if start == n - 1:
                        if local_ans >= 3:
                            return True
                        break
        return False

17. 电话号码的字母组合

https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/

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

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

题解

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

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

    def letterCombinations(self, digits):
        """
        :type digits: str
        :rtype: List[str]
        """
        if not digits:
            return self.res

        rec = {"2": "abc", "3": "def", "4": "ghi",
               "5": "jkl", "6": "mno", "7": "pqrs",
               "8": "tuv", "9": "wxyz"}

        self.DFS(0, "", digits, rec)
        return self.res

    def DFS(self, idx, tmp_res, digits, rec):
        if idx >= len(digits):
            self.res.append(tmp_res)
            return 

        # for i in xrange(idx, len(digits)):
        for j in xrange(0, len(rec[digits[idx]])):
            self.DFS(idx + 1, tmp_res + rec[digits[idx]][j], digits, rec)

22. 括号生成

https://leetcode-cn.com/problems/generate-parentheses/

数字 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地址

https://leetcode-cn.com/problems/restore-ip-addresses/

给定一个只包含数字的字符串,复原它并返回所有可能的 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. 分割回文串

https://leetcode-cn.com/problems/palindrome-partitioning/

给定一个字符串 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. 全排列

https://leetcode-cn.com/problems/permutations/

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

示例:

输入: [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 __init__(self):
        self.res = []

    def permute(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        if not nums:
            return self.res
        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 xrange(len(nums)):
            if not used[i]:
                tmp_res.append(nums[i])
                used[i] = True
                self.helper(tmp_res, used, nums)
                tmp_res.pop()
                used[i] = False

47. 全排列 II

https://leetcode-cn.com/problems/permutations-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. 组合

https://leetcode-cn.com/problems/combinations/

输入: 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. 组合总和

https://leetcode-cn.com/problems/combination-sum/

给定一个无重复元素的数组 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

https://leetcode-cn.com/problems/combination-sum-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

 

展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 黑客帝国 设计师: 上身试试
应支付0元
点击重新获取
扫码支付

支付成功即可阅读