这是我试图自己解决的一个问题,在递归(而不是家庭作业)方面要好一些。我相信我找到了一个解决方案,但我不确定时间复杂度(我知道DP会给我更好的结果)。
如果你可以一次采取k步,那么找出你可以上n阶梯的所有方法,以便k <= n
例如,如果我的步长是[1,2,3]并且楼梯的大小是10,我可以采取10步1的大小[1,1,1,1,1,1,1 ,1,1,1] = 10或者我可以采取3步3步和1步1尺寸[3,3,3,1] = 10
这是我的解决方案:
static List<List<Integer>> problem1Ans = new ArrayList<List<Integer>>();
public static void problem1(int numSteps){
int [] steps = {1,2,3};
problem1_rec(new ArrayList<Integer>(), numSteps, steps);
}
public static void problem1_rec(List<Integer> sequence, int numSteps, int [] steps){
if(problem1_sum_seq(sequence) > numSteps){
return;
}
if(problem1_sum_seq(sequence) == numSteps){
problem1Ans.add(new ArrayList<Integer>(sequence));
return;
}
for(int stepSize : steps){
sequence.add(stepSize);
problem1_rec(sequence, numSteps, steps);
sequence.remove(sequence.size()-1);
}
}
public static int problem1_sum_seq(List<Integer> sequence){
int sum = 0;
for(int i : sequence){
sum += i;
}
return sum;
}
public static void main(String [] args){
problem1(10);
System.out.println(problem1Ans.size());
}
我的猜测是这个运行时间是k ^ n,其中k是步长的数量,n是步数(在这种情况下是3和10)。
我得出了这个答案,因为每个步长都有一个调用k个步长的循环。但是,对于所有步长,其深度并不相同。例如,序列[1,1,1,1,1,1,1,1,1,1]具有比[3,3,3,1]更多的递归调用,所以这让我怀疑我的答案。
什么是运行时? k ^ n是否正确?
答案 0 :(得分:1)
如果要以递归方式解决此问题,则应使用允许缓存先前值的其他模式,例如计算Fibonacci数时使用的模式。 Fibonacci函数的代码基本上与您所寻求的相同,它通过索引添加先前和前一个数字,并将输出作为当前数字返回。您可以在递归函数中使用相同的技术,但不添加f(k-1)和f(k-2),而是收集f的总和(k-steps [i])。像这样的东西(我没有Java语法检查器,所以请注意语法错误):
static List<Integer> cache = new ArrayList<Integer>;
static List<Integer> storedSteps=null; // if used with same value of steps, don't clear cache
public static Integer problem1(Integer numSteps, List<Integer> steps) {
if (!ArrayList::equal(steps, storedSteps)) { // check equality data wise, not link wise
storedSteps=steps; // or copy with whatever method there is
cache.clear(); // remove all data - now invalid
// TODO make cache+storedSteps a single structure
}
return problem1_rec(numSteps,steps);
}
private static Integer problem1_rec(Integer numSteps, List<Integer> steps) {
if (0>numSteps) { return 0; }
if (0==numSteps) { return 1; }
if (cache.length()>=numSteps+1) { return cache[numSteps] } // cache hit
Integer acc=0;
for (Integer i : steps) { acc+=problem1_rec(numSteps-i,steps); }
cache[numSteps]=acc; // cache miss. Make sure ArrayList supports inserting by index, otherwise use correct type
return acc;
}
答案 1 :(得分:1)
TL; DR:你的算法是O(2 n ),它比O(k n )更紧密,但由于一些容易纠正的低效率实现在O(k 2 ×2 n )中运行。
实际上,您的解决方案通过连续枚举这些步骤序列的所有可行前缀来枚举所有具有和n
的步骤序列。因此,操作的数量与其总和小于或等于n
的步骤序列的数量成比例。 [见注1和2]。
现在,让我们考虑给定值n
有多少可能的前缀序列。精确计算将取决于步长矢量中允许的步骤,但我们可以很容易地得出最大值,因为任何步骤序列都是从1到n
的整数集的子集,我们知道这个子集恰好有2个 n 。
当然,并非所有子集都符合条件。例如,如果步长大小为[1, 2]
,那么您将枚举斐波纳契数列,并且存在O(φ n )这样的序列。当k
增加时,您将越来越接近O(2 n )。 [注3]
由于您的编码效率低,如上所述,您的算法实际上是O(k 2 α n )其中α是φ和2之间的某个数字,接近2当k接近无穷大时。 (φ是1.618 ......,或(1 + sqrt(5))/ 2))。
可以对您的实现进行一些改进,特别是如果您的意图是计算而不是枚举步长。但据我所知,这不是你的问题。
这不太准确,因为你实际上列举了一些你拒绝的额外序列;这些拒绝的成本乘以可能的步长矢量的大小。但是,一旦发现拒绝,您就可以通过终止for循环轻松消除拒绝。
枚举的成本是O(k)而不是O(1),因为您计算每个枚举的序列参数的总和(通常是两次)。这产生了k
的额外因子。您可以通过将当前总和传递给递归调用(这也将消除多个评估)来轻松消除此成本。避免将序列复制到输出列表中的O(k)成本更为棘手,但可以使用更好的(结构共享)数据结构来完成。
标题中的问题(与问题正文中的代码解决的问题相反)确实需要枚举{1 ... n}的所有可能子集,在这种情况下,可能的序列数量将是2 n 。