首先这个问题是我在牛客网练算法题(具体题号:No.HJ26)的时候遇见的,也是困扰了我好久才解决,所以我打算写成博客的形式,给遇到类似问题的童鞋速度解决困扰。
一. 问题阐述
算法中需要对字符串里的英文字母不分大小写按字典顺序重新排列,特殊字符原位输出,例子如下:
- Type ——> epTy
- BabA ——> aABb
- By?e ——> Be?y
使用字符串A Famous Saying: Much Ado About Nothing (2012/8).
分别的输出情况:
- 在vscode里的Node.js环境下运行结果为:
- 之后粘到牛客网上的编辑器窗口里运行(Google V8 6.0.0环境)。显示没通过所有的测试用例,细看那个未通过的用例,我突然发现怎么输出结果和在vscode里不一样???
仔细比对后发现原来是字母相同的部分排列顺序有出入…啊…这…(ˉ▽ˉ;)…
二. 结论👇
Array.sort()
排序在v8引擎中可能会存在同值乱序的BUG:
具体的原因是因为Google v8引擎为了排序的高效性,针对不同数组长度采取了不同的排序策略:
数组长度在10以下用的是快速排序算法,和其他浏览器环境或者Node.js环境下的运行结果相同;超过10之后Google v8引擎就会调用另一种排序方法——插入排序(又称为不稳定排序) ,这就是同值乱序BUG的根源所在。
我们来验证一下:
先输入一共10个字符(8个大小写不同的A和两个空格字符):按以上结论应该会原样输出,通过测试。
果然是这样,然后追加一个 b,这样数组长度就超过10了,运行发现果然之前的A都乱序了。
三. 解决方案
摘出之前的sort
排序代码块,字母相同情况下返回 0 ,即为不排序,按正常思维不都是值相等默认按原顺序排列嘛,这不就乱序了捏…
arr.sort(function(a, b){ // 不分大小写的字典顺序排列
var x = a.toLowerCase(), y = b.toLowerCase();
return x < y ? -1 : x > y ? 1 : 0;
});
现在可以按如下方案修改代码:
在分离字符的时候也保存每个字符在原数组中的位置下标。在字符值相同的情况下,就可以比较两者原数组下标来排序。
for(var i=0;i<arr.length;i++){
if(arr[i] < 'A' || (arr[i] > 'Z' && arr[i] < 'a') || arr[i] > 'z'){
arr1.push([i,arr[i]]);
}else{
arr2.push([i,arr[i]]);
};
};
arr2.sort(function(a, b){
var x = a[1].toLowerCase(), y = b[1].toLowerCase();
return x < y ? -1 : x > y ? 1 : a[0] - b[0];
});
刚才出错的用例也顺利通过了!!!
2020.8.11 更新
四. 问题现状(内核7.0.0以上Google v8引擎已不存在)
首先说明内核版本7.0.0以上的Google v8引擎中早已经升级了Timsort排序算法,这个问题早就不存在了。地址栏输入chrome://version/
可查看自己的内核版本:
不得不说牛客网上的6.0.0的Google V8引擎是真的坑…