问题描述
我有一个问题,我一直在努力解决解决方案的时间和空间的复杂性:
给出一个整数数组(可能重复)A
和min
,low
,high
是整数。
在A
中找到以下项的组合总数:
-
low <= A[i] <= high
- 每个组合至少具有
min
个数字。 - 一个组合中的数字可以重复,因为它们在A中被认为是唯一的,但是组合不能重复。例如:
[1,1,2]
->组合:[1,1],[1,2],2]
可以,但[1,[2,1] ...
不能。
示例:A=[4,6,3,13,5,10],min = 2,low = 3,high = 5
有四种方法可以在A中组合有效的整数:[4,3],[4,5],[3,5]
这是我的解决方案,它有效:
class Solution:
def __init__(self):
pass
def get_result(self,arr,min_size,low,high):
return self._count_ways(arr,high,0)
def _count_ways(self,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,i + 1,comb_size)
comb_size -= 1
return count
我使用回溯,所以:
时间:O(n!)
,因为在最坏的情况下-当所有整数都可以形成组合时,我会检查每一个整数是否剩余。
空格:O(n)
最多只能在调用堆栈上进行n次调用,并且我仅使用2个变量来跟踪我的组合。
我的分析正确吗?
也有点超出范围,但是:我是否应该做一些记忆来改善它?
解决方法
如果我正确理解您的要求,则您的算法太复杂了。您可以按照以下步骤进行操作:
- 计算数组
B
,其中包含A
和low
之间的high
中的所有元素。 - 为
sum of Choose(B.length,k)
返回k = min .. B.length
,其中Choose(n,k)
是n(n-1)..(n-k+1)/k!
。
如果您使用备忘录来计算O(n)
函数的分子/分母,则时间和空间的复杂度为Choose
(例如,如果您已经计算了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!)
:每次进行递归调用时,for i in range(idx,len(arr)):
循环的长度都会减少,因此好像您在进行n*(n-1)*(n-2)*...
。
但是,长度为m
的循环的递归调用并不总是包含大小为m-1
的循环。假设您最外面的电话有3个元素。循环迭代3个可能的值,每个值都产生一个新的调用。第一个这样的调用将具有一个循环,该循环迭代2个值,但是下一个调用仅迭代1个值,而最后一个立即击中您的基本情况并停止。因此,您得到的是3*2*1=((1+1)+(1+1)+(1+1))
,而不是((1+0)+1+0)
。
对大小为_count_ways
的数组n
的调用所花费的时间是大小为n-1
的调用的两倍。要看到这一点,请考虑调用大小为n
的第一个分支,该分支是否选择第一个元素。首先,我们选择第一个元素,这导致大小为n-1
的递归调用。其次,我们不选择第一个元素,它使我们n-1
个元素可以迭代,因此好像我们有第二个递归调用,大小为n-1
。
n的每次增加都会使时间复杂度增加2倍,因此解决方案的时间复杂度为O(2^n)
。这很有意义:您正在检查每个组合,并且2^n
个组合的大小为n
。
但是,由于您仅尝试计算组合而不对组合进行任何操作,因此效率非常低。有关更好的解决方案,请参见@Mo B.的答案。