js常用算法
1.判断一个字符串是“回文”类型,回文:形如‘abcba’、‘mamam’这种第一个与最后一个字符相同,第二个和倒数第二个字符相同。。。一次类推,该怎么实现呢?
对与我来说,首先看到第一眼,还真没想起来怎么处理,后来想到用reverse()就可以啊!MD!那是数组的方法!!!总之要用reverse()方法,那就先转成数组,再转成字符串来比较就行了啊
function checkReverseString(str) { return str == str.split('').reverse().join(''); }
2.数组去重:
先来看用老方法处理:
1).object对象中是否存在当前key
var arr = [1,2,3,1,2,3] function uniqueArr(arr) { var obj = {} var result = [] for (var i = 0, len = arr.length; i < len; i++) { if (!obj[arr[i]]) { obj[arr[i]] = true result.push(arr[i]) } } return result }
2) Array.indexOf()
var arr = [1,2,3,1,2,3] function uniqueArr(arr) { var obj = arr var result = [] for (var i = 0, len = arr.length; i < len; i++) { if (result.indexOf(arr[i]) === -1) { result.push(arr[i]) } } return result }
3) ES5 filter
[1,1,2,2,3,'a','a'].filter(function(ele,index,array){ return index === array.indexOf(ele) })
indexOf(ele)检查到的是第一次遇到当前项的下标,我们看到数字1,出现了两次次,0位置和1位置,indexOf(1) 永远都等于0,所以1位置的1就不符合filter传入的规则,只返回0位置的1.
4)ES5 reduce(fn(), el, index, array)
[1,1,2,3].reduce((result, el, i, arr) => { if (i === arr.indexOf(el)) { result.push(el) } return result }, [])
也是循环每一项,每一项的处理都在内部函数中处理。
5) ES6 Array.from(new Set()),set中不存在重复的项:
var arr = [1,2,3,1,2,3] var arr1 = Array.from(new Set(arr)) console.log(arr1)
还是这种方法好啊,清清爽爽!
3.统计一个字符串中出现次数最多的字符和出现的次数
刚开始没有太多的思路,刚才数组去重,用到了obj key的属性,那这里是不是也可以用呢?遇到新的字符,obj的该字符的值设为1,再遇到就+1,来试试:
var str = 'aaaabbbc' function maxSym(str) { if (str && str.length <= 1) { return str } var obj = {} for (var i = 0, len = str.length; i < len; i++) { obj[str[i]] = obj[str[i]] ? obj[str[i]] + 1 : 1 // 这里只能写obj[str[i]] + 1,其他写法,例如++,执行会有问题,如果你知道为什么请告诉我 } var maxStr = '' var maxCount = 0 for (var key in obj) { if (obj[key] > maxCount) { maxStr = key maxCount = obj[key] } } return maxStr + ':' + maxCount } console.log(maxSym(str))
4.不愿意看到的排序,还是要来的
1).先看看冒泡排序,必须要掌握
var arr = [2,34,4,1] function bubbleSort(arr) { if (arr && arr.length <= 1) { return arr } for (var i = 0, len = arr.length; i < len - 1; i ++) { // 控制循环的轮数,为什么要len - 1?要比较有两个元素的数组[2,1],请问比较几轮?1轮!以此类推:len-1 for (var j = 0; j < len - i - 1; j ++) { // 控制每轮要比较的次数 if (arr[j] > arr[j+1]) { var temp = arr[j] arr[j] = arr[j+1] arr[j+1] = temp } } console.log(arr) } return arr } console.log(bubbleSort(arr))
2). 选择排序:
和冒泡排序原理差不多,但不是两两比较,立即换位置,而是执行完一轮,再互换位置:
[3,4,5,1,7],假设第0位3是最小的数,与后一位比较3 < 4,再往后 3 < 5, 再往后 3 > 1, 到这里,最小的数的位置应该变成第3位,把这个位置记下来,然后继续往后比较 1 < 7,到这里就比较结束了,现在需要互换位置了,现在最小的数的位置在第3位,应该转换成[1,4,5,3,7],也就是“假定最小的”和真实最小的互换位置。第0位已经是最小的数了。第二轮,从第1位开始算起,以此类推,完成排序
function selectionSort(arr) { var len = arr.length, min; for (var i = 0; i < len; i ++) { min = i // 假定第i轮循环的第一个数最小,把这个位置保存起来 for (var j = i + 1; j < len; j ++) { if (arr[j] < arr[min]) { min = j // 如果后面有更小的数,把这个位置记为最小 } } if (i != min) { // 如果后面有更小的数,第i轮第一个数要和更小的数互换位置 var temp = arr[min] arr[min] = arr[i] arr[i] = temp } } return arr } var arr = [3,4,5,1,7] console.log(selectionSort(arr))
过程:
[3,4,5,1,7] => [1,4,5,3,7] => [1,3,5,4,7] => [1,3,4,5,7] => [1,3,4,5,7]
3).插值排序:(斗地主的时候,左手里面的牌,是不是排好序的?右手随意起牌,差到左手里已经排好序的牌中,这个过程就是插值排序)
function inputSort(data) {
var temp;
for (var i = 1; i < data.length; i++) {
for(var j = i; (j > 0) && (data[j] < data[j - 1]); j--) {
temp = data[j];
data[j] = data[j - 1]
data[j - 1] = temp
}
}
return data
}
var arr = [4,3,2,5,1]
console.log(inputSort(arr))
4)合并排序,MD,老菜鸟,写不动了,先到这里,让我好好理解一下
归并排序,采用了‘分’,分而治之,然后再合。
// 先看把两个有序数组合并 function merge(left, right) { var result = [] while (left.length > 0 && right.length > 0) { if (left[0] < right[0]) { // 左边数组的第一个数值,小于右边数组第一个数值 result.push(left.shift()) // 把左边数组第一个数值取出来,并添加到result中 } else { result.push(right.shift()) // 否则把右边数组第一个数值取出来,并添加到result中 } } // 比如 left = [1, 2] right = [3, 4] , // 只发生两次比较,第一次: 1 < 3 , result = [1] // 第二次: 2 < 3 , result = [1, 2] // 这个时候left已经空了,length => 0 , 所以while 循环结束,但是right = [3, 4] // 当然也可能是right先变成[], // 所以要把result、left和right拼接在一起返回,得到最终结果 return result.concat(left, right) } // 这里的主要功能是拆分数组,至少拆成两个数组,才能使用merge方法,所以使用递归,一直拆下去, // 当数组的长度小于等于1的时候,终止递归拆分,执行merge操作,并返回merge后的结果
// 递归调用的逻辑,可能会比较绕,多理解一下,还是没大问题的!加油!
function mergeSort(arr) { if (arr.length <= 1) { return arr } var middle = Math.floor(arr.length / 2) // 取中间序列号 var left = arr.slice(0, middle) // 取中间序列号之前的部分,赋值给left var right = arr.slice(middle) // 取中间序列号之后的部分,赋值给right return merge(mergeSort(left), mergeSort(right)) // 执行merge操作,其实这里面是先把大数组递归拆成单元素数组,再merge } var arr = [3,1,4,2,6] console.log(mergeSort(arr))
结果:
5).快速排序,先找到一个中间值,然后遍历,把比中间值小的放在左边数组,把比中间值大的放在右边数组,然后再分别按这种思路对两个新数组比较,以此类推,直到结束。
var arr = [2,34,3,4]
function quickSort(arr) {
if (arr && arr.length <= 1) {
return arr
}
var midEl = arr[0] //
var left = []
var right = []
for (var i = 1, len = arr.length; i < len; i++) {
if (arr[i] < midEl) {
left.push(arr[i]) // 比第一个数小的数放在左边
} else {
right.push(arr[i])
}
}
return quickSort(left).concat([midEl], quickSort(right))
}
console.log(quickSort(arr))
这种方式会额外增加两个数组,空间复杂度高,下面说一个更节省空间的写法,必须掌握。
function quickSort(arr) { if (arr.length <= 1) { // 数组长度为1时,默认数组有序 return arr } let flag = arr[0] // 取第0个元素为 中间值(当然你可以随便取,比如取最后一个) let i = 1 // 令左边指针初始化为1 let j = arr.length - 1 // 令右边指针初始化为 数组长度-1 while(i < j) { // 如果左指针还小于又指针,则继续判断 while(arr[j] >= flag && i < j) { // 如果 右边 的数大于 flag , 不做操作,继续左移,因为大数本来就该在右边 j-- } while(arr[i] <= flag && i < j) { // 如果 左边 的数小于 flag, 不做操作,继续右移,因为小数本来就该在左边 i++ } let temp = arr[j] // 如果不满足上面两个条件,也就是说,左边出现了大数,且右边出现了小数,则执行交换 arr[j] = arr[i] arr[i] = temp } let temp = arr[0] // 最后调换 中间值 与 右指针(也是左指针,因为此时左右指针索引相等),到此第一次遍排序结束 arr[0] = arr[j] arr[j] = temp return quickSort(arr.slice(0,i)).concat([flag]).concat(quickSort(arr.slice(j+1))) // 递归调用即可实现整体排序 } var a = [13,1,3,45,2,56,6,10] var b = quickSort(a) console.log(b)
结果:
5、递归:求和问题、(待补充其他类型)
function fn(n) { if (n === 1) return 1 return n + fn(n - 1) }
function fn(n) {
return n && n + fn(n - 1)
}
fn(5) // 15
6、产生斐波那契数列,并用canvas画图,[0, 1, 1, 2, 3, 5, 8, 13, 21, 34...],数组中的每一项当做圆的半径,每次画1/4圆。
var canvas = document.getElementsByTagName('canvas')[0] canvas.width = 600 canvas.height = 480 var ctx = canvas.getContext('2d') var coor = {x: 300, y: 240} function draw(r1, n, r2) { // r1-前一个圆的半径,n-数组下标,r2-下标圆的半径 var r = r2 * 5 // 半径太小,放大5倍 var startAngle = Math.PI // 起始角 var endAngle = Math.PI * 0.5 // 终止角 var antiClockWise = true // 逆时针方向 // 两内切圆满足的条件: 1、两圆的圆心距d,等于大圆的半径r2减去小圆的半径r1,即: d = r2 - r1.
// 依据上述条件,可以得出下一个圆的圆心坐标:注意,下一个圆的坐标值,有一个值和当前圆的一致,因为无论哪种情况,两圆心都会在一条直线上。
// 所以有一下结论: x2 = x1 +(-) d;y2 = y1 +(-) d => x2 = x1 +(-) (r2 - r1);y2 = y1 +(-) (r2 - r1)
if (n > 2) { switch(n % 4) { case 0: // n = 4、8、12...第一象限 coor.x = coor.x - (r - (r1 * 5)) // 这里刚开始会比较难理解,注意对照上面的推导关系,会好理解一些. *5 是因为r = r2 * 5,所以
// r1也放大5倍 startAngle = 0 endAngle = Math.PI * 1.5 break; case 1: // n = 5、9、13...第二象限 coor.y = coor.y + (r - (r1 * 5)) startAngle = Math.PI * 1.5 endAngle = Math.PI break; case 2: // n = 6、10、14...第三象限 coor.x = coor.x + (r - (r1 * 5)) startAngle = Math.PI endAngle = Math.PI * 0.5 break; case 3: // n = 3、7、11...第四象限 coor.y = coor.y - (r - (r1 * 5)) startAngle = Math.PI * 0.5 endAngle = 0 break; } } ctx.beginPath() ctx.arc(coor.x, coor.y, r, startAngle, endAngle, antiClockWise) ctx.lineWidth = 3 ctx.strokeStyle = "#f34e56" ctx.stroke() } function getFibonacci(n) { var i = 0 var arr = [] while(i < n) { if (i <= 1) { arr.push(i) } else { arr.push(arr[i - 2] + arr[i - 1]) // arr[i] = arr[i - 2] + arr[i - 1] } i++ } return arr } var arrFibonacci = getFibonacci(10) console.log(arrFibonacci) for (var j = 0; j < arrFibonacci.length; j++) { if (j >= 2) { // 数组长度至少为3,才满足斐波那契数组定义 draw(arrFibonacci[j - 1], j, arrFibonacci[j]) } }
结果如图:
7、合并两个有序数组:
var a = [2,4,6], b = [1,3] function two(left = [], right = []) { var result = [] while(left.length && right.length) { result.push(left[0] <= right[0] ? left.shift() : right.shift()) } return result.concat(left, right) }