前端面试资料整理【算法篇】

排序与查找
排序 参考
稳定排序:两个相等的记录,排序前 A,A',排序后仍然是 A,A'
不稳定排序:与上面结构相斥
10个常见的排序:
冒泡排序
稳定排序,O(n)

function bubble(array) { if (typeof array === 'object' && array.length) { for (let i = 0; i < array.length; i ++) { for (let j = i; j < array.length - i - 1; j++) { if (array[j] > array[j + 1]) { let tmp = array[j] array[j] = array[i + 1] array[j+1] = tmp; } } } } }

快排
  1. 最右边为基值
  2. pos 记录目标位置
  3. 逐个遍历,当遇到索引为i的值小于基值时,发生 i 与 pos 为发生替换。
  4. 此时 pos 为基值的正确位置,以 pos 划分,左边是小于基值的,右边是大于基值的。
function quick(array, left = 0, right = array.length - 1) { if (left < right) { // 标识基数正确的位置 let pos = left - 1; // 基数 const rightVal = array[right]; for (let i = left; i <= right; i++) { if (array[i] <= rightVal) { pos++; // 如果位置合理,是自己替换自己,如果不位置不合理,pos记录的是替换的位置 [array[i], array[pos]] = [array[pos], array[i]] } } quick(array, left, pos -1) quick(array, pos + 1, right) } return array } quick([2, 1, 4, 5, 3])

PS.实现一个 quickSortByDESC
选择排序
简单,理解就是,找到后放到相应位置,然后再次重复
function select(array) { for (let i = 0; i < array.length; i++) { min = i; for (let j = i; j < array.length -j; j++) { if (array[min] > array[j]) { min = j; } [array[min], array[i]] = [array[i], array[min]]; } } }

插入排序
归并排序
采用分治法
function mergeSort(array) { if (array.length < 2) { return array; }const mid = Math.floor(array.length / 2); const left = array.slice(0, mid); const right = array.slice(mid); return merge(mergeSort(left), mergeSort(right)); }function merge(left, right) { const result = []; while(left.length && right.length) { if (left[0] > right[0]) { result.push(right.shift()); } else { result.push(left.shift()); } }while(left.length) { result.push(left.shift()); }while(right.length) { result.push(right.shift()); }return result; }mergeSort([23, 5, 6, 1, 75, 7])

查找 参考
7个常见的查找:
1. 顺序查找

2. 二分查找
function search(array, target) { if (!array.length) { return -1; } let left = 0; let right = array.length -1; while(left <= right) { let mid = Math.floor(((right - left) / 2) + left); if (array[mid] === target) { return mid; } if (array[mid] < target) { left = mid + 1 } else if (array[mid] > target) { right = mid -1 } } return -1; }

3. 差值查找
待补充
4. 斐波那契查找
待补充
5. 树表查找
待补充
6. 分块查找
待补充
7. hash查找
待补充
算法题
数组 数组去重
// Set// 暴力,for 双循环// 暴力,用 Array api 代替,[].indexOf,[].includes,[].fliter// 排序 + 双指针// 转化成对象,存在向字符串转化的问题。

数组交集
待补充
平铺数组 flat
function flat (array) { // 健壮性判断 let r = [] array.forEach((item) => { if (item.length){ r = r.concat(flat(item)) } else { r.push(item) } }) return r; // es6 // return array.join().split(',') // // array.reduce 也可以 }console.log(flat([1, [2], [[3]], 4, [5]]))

合并两个有序的数组
给出两个有序的整数数组 A 和 B,将数组 B 合并到 A,变成一个有序数组。假设 A 数组有足够的空间存放 B 数组元素,A 和 B中初始元素数分别为 m 和 n
function mergeArray(arrayA, arrayB) { // 健壮性检查:数据类型,数组长度 const m = arrayA.length; const n = arrayB.length; let posA = 0; let posB = 0; let array = []; for (let i = 0; i < Math.max(m, n); i++) { if (arrayA[i] === undefined || arrayB[i] === undefined) { break; } if (arrayA[posA] >= arrayB[posB]) { array.push(arrayB[posB++]) array.push(arrayA[posA++]) } else { array.push(arrayA[posA++]) array.push(arrayB[posB++]) } } if (posB < n) { array = array.concat(arrayB.slice(posB, n)) } if (posA < m) { array = array.concat(arrayA.slice(posA, m)) } return array; } const A = [1, 3, 5, 7, 9]; const B = [2, 4] mergeArray(A, B)

给出一组数字,给出所有数字的所有排列
例如输入 [1, 2, 3],输出 [[1, 2, 3] [1, 3, 2] [2, 1, 3] [2, 3, 1] [3, 2, 1] [3, 1, 2]] ,按数字在数组中的位置为排列依据,按字典排序输出
// 回溯 function permute(array) { const length = array.length; if (length === 0) { return array; }const result = []; // 结果数组 [[], ...] const used = []; // 已经使用过的数组 const path = []; // 当前生成的组合 dfs(array, length, 0, path, used, result) return result }/** * dfs 深度优先遍历 * @param{array}array原始数组 * @param{number} len原始数组长度,代表停止遍历 * @param{number} depth当前遍历深度 * @param{array}used 当前遍历的结果 * @param{array}result存储所有符合的结果 */ function dfs(array, len, depth, path, used, result) {if (depth === len) { result.push([].concat(path)) return; }for (let i = 0; i < len; i++) { if (!used[i]) {path.push(array[i]); used[i] = true; dfs(array, len, depth + 1, path, used, result)// 回溯 used[i] = false; path.pop(); } } }permute([1, 2, 3])

【前端面试资料整理【算法篇】】数组之和为零
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
字符串 最长回文子串【双指针】
给你一个字符串 s,找到 s 中最长的回文子串。
输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。
// 暴力,时间复杂度O(n^3) 空间复杂度 O(1) function longestPalindrome(s) { if (s.length < 2) { return s; }let maxLength = 0; let begin = 0; for (let i = 0; i < s.length; i++) { for(let j = i; j < s.length; j++) { if (j - i + 1 > maxLength & validPalindromic(s, i, j)) { maxLength = j - i +1; begin = i } } } return s.substring(begin, maxLength) } // 验证是否回文 function validPalindromic(s, left, right) { while(left < right) { if (s[left++] != s[right--]) { return false; } } return true }longestPalindrome('babad') // bab longestPalindrome('babaaaaa') // aa, 有问题,应该是 aaaa// 中心扩展算法,时间复杂度O(n^2) 空间复杂度 O(1) function longestPalindrome(s) { if (s.length < 2) { return s; } const len = s.length; let start = 0; let end = 0; for (let i = 0; i < s.length; i++) { // 奇数对称 let len1 = entendAroundCenter(s, i, i); // 偶数对称 let len2 = entendAroundCenter(s, i, i+1); let len = Math.max(len1, len2); // 获取最长回文长度,大于目前起始位,更新,i 是中心位 if (len > end - start && len1 > len2) { // 奇数 start = i - ((len - 1) / 2); end = i + ((len - 1) / 2); } else if (len > end - start && len2 > len1) { // 偶数 start = i - (len / 2); end = i + (len / 2); } } return s.substring(start, end + 1) // end + 1 是因为要包含 end 处 } // 由中心向外扩展 function entendAroundCenter(s, left, right) { // 开始扩散 while(left > 0 && right < s.length && s[left] === s[right]) { left--; right++; } // right 和 left 多扩了1位 return (right - 1) - (left + 1) + 1; // 合并计算 right - left -1; }longestPalindrome('babaaaaa')

无重复字符的最长子串【双指针】
滑动窗口,有重复的就左边出,右边进
var lengthOfLongestSubstring = function(str) { if (!str.length) return 0 let tmpStr = ''// 每次循环找到的不含重复字符的子字符串 let maxStrLen = 0// 最大不含重复字符的子字符串的长度 let len = str.length let left = 0// 不含重复字符的子字符串的左游标 for (let i = 0; i < len; i++) { if (tmpStr.indexOf(str[i]) !== -1) { left += (str.slice(left, i).indexOf(str[i]) + 1) continue } tmpStr = str.slice(left, i + 1) maxStrLen = Math.max(maxStrLen, tmpStr.length) } return maxStrLen };

最长无重复子串
例如 abcabcbb 结果 abc
利用平移窗口
var lengthOfLongestSubstring = function(str) { if (!str.length) return 0 let tmpStr = ''// 每次循环找到的不含重复字符的子字符串 let maxStrLen = 0// 最大不含重复字符的子字符串的长度 let len = str.length let left = 0// 不含重复字符的子字符串的左游标 for (let i = 0; i < len; i++) { if (tmpStr.indexOf(str[i]) !== -1) { left += (str.slice(left, i).indexOf(str[i]) + 1) continue } tmpStr = str.slice(left, i + 1) maxStrLen = Math.max(maxStrLen, tmpStr.length) } return maxStrLen };

动态规划 全部规划路径数
仅返回数即可。
输入m = 3, n = 2
输出 3
var uniquePaths = function(m, n) { let dp = new Array(m) for(let i = 0; i < dp.length; i++) { dp[i] = new Array(n).fill(1) }for(let i = 1; i < dp.length; i++) { for(let j = 1; j < dp[0].length; j++) { dp[i][j] = dp[i - 1][j] + dp[i][j - 1] } } return dp[m - 1][n - 1] }; uniquePaths(3, 2)

背包问题本
假设我们有 5 个物品,重量分别为 2,2,4,6,3,背包总容量为 9 Kg。
最短路径
假设我们有一个 n*n 的矩阵,矩阵中每个元素都是正数。
var shortestPath = function(array) { const len = array[0].length; for (let i = 1; i < len; i++) { for(let j = 1; j < len; j++) { array[i][j] += Math.min(array[i - 1][j], array[i][j - 1]) }} return array[len - 1][len - 1]; }shortestPath( [ [1, 3, 5, 9], [2, 1, 3, 4], [5, 2, 6, 7], [6, 8, 4, 3] ] )

其他 菲波那切数列
要求输入一个整数 n,输出菲波那切数列数列的第 n 项, 第0项为 0 ,第1项为1。
// 最耗性能的递归写法。 function fibonacci(n) { if (n < 2) { return n; } return fibonacci(n-1) + fibonacci(n-2) }function fibonacci(n) { if (n < 2) { return n; } let a = 0; // 初始0位 let b = 1; // 初始1位 let c = 0; // fibonacci 数 for (let i = 2; i <= n; i++) { c = a + b; // 不能直接用i,会变成c = 2i - 3 a = b; b = c; } return c; }

反转链表
参考
// 迭代法 空间复杂度:O(1) 时间复杂度:O(n) function reverseList(head) { if (!head || !head.next) { return head; }let prev = null; let cur = head; while (cur) { const next = cur.next; cur.next = prev; prev = cur; cur = next; } return head; }// 尾递归 空间复杂度:O(n) 时间复杂度:O(n) // 从头节点开始,递归反转它的每一个节点,直到 null function reversList(head) { if (!head || !head.next) { return head; } head = reverse(null, head) return read; }function reverse(prev, cur) { if (!cur) return prev; const next = cur.next; cur.next = prev return reverse(cur, next); }// 递归 // 根据 head.next 推入栈中,重复:next = head.next, head = stach.pop(), next.next = head, head.next = null, 跳出 head.next === null function reversList(head) { if (!head || !head.next) { return head; } const next = head.next; const head = reversList(next); next.next = head; next.next = null; return head; }

递归的逻辑图示
前端面试资料整理【算法篇】
文章图片
并行请求
未完成,未测试
const f = (url) => new Promise((resolve) => { console.log('f:', url) setTimeout(() => resolve({ code: 200 }), 500) })// 并发控制 function createRequest(num){ // 健壮性检查 const queue = []; let count = 0; // 正在执行的请求数const create = (url, opitons) => () => f(url, opitons).then((res) => { count++; return res }).catch(() => { // 维护个错误队列 }).finally(() => { count--; if (queue.length) { queue.shift()() } })return function(url, options) { const stack = create(url, options) // 排队 if (count >= num) { queue.push(stack) } else { // 0, 1, 2 return stack() } } }// ----> // ----> // ----> //----> //----> const request = createRequest(3); // 利用 webmessage 实现并发 request('/a').then((r) => console.log('res', r)) request('/b').then((r) => console.log('res', r)) request('/c').then((r) => console.log('res', r)) request('/e').then((r) => console.log('res', r)) request('/f').then((r) => console.log('res', r))// -----// 模拟 fetch const f = (url) => new Promise((resolve) => { const time = Math.random() * 1000 setTimeout(() => { console.log(url, time) resolve({ time }) }, time) })function createlimitRequest(limit) { const waitQueue = []; let requestintCount = 0; const r = (url) => { requestintCount++; return f(url) .then((res) => res).finally(() => { if (waitQueue.length) { requestintCount++; return waitQueue.shift(); } }) } return function (url) { if (requestintCount > limit) { waitQueue.push(r) } return r(url) } }const request = createlimitRequest(4); function requestAll(urls) { const results = [] let count = 0; return new Promise((resolve) => { urls.forEach((url, index) => request(url).then((res) => { results[index] = res }).catch((err) => { results[index] = err }).finally(() => { count++; if (count === urls.length) { resolve(results) } }) ) }) }requestAll([ '/a', '/b', '/c', '/d', '/e', '/f', ])

复原 IP 地址
给定一个只包含数字的字符串,用以表示一个 IP 地址,返回所有可能从 s 获得的 有效 IP 地址 。你可以按任何顺序返回答案。
有效 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 '.' 分隔。
例如:"0.1.2.201" 和 "192.168.1.1" 是 有效 IP 地址,但是 "0.011.255.245"、"192.168.1.312" 和 "192.168@1.1" 是 无效 IP 地址。
输入:s = "25525511135"
输出:["255.255.11.135","255.255.111.35"]
待补充
买股票时机
假设你有个数组,第 i 个元素是股票第 i 天的价格。你有一次买入和卖出的机会。(只有买入后才能卖出)。求最大收益。
待补充

    推荐阅读