因为编程的特性,算法始终贯穿在整个编程的过程之中。算法(Algorithm)是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制。我们将讨论不同的方法和它们的利弊以及运行中的时间复杂性。最优雅的解决方案通常会利用特殊的“技巧”或者敏锐的洞察力,这些都很好地体现在下面的4个热门JavaScript算法问题。
把一个给定的一串字符当作输入,编写一个函数,将传入字符串反转字符顺序后返回。
describe("String Reversal",()=>{
it("**Should reverse string**",()=\>{
assert.equal(reverse("Hello World!"),"!dlroW olleH");
});
});
分析:
如果我们知道“技巧”,那么解决方案就不重要了。技巧就是意识到我们可以使用数组的内置方法reverse。首先,我们对字符串使用split方法生成一个字符数组,然后我们可以用reverse方法,最后用join方法将字符数组重新组合回一个字符串。这个解决方案可以用一行代码来完成!虽然不那么优雅,但也可以借助最新的语法和帮助函数来解决问题。使用新的for of循环迭代字符串中的每一个字符,可以展示出我们对最新语法的熟悉情况。或者,我们可以用数组的reduce方法,它使我们不再需要保留临时基元。
对于给定的字符串的每个字符都要被“访问”一次。虽然这中访问会多次发生,但是时间可以被归一化为线性时间。并且因为没有单独的内部状态需要被保存,因此空间是恒定的。
给定一个由字符组成的字符串,返回字符串中出现频次最高的字符。
describe("Max Character",()=>{
it("**Should return max character**",()=\>{
assert.equal(max("Hello World!"),"l");
});
});
分析:
这里的技巧是创建一个表格,用来记录遍历字符串时每个字符出现的次数。这个表格可以用对象字面量来创建,用字符作为对象字面量的键,用字符出现的次数作为值。然后,我们遍历表格,通过一个保存每个键值对的临时变量来找到出现频次最大的字符。
虽然我们使用了两个独立的循环来遍历两个不同的输入值(字符串和字符映射),但时间复杂度仍然是线性的。虽然循环是对于字符串,但最终,字符映射的大小会有一个极限,因为任何一种语言的字符都是有限个的。出于同样的原因,虽然要保存内部状态,但不管输入字符串如何增长,空间复杂度也是恒定的。临时基元在大尺度上看也是可以忽略不计的。
对于一个给定大小的数组,将数组元素分割成一个给定大小的数组类型的列表。
describe("Array Chunking",()=>{
it("**Should implement array chunking**",()=\>{
assert.deepEqual(chunk(\[1,2,3,4\],2),\[\[1,2\],\[3,4\]\]);
assert.deepEqual(chunk(\[1,2,3,4\],3),\[\[1,2,3\],\[4\]\]);
assert.deepEqual(chunk(\[1,2,3,4\],5),\[\[1,2,3,4\]\]);
});
});
分析:
一个显而易见的方法是保持一个对最后一个“块”的引用,并在遍历数组元素时检查其大小来判断是否应该向最后一个块中放元素。更优雅的解决方案是使用内置的slice方法。这样就不需要“引用”,从而使代码更清晰。这可以通过while循环或for循环来实现,该循环以给定大小的step递增。
这些算法都具有线性时间复杂度,因为每个数组项都需要被访问一次。它们也都有线性的空间复杂度,因为需要保存一个内在的“块”类型数组,该数组大小会随着输入值变化而变化。
实现一个返回给定索引处的斐波纳契数的函数。
describe("Fibonacci",()=>{
it("**Should implement fibonacci**",()=\>{
assert.equal(fibonacci(1),1);
assert.equal(fibonacci(2),1);
assert.equal(fibonacci(3),2);
assert.equal(fibonacci(6),8);
assert.equal(fibonacci(10),55);
});
});
分析:
由于斐波纳契数是前两者的总和,最简单的方法就是使用递归。斐波纳契数列假定前两项分别是1和1;因此我们可以基于这个事实来创建我们的基本情形。对于索引大于2的情况,我们可以调用自身函数的前两项。虽然看着很优雅,这个递归方法的效率却非常糟糕,它具有指数型的时间复杂度和线性的空间复杂度。因为每个函数调用都需要调用堆栈,所以内存使用以指数级增长,如此一来它会很快就会崩溃。
迭代的方法虽然不那么优雅,但是时间复杂度却更优。通过循环,建立一个完整的斐波纳契数列前N项目(N为给定索引值),这可以达到线性的时间和空间复杂度。
上述的4个JavaScript算法问题都是历史上很出名的问题,一度成为经典。当然,JavaScript中的算法问题远远不止这几个,在动力节点在线的JavaScript视频教程中还有其他类型的算法问题,如利润最大化问题、多重求和问题和元音问题等等,想要把这些算法问题一举参透也许不是一件容易的事,我们只能循序渐进,方能成功弄懂其中的各种关键所在。
代码小兵49806-11 15:28
代码小兵49806-11 15:51
代码小兵49806-11 16:22
代码小兵51603-29 17:28
暴风城-小飞04-06 20:49