回顾
在上个问题,我们实现了如何返回一个字符串的组成的单词的集合的所有可能的情况
但是,有一个比较严重的问题就是:时间复杂度过高,这一节,我们的核心目的就是在于去分析,为什么它的时间复杂度如此之高?
直接看原文代码吧:
//请在构造初始化
public List<String> l3;
String output = "";
public void recursion3(String data,int len){
//递归结束条件
if(len == data.length()+1){
return;
}
for (int i =0;i<=data.length()-len;i++){
String singleWorld = data.substring(i,i+len);
if(dict.contains(singleWorld)&&i ==0){
output+= singleWorld+" ";
recursion3(data.substring(len),1);
if(singleWorld.length() == data.length()){
l3.add(output);
output = "";
return;
}
}
}
recursion3(data,len+1);
}
我们每一次递归呢,都会将一串单词按照长度按顺序截取
比如abcde,长度为2时,就会派生出[ab],[bc],[cd],[de]...
然后再根据这个词是否出现在set里面,将字符串截断成cde,然后对cde再次派生
所以,现在来尝试分析其时间复杂度:
尝试绘制一个地推树出来
哎,头皮发麻...
比如原题算法实验室-21末端的数据,我们遍历的所有可能情况输出打印出来:
[n] [0]
[o] [1]
[w] [2]
[c] [3]
[o] [4]
[d] [5]
[e] [6]
[r] [7]
[i] [8]
[s] [9]
[b] [10]
[e] [11]
[s] [12]
[t] [13]
[no] [14]
[ow] [15]
[wc] [16]
[co] [17]
[od] [18]
[de] [19]
[er] [20]
[ri] [21]
[is] [22]
[sb] [23]
[be] [24]
[es] [25]
[st] [26]
[now] [27]
[c] [28]
[o] [29]
[d] [30]
[e] [31]
[r] [32]
[i] [33]
[s] [34]
[b] [35]
[e] [36]
[s] [37]
[t] [38]
[co] [39]
[od] [40]
[de] [41]
[er] [42]
[ri] [43]
[is] [44]
[sb] [45]
[be] [46]
[es] [47]
[st] [48]
[cod] [49]
[ode] [50]
[der] [51]
[eri] [52]
[ris] [53]
[isb] [54]
[sbe] [55]
[bes] [56]
[est] [57]
[code] [58]
[oder] [59]
[deri] [60]
[eris] [61]
[risb] [62]
[isbe] [63]
[sbes] [64]
[best] [65]
[coder] [66]
[oderi] [67]
[deris] [68]
[erisb] [69]
[risbe] [70]
[isbes] [71]
[sbest] [72]
[coderi] [73]
[oderis] [74]
[derisb] [75]
[erisbe] [76]
[risbes] [77]
[isbest] [78]
[coderis] [79]
[b] [80]
[e] [81]
[s] [82]
[t] [83]
[be] [84]
[es] [85]
[st] [86]
[bes] [87]
[est] [88]
[best] [89]
[oderisb] [90]
[derisbe] [91]
[erisbes] [92]
[risbest] [93]
[coderisb] [94]
[oderisbe] [95]
[derisbes] [96]
[erisbest] [97]
[coderisbe] [98]
[oderisbes] [99]
[derisbest] [100]
[coderisbes] [101]
[oderisbest] [102]
[coderisbest] [103]
[owc] [104]
[wco] [105]
[cod] [106]
[ode] [107]
[der] [108]
[eri] [109]
[ris] [110]
[isb] [111]
[sbe] [112]
[bes] [113]
[est] [114]
[nowc] [115]
[owco] [116]
[wcod] [117]
[code] [118]
[oder] [119]
[deri] [120]
[eris] [121]
[risb] [122]
[isbe] [123]
[sbes] [124]
[best] [125]
[nowco] [126]
[owcod] [127]
[wcode] [128]
[coder] [129]
[oderi] [130]
[deris] [131]
[erisb] [132]
[risbe] [133]
[isbes] [134]
[sbest] [135]
[nowcod] [136]
[owcode] [137]
[wcoder] [138]
[coderi] [139]
[oderis] [140]
[derisb] [141]
[erisbe] [142]
[risbes] [143]
[isbest] [144]
[nowcode] [145]
[owcoder] [146]
[wcoderi] [147]
[coderis] [148]
[oderisb] [149]
[derisbe] [150]
[erisbes] [151]
[risbest] [152]
[nowcoder] [153]
[i] [154]
[s] [155]
[b] [156]
[e] [157]
[s] [158]
[t] [159]
[is] [160]
[b] [161]
[e] [162]
[s] [163]
[t] [164]
[be] [165]
[es] [166]
[st] [167]
[bes] [168]
[est] [169]
[best] [170]
[sb] [171]
[be] [172]
[es] [173]
[st] [174]
[isb] [175]
[sbe] [176]
[bes] [177]
[est] [178]
[isbe] [179]
[sbes] [180]
[best] [181]
[isbes] [182]
[sbest] [183]
[isbest] [184]
[owcoderi] [185]
[wcoderis] [186]
[coderisb] [187]
[oderisbe] [188]
[derisbes] [189]
[erisbest] [190]
[nowcoderi] [191]
[owcoderis] [192]
[wcoderisb] [193]
[coderisbe] [194]
[oderisbes] [195]
[derisbest] [196]
[nowcoderis] [197]
[owcoderisb] [198]
[wcoderisbe] [199]
[coderisbes] [200]
[oderisbest] [201]
[nowcoderisb] [202]
[owcoderisbe] [203]
[wcoderisbes] [204]
[coderisbest] [205]
[nowcoderisbe] [206]
[owcoderisbes] [207]
[wcoderisbest] [208]
[nowcoderisbes] [209]
[owcoderisbest] [210]
[nowcoderisbest] [211]
总而言之,就是时间复杂度>n^2了,卧槽,不挂你挂谁?
所以,怎么将这个时间复杂度降下来,是当务之急。
优化
我们看到上面一组输出长度达到212的数据,可以发现存在大量的重复搜索的事项,我们可以通过预制一个set,判断是否检索过当前这个词汇,从而预先的终结掉往下搜索的分支
String output = "";
Set<String> dict;
static int lens =0;
Set<String> repetition;
public void recursion3(String data,int len){
//递归结束条件
if(len == data.length()+1){
return;
}
for (int i =0;i<=data.length()-len;i++){
String singleWorld = data.substring(i,i+len);
lens++;
if(!repetition.contains(singleWorld)){
repetition.add(singleWorld);
}else {
if(i!= 0){
break;
}
}
Debug.Log(singleWorld,lens);
if(dict.contains(singleWorld)&&i ==0){
output+= singleWorld+" ";
recursion3(data.substring(len),1);
if(singleWorld.length() == data.length()){
l3.add(output.trim());
output = "";
return;
}
}
}
recursion3(data,len+1);
}
跳出条件,注意,我们是跳出循环所以用break,而不是结束子递归的return
[n] [1]
[o] [2]
[w] [3]
[c] [4]
[no] [6]
[ow] [7]
[wc] [8]
[co] [9]
[od] [10]
[de] [11]
[er] [12]
[ri] [13]
[is] [14]
[sb] [15]
[be] [16]
[es] [17]
[st] [18]
[now] [19]
[c] [20]
[co] [22]
[cod] [24]
[ode] [25]
[der] [26]
[eri] [27]
[ris] [28]
[isb] [29]
[sbe] [30]
[bes] [31]
[est] [32]
[code] [33]
[oder] [34]
[deri] [35]
[eris] [36]
[risb] [37]
[isbe] [38]
[sbes] [39]
[best] [40]
[coder] [41]
[oderi] [42]
[deris] [43]
[erisb] [44]
[risbe] [45]
[isbes] [46]
[sbest] [47]
[coderi] [48]
[oderis] [49]
[derisb] [50]
[erisbe] [51]
[risbes] [52]
[isbest] [53]
[coderis] [54]
[b] [55]
[e] [56]
[s] [57]
[t] [58]
[be] [59]
[bes] [61]
[best] [63]
[oderisb] [64]
[derisbe] [65]
[erisbes] [66]
[risbest] [67]
[coderisb] [68]
[oderisbe] [69]
[derisbes] [70]
[erisbest] [71]
[coderisbe] [72]
[oderisbes] [73]
[derisbest] [74]
[coderisbes] [75]
[oderisbest] [76]
[coderisbest] [77]
[owc] [78]
[wco] [79]
[nowc] [81]
[owco] [82]
[wcod] [83]
[nowco] [85]
[owcod] [86]
[wcode] [87]
[nowcod] [89]
[owcode] [90]
[wcoder] [91]
[nowcode] [93]
[owcoder] [94]
[wcoderi] [95]
[nowcoder] [97]
[i] [98]
[is] [100]
[b] [101]
[be] [103]
[bes] [105]
[best] [107]
[isb] [109]
[isbe] [111]
[isbes] [113]
[isbest] [115]
[owcoderi] [116]
[wcoderis] [117]
[nowcoderi] [119]
[owcoderis] [120]
[wcoderisb] [121]
[nowcoderis] [123]
[owcoderisb] [124]
[wcoderisbe] [125]
[nowcoderisb] [127]
[owcoderisbe] [128]
[wcoderisbes] [129]
[nowcoderisbe] [131]
[owcoderisbes] [132]
[wcoderisbest] [133]
[nowcoderisbes] [134]
[owcoderisbest] [135]
[nowcoderisbest] [136]
输出数据少了接近一半,也就是时间复杂度<n^2
再去试试:
哎,继续优化呗...
...
一段时间后,发现,这种姿势,已经无法进一步优化,所以我们不得不通过新的方向去研究这个问题,当然工具还是递归穷举:
回顾之前的代码,我们使用了一个len来约定每一次搜索的单词的长度,这种方式,无疑是一种对性能的极大开销,最简单的解释就是
在缺乏更多已知条件(比如单词的词长)的情况下,我们进行穷举,是很难进行剪枝的。
而在算法的过程中,尽量挖掘可能的条件,是一个程序员的基本礼仪,在这里,我们可以尝试获得单词的len(通过给定的每一个单词的长度)
我们可以将len直接限定到某个区间(最差也不过是1,2,3,~,n)最好甚至可以直接约定为(2),当单词长度为2时,一个字符串abcdef一定是ab,cd,ef三个单词组成
所以,从一定程度上我们可以对穷举进行剪枝:
public void recursion4(String data){
//递归结束条件
if(data.length() == 0){
return;
}
for (String key:dict){
if(data.length()>=key.length()){
String singleWorld = data.substring(0,key.length());
if(singleWorld.equals(key)){
String d2 = data.substring(key.length());
recursion4(d2);
Debug.Log(singleWorld);
}
}
}
}
[nowcoder] [0]
[isb] [1]
[is] [2]
[bes] [3]
[be] [4]
[best] [5]
[isbe] [6]
[now] [7]
[coderisb] [8]
[cod] [9]
[co] [10]
[code] [11]
[coderis] [12]
[bes] [13]
[be] [14]
[best] [15]
[no] [16]
[nowc] [17]
[nowcode] [18]
仅仅只执行了18次....surprise
接下来的操作就十分容易了:
修改一下:
public void recursion4(String data,int len,List<String> list){
//递归结束条件
if(data.length() == 0){
return;
}
for (String key:dict){
if(data.length()>=key.length()){
String singleWorld = data.substring(0,key.length());
Debug.Log(singleWorld,lens);
lens++;
if(singleWorld.equals(key)){
String d2 = data.substring(key.length());
list.add(singleWorld);
recursion4(d2,len,list);
if(data.length() == len){
String temp = "";
for (String key2: list){
temp+= key2+" ";
}
//一个全局的ArrayList
l3.add(temp.trim());
list.clear();
}
}
}
}
}
已经降到了18次运算....,但是,牛客还是不给过!!!
妈耶...
我研究了下别人的代码,主要是通过记忆去将已经做过的搜索存储到一个ArrayList里面,并传承下去,所以对于“记忆传承”我们将会放在算法实验室-23里面进行讲解