前言
对于一个前端来说,不管是刚入行的小白还是工作了几年的开发者,算法对于我们大部分人都是最薄弱的技能,没有之一。 对于笔者本人来说,最近面试经常遇到算法问题,也经常被虐,成为了进入大厂最大的瓶颈。所以我决心重新学习算法和数据结构,并在此记录和分享。希望前端菜鸟学算法,能成为一个高质量的学习算法系列博客。
虽无算法之才,亦必有驾驭算法之志!
1.字符串

function matchChar(str) {
let reg = /0+|1+/
let matchArr = []
while(str) {
let mChar = reg.exec(str)[0]
let rChar = String(mChar[0] ^ 1)
let targetChar = mChar + rChar.padStart(mChar.length,rChar)
str.indexOf(targetChar) == 0 && matchArr.push(targetChar)
str = str.substr(1)
}
return matchArr
}
2.数组
- 2.1 电话号码组合
这个题考察对数组api的理解和运用,笔者实现如下:
function arrange() {
let targetArr = []
let strMap = {
2: 'abc',
3: 'def',
4: 'ghi',
5: 'jkl',
6: 'mno',
7: 'pqrs',
8: 'tuv',
9: 'wxyz',
}
let args = [].slice.call(arguments)
if(args.some(num => num < 2 || num > 9)) {
return console.log('输入不合法')
}
args = args.map(num => strMap[num])
if(args.length < 1) {
return []
}
if(args.length == 1) {
return args[0].split('')
}
targetArr = compose(...args.splice(0,2))
while(args.length) {
let arr = args.splice(0,1)
targetArr = compose(targetArr,arr[0])
}
function compose(str1,str2) {
let ouputArr = []
let arr1 = Array.isArray(str1) ? str1 : str1.split('')
let arr2 = str2.split('')
for(let i = 0, len = arr1.length; i < len; i++) {
for(let j = 0, len = arr2.length; j < len; j++) {
ouputArr.push(arr1[i] + arr2[j])
}
}
return ouputArr
}
return targetArr
}
- 2.2 卡牌分组

function cardGroup(arr) {
if(!Array.isArray(arr)) return false
arr.sort()
let newArr = split(arr)
let lenArr = newArr.map(item => item.length)
if(lenArr.includes(1)) {
return false
}
let minNum = Math.min.apply(null, lenArr)
return lenArr.every(item => item % minNum === 0)
}
function split(arr) {
let retArr = []
let tempArr = []
for(var i = 0; i < arr.length; i++) {
tempArr.push(arr[i])
if(!arr[i+1]) {
retArr.push(tempArr)
tempArr = []
break
}
if(arr[i] < arr[i+1]) {
retArr.push(tempArr)
tempArr = []
}
}
return retArr
}
- 2.3 种花问题

function plantFlowers(arr,fNum) {
for(var i = 0; i < arr.length; i++) {
if(arr[i] == 0 && fNum !== 0) {
if(arr[i-1] !== undefined && arr[i+1] !== undefined) {
if(arr[i-1] === 0 && arr[i+1] === 0) {
arr[i] = 1 // 种花
fNum--
}
}else if(arr[i-1] === undefined) { //开头是0的情况
if(arr[i+1] === 0) {
arr[i] = 1 // 种花
fNum--
}
}else if(arr[i+1] === undefined) { //结尾是0的情况
if(arr[i-1] === 0) {
arr[i] = 1 // 种花
fNum--
}
}
}
}
return {
ability: fNum === 0,
arr
}
}
- 2.4 格雷编码

function genCode(n) {
function grayCode(n) {
let retArr = []
if(n==0) return ['0']
if(n==1) return ['0','1']
let arr = grayCode(n-1)
let len = 2**n - 1
for(var i = 0; i < arr.length; i++) {
retArr[i] = `0${arr[i]}`
retArr[len - i] = `1${arr[i]}`
}
return retArr
}
return grayCode(n).map(num => parseInt(num,2))
}
3.正则
- 3.1 子字符串模式匹配

function match(str) {
let reg = /^(\w+)\1+$/
return reg.test(str)
}
- 3.2 正则表达式匹配

function match(str, mode) {
let modeArr = mode.match(/([a-z.]\*)|([a-z]+(?=([a-z.]\*)|$|\.))|(\.(?!\*))/g)
let cur = 0
let strLen = str.length
for(let i = 0, len = modeArr.length, m; i < len; i++) {
//对于模式分为四类 .*|a*|cdef|非.*组合的单个.
m = modeArr[i].split('')
// 如果第二位是*表示是有模式的
if(m.length == 1 && m[0] == '.') {
cur ++
}else if(m[1] === '*') {
if(m[0] === '.') {
cur = strLen
break
}else {
while(str[cur] === m[0]) {
cur ++
}
}
}else {
for(let j = 0, jl = m.length; j < jl; j++) {
if(m[j] !== str[cur]) {
return false
}else {
cur ++
}
}
}
}
return cur === strLen
}
4.排序
时间复杂度:反映了程序运行从开始到结束所需要的时间。把算法中基本操作重复执行的次数(频度)作为算法的时间复杂度。空间复杂度:指运行完一个程序所需内存的大小
- 4.1 冒泡排序

function bubbleSort(arr) {
for(var i = 0 ;i<arr.length-1;i++) { // 遍历数组,次数就是arr.length - 1
let isOver = true // 加一个标志位
for(var j = 0;j<arr.length-i-1;j++) { // 当第一次,找到最大数,放到最后,那么下一次,遍历的时候就要不需要比较最大的数了;这里要根据外层for循环的 i,逐渐减少内层 for循环的次数
// 如果前一个数 大于 后一个数 就交换两数位置
if(arr[j]>arr[j+1]) {
[arr[j],arr[j+1]] = [arr[j+1],arr[j]]
isOver = false
}
}
if(isOver) break //如果某次循环完后,没有任何两数进行交换,就将标志位 设置为 true,减少不必要的排序
}
return arr
}
总结
- 1、外层 for 循环控制循环次数
- 2、内层 for 循环进行两数交换,找每次的最大数,排到最后
- 3、设置一个标志位,减少不必要的循环
时间复杂度:O(n) ~ O(n2) 最好的情况是传入的数组本身就已经排序了,只会走内层循环,加一次外层循环; 空间复杂度: O(1)
- 4.2 选择排序

function selectionSort(array) {
for (let i = 0; i < array.length - 1; i++) {
let minIndex = i;
for (let j = i + 1; j < array.length; j++) {
if (array[j] < array[minIndex]) {
minIndex = j;
}
}
[array[minIndex], array[i]] = [array[i], array[minIndex]];
}
return array
}
总结
每次循环选取一个最小的数字放到前面的有序序列中
时间复杂度:O(n2) 空间复杂度: O(1)
- 4.3 插入排序

function insertSort(array) {
for (let i = 1; i < array.length; i++) {
let target = i;
for (let j = i - 1; j >= 0; j--) {
if (array[target] < array[j]) {
[array[target], array[j]] = [array[j], array[target]]
target = j;
} else {
break;
}
}
}
return array;
}
总结
将左侧序列看成一个有序序列,每次将一个数字插入该有序序列。
插入时,从有序序列最右侧开始比较,若比较的数较大,后移一位。
时间复杂度:O(n2) 空间复杂度: O(1)
- 4.4 快速排序
function quickSort(arr){
if(arr.length<=1) return arr
let left=[]
let right=[]
let middleInx=Math.floor(arr.length/2)
let middle=arr.splice(middleInx,1)[0]
for(var i=0,len=arr.length;i<len;i++){
if(arr[i]<middle){
left.push(arr[i])
}else{
right.push(arr[i])
}
}
return quickSort(left).concat([middle],quickSort(right))
}
总结
选择一个基准元素target(一般选择第一个数) 将比target小的元素移动到数组左边,比target大的元素移动到数组右边 分别对target左侧和右侧的元素进行快速排序
时间复杂度:平均O(nlogn),最坏O(n2) 空间复杂度:O(logn)
- 4.5. 计算最大区间
给定一个无序的数组,找出数组在排序之后,相邻元素之间的最大差值。如果数组元素个数小于2,则返回0
例如:
输入[3,6,9,1] 输出 3
这个题结合了冒泡排序,实现了较高的性能,笔者实现如下:
function calcMax(arr) {
if(arr.length < 2) return 0
let max = 0
for(var i = 0, len = arr.length - 1; i <= len; i++) {
for(var j = 0; j < arr.length-i-1; j++) {
if(arr[j] > arr[j+1]) {
[arr[j],arr[j+1]] = [arr[j+1],arr[j]]
}
}
if(i) {
let sub = arr[len+1-i] - arr[len-i]
if(max < sub) max = sub
}
}
return max
}
- 4.6. 奇偶排序
奇书偶数排序
例如:
输入[1,3,2,6,4,9] 输出 [2, 1, 4, 3, 6, 9]
排序的一种思路,根据下标的移动来实现:
function crossSort(arr) {
arr = arr.sort()
let odd = 1, even = 0, ret = []
arr.forEach(item=> {
if(item %2 ==0) {
ret[even] = item
even += 2
}else {
ret[odd] = item
odd += 2
}
})
return ret
}
- 4.7. 缺失的一个正数
给定一个未排序的整数数组,找出其中没有出现的最小的正整数
例子:
输入:[1,2,0] 输出:3
输入:[3,4,-1,1] 输出:2
输入:[7,8,9,11,12] 输出:1
代码实现:
function find(arr) {
let max = Math.max.apply(null,arr)
let ret = null
for(var i = 1; i < max; i++) {
if(!arr.includes(i)) {
ret = i
break
}
}
if(ret === null) {
ret = (max < 0 ? 0 : max) + 1
}
return ret
}
5.递归
复原IP地址
给定一个只包含数字的字符串,复原它并返回所有可能的IP地址格式
例子
输入 '25525511135' 输出 ['255.255.11.135','255.255.111.35']
代码实现:
function recoverIp(ip) {
let ret = []
search(ip,[])
function search(str,arr) {
if(arr.length === 4 && arr.join('') === ip) {
ret.push(arr.join('.'))
}else {
for(let i = 0, len = Math.min(3, str.length); i< len; i++) {
let temp = str.substr(0, i + 1)
if(temp < 256) {
search(str.substr(i+1), arr.concat([temp]))
}
}
}
}
return ret
}