回溯解决方案的大 O 计算范围内的排列
Posted
技术标签:
【中文标题】回溯解决方案的大 O 计算范围内的排列【英文标题】:Big O of backtracking solution counts permutations with range 【发布时间】:2020-10-29 13:05:00 【问题描述】:我有一个问题,我一直在努力解决解决方案的时间和空间复杂性:
给定一个整数数组(可能重复)A
和 min
、low
、high
是整数。
求A
中的项目组合总数:
low <= A[i] <= high
每个组合至少有min
号码。
一个组合中的数字可以重复,因为它们在 A 中被认为是唯一的,但组合不能重复。例如:[1,1,2]
-> 组合:[1,1],[1,2],[1,1,2]
可以,但[1,1],[1,1], [1,2], [2,1] ...
不行。
示例:A=[4, 6, 3, 13, 5, 10], min = 2, low = 3, high = 5
在A中有4种组合有效整数的方法:[4,3],[4,5],[4,3,5],[3,5]
这是我的解决方案,它有效:
class Solution:
def __init__(self):
pass
def get_result(self, arr, min_size, low, high):
return self._count_ways(arr, min_size, low, high, 0, 0)
def _count_ways(self, arr, min_size, low, high, idx, comb_size):
if idx == len(arr):
return 0
count = 0
for i in range(idx, len(arr)):
if arr[i] >= low and arr[i] <= high:
comb_size += 1
if comb_size >= min_size:
count += 1
count += self._count_ways(arr, min_size, low, high, i + 1, comb_size)
comb_size -= 1
return count
我使用回溯:
时间:O(n!)
因为对于每一个整数,我都会在最坏的情况下检查每一个剩余的整数 - 当所有整数都可以形成组合时。
空格:O(n)
,因为我最多需要调用堆栈上的 n 次调用,并且我只使用 2 个变量来跟踪我的组合。
我的分析正确吗?
另外,有点超出范围,但是:我应该做一些记忆来改进它吗?
【问题讨论】:
【参考方案1】:如果我正确理解您的要求,您的算法就太复杂了。你可以这样做:
-
计算数组
B
,包含A
中low
和high
之间的所有元素。
为k = min .. B.length
返回sum of Choose(B.length, k)
,其中Choose(n,k)
是n(n-1)..(n-k+1)/k!
。
如果您使用记忆化来计算 Choose
函数的分子/分母,则时间和空间复杂度为 O(n)
(例如,如果您已经计算了 5*4*3
,则只需一次乘法即可计算 5*4*3*2
等。 )。
在你的例子中,你会得到B = [4, 3, 5]
,所以B.length = 3
,结果是
Choose(3, 2) + Choose(3, 3)
= (3 * 2)/(2 * 1) + (3 * 2 * 1)/(3 * 2 * 1)
= 3 + 1
= 4
【讨论】:
感谢您的回答!我试图做O(n)
以获得low
和high
之间的数字,但我无法弄清楚第二步。我会尝试实现它。
你有一个链接,我可以找到一种技术来找出你写的公式吗?
@Viet Sure: en.wikipedia.org/wiki/Combination
谢谢莫。这很有帮助!【参考方案2】:
您对时间复杂度的分析不太正确。
我知道你从哪里得到O(n!)
:for i in range(idx, len(arr)):
循环的长度随着每次递归调用而减少,所以看起来你正在做n*(n-1)*(n-2)*...
。
但是,来自长度为 m
的循环的递归调用并不总是包含大小为 m-1
的循环。假设您最外层的调用有 3 个元素。循环遍历 3 个可能的值,每个值都产生一个新调用。第一个这样的调用将有一个迭代 2 个值的循环,但下一个调用只迭代 1 个值,最后一个调用会立即达到您的基本情况并停止。所以你得到的是((1+0)+1+0)
,而不是3*2*1=((1+1)+(1+1)+(1+1))
。
使用大小为 n
的数组调用 _count_ways
所需的时间是使用大小为 n-1
的调用的两倍。要看到这一点,请考虑大小n
调用中的第一个分支,即选择或不选择第一个元素。首先,我们选择第一个元素,这会导致大小为n-1
的递归调用。其次,我们不选择第一个元素,这给了我们n-1
元素进行迭代,所以就好像我们有第二个大小为n-1
的递归调用。
n 每增加一次,时间复杂度就会增加 2 倍,因此您的解决方案的时间复杂度为 O(2^n)
。这是有道理的:您正在检查每个组合,并且在一组大小为 n
的集合中有 2^n
组合。
但是,由于您只是尝试计算组合而不是对它们进行处理,因此效率非常低。请参阅@Mo B. 的答案以获得更好的解决方案。
【讨论】:
以上是关于回溯解决方案的大 O 计算范围内的排列的主要内容,如果未能解决你的问题,请参考以下文章