#discuss使用高性能JavaScript解决难题
过早优化是万恶之源。它也是本文的根源。
我喜欢编程拼图。我也喜欢快走。我们将采取一些LeetCode问题并解决它们几次,首先在广泛的笔划中提高运行时复杂性,然后寻找次要的优化。我们追随这些美妙的话语:
快于100.00%的JavaScript在线提交
我们所针对的环境是 nodejs 10.15.0
同 - 和谐
(资源)。据我所知,在线判断系统对测试用例使用相对较小的输入。
第一个问题
771.珠宝和石头〜你被赋予了弦乐 Ĵ
代表珠宝的石头类型,和 小号
代表你拥有的宝石。中的每个角色 小号
是你拥有的一种石头。你想知道你有多少宝石也是珠宝。
这里一个天真的解决方案是穿过我们的石头,穿过每块石头的珠宝。我们将在本文中使用标准for循环,因为它们通常是在JavaScript中迭代数据的最快方式。
VAR numJewelsInStones = 功能(Ĵ, 小号) { 让 myJewels = 0; //珠宝 对于 (VAR 一世 = 0; 一世 < Ĵ。长度; 一世++) { //石头 对于 (VAR Ĵ = 0; Ĵ < 小号。长度; Ĵ++) { //嵌套 如果 (Ĵ(一世) === 小号(Ĵ)) { myJewels++; } } } 返回 myJewels; };
运行时是二次的, O(N ^ 2)
。他们的在线评委实际上不接受这个解决方案我们得到一个超级大的超时限制。课?应尽可能避免使用嵌套for循环。
让我们抓住一个Set来摆脱其中一个循环。将运行时间降低到线性, 上)
。在JavaScript中查找Set是一个恒定的时间, O(1)
。
VAR numJewelsInStones = 功能(Ĵ, 小号) { 常量 珠宝 = 新 组(Ĵ); // Set接受一个可迭代对象 让 myJewels = 0; 对于 (VAR 一世 = 0; 一世 < 小号。长度; 一世++) { 如果 (珠宝。具有(小号(一世))) { myJewels++; } } 返回 myJewels; };
为了这项努力,我们得到了回报 快于97.84%
。我很满意这段代码。它高效可读。如果我需要更好的性能,我可能会找到与JavaScript不同的技术。我们必须至少走一次琴弦的长度,并且没有绕过它。我们无法击败 上)
但我们可以做出优化。
石头和珠宝被定义为字母。所以 A-Z
和 A-Z
。这意味着我们的价值观只有52个不同的桶我们可以使用布尔数组而不是Set。要将字母转换为数字,我们将通过charCodeAt使用其ASCII代码点。我们将索引设置为 真正
代表一颗宝石。
但是,JavaScript中没有布尔数组。我们可以使用标准数组并将其初始化为长度 52
。或者我们可以使用Int8Array并允许编译器进行其他优化。使用范围进行基准测试时,键入的数组的速度提高约6% 0-52
随机字符输入为 Ĵ
和 小号
。
你发现我们的长度错了吗?这是我在测试时忘记的事情。之间有七个字符 ž
和 一个
在ASCII代码K线走势图上,所需的长度实际上是59。
VAR numJewelsInStones = 功能(Ĵ, 小号) { 常量 珠宝 = 新 Int8Array(59); 对于 (VAR 一世 = 0; 一世 < Ĵ。长度; 一世++) { 珠宝(Ĵ。charCodeAt(一世)-65) = 1; } 让 myJewels = 0; 对于 (VAR 一世 = 0; 一世 < 小号。长度; 一世++) { 如果 (珠宝(小号。charCodeAt(一世)-65) === 1) { myJewels++; } } 返回 myJewels; };
瞧,我们的 100%最快
提交。在我的测试中,这实际上是Set版本的两倍。我跳过测试的其他优化是缓存长度,使用while循环而不是for循环,并将incrementor放在数字之前(++ myJewels
VS myJewels ++
)。
第二个问题
345.字符串的反向元音〜编写一个函数,该函数将字符串作为输入并仅反转字符串的元音。
对此的一个天真的解决方案可能是循环数组两次,替换第二个循环。我们先试试吧。
VAR reverseVowels = 功能(小号) { 常量 元音 = 新 组(('一个','E','一世','O','U', '一个', 'E', '一世', 'O', 'U')); 常量 反向的 = (); 让 vowelsFound = (); //找到任何元音 对于 (VAR 一世 = 0; 一世 < 小号。长度; 一世++) { 如果 (元音。具有(小号(一世))) { vowelsFound。推(小号(一世)); } } //构建最终字符串 对于 (VAR 一世 = 0; 一世 < 小号。长度; 一世++) { 如果 (元音。具有(小号(一世))) { 反向的。推(vowelsFound。流行的()); } 其他 { 反向的。推(小号(一世)); } } 返回 反向的。加入(“”); };
这让我们知道了 快于97.00%
。运行时是线性的, O(2N) - > O(N)
,它读得很好,但我不禁想到我们再循环字符串比我们更多。让我们尝试一种双指针方法。从前面和后面一步一步地走进去,交易所我们看到的任何元音。如果有一个中间的元音,我们就离开它。
VAR reverseVowels = 功能(小号) { 常量 元音 = 新 组(('一个','E','一世','O','U', '一个', 'E', '一世', 'O', 'U')); 小号 = 小号。分裂(“”); 让 面前 = 0; 让 背部 = 小号。长度 - 1; 而 (面前 < 背部) { 如果 (元音。具有(小号(面前))) { 面前++; 继续; } 如果 (元音。具有(小号(背部))) { 背部-; 继续; } 让 温度 = 小号(面前); 小号(面前) = 小号(背部); 小号(背部) = 温度; 面前++; 背部-; } 返回 小号。加入(“”); };
我们减少了完整的迭代这让我们 快于98.89%
在这一点上,我们需要记住,LeetCode的基准并不是结论性的,也不是一致的。使用混合测试用例运行大量迭代是不可行的。如果你正在练习拼图,请停下来 97%
而且。但这不是本文的重点,读者,我会得到它 100%
为了你。
首先,我扔掉了Set。元音的数量是不变的,我们不需要进行所有的散列。我尝试了一个switch语句但后来发现了一个链接if语句更快。我发现内置这个逻辑比一个函数更快。然后我把它减少到表达式。我想说的是:代码即将发布。这是你的IDE-and-talk-a-walk总结。但是..它是 快于100.00%
。
VAR reverseVowels = 功能(小号) { 小号 = 小号。分裂(“”); 让 面前 = 0; 让 背部 = 小号。长度 - 1; 而 (面前 < 背部) { 如果 (小号(面前) == '一个' && 小号(面前) == 'E' && 小号(面前) == '一世' && 小号(面前) == 'O' && 小号(面前) == 'U' && 小号(面前) == '一个' && 小号(面前) == 'E' && 小号(面前) == '一世' && 小号(面前) == 'O' && 小号(面前) == 'U') { 面前++; 继续; } 如果 (小号(背部) == '一个' && 小号(背部) == 'E' && 小号(背部) == '一世' && 小号(背部) == 'O' && 小号(背部) == 'U' && 小号(背部) == '一个' && 小号(背部) == 'E' && 小号(背部) == '一世' && 小号(背部) == 'O' && 小号(背部) == 'U') { 背部-; 继续; } 让 温度 = 小号(面前); 小号(面前++) = 小号(背部); 小号(背部-) = 温度; } 返回 小号。加入(“”); };
(对不起)。
第三个问题
509.斐波纳契数〜计算第n个斐波那契数。
这是一个常见的难题,因为在最终解决方案中移动部件很少,所以最难改进运行时间。我确信一些RNG也参与了LeetCode的评分。让我们开始使用天真的解决方案。 Fibonacci序列通常用于教授递归。但是,使用的算法具有运行时间 O(2 ^ n)的
(非常慢)。
我实际上通过尝试使用此函数计算第50个项来崩盘浏览器选项卡。
VAR FIB = 功能(ñ) { 如果 (ñ < 2) { 返回 ñ; } 返回 FIB(ñ - 1) + FIB(ñ - 2); }
我们得到了 快于36.63%
对于这个答案。哎哟。在制作中,这是一种可以通过memoization解决的难题(稍后缓存一些工作)。这是最好的解决方案,因为我们只计算线性时间内所需的值 上)
然后在该限制下再次运行算法是恒定时间 O(1)
。
常量 备忘录 = (0, 1); VAR FIB = 功能(ñ) { 如果 (备忘录(ñ) == 未定义) { 返回 备忘录(ñ); } 常量 结果 = FIB(ñ - 1) + FIB(ñ - 2); 备忘录(ñ) = 结果; 返回 结果 };
快于94.25%
。 LeetCode不会在每次代码运行之间存储数据,所以我们必须尝试不同的东西。我们只想计算一次序列的数量。我想我们可以扔掉那个阵列。让我们看一下迭代解决方案。
VAR FIB = 功能(ñ) { 如果 (ñ < 2) { 返回 ñ; } 让 一个 = 1; 让 b = 1; 对于 (让 一世 = 3; 一世 <= ñ; ++一世) { 一个 = 一个 + b; b = 一个 - b; } 返回 一个; };
如果这看起来与您可能看到的其他迭代版本略有不同,那是因为我避免了我们必须在JavaScript中使用交易所值的第三个临时变量(还有其他方法,但它们太慢了)。我做了一些基准,我发现使用算术而不是.. 快于100.00%
。
我将独特内容发布到我的每周时事通讯?。