整理了一些面试中常用的算法题,正在不断更新中,大家在面试和工作中有常遇到的算法题,,欢迎评论区共享
1 查找字符串中的最长公共前缀
示例: 输入: [“flower”,“flow”,“flight”]
输出: “fl”
function getMaxAndIndex( arr ){
var firstStr = arr[0], result = ''
if(!arr.length) return result
for(let i=0;i<firstStr.length;i++){
for(let j=1;j<arr.length;j++){
if(firstStr[i]!=arr[j][i]){
return result
}
}
result += firstStr[i]
}
return result
}
2 深度拷贝、浅度拷贝
-
浅拷贝
a. 对象的合并 Object.assign(),第一个参数必须是个空对象var obj1 = {a: 1, b: 2}; var obj2 = Object.assign({}, obj1);
b. 对象的解构赋值var obj1 = {a: 1, b: 2}; var obj2 = {...obj1};
-
深度拷贝
a. 数组或对象深拷贝(利用循环和递归)function deepClone(obj) { let objClone = Array.isArray(obj) ? [] : {}; if (obj && typeof obj === 'object') { for (key in obj) { if (obj.hasOwnProperty(key)) { if (obj[key] && typeof obj[key] === 'object') { objClone[key] = deepClone(obj[key]) } else { objClone[key] = obj[key] } } } } return objClone; } let a = [20,8,6,19], b = deepClone(a); a[1] = 2; console.log(a, b)
b. 利用jQuery的$.extend方法
//第一个参数不传(false是不能够显示的写出来的)默认为false,是浅拷贝。传true为深拷贝。 $.extend(true,object1, object2) //newObject 即为深拷贝出来的对象 var newObject = $.extend(true , {} , object);
c. JSON.parse( JSON.stringify())
//通过js的内置对象JSON来进行数组对象的深拷贝 function deepClone(obj) { var _obj = JSON.stringify(obj), objClone = JSON.parse(_obj); return objClone; }
3 手写EventEmiter
class EventEmitter {
constructor() {
this.events = {}
}
on(eventName, fn) {
if (!this.events[eventName]) {
this.events[eventName] = []
}
this.events[eventName].push(fn)
}
emit(eventName, ...rest) {
this.events[eventName] && this.events[eventName].forEach(f => f.apply(this, rest))
}
// remove(eventName, fn) {
// if (this.events[eventName]) {
// this.events[eventName].filter(f => f !== fn)
// }
// }
// once(eventName, callback) {
// const fn = () => {
// callback();
// this.remove(eventName, fn)
// }
// this.on(eventName, fn)
// }
}
const event = new EventEmitter()
const handle = (...pyload) => console.log(pyload)
event.on('click', handle)
// event.emit('click', 100, 200, 300, 100)
// event.remove('click', handle)
event.once('dbclick', function () {
console.log('click')
})
event.emit('dbclick', 100);
3 给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标值,返回 [-1, -1]。
示例: 输入: nums = [5,7,7,8,8,10], target = 8, 输出:[3,4]
输入: nums = [5,7,7,8,8,10], target = 6, 输出:[-1,-1]
思路:1. 开始位置 indexOf
2. 结束位置:翻转数组,求arr.length-(indexOf+1)
function pop(arr, target){
const tmp = [];
tmp[0] = arr.indexOf(target);
tmp[1] = arr.reverse().indexOf(target);
if(tmp[1] !== -1){
tmp[1] = arr.length - tmp[1]-1;
}
console.log(tmp);
return tmp;
}
const nums = [5,7,7,8,8,10];
const target = 8;
pop(nums, target)
4 js实现promise.all
function PromiseAll(promise){
return new Promise((resolve, reject) => {
let index=0;
let result = [];
if(promise.length === 0) resolve(result)
for(let i = 0; i<promise.length; i++){
Promise.resolve(promise[i]).then(data => {
result[i] = data;
if(++index === promise.length){
resolve(result)
}
}, err => {
reject(err)
})
}
})
}
let p1 = new Promise((resolve) => {
resolve('成功了')
})
let p2 = new Promise((resolve) => {
resolve('success')
})
PromiseAll([p1, p2]).then((result) => {
console.log(result) //['成功了', 'success']
}).catch((error) => {
console.log(error)
})
输出结果:(2) ["成功了", "success"]
5.判断一个单词是否是回文?
回文是指把相同的词汇或句子,在下文中调换位置或颠倒过来,产生首尾回环的情趣,叫做回文,也叫回环。比如 mamam redivider .
很多人拿到这样的题目非常容易想到用for 将字符串颠倒字母顺序然后匹配就行了。其实重要的考察的就是对于reverse的实现。其实我们可以利用现成的函数,将字符串转换成数组,这个思路很重要,我们可以拥有更多的自由度去进行字符串的一些操作。
function checkPalindrom(str) {
return str == str.split('').reverse().join('');
}
6. 去掉一组整型数组重复的值
比如输入: [1,13,24,11,11,14,1,2]
输出: [1,13,24,11,14,2]
需要去掉重复的11 和 1 这两个元素。
let unique = function(arr) {
let hashTable = {};
let data = [];
for(let i=0,l=arr.length;i<l;i++) {
if(!hashTable[arr[i]]) {
hashTable[arr[i]] = true;
data.push(arr[i]);
}
}
return data
}
7.统计一个字符串出现最多的字母
输入 : afjghdfraaaasdenas
输出 : a
前面出现过去重的算法,这里需要是统计重复次数。
function findMaxDuplicateChar(str) {
if(str.length == 1) {
return str;
}
let charObj = {};
for(let i=0;i<str.length;i++) {
if(!charObj[str.charAt(i)]) {
charObj[str.charAt(i)] = 1;
}else{
charObj[str.charAt(i)] += 1;
}
}
let maxChar = '',
maxValue = 1;
for(var k in charObj) {
if(charObj[k] >= maxValue) {
maxChar = k;
maxValue = charObj[k];
}
}
return maxChar;
}
另一种写法:
function getMaxAndIndex( arr ){
if(!arr.length) return;
if(arr.length==1) return 1;
var h={}, maxnum=0,maxel='';
for(var i=0;i<arr.length;i++){
var a=arr[i]
h[a]==undefined ? h[a]=1 : (h[a]++)
if(h[a]>maxnum){
maxnum=h[a]
maxel=a
}
}
return '出现次数最多的元素为:'+maxel+'出现次数:'+maxnum
}
8.排序算法
冒泡排序
思路:它重复地走访要排序的元素列,依次比较两个相邻的元素,如果他们的顺序(如从大到小、首字母从A到Z)错误就把他们位置交换过来.
function bubbleSort(arr) {
for(let i = 0,l=arr.length;i<l-1;i++) {
for(let j=0;j<arr.length-1-i;j++) {
if(arr[j]>arr[j+1]) {
[arr[j],arr[j+1]] = [arr[j+1], arr[j]]
}
}
}
return arr;
}
快速排序
思路:选择一个元素作为基数(通常是第一个元素),把比基数小的元素放到它左边,比基数大的元素放到它右边(相当于二分),再不断递归基数左右两边的序列。
function quickSort(arr){
if(arr.length<=1)return arr;
let leftArr=[],rightArr=[];
for(let i=1;i< arr.length; i++){
if(arr[i]<arr[0]){
leftArr.push(arr[i])
} else {
rightArr.push(arr[i])
}
}
return [].concat(quickSort(leftArr),arr[0],quickSort(rightArr))
}
选择排序
思路:和冒泡排序相似,区别在于选择排序是将每一个元素和它后面的元素进行比较和交换。
function selectSort(arr){
for(let i=0;i< arr.length; i++){
for(let j=i+1;j< arr.length; j++){
if(arr[i]>arr[j])
[arr[i],arr[j]] = [arr[j], arr[i]]
}
}
return arr;
}
插入排序
思路:依然要通过两层循环,外循环便利每个数组项,内循环从外循环的数组项(i)开始往前遍历,如果当前数组项比前一个小,则与前一个调换位置,这样一直循环重复,数组就逐渐归位了;
function insertSort(arr){
for(let i=1;i< arr.length; i++){
for(j=i;j>0;j--){
if(arr[j]<arr[j-1]){
[arr[j], arr[j-1]]= [arr[j-1], arr[j]]
}
}
}
return arr
}
希尔排序
思路:希尔排序将插入排序作为它的子进程,它的核心是一个叫步长(gap)的概念,这个步长可以理解为一个类似疏密程度的概念。它共有3层循环,外层是以步长为循环,一般取数组的一半,循环一次再除一半,中层和里层就是插入排序的操作,不过不是跟前一项比,是跟当前索引减去步长后的那一项比。说到这里就可以理解为什么我说步长是类似疏密程度的概念,当步长不断除于2,变得越来越小,直到零停止循环,这个过程中,插入排序的比较项间越来越近,逐渐数组被排列出来。
function shellSort(arr){
let gap = Math.floor(arr.length/2);
while(gap>0){
for(let i=gap;i< arr.length; i++){
for(let j=i;j>0;j-=gap ){
if(arr[j]<arr[j-gap])
[arr[j], arr[j-gap]] = [arr[j-gap],arr[j]]
}
}
gap = Math.floor(gap/2)
}
return arr
}
桶排序
思路:取 n 个桶,根据数组的最大值和最小值确认每个桶存放的数的区间,将数组元素插入到相应的桶里,最后再合并各个桶。
function bucketSort(arr){
let data = Array.from({length:10}).fill(0);
let newArr = [];
arr.forEach(el=>data[el]++)
for(let i=0;i< data.length; i++){
for(let j=0;j< data[i]; j++){
newArr.push(i)
}
}
return newArr;
}
二叉树查找
let tree = { // 树结构
id: 1,
left: { id: 2, left: { id: 4}, right: { id: 5 }},
right: { id: 3, left: { id: 6}, right: { id: 7 }}
}
// 1.根左右 前序递归 1245367
function rootLR(tree) {
console.log("===", tree.id);
if (tree.left) rootLR(tree.left);
if (tree.right) rootLR(tree.right);
}
// 2.左跟右 中序遍历 4251637
function LRootR(tree) {
if (tree.left) LRootR(tree.left);
console.log("===", tree.id);
if (tree.right) LRootR(tree.right);
}
// 3.左右跟 后序遍历 4526731
function LRRoot(tree) {
if (tree.left) LRRoot(tree.left);
if (tree.right) LRRoot(tree.right);
console.log("===", tree.id);
}
// 4.根左右 前序非递归 1245367
function w_rootR(tree) {
let res = [],
arr = [tree]
while (arr.length) {
let tmp = arr.pop();
res.push(tmp.id);
if (tmp.right) arr.push(tmp.right)
if (tmp.left) arr.push(tmp.left)
}
console.log(res);
return res
}
//5 左根右 中序非递归 4251637
function w_LRootR(tree) {
let arr = [],
res = [];
while (true) {
while (tree) {
arr.push(tree);
tree = tree.left;
}
if (!arr.length) break;
let tmp = arr.pop();
res.push(tmp.id);
tree = tmp.right;
}
return res;
}
// 6左右跟 后序非递归遍历 4526731
function w_LRRoot(tree) {
let arr = [tree],
res = [];
while (arr.length) {
let tmp = arr.pop();
res.push(tmp.id);
if (tmp.left) arr.push(tmp.left)
if (tmp.right) arr.push(tmp.right)
}
return res.reverse();
}
9.不借助临时变量,进行两个整数的交换
输入 a = 2, b = 4 输出 a = 4, b =2
主要是利用 + - 去进行运算,类似 a = a + ( b - a) 实际上等同于最后 的 a = b;
function swap(a , b) {
b = b - a;
a = a + b;
b = a - b;
return [a,b];
}
使用canvas 绘制一个有限度的斐波那契数列的曲线?
斐波那契数列,又称黄金分割数列,指的是这样一个数列:0、1、1、2、3、5、8、13、21、34、……在数学上,斐波纳契数列主要考察递归的调用。我们一般都知道定义
fibo[i] = fibo[i-1]+fibo[i-2];
生成斐波那契数组的方法
function getFibonacci(n) {
var fibarr = [];
var i = 0;
while(i<n) {
if(i<=1) {
fibarr.push(i);
}else{
fibarr.push(fibarr[i-1] + fibarr[i-2])
}
i++;
}
return fibarr;
}
剩余的工作就是利用canvas arc方法进行曲线绘制了
10.出下列正数组的最大差值比如:
输入 [10,5,11,7,8,9]
输出 6
function getMaxProfit(arr){
var max=arr[0];
var min=arr[0];
var res=0;
for (var i = 1; i < arr.length; i++) {
if(arr[i]>max){
max=arr[i];
}
if(arr[i]<min){
min=arr[i]
}
res=max-min;
}
return res;
}
11 随机生成指定长度的字符串
比如给定 长度 8 输出 4ldkfg9j
function getMaxStr(n) {
let str = 'abcdefghijklmnopqrstuvwxyz9876543210';
var tmp=''
for(let i=0;i<n;i++){
tmp+= str.charAt(Math.floor(Math.random()*str.length))
}
return tmp
}
12.实现类似getElementsByClassName 的功能
自己实现一个函数,查找某个DOM节点下面的包含某个class的所有DOM节点?不允许使用原生提供的 getElementsByClassName querySelectorAll 等原生提供DOM查找函数。
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>原生JS实现getElementsByClassName()</title>
<script>
window.onload = function() {
var oUl = document.getElementById('ull');
function getElementsByClassName(obj,classname){
var result = [];
var aEls = document.getElementsByTagName('*');
for(var i = 0; i<aEls.length; i++){
//查找有多个属性的元素
var classnames = aEls[i].className.split(' ')
if(indexOf(classname,classnames)!=-1){
result.push(aEls[i]);
}
}
return result;
}
function indexOf(classname,classnames){
for(var i = 0; i<classnames.length; i++){
if(classname==classnames[i]){
return i;
break;
}
}
if(i = classnames.length) return -1;
}
//alert(indexOf('box',['box1','box']));
alert(getElementsByClassName(oUl,'box').length);
}
</script>
</head>
<style>
li{
width:100px;
height:28px;
margin-left:20px;
list-style: none;
background: gray;
margin:2px;
}
.box{
background:orange;
}
.box1{
background:green;
}
</style>
<body>
<ul id="ull">
<li>0</li>
<li class='box box'>1</li>
<li>2</li>
<li class='box1'>3</li>
<li class='box1'>4</li>
<li class='box box1'>5</li>
</ul>
</body>
</html>
13.获取指定范围内的随机数
当我们需要获取指定范围(min,max)内的整数的时候,下面的代码非常适合。
function getRadomNum(min,max){
return Math.floor(Math.random() * (max - min + 1)) + min;
}
14.随机获取数组中的元素
function getRadomFromArr(arr){
return arr[Math.floor(Math.random()*arr.length)];
}
15 生成从0到指定值的数字数组
var arr=[]
for(var i=1;i<=100;i++){
arr.push(i)
}
console.log(arr)
16 打乱数字数组的顺序
var arr = [1,2,3,4,5,6,7,'a','dsfs',8,9,'v'];
arr.sort(function(){return Math.random()-0.5});
17 对象转换为数组
//注意对象必须是以下格式的才可以通过此方式转化为数组
//获取的DOM集合,以及函数的arguments也可以通过此方式转化为数组
var obj={
0:'qian',
1:'long',
2:'chu',
3:'tian',
length:4
}
var _slice=[].slice;
var objArr=_slice.call(obj);
18. 验证是否为数组
function isArray(obj){
return Object.prototype.toString.call(obj) === '[object Array]' ;
}
19 获取数组中最大或者最小值
该方法适合一维或者多维数组求最大最小值的情况
function maxAndMin(arr){
return {
max:Math.max.apply(null,arr.join(',').split(',')),
min:Math.min.apply(null,arr.join(',').split(','))
}
}
20 清空数组
//方式一 通过将长度设置为0
var arr=[1,2,3,4,5];
arr.length=0;
//方式二 通过splice方法
var arr=[1,2,3,4,5];
arr.splice(0,arr.length);
//方式三 通过将空数组 [] 赋值给数组(严格意义来说这只是将ary重新赋值为空数组,之前的数组如果没有引用在指向它将等待垃圾回收。)
var arr=[1,2,3,4,5];
arr=[];
21保留指定小数位
var num =4.345678;
num = num.toFixed(4); // 4.3457 第四位小数位以四舍五入计算
22 找出数组中出现次数最的元素,并给出其出现过的位置
function getMaxAndIndex( arr ){
var obj = {};
arr.forEach(function(item,index){
if(!obj[item]){
obj[item]= {indexs: [index]}
}else{
obj[item]['indexs'].push(index);
}
});
var num=0;//记录出现次数最大值
var str='';//记录出现次数最多的字符
var reArr;//返回最大值的位置数组
for(var attr in obj){
var temp=obj[attr]['indexs'];
if(temp.length>num){
num=temp.length;
str=attr;
reArr=temp;
}
}
return {
maxStr:str,
indexs:reArr
}
}
##手写 call / apply / bind
参考https://www.cnblogs.com/echolun/p/12178655.html
// 手写bind
Function.prototype.bind = function (context, ...bindments) {
context = context || window;
const func = this;
return function F(...callments) {
let args = bindments.concat(callments);
if (this instanceof F) return new func(...args)
return func.call(context, ...args)
}
}
// call
Function.prototype.call = function (context, ...callargs) {
context = context || window;
context.func = this;
if (typeof context.func !== 'function') {
throw new TypeError("必须是函数调用call")
}
let res = context.func(...callargs);
delete context.func;
return res;
}
// apply
Function.prototype.apply = function (context, arr) {
context = context || window;
context.func = this;
if (typeof context.func !== 'function') {
throw new TypeError("必须是函数调用apply")
}
let res = context.func(...arr)
delete context.func;
return res;
}
另一个版本:
Function.prototype.bind_ = function (context) {
if (typeof this !== 'function') {
throw new Error('必须函数调用bind');
}
let args = [...arguments].slice(1);
let fn = this;
let bound = function() {
fn.apply(this.constructor === fn ? this : context, args.concat(arguments));
}
let fn_ = function() {};
fn_.prototype = fn.prototype;
bound.prototype = new fn_();
return bound;
}
// call
Function.prototype.call = function (context) {
if (typeof this !== 'function') {
throw new Error('必须函数调用call');
}
let args = [...arguments].slice(1);
let res = null;
context = context || window;
context.fn = this;
res = context.fn(...args);
delete context.fn;
return res;
}
// apply
Function.prototype.apply = function (context) {
if (typeof this !== "function") {
throw new Error("Type error");
}
let result = null;
context = context || window;
const fnSymbol = Symbol();
context[fnSymbol] = this;
if (arguments[1]) {
result = context[fnSymbol](...arguments[1]);
} else {
result = context[fnSymbol]();
}
delete context[fnSymbol];
return result;
}