java - 这里怎么分析时间复杂度呢?

标签 java recursion time

假设您正在和您的 friend 玩以下 Flip Game:给定一个仅包含这两个字符的字符串:+-,您和您的 friend 轮流将两个连续的 "++" 翻转为 "--"。当一个人不能再移动时,游戏结束,因此另一个人将成为赢家。

编写一个函数来判断起始玩家是否可以保证获胜。

例如,给定 s = "++++",返回 true。起始玩家可以通过翻转中间的“++”成为“+--+”来保证获胜。

这是我的代码:

public boolean canWin(String s) {

    if(s==null || s.length()<2) return false;
    char[] arr=s.toCharArray();
    return canWinHelper(arr);
}

public boolean canWinHelper(char[] arr){
    for(int i=0; i<arr.length-1; i++){
        if(arr[i]=='+' && arr[i+1]=='+'){
            arr[i]='-';
            arr[i+1]='-';
            boolean win=!canWinHelper(arr);
            arr[i]='+';
            arr[i+1]='+';
            if(win) return true;
        }
    }
    return false;
}

它有效,但我不确定如何计算这里的时间复杂度,因为该函数将继续调用自身,直到返回 false。有人在这里分享一些想法吗?

同样在查找过程中,我们会遇到重复计算,所以我想我可以使用hashmap来避免这些重复。键:字符串,值: boolean 值。

我使用 HashMap 更新的代码:

 public boolean canWin(String s){

    if(s==null || s.length()<2) return false;
    HashMap<String,Boolean> map=new HashMap<String,Boolean>();
    return helper(s,map);
 }

 public boolean helper(String s, HashMap<String,Boolean> map){
     if(map.containsKey(s)) return map.get(s);
     for(int i=0; i<s.length()-1; i++){
         if(s.charAt(i)=='+' && s.charAt(i+1)=='+'){
             String fliped=s.substring(0,i)+"--"+s.substring(i+2);
             if(!helper(fliped,map)){
                 map.put(s,true);
                 return true;
             }
         }
     }
     map.put(s,false);
     return false;
 }

还是想知道这里的时空复杂度怎么分析?

最佳答案

取 n = arr.length - 1

首先,您有 n 个递归调用。对于每一个,您都删除了两个 +,因此每个将最多有 n-2 次递归调用,依此类推。

所以你最多有 n+n(n-2)+n(n-2)(n-4)+... 递归调用。

本质上这是 n!!(1+1/2+1/(2*4)+1/(2*4*8)+...) 因为 1+1/2+1/(2 *4)+1/(2*4*8)+...收敛,≤2,你有O(n!!)

关于内存,对于每个递归调用,你有一个长度为 n 的数组,所以你有 n + nn + nnn + n ... (n/2 次) ... *n = n(n^(n/2)-1)/(n-1) 这是 O(n^(n/2))

这显然表明性能并不比穷举搜索好多少。

对于散列改进,您要求提供您已设法使用代码创建的所有可能组合。但是,您的代码与实际创建所有组合的代码没有太大区别,除了您将两个 + 替换为两个 - 之外,这通过某种因素降低了复杂性,但没有降低它的水平.总的来说,最坏的情况与 n/2 个位置之间的位组合数相同,即 2^(n/2)。观察哈希函数本身可能有一些隐藏的日志,所以总的复杂度是搜索 O(2^(n/2)*ln(n/2)) 和内存 O(2^(n/2))。

这是最坏的情况。但是,如果有无法取胜的安排,当没有取胜策略时,以上就是您需要指望的复杂性。

平均场景的问题就是你能/不能赢的情况的数量以及它们在所有安排中的分布问题。这个问题与您的算法没有太大关系,需要一套完全不同的工具才能解决。

经过片刻检查上述推理是否正确和切题后,我会对结果感到非常满意,因为它告诉了我所有我需要知道的事情。你不能指望你会有一个有利的安排,我真的怀疑你只有 0.01% 的最坏情况安排,所以无论如何你都需要准备最坏的情况,除非这是一些特殊的项目,否则 -大胆的计算是您的 friend 。

无论如何,这些类型的计算是为了正确准备测试用例,而不是为了正确和最终实现。使用这些测试,您可以找到 O() 中真正隐藏的因素,同时考虑编译器、内存消耗、分页等。

当然,我们仍然不能保持原样,我们总是可以改进粗略的推理。例如,您实际上在每一步都没有 n-2,因为它取决于奇偶校验。例如对于++++++++... 如果你替换第三个+++--+++++... 很明显你将有 n-3 个,而不是 n-2 个递归调用,甚至 n-4。所以一半的调用可能有 n-3 个递归调用,这将是 n/2(n-3)+n/2(n-2)=n(n-5/2)

观察到因为 n!=n!!(n-1)!!我们可以取 n!!≈√n!, n!=n!!!(n-1)!!!(n-2)!!!或 n!!!≈∛n!这可能会得出一个结论,我们应该有类似 O((n!)^(5/2)) 的东西。测试会告诉我我们可以在 O((n!)^(x)) 中减少多少 x=3。

(寻找一种特定形式的复杂度是很正常的,就像我们有 O((n!)^(x) 一样),尽管它可以用不同的方式表达。所以我会继续复杂度形式 O( (n!)^(x)),1≤x≤3)

关于java - 这里怎么分析时间复杂度呢?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34254085/

相关文章:

java - Sikulix Maven 项目

java - 如何借助IP地址获取硬件设备序列号?

python - 使用递归计算字符串中的元音

recursion - Foldr 与 Foldl(或 Foldl')的含义

excel - 在 Excel 中使用 IF() 和 TIMEVALUE()

java - Jersey 和 HK2 - 注入(inject)当前用户

java - 实现基本的字符串压缩

javascript - JavaScript 中的日期时间

python - 嵌套列表 Python 3 中的数字平方

c - 使用 struct 的耗时 C 程序