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