之前,我在Codewars上看到一道名为Recover a secret string from random triplets的题,这道题使我沉思了很久,最终在朋友的帮助下我完成了这个题目。当我完成这个题目并且看到其他大神的答案时, 我就觉得我真的很有必要记录一下这道题,并且思考它在JavaScript中的实现。
这道题目的大概意思是讲:有一个密码字符串存在,而你会获得这个密码字符串的随机三个字符的集合,根据这个集合你需要还原出这个密码字符串。
已知:
1.密码字符串中不会出现重复的字母
2.这个集合包含了足可以推断出密码字符串的信息
3.密码字符串不会不包含集合中的字母
英文好的朋友可以看下面的原文题干,题目作者解释的比我清楚:
There is a secret string which is unknown to you. Given a collection
of random triplets from the string, recover the original string.A triplet here is defined as a sequence of three letters such that each letter occurs somewhere before the next in the given string.
"whi" is a triplet for the string "whatisup".
As a simplification, you may assume that no letter occurs more than once in the secret string. You can assume nothing about the triplets given to you other than that they are valid triplets and that they contain sufficient
information to deduce the original string. In particular, this means
that the secret string will never contain letters that do not occur in
one of the triplets given to you.
也可以通过一个例子来理解题目:
secret1 = "whatisup" // 密码字符串-->程序的输出
triplets1 = [ // 集合 --> 程序的输入
['t','u','p'],
['w','h','i'],
['t','s','u'],
['a','t','s'],
['h','a','p'],
['t','i','s'],
['w','h','s']
]
var recoverSecret = function(triplets) {
// ... -->程序的实现
}
我认为这道题目做起来很舒服,虽然答案并不是第一时间很快就写出来的,但是题干清楚简单就免除了阅读理解的烦扰也是杜绝了由于题意冗长 理解不到位造成了程序错误。而且实现方法也多种多样,在做完答案后看看大神的最佳实践也是一种享受。
下面是我的解决方案:
首先,列出集合中所有的字母,因为集合和密码字符串中的字母种类是一致且无重复的,所以只需要把列出的所有字母去重就可以得到密码字符串的无序排列。
triplets1 = [ // 原集合
['t','u','p'],
['w','h','i'],
['t','s','u'],
['a','t','s'],
['h','a','p'],
['t','i','s'],
['w','h','s']
]
triplets2 =['t','u','p', // 列出的所有集合字母
'w','h','i','t','s','u',
'a','t','s','h','a','p',
't','i','s','w','h','s']
triplets3 =['t','u','p','w','h','i','s','a'] // 去重后的集合 无序字符串
然后我们再根据原集合给出的排列规则将无序字符串进行排列,而通过观察,我发现原集合中每个小数组之间的顺序关系太过繁杂甚至有的小数组之间都没有顺序关系(比如 triplets1[0] 和 triplets1[1] ),那么最方便实现的解决办法就是只顾及每个小数组三个字母之间的顺序关系:
['t','u','p'] // 粗体--> 第一个小数组的第一个顺序关系
['t','u','p','w','h','i','s','a'] // 找到 't' 和 'u' 发现顺序正确
['t','u','p'] // 粗体--> 第一个小数组的第二个顺序关系
['t','u','p','w','h','i','s','a'] // 找到 'u' 和 'p' 发现顺序正确
['w','h','i'] // 粗体--> 第二个小数组的第一个顺序关系
['t','u','p','w','h','i','s','a'] // 找到 'w' 和 'h' 发现顺序正确
['w','h','i'] // 粗体--> 第二个小数组的第二个顺序关系
['t','u','p','w','h','i','s','a'] // 找到 'h' 和 'i' 发现顺序正确
['t','s','u'] //粗体--> 第三个小数组的第一个顺序关系
['t','u','p','w','h','i','s','a'] // 找到 't' 和 's' 发现顺序不正确
['s','u','p','w','h','i','t','a'] // 交换位置
// ...依次类推,最后的结果是
['w','t','a','h','p','i','u','s']
很明显可以看到只根据一次的比较还不足以得到结果的顺序,所以我们要外套一个循环,继续使用原集合的顺序信息继续排序,直到全部排序完成输出答案。
下面是我JavaScript的程序实现:
const recoverSecret =(triplets)=> {
let result = ""
// 排列集合字符串 去重
for(let i=0; i < triplets.length; i++){
for(let j = 0; j < triplets[i].length; j++){
if (result.indexOf(triplets[i][j]) == -1){
result += triplets[i][j]
}
}
}
// 用于标识while大循环是否进行
var flag = true
while(flag){
flag = false
for(let i = 0; i < triplets.length; i++){
for(let j = 0; j < 2; j++){
if (result.indexOf(triplets[i][j]) > result.indexOf(triplets[i][j+1])){
flag = true
// 给交换字母位置
result = result.replace(triplets[i][j], triplets[i][j+1]).replace(triplets[i][j+1], triplets[i][j])
}
}
}
}
return result
}
代码中用到的API比较少,差不多为以下两个:
String.indexOf(searchValue) 方法返回调用String对象中第一次出现的指定值的索引,开始在 fromIndex进行搜索。 searchValue表示被查找的值 String.replace(regexp, substr) 方法返回一个由替换值替换一些或所有匹配的模式后的新字符串。模式可以是一个字符串或者一个正则表达式, 替换值可以是一个字符串或者一个每次匹配都要调用的函数。 regexp表示要被替换的字符串 substr表示替换的字符串
*以上API资料来源于MDN
最后我要说 这个题目还远远没有完,而它的迷人之处就在于它不止一个解决办法,如果你没有思路,可以在Codewars上粘贴我上面的代码并提交(这样你才可以看到别人的答案)研究大神的解决方案就会发现它的魅力。举一反三,多多思考,多多实践才是学习前端的最佳实践。