JavaScript算法(数组,字符串...)

4,215 阅读12分钟
数组的排序和去重,一直是面试中的一个热点,一般是要求手写数组去重方法的代码,因为它能够很好地考察一个人的逻辑思维能力。如果是被提问到,数组去重的方法有哪些?你能答出其中的10种,面试官很有可能对你刮目相看。这些知识只要经过一定地刻意训练,多看,多思考,多敲代码,这些算法的掌握其实是一件很简单的事情。

全文分为两部分,数组的排序 和 数组的去重。

一. 数组去重

1、建立一个空的数组,用indexOf去重

function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error!')
        return
    }
    var array = [];//定义一个空的数组
    for (var i = 0; i < arr.length; i++) {
        if (array .indexOf(arr[i]) === -1) {//判断数组中是否有arr[i]这个元素
            array .push(arr[i])
        }
    }
    return array;
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
   // [1, "true", true, 15, false, undefined, null, NaN, NaN, "NaN", 0, "a", {…}, {…}]  //NaN、{}没有去重

新建一个空的结果数组,for 循环原数组,判断结果数组是否存在当前元素,如果有相同的值则跳过,不相同则push进数组。

2、利用for嵌套for,然后splice去重(ES5中最常用)

方法一:

双层循环,外层循环元素,内层循环时比较值。值相同时,则删去这个值。

function unique(arr){            
        for(var i=0; i<arr.length; i++){
            for(var j=i+1; j<arr.length; j++){
                if(arr[i]==arr[j]){ //第一个等同于第二个,splice方法删除第二个
                    arr.splice(j,1);
                    j--;
                }
            }
        }
return arr;
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
//[1, "true", 15, false, undefined, NaN, NaN, "NaN", "a", {…}, {…}]     
//NaN和{}没有去重,两个null直接消失了

方法二:

var arr=[11,22,33,44,55,22,33,54];
for(var n=0;n<arr.length;n++){
	if (arr.indexOf(arr[n])!=arr.lastIndexOf(arr[n])){
		//判断arr[n]出现的次数,如果次数不是一次,那么删除后边的元素
		arr.splice(arr.lastIndexOf(arr[n]),1);
		n--;//没有这个n--,相当于执行了8次,
	}
}
console.log(arr);

3、利用ES6 Set去重(ES6中最常用)

function unique (arr) {
  return Array.from(new Set(arr))
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
 //[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {}, {}]

不考虑兼容性,这种去重的方法代码最少。这种方法还无法去掉“{}”空对象,后面的高阶方法会添加去掉重复“{}”的方法。

4、利用sort()

function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error!')
        return;
    }
    arr = arr.sort()
    var arrry= [arr[0]];
    for (var i = 1; i < arr.length; i++) {
        if (arr[i] !== arr[i-1]) {
            arrry.push(arr[i]);
        }
    }
    return arrry;
}
     var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
        console.log(unique(arr))
// [0, 1, 15, "NaN", NaN, NaN, {…}, {…}, "a", false, null, true, "true", undefined]      
//NaN、{}没有去重

利用sort()排序方法,然后根据排序后的结果进行遍历及相邻元素比对。

5、利用对象的属性不能相同的特点进行去重(这种数组去重的方法有问题,不建议用,有待改进)

function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error!')
        return
    }
    var arrry= [];
     var  obj = {};
    for (var i = 0; i < arr.length; i++) {
        if (!obj[arr[i]]) {
            arrry.push(arr[i])
            obj[arr[i]] = 1
        } else {
            obj[arr[i]]++
        }
    }
    return arrry;
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
//[1, "true", 15, false, undefined, null, NaN, 0, "a", {…}]    
//两个true直接去掉了,NaN和{}去重

6、利用includes

function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error!')
        return
    }
    var array =[];
    for(var i = 0; i < arr.length; i++) {
            if( !array.includes( arr[i]) ) {//includes 检测数组是否有某个值
                    array.push(arr[i]);
              }
    }
    return array
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
//[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}, {…}]     
//{}没有去重

7、利用hasOwnProperty

function unique(arr) {
    var obj = {};
    return arr.filter(function(item, index, arr){
        return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true)
    })
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
//[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}]   
//所有的都去重了

利用hasOwnProperty 判断是否存在对象属性

8、利用filter

function unique(arr) {
  return arr.filter(function(item, index, arr) {
    //当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素
    return arr.indexOf(item, 0) === index;
  });
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
//[1, "true", true, 15, false, undefined, null, "NaN", 0, "a", {…}, {…}]

9、利用递归去重

function unique(arr) {
        var array= arr;
        var len = array.length;

    array.sort(function(a,b){   //排序后更加方便去重
        return a - b;
    })

    function loop(index){
        if(index >= 1){
            if(array[index] === array[index-1]){
                array.splice(index,1);
            }
            loop(index - 1);    //递归loop,然后数组去重
        }
    }
    loop(len-1);
    return array;
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
//[1, "a", "true", true, 15, false, 1, {…}, null, NaN, NaN, "NaN", 0, "a", {…}, undefined]

10、利用Map数据结构去重

function arrayNonRepeatfy(arr) {
  let map = new Map();
  let array = new Array();  // 数组用于返回结果
  for (let i = 0; i < arr.length; i++) {
    if(map .has(arr[i])) {  // 如果有该key值
      map .set(arr[i], true); 
    } else { 
      map .set(arr[i], false);   // 如果没有该key值
      array .push(arr[i]);
    }
  } 
  return array ;
}
 var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
    console.log(unique(arr))
//[1, "a", "true", true, 15, false, 1, {…}, null, NaN, NaN, "NaN", 0, "a", {…}, undefined]

创建一个空Map数据结构,遍历需要去重的数组,把数组的每一个元素作为key存到Map中。由于Map中不会出现相同的key值,所以最终得到的就是去重后的结果。

11、利用reduce+includes

function unique(arr){
    return arr.reduce((prev,cur) => prev.includes(cur) ? prev : [...prev,cur],[]);
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr));
// [1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}, {…}]

12、[...new Set(arr)]

[...new Set(arr)] 
//代码就是这么少----(其实,严格来说并不算是一种,相对于第一种方法来说只是简化了代码)

二. 多维数组扁平化

综合拓展:多维数组化为一维数组,然后进行去重,升序,

方法1:

function f (arr) {            
    if (Object.prototype.toString.call(arr) !='[object Array]') {//判断是否为数组       
        return;            
    }            
    var newarr = [];            
    function fn (arr) {                
        for (var i = 0; i < arr.length; i++) {                    
            if (arr[i].length) {                        
                fn(arr[i]);                    
            }else{                        
                newarr.push(arr[i]);                    
            }                
        }            
    }            
    fn(arr);                       
    return newarr;        
}        
Array.prototype.u = function () {//去重            
    var newarr = [];            
    var obj = {};            
    for (var i = 0; i < this.length; i++) {                
        if (!obj[this[i]]) {                   
            newarr.push(this[i]);                    
            obj[this[i]] = 1;                
        }            
    }            
    return newarr;        
}        
function compare (c1,c2) {//排序            
    return c1 -c2;        
}               
var arr = [2, -5, 6, [6, -5, [2, 5], [8, 10, [6, 8], -3], 2], 5]      
var a = [];        
a = f(arr);        
b = a.u();        
c = b.sort(compare);        
console.log(c);

方法2:

Array.prototype.flat= function() {
    return [].concat(...this.map(item => (Array.isArray(item) ? item.flat() :
    [item])));
}
Array.prototype.unique = function() {
    return [...new Set(this)]
}
const sort = (a, b) => a - b;
console.log(arr.flat().unique().sort(sort));

方法3:

function spreadArr(arr=[]){
    if(arr.some(ele=>Array.isArray(ele))){
        let newArr = [];
        arr.forEach((ele) => {
            if(Array.isArray(ele)){
                newArr = newArr.concat(...ele)
            }else{
                if(!newArr.includes(ele))
                newArr.push(ele)
            }
        })
        return spreadArr(newArr);
    }
    return arr.sort((a,b)=> a-b);
}
spreadArr([ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10]);

方法4:

var arr = [2, -5, 6, [6, -5, [2, 5], [8, 10, [6, 8], -3], 2], 5];        
function f (arr) {            
    var newarr = [];            
    function fn (arr) {                
        for (var i = 0; i < arr.length; i++) {                    
            if (arr[i].length) {
                if (Array.isArray(arr[i])) {  //加这步判定的目的是,判断这个元素是否为数组
                    fn(arr[i]);
                } else {
                    newarr.push(arr[i]);             
            }else{                        
                newarr.push(arr[i]);                    
            }                
        }            
    }            
    fn(arr);            
    return newarr;        
}        
var x = f(arr);        
var newarr = [];        
for(var n = 0;n < x.length; n++) {           
    if (newarr.indexOf(x[n]) == -1) {               
        newarr.push(x[n]);           
    }        
}        
newarr.sort((a, b) => a - b)        
console.log(newarr)

方法5:

评论区一位朋友提出的方法,我觉得思路也很好,多维数组化为一维数组,

var arr = [12, 23, 34, [6, [6], [6,[7,[8]]]], [5,[5]], [4, [7]], [4, [[3]]], 45, 6];
arr.toString().split(',').map(Number);
//[12, 23, 34, 6, 6, 6, 7, 8, 5, 5, 4, 7, 4, 3, 45, 6] 

这但是如果内部有汉字(或者别的字符串)的话,就不能化为纯数字,建议使用前文的算法类方法。


三. 经典面试题

1.栈的压入和弹出

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列5,4,3,2,1或3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。

function IsPopOrder(pushV,popV){
    if(pushV.length === 0) return false;
    var stack = []; // 模拟栈
    for(var i = 0, j = 0; i < pushV.length;){
        stack.push(pushV[i]);
        i += 1;
        // 压入栈的值需要被弹出
        while(j < popV.length && stack[stack.length-1] === popV[j]){
            stack.pop();
            j++;
            if(stack.length === 0) break;
        }
    }
    return stack.length === 0;
}

2.利用栈模拟队列

思路:

  • 对栈A添加数据。
  • 如果栈B为空,循环将栈A中内容弹出放入栈B,并弹出栈B最后一项
  • 如果栈B不为空,则直接弹出栈B的最后一项

ar stackA = [];
var stackB = [];

function push(node){
    stackA.push(node);
}
function pop(){
    if(!stackB.length){
        while(stackA.length){
            stackB.push(stackA.pop());
        }
    }
    return stackB.pop();
}

3. 找出连续最长不重复字符串

在一个字符串中找出连续的不重复的最大长度的字符串,解决这类问题的思路:

  • 利用循环叠加字符串,直到出现重复为止
  • 每一次叠加,记录下来最大长度的字符串

// 连续最长不重复字符串
function getMaxLenStr(str) {
    var cur = [];
    var maxLenStr = '';
    for(var i = 0; i < str.length; i++) {
        if(!cur.includes(str[i])) {
            cur.push(str[i]);
        } else {
            cur = []; // 置为空
            cur.push(str[i]);
        }
        
        // 存储最大长度的字符串
        if(maxLenStr.length < cur.length) {
            maxLenStr = cur.join('');
        }        
    }
    return maxLenStr;
}

getMaxLenStr('ababcabcde'); // abcde

方法二:

var str = "aababcabcdeabcdefaababc";        
var x = str.split('').reduce((a, b, index) => {            
    if(a.indexOf(b) !== -1) {                
        return a;            
    }            
    a.push(b);            
    return a;        
}, []).join('');          
console.log(x) 

方法三:

var s = "aababcabcdeabcdefaababc";        
var lengthOfLongestSubstring = function(s) {            
    var str = ""; // 用于存放无重复字符串            
    var arr = [];            
    for(var i = 0; i < s.length; i++) {                
        var char = s.charAt(i);                
        var index = str.indexOf(char);                
        if(index === -1) {                    
            str += char;                    
            arr.push(str);                
        } else {                    
            str = str.substr(index + 1) + char;                
        }            
    }            
    return arr.reduce((a, b) => {                
        return b.length > a.length ? b : a;            
    }, '');        
};        
console.log(lengthOfLongestSubstring(s));

4. 求一个数组当中,连续子向量的最大和。

function FindGreatestSumOfSubArray(arr) {
    let sum = arr[0];
    let max = arr[0];
    for(let i = 1; i < arr.length; i++) {
        if(sum < 0) {
            sum = arr[i];
        }else{
            sum += arr[i];
        }
        // 记录最大值
        if(max < sum) {
            max = sum;
        }
    }
    return max;
} 

5. 给定一个编码字符,按编码规则进行解码,输出字符串

编码规则:coount[letter] ,将letter的内容count次输出,count0或正整数,letter是区分大小写的纯字母。
实例:

  • const s= 3[a]2[bc]; decodeString(s); // 返回'aaabcbc'
  • const s= 3[a2[c]]; decodeString(s); // 返回 'accaccacc'
  • const s= 2[ab]3[cd]ef; decodeString(s); // 返回 'ababcdcdcdef'

思路: 使用栈这种数据结构,如果 push 的内容为‘]’,则循环 pop 字符,直到碰到 ’[‘,然后将pop 出来的字符串按规则整理后,重新 push 进栈中,最后将栈内的内容拼接成字符串输出即可。

方法1:

function decodeString(str) {
    let stack = []; // 存储字符串的栈
    for (let i = 0; i < str.length; i++) {
        let cur = str[i];
        if (cur !== ']') {
            stack.push(cur);
        } else { // 弹出
            let count = 0;
            let loopStr = [];
            let popStr = '';
            while ((popStr = stack.pop()) !== '[') {
                loopStr.unshift(popStr);
            }
            count = stack.pop();
            // 添加结果
            let item = '';
            for (let i = 0; i < count; i++) {
                item += loopStr.join('');
            }
            stack.push(...(item.split('')));
        }
    }
    return stack.join('');
}

方法2:

const str = '3[a]2[bc]';        
function decodeString(str) {            
    let Counts = str.split(/\[[a-zA-Z]+\]/);            
    let Letters = str.match(/[a-zA-Z]+/g);            
    let newString = "";            
    Letters.forEach((item,index)=>{                
        for (var n=0;n<Counts[index];n++) {                    
            newString += item;                
        }           
    })            
    return newString;        
}        
console.log(decodeString(str))

6. 实现一个方法,限定数组中元素出现的次数,第一个参数为数组,第二个参数为限制数组中元素出现的最多次数;要求不改变原素组的顺序;

例如:

deleteNth((1, 1 1, 1), 2); //return [1, 1]
deleteNth((20, 37, 34, 20, 20, 37), 2); //return [20, 37, 34, 20, 37];

方法1:

var arr = [4, 4, 4, 4, 3, 3, 3, 3, 1, 2, 4, 3, 90];        
function deleteNth(arr, n) {            
    var newArr = [];            
    for (var i = 0; i < arr.length; i++) {                
        if (newArr.indexOf(arr[i]) == -1) {                    
            newArr.push(arr[i]);                
        }             
    }            
    for (var i = 0; i < newArr.length; i++) {                
        var sum = 0;                
        for (var j = 0; j < arr.length; j++) {                    
            if (arr[j] == newArr[i]) {                        
                sum ++;                        
                if (sum > n) {                            
                    arr.splice(j, 1);                            
                    j--;                        
                }                    
            }                
        }            
    }            
    return arr;        
}        
console.log(deleteNth(arr, 2))

方法2:

var arr = [1, 1, 2, 5, 23, 23, 1, 1];         
function deleteNth(arr, n) {             
    let newArr = arr.map( item => {                 
        return item;             
    })//原始数据副本             
    let newArr1 = [];//处理后的数据             
    for (var i = 0; i < newArr.length; i++) {                 
        if (newArr1.indexOf(newArr[i]) < 0) {                     
            newArr1.push(newArr[i]);                 
        } else if (newArr1.indexOf(newArr[i]) > -1) {                     
            let hasIndexArr = [];//用于存放相匹配的项的索引                     
            for (let j = 0; j < newArr1.length; j++) {                         
                if (newArr1[j] == newArr[i]) {//将匹配的项的索引扔进hasIndexArr 
                    hasIndexArr.push(j);                         
                }                     
            }                     
            if (hasIndexArr.length < n) {//如果数量还不满足n,扔进去                         
                newArr1.push(newArr[i]);                     
            }//如果数量已经满足,则跳过                 
        }             
    }             
    return newArr1;         
}         
console.log(deleteNth(arr,1))

方法3:

var arr = [4, 4, 4, 4, 3, 3, 3, 3, 1, 2, 4, 3, 90];        
var cache = {};        
function deleteNth(arr, x) {            
    return arr.filter(function (n) {                
        cache[n] = (cache[n]||0) + 1;                
        return cache[n] <= x;            
    })        
}        
console.log(deleteNth(arr, 1))

6. 实现一个方法,于数组中寻找某个值作为分割的界点,使得该值左右两边的数相加相等

[1, 2, 3, 4, 3, 2, 1]  => 返回下标3
[1, 100, 50, -51, 1, 1] => 返回下标1

方法1:

var arr = [1, 2, 3, 4, 3, 2, 1];    
function find (arr) {        
    var sum1 = 0;        
    for (var i = 0 ; i < arr.length ; i ++) {            
        sum1 += arr[i];            
        var sum2 = 0;            
        for (var j = i + 2 ; j < arr.length ; j ++) {                
            sum2 += arr[j];            
        }            
        if (sum1 == sum2) {               
            return i + 1;            
        } else if (i == arr.length - 3 && sum1 !== sum2) {                
            return "该值不存在";            
        }        
    }    
}    
console.log(find(arr))

方法2:

var arr = [1, 2, 3, 4, 3, 2, 1];        
for (var i = 0 ; i < arr.length - 2 ; i ++) {            
    var arr1 = arr.slice(0, i+1);            
    var arr2 = arr.slice(i+2);            
    var sum1 = sum2 = 0;            
    for (var m = 0 ; m < arr1.length ; m ++) {                
        sum1 += arr1[m];            
    }            
    for (var n = 0 ; n < arr2.length ; n ++) {                
        sum2 += arr2[n];            
    }            
    if (sum1 == sum2) {                
        console.log(i + 1);                
        break;            
    } else if (i == arr.length - 3 && sum1 !== sum2) {                
        console.log("该值不存在");            
    }        
}


7. 自定义事件

    var content = document.querySelector('.content');
    // 自定义事件
    var evt = new Event('custom');
    var customEvt = new CustomEvent('customEvt', {
        // 通过这个属性传递参数
        detail: {
            name: 'tom',
            age: 12
        }
    });
    content.addEventListener('custom', (e) => {
        console.log('自定义事件被触发,无参数...');
        console.log(e);
    });
    content.addEventListener('customEvt', (e) => {
        console.log('自定义事件被触发,有参数...');
        console.log(e);
        console.log(e.detail);
    });
    // 点击时触发这个自定义事件
    content.addEventListener('click', (e) => {
        content.dispatchEvent(evt);
        content.dispatchEvent(customEvt);
    });

四. 数组排序

1. sort排序法

(1)简单数组的简单排序

var arrSimple=new Array(1,8,7,6);
arrSimple.sort();
console.log(arrSimple.join()) ;

(2)简单数组的自定义排序

var arrSimple2=new Array(1,8,7,6);
arrSimple2.sort(function(a,b){    
    return a-b
});
console.log(arrSimple2.join())

解释:a,b表示数组中的任意两个元素,a-b输出从小到大排序,b-a输出从大到小排序。

(3)简单对象 List 自定义属性排序

var objectList = new Array();
function Persion(name,age){      
    this.name=name;      
    this.age=age;
}
objectList.push(new Persion('jack',20));
objectList.push(new Persion('tony',25));
objectList.push(new Persion('stone',26));
objectList.push(new Persion('mandy',23));
//按年龄从小到大排序
objectList.sort(function(a,b){      
    return a.age-b.age
});
for(var i=0;i<objectList.length;i++){      
    document.writeln('<br />age:'+objectList[i].age+' name:'+objectList[i].name);
}

(4)简单对象 List 对可编辑属性的排序

var objectList2 = new Array();        
function WorkMate(name,age){            
    this.name=name;            
    var _age=age;            
    this.age=function(){                
        if(!arguments){                    
            _age=arguments[0];
         }else{                   
            return _age;
         }                
    }                            
}        
objectList2.push(new WorkMate('jack',20));        
objectList2.push(new WorkMate('tony',25));        
objectList2.push(new WorkMate('stone',26));        
objectList2.push(new WorkMate('mandy',23));        
//按年龄从小到大排序        
objectList2.sort(function(a,b){            
    return a.age()-b.age();          
});        
for(var i=0;i<objectList2.length;i++){            
    document.writeln('<br />age:'+objectList2[i].age()+' name:'+objectList2[i].name);  
}

更多排序的内容请见我的文章 

十大经典排序算法(Python版本 和 JavaScript版本)