简体   繁体   中英

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).

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

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

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).

I came to this answer because each step size has a loop that calls k number of step sizes. 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.

What is the runtime? Is k^n correct?

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. 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. 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]). Something like this (I don't have a Java syntax checker, so bear with syntax errors please):

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 ).


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. So the number of operations is proportional to the number of step sequences whose sum is less than or equal to n . [See Notes 1 and 2].

Now, let's consider how many possible prefix sequences there are for a given value of 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.

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. As k increases, you will get closer and closer to O(2 n ). [Note 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. (φ is 1.618..., or (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.

  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). That produces an additional factor of 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.

  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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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