这个题目看似简单,但是想实现一个高性能的复制拼接方法,还是很不容易的,我们先由简单的方法入手。
简单的方法
function repeat(target, n) {
return Array.from({length: n + 1}).join(target)
}
这个方法比较简单,只有一点需要特别关注,就是数组的长度是 n + 1 ,而不是 n ,因为 target 是作为数组各项的拼接物的形式出现的,所以需要 +1 。
高级的方法:二分法
首先要声明一下,我能想到的方法是上面的简单方法,这个方法是我在看面试题答案的时候学到的。当时我觉得这个方法很神奇,但是不知道为什么可以,就去网上搜了一下,发现没有文章在讲解实现原理的,都只是直接把代码贴了出来(也可能是我没有找到😂)。
没办法,只能靠自己琢磨了,最后,终于还是被我想明白了,接下来,简单说一下我的想法。
这种方式虽然叫二分法,但是,我觉得,不知所谓。下面,我会分析一下这个方法的底层原理,先上代码:
function repeat(target, n) {
var s = target, total = [];
while (n > 0) {
if (n % 2 === 1) {
total.push(s);
}
if (n === 1) {
break;
}
s += s;
n = n >> 1; // Math.floor(n / 2);
}
return total.join('');
}
这段代码有可以改进的地方,但是更容易解释底层原理,所以先把原理说通,再改进。
要解释这个方法的原理,首先要把 n 这个变量的值转换为二进制形式,我们现在假设 n = 37 ,其对应的二进制形式如下:
100101
这个二进制的形式可以换算成如下相加的形式:
100000 + 100 + 1
这就是这个方法的本质,这个方法就是找出这 0b100000 个、 0b100 个 和 1 个重复的字符串,然后相加。看这段代码:
if (n % 2 === 1) {
total.push(s);
}
这段代码的含义是,在二进制的数字中,碰到 1 的话,就把当前的字符串放到数组中,也就是上面提到的,分别找到三组重复的字符串。
再看这段代码:
s += s;
n = n >> 1; // Math.floor(n / 2);
s += s 是在不停的将字符串加倍,而 n = n >> 1 是在不停的将数字减半(当然,不是精确的减半,就像注释中写的那样,本质上是向下取整),这里减掉的数字的大小,就是字符串加倍后的重复次数。这里可能有点绕,也很难用文字解释清楚,所以,如果没有理解,可以多看几遍上面的解释,然后再多思考一下。
好了,解释的差不多了,接下来再稍微改进一下上面的方法:
function repeat(target, n) {
var s = target, total = "";
while (n > 0) {
if (n % 2 === 1) {
total += s;
}
if (n === 1) {
break;
}
s += s;
n = n >> 1;
}
return total;
}
这里其实就是将 total 从数组改为字符串,也就是直接拼接出结果,省去了最后的 join() 方法。
当然,还有其他基于二分法的优化,这里就不说了,感兴趣的可以自行搜索。