Java 如果您可以一次走k步,找到所有可以上n步楼梯的方法,使k<;=N

Java 如果您可以一次走k步,找到所有可以上n步楼梯的方法,使k<;=N,java,algorithm,Java,Algorithm,这是一个我试图自己解决的问题,以便更好地处理递归(而不是家庭作业)。我相信我找到了解决方案,但我不确定时间复杂性(我知道DP会给我更好的结果) 如果你一次可以走k步,那么你可以找到爬n步楼梯的所有方法,使k步){ 返回; } 如果(问题1_sum_seq(序列)=numSteps){ 问题1。添加(新ArrayList(序列)); 返回; } 用于(整数步长:步长){ 顺序。添加(步长); 问题1_rec(顺序、步骤、步骤); sequence.remove(sequence.size()-1)

这是一个我试图自己解决的问题,以便更好地处理递归(而不是家庭作业)。我相信我找到了解决方案,但我不确定时间复杂性(我知道DP会给我更好的结果)

如果你一次可以走k步,那么你可以找到爬n步楼梯的所有方法,使k步){ 返回; } 如果(问题1_sum_seq(序列)=numSteps){ 问题1。添加(新ArrayList(序列)); 返回; } 用于(整数步长:步长){ 顺序。添加(步长); 问题1_rec(顺序、步骤、步骤); sequence.remove(sequence.size()-1); } } 公共静态整型问题1_sum_seq(列表序列){ 整数和=0; for(int i:序列){ 总和+=i; } 回报金额; } 公共静态void main(字符串[]args){ 问题1(10); System.out.println(problem1Ans.size()); } 我猜这个运行时是k^n,其中k是步长的数量,n是步长的数量(在本例中为3和10)

我得出这个答案是因为每个步长都有一个称为k个步长的循环。但是,并非所有步长的深度都相同。例如,序列[1,1,1,1,1,1,1,1,1,1,1]比[3,3,3,1]有更多的递归调用,因此我怀疑我的答案


什么是运行时?k^n正确吗?

如果你想递归地解决这个问题,你应该使用一种不同的模式,允许缓存以前的值,就像计算斐波那契数时使用的模式一样。Fibonacci函数的代码基本上与您要查找的内容相同,它通过索引添加previous和pred previous数字,并将输出作为当前数字返回。您可以在递归函数中使用相同的技术,但不能添加f(k-1)和f(k-2),而是收集f(k步[i])之和。类似这样的内容(我没有Java语法检查器,请容忍语法错误):

静态列表缓存=新建ArrayList;
静态列表storedSteps=null;//如果与相同的步长值一起使用,则不清除缓存
公共静态整数问题1(整数numSteps,列出步骤){
如果(!ArrayList::equal(steps,storedSteps)){//请按数据检查是否相等,而不是按链接检查是否相等
storedSteps=steps;//或使用任何方法复制
cache.clear();//删除所有数据-现在无效
//TODO使cache+storedSteps成为单个结构
}
返回问题1_rec(numSteps,steps);
}
私有静态整数问题1\u rec(整数numSteps,列出步骤){
如果(0>numSteps){返回0;}
如果(0==numSteps){return 1;}
如果(cache.length()>=numSteps+1){return cache[numSteps]}//缓存命中
整数acc=0;
对于(整数i:steps){acc+=problem1_rec(numSteps-i,steps);}
cache[numSteps]=acc;//缓存未命中。请确保ArrayList支持按索引插入,否则请使用正确的类型
返回acc;
}
TL;DR:您的算法是O(2n),这是一个比O(kn)更紧的界限,但是由于一些容易纠正的低效,实现在O(k2×2n)中运行


实际上,您的解决方案通过依次枚举这些步骤序列的所有可行前缀,以sum
n
枚举所有步骤序列。因此,操作的数量与其和小于或等于
n
的步骤序列的数量成正比。[见附注1和2]

现在,让我们考虑给定的值<代码> N< /代码>有多少可能的前缀序列。精确的计算将取决于步长向量中允许的步长,但我们很容易得出最大值,因为任何步长序列都是从1到

n
的整数集的子集,我们知道这样的子集正好有2n个

当然,并非所有子集都符合条件。例如,如果步长集是
[1,2]
,则您正在枚举斐波那契序列,并且存在O(φn)个这样的序列。随着
k
的增加,你会越来越接近O(2n)。[注3]

如前所述,由于编码效率低下,您的算法实际上是O(k2αn),其中α是φ和2之间的某个数字,当k接近无穷大时接近2。(φ为1.618…,或(1+sqrt(5))/2)

可以对您的实现进行许多改进,特别是当您的目的是计算而不是枚举步长时。但据我所知,这不是你的问题


笔记
  • 这并不完全准确,因为你实际上列举了一些额外的序列,然后你拒绝了它们;这些拒绝的成本是可能步长向量大小的乘数。但是,您可以通过在发现拒绝后立即终止for循环来轻松消除拒绝

  • 枚举的代价是O(k)而不是O(1),因为计算每个枚举的序列参数之和(通常两次)。这将产生一个额外的系数
    k
    。通过将当前总和传递到递归调用(这也将消除多个求值),可以很容易地消除此开销。要避免将序列复制到输出列表中的O(k)开销更为棘手,但这可以使用更好的(结构共享)数据结构来实现

  • 标题中的问题(与问题主体中的代码解决的问题相反)实际上需要枚举{1…n}的所有可能子集,在这种情况下,可能序列的数量正好是2n


  • 如果您不确定复杂性,可以测量不同值的时间并绘制图表。在大多数情况下,这会让你对复杂度函数有一个很好的了解。对于这个算法,时间复杂度是正确的,至少如果我们衡量最坏的情况。改进这一点的一种方法是使用memonization(参见链接),它是basica的一种
    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());
    
    }
    
    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;
    }