简体   繁体   English

找到你可以上n阶梯的所有方法,如果你可以一次采取k步,使k <= n

[英]Find all the ways you can go up an n step staircase if you can take k steps at a time such that k <= n

This is a problem I'm trying to solve on my own to be a bit better at recursion(not homework). 这是我试图自己解决的一个问题,在递归(而不是家庭作业)上要好一点。 I believe I found a solution, but I'm not sure about the time complexity (I'm aware that DP would give me better results). 我相信我找到了一个解决方案,但我不确定时间复杂度(我知道DP会给我更好的结果)。

Find all the ways you can go up an n step staircase if you can take k steps at a time such that k <= n 找到你可以上n阶梯的所有方法,如果你可以一次采取k步,使k <= n

For example, if my step sizes are [1,2,3] and the size of the stair case is 10, I could take 10 steps of size 1 [1,1,1,1,1,1,1,1,1,1]=10 or I could take 3 steps of size 3 and 1 step of size 1 [3,3,3,1]=10 例如,如果我的步长是[1,2,3]并且楼梯的大小是10,我可以采取10步1的大小[1,1,1,1,1,1,1,1, 1,1] = 10或者我可以采取3步和3步1尺寸[3,3,3,1] = 10

Here is my solution: 这是我的解决方案:

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());

}

My guess is that this runtime is k^n where k is the numbers of step sizes, and n is the number of steps (3 and 10 in this case). 我的猜测是这个运行时间是k ^ n,其中k是步长的数量,n是步数(在这种情况下是3和10)。

I came to this answer because each step size has a loop that calls k number of step sizes. 我得到了这个答案,因为每个步长都有一个循环,调用k个步长。 However, the depth of this is not the same for all step sizes. 但是,对于所有步长,其深度并不相同。 For instance, the sequence [1,1,1,1,1,1,1,1,1,1] has more recursive calls than [3,3,3,1] so this makes me doubt my answer. 例如,序列[1,1,1,1,1,1,1,1,1,1]比[3,3,3,1]有更多的递归调用,所以这让我怀疑我的答案。

What is the runtime? 什么是运行时? Is k^n correct? k ^ n是否正确?

If you want to solve this recursively, you should use a different pattern that allows caching of previous values, like the one used when calculating Fibonacci numbers. 如果要以递归方式解决此问题,则应使用允许缓存先前值的其他模式,例如计算Fibonacci数时使用的模式。 The code for Fibonacci function is basically about the same as what do you seek, it adds previous and pred-previous numbers by index and returns the output as current number. Fibonacci函数的代码基本上与您所寻求的相同,它通过索引添加先前和前一个数字,并将输出作为当前数字返回。 You can use the same technique in your recursive function , but add not f(k-1) and f(k-2), but gather sum of f(k-steps[i]). 您可以在递归函数中使用相同的技术,但不添加f(k-1)和f(k-2),而是收集f的总和(k-steps [i])。 Something like this (I don't have a Java syntax checker, so bear with syntax errors please): 像这样的东西(我没有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;
}

TL;DR: Your algorithm is O(2 n ), which is a tighter bound than O(k n ), but because of some easily corrected inefficiencies the implementation runs in O(k 2 × 2 n ). TL; DR:你的算法是O(2 n ),它比O(k n )更紧密,但由于一些容易纠正的低效率,实现在O(k 2 ×2 n )中运行。


In effect, your solution enumerates all of the step-sequences with sum n by successively enumerating all of the viable prefixes of those step-sequences. 实际上,您的解决方案通过连续枚举这些步骤序列的所有可行前缀来枚举所有具有和n的步骤序列。 So the number of operations is proportional to the number of step sequences whose sum is less than or equal to n . 因此,操作的数量与其总和小于或等于n的步骤序列的数量成比例。 [See Notes 1 and 2]. [见注1和2]。

Now, let's consider how many possible prefix sequences there are for a given value of n . 现在,让我们考虑给定的n值有多少可能的前缀序列。 The precise computation will depend on the steps allowed in the vector of step sizes, but we can easily come up with a maximum, because any step sequence is a subset of the set of integers from 1 to n , and we know that there are precisely 2 n such subsets. 精确的计算将取决于步长矢量中允许的步骤,但我们可以很容易地得出一个最大值,因为任何步骤序列都是从1到n的整数集的子集,并且我们知道有精确的2 n个这样的子集。

Of course, not all subsets qualify. 当然,并非所有子集都符合条件。 For example, if the set of step-sizes is [1, 2] , then you are enumerating Fibonacci sequences, and there are O(φ n ) such sequences. 例如,如果设定步长是[1, 2]那么你枚举斐波纳契序列,并有O(φn)的此类序列。 As k increases, you will get closer and closer to O(2 n ). 随着k增加,您将越来越接近O(2 n )。 [Note 3] [注3]

Because of the inefficiencies in your coded, as noted, your algorithm is actually O(k 2 α n ) where α is some number between φ and 2, approaching 2 as k approaches infinity. 因为编码的,如所指出的在你的低效率的,你的算法实际上是O(K 2αn)其中α是φ和2之间的一些数量,接近2为k接近无穷大。 (φ is 1.618..., or (1+sqrt(5))/2)). (φ是1.618 ......,或(1 + sqrt(5))/ 2))。

There are a number of improvements that could be made to your implementation, particularly if your intent was to count rather than enumerate the step sizes. 可以对您的实现进行一些改进,特别是如果您的意图是计算而不是枚举步长。 But that was not your question, as I understand it. 但据我所知,这不是你的问题。


Notes 笔记

  1. That's not quite exact, because you actually enumerate a few extra sequences which you then reject; 这不太准确,因为你实际上列举了一些你拒绝的额外序列; the cost of these rejections is a multiplier by the size of the vector of possible step sizes. 这些拒绝的成本乘以可能的步长矢量的大小。 However, you could easily eliminate the rejections by terminating the for loop as soon as a rejection is noticed. 但是,一旦发现拒绝,您就可以通过终止for循环轻松消除拒绝。

  2. The cost of an enumeration is O(k) rather than O(1) because you compute the sum of the sequence arguments for each enumeration (often twice). 枚举的成本是O(k)而不是O(1),因为您计算每个枚举的序列参数的总和(通常是两次)。 That produces an additional factor of k . 这产生了额外的k因子。 You could easily eliminate this cost by passing the current sum into the recursive call (which would also eliminate the multiple evaluations). 您可以通过将当前总和传递给递归调用(这也将消除多个评估)来轻松消除此成本。 It is trickier to avoid the O(k) cost of copying the sequence into the output list, but that can be done using a better (structure-sharing) data-structure. 避免将序列复制到输出列表中的O(k)成本更为棘手,但这可以使用更好的(结构共享)数据结构来完成。

  3. The question in your title (as opposed to the problem solved by the code in the body of your question) does actually require enumerating all possible subsets of {1…n}, in which case the number of possible sequences would be exactly 2 n . 标题中的问题(与问题正文中的代码解决的问题相反)确实需要枚举{1 ... n}的所有可能子集,在这种情况下,可能序列的数量恰好为2 n

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM