Partition类题目分为两类
(如果时间有限,可先刷带加粗的题)隔板划分:比如在19216811这串数字中找到所有有效地IP地址划分,输入顺序固定Medium: 93, 131,842
Hard: 282
2.桶分类:给一个数组[2, 2, 3, 3] 问是否可以分成和相同的两部分,顺序可以打乱Medium: 198
两类问题都使用backtracking回溯法的方法实现,首先来看131题
这题需要按隔板划分一个str,并且每一个partition都要求是回文,可用递归搜索实现,每次找到符合要求的划分后记录,实现如下
def partition(self, s: str) -> List[List[str]]:
def isPali(s):
return s == s[::-1]
# s - str to search, path - previous result
def dfs(s, path):
if not s: yield path
for i, _ in enumerate(s):
cur_str = s[: i + 1]
if isPali(cur_str):
yield from dfs(s[i + 1: ], path + [cur_str])
return list(dfs(s, []))
N - length of input string
Time: O(2^N * N)
Space: O(N)
时间复杂度上,由于搜索会遍历所有划分,那么输入长度为n的str,每一个间隔都可放隔板或不放,带来2^N种可能,每次判断是否为回文需要N
空间上,递归最深为N,另外不需要考虑答案数量带来的空间复杂度,因为这是不能被解法影响的,所以假装他是常数吧
这题为这种类型最基本的模板,下一题非常类似,只是改了substring判断条件和一些细节
def restoreIpAddresses(self, s: str) -> List[str]:
def is_valid(s: str) -> bool:
if not len(s) in range(1,4): return False
if s[0] == '0' and len(s) > 1: return False
return int(s) in range(0, 256)
def search(s: str, path: List[str]):
if len(path) > 4: return
if s == "" and len(path) == 4:
yield functools.reduce(lambda str1, str2: f"{str1}.{str2}", path)
# Try all possible next ip part
for i in range(min(3, len(s))):
head = s[: i + 1]
if is_valid(head):
yield from search(s[i + 1: ], path + [head])
else:
return
return list(search(s, []))
Time: O(1)
Space: O(1)
有效IP最长12位,递归深度和划分总数都是常数
下一题稍为复杂,递归函数内还要附加信息,之前的暂存搜索结果也成为判断条件的一部分
def splitIntoFibonacci(self, S: str) -> List[int]:
def search(S: str, pre: int, cur: int, path: List[int]):
# If found: save and return
if not S:
if pre != None and cur != None and len(path) >= 3:
yield path
return
# search forward
# Only read 0 or 1 numbers
elif pre == None:
for i, _ in enumerate(S):
# Leading zeros are not allowed
if i > 0 and S[0] == '0': return
head = int(S[:i + 1])
yield from search(S[i + 1: ], cur, head, path + [head])
# Have read >= 2 numbers
else:
expected = str(pre + cur)
# F[i] <= 2**31 - 1
if int(expected) > 2**31 - 1: return
head = S[: len(expected)]
if head == expected:
yield from search(S[len(expected): ], cur, pre + cur, path + [pre + cur])
return next(search(S, None, None, []), [])
N - length of input string
Time: O(N^2)
Space: O(N)
因为只要确定下前两个数,接下来就可以用N时间判断整个str是否满足条件了,确定前两个数需要N^2
空间上递归最深是O(N)层,也只需要返回一个符合的partition
下一题比这题在调用递归函数的条件判断更复杂
def addOperators(self, num: str, target: int) -> List[str]:
def dfs(num: str, prev: int, cur: int, goal: int, path: str):
if not num:
if goal == target and cur == 0:
yield path[1:]
return
# Extend
cur = cur * 10 + int(num[0])
# Avoid starting 0
if cur > 0:
yield from dfs(num[1:], prev, cur, goal, path)
# Addition
yield from dfs(num[1:], cur, 0, goal + cur, f"{path}+{cur}")
# Can * or - only if there are some prev operands
if path:
# -
yield from dfs(num[1:], -cur, 0, goal - cur, f"{path}-{cur}")
# *
yield from dfs(num[1:], cur * prev, 0, goal - prev + (cur * prev), f"{path}*{cur}")
return list(dfs(num, 0, 0, target, ""))
这题思路上非常拧巴,需要一边搜索一边帮目前的表达式估值
dfs(num, prev, cur, goal, path)num:剩余待搜索的字符串
prev: 上一次被加上的值
cur:当前数字的值
goal: 目前表达式的值
path: 记录的待输出结果
重点是prev,当遇到乘法,因为优先级更高,需要退回之前被加的值,重新计算再往前搜索
这题为桶划分类型,与隔板划分略有不同。在尝试把nums[i]加入某个桶中过程中,在往前搜索时i总是递增的,板划分类的题因为具有这个特性,所以只需要往前搜索-返回,不需要额外记录状态。这题因为尝试放进桶的编号不是随着搜索往前一直增加的,所以像通常回溯法一样:先尝试走一步,记录状态 -> 往前搜索 -> 退回
def canPartitionKSubsets(self, nums, k):
subsum, remainder = divmod(sum(nums), k)
if remainder != 0: return False # Prune1
nums.sort(reverse = True) # Prune2: Pick larger nums first
buckets = [0] * k # Sum of nums in a subset
# Try to add nums[index] to buckets
def search(index: int) -> bool:
if index == len(nums):
return len(set(buckets)) == 1
for i, val in enumerate(buckets):
buckets[i] += nums[index] # Try to add
if buckets[i] <= subsum and search(index + 1): # Search Forward
return True
buckets[i] = val # Revert
# Prune3: If nums[index] can't be added to either, then no valid partition
if buckets[i] == 0: return False
return search(0)
N = len(nums)
Time complexity: O(K^N)
Space complexity: O(N)
时间复杂度即每次有K种放法,一共要放N次。可以依靠各种剪枝优化
空间复杂度为递归深度N
这系列文章希望总结LeetCode中不同类型题目的共通规律,模板
参考了刷题界思想领袖花花酱的题目分类。可能不完全,之后会持续更新
本系列其他文章麦当劳No1:LeetCode二分查找专题1 易于理解的边界详解(Python)zhuanlan.zhihu.com