前端算法题

251 阅读4分钟

通过字符串、数组等经常遇到的面试题进行分析解答,希望能够帮助到大家;欢迎建议、评论

无重复字符串的最大长度计算

举例1: 'abcdadda' 'abcd' 4

举例2:'abababab' 'ab' 2

解法一 (维护数组)

const notRepeatMaxLength = (str) => {
    if(typeof str !== 'string') return 0;
    const arr = [];
    let max = 0;
    for(let i=0;i<str.length;i++){
        const index = arr.indexOf(str[i]);
        if(index !== -1){
            arr.splice(0, index+1);
        }
        arr.push(str[i]);
        max = Math.max(arr.length, max); 
    }
    return max;
}

判断是不是回文字符串

题目分析:字符串正反都是相等 例如: '123321', 'asdfdsa'

解法一

const plalindrome1 = (str)=>{
  if(typeof str !== 'string') return false;
    const len = str.length;
    let isPlalindrome = true;
    for(let i=0;i<len-i-1;i++){
        if(str[i] !== str[len-i-1]){
            isPlalindrome = false;
        }
    }
    return isPlalindrome;
}

解法二

const plalindrome2 = (str)=>{
    if(typeof str !== 'string') return false;
    if(str.split('').reverse().join('') === str) return true;
    return false;
}

字符串中出现最多的字符个数

例如: 'abcddaddd' // d 4

思路: 利用对象来计数

const mostString = (str) => {
    const obj = {};
    for(let i=0;i<str.length;i++){
        if(obj[str[i]]){
            obj[str[i]]++
        } else {
            obj[str[i]] = 1;
        }
    }
    return Math.max.apply(null,Object.values(obj));
    // 或者
    return Math.max(...Object.values(obj));
}

判断一个字符串是否为括号正确闭合

例如:"{[()]}", "(){()}[]"

错误列子: "((])", "{([)]}"

思路:利用栈结构,依次循环查找成对括号

const computed = (str) => {
  if (typeof str !== 'string') return false;
  const arr = [];
  const obj = {
    '(': ')',
    '[': ']',
    '{': '}'
  };
  for (let i = 0; i < str.length; i++) {
    if (obj[str[i]]) {
      arr.push(str[i]);
    } else if (str[i] !== obj[arr.pop()]) {
      return false;
    }
  }
  return !arr.length;
}

依次删除字符串中相邻相等字符串

例子:"abbac" // c "abbac"=>"aac"=>"c"

思路:建立一个空数组,利用栈结构依次消除将要进入数组的字符串和数组最后一个字符串相等元素;

const removeRepeat = (str) => {
  if (typeof str !== 'string') return false;
  const arr = [];
  for (let i = 0; i < str.length; i++) {
    const s = arr.pop();
    if (str[i] !== s) {
      if (s) arr.push(s);
      arr.push(str[i]);
    }
  }
  return arr.join('');
}

英文句翻转且去除空格

例子: "i miss you " // "you miess i"

const turn = (str) => {
	if(typeOf str !== "string") return false;
    return str.split(" ").filter(s => !!s).reverse().join(' ');
}

合并两个有序数组

例如:[1,2,4,5] [3,5,7,9] // [1,2,3,4,5,5,7,9]

思路:使用双数组总长度循环和各个数组长度,依次从末尾对比两个数组的最大值,谁大unshift进入新数组,并且当前数组长度减1;

const mergeOderArray = (arr1, arr2)=>{
    let arr = [];
    let len1 = arr1.length;
    let len2 = arr2.length;
    const len = len1+len2;
    if(!len1) return arr2;
    if(!len2) return arr1;
    for(let i=0;i<len;i++){
        if(len1 === 0 && len2 === 0) return;
        if(len1 === 0){
            arr = [...arr2.slice(0, len2), ...arr];
            break;
        }
        if(len2 === 0){
            arr = [...arr1.slice(0, len1), ...arr];
            break;
        }
        arr1[len1-1]>arr2[len2-1] ? arr.unshift(arr1[--len1]) : arr.unshift(arr2[--len2]);
    }
    return arr;
}

边界分析:

  • arr1长度为0直接返回arr2
  • arr2长度为0直接返回arr1
  • 循环过程中出现len1或者len2为0时处理

两个数组求并集、交集、差集

例如:[1,2],[1,3] // 并集[1,2,3]、交集[1]、list1相对list2差集[2,3]

// 并集
const unionSet = (list1, list2) => [...new Set([...list1, ...list2])]
// 交集
onst intersectionSet = (list1, list2) => [...new Set(list1.filter(item=>{
    return list2.includes(item);
}))]
// 差集
const differenceSet = (list1, list2) => [...new Set(list1.filter(item=>{
    return !list2.includes(item);
}))]

数组扁平化

例如:[1,2,[2,3,[3,4],6],6] // [1,2,2,3,3,4,6,6]

解法一 (es6方法)

arr.flat(Infinity)

解法二 (字符串或者分解数组然后组合方式)

arr.join(",").split(',').map(item=> Number(item));
// 或者
arr.toString().split(',').map(item=> Number(item))

解法三 (结构数组方式)

注:判断数组的方法

Array.isArray(arr) // true
Object.prototype.toString.call(arr) // [object, Array]
arr.constructor// Array
arr instanceof Array // true
const flatten = (arr)=>{
    while(arr.some(item=> Array.isArray(item))){
        arr = [].concat(...arr);
    }
    return arr;
}
或者
let index = 1;
const flatten = (arr, num)=>{
    if(index>num){
        return arr;
    }
    console.log(index)
    if(arr.some(item=>Array.isArray(item))){
        arr = [].concat(...arr);
        index++;
        return flatten(arr, num);
    } else {
        return arr;
    }
}
console.log(flatten(arr, 1)); // [1,2,2,3,[3,4],6,6]

数组中三个数字求和为0的组合

例如:[-1, 0, 1, 2, -1, -4] // [[-1, -1, 2],[-1, 0, 1]];

备注:三层循环方式就不赘述了

思路:先对数组排序,通过利用双指针方式,循环数组当前坐标i,从两头开始向中间靠拢l=i+1,r=length-1,求出三个坐标的和,大于0说明r大,r--后继续;反之i小,i++

const threeSum = (nums)=> {
    nums = nums.sort((a,b)=> a-b);
    const arr = [];
    const len = nums.length;
    if(len<3) return arr;
    for(let i=0;i<len;i++){
        let l = i+1;
        let r = len-1;
        if(nums[i]>0) break;
        if(nums[i] === nums[i-1]) continue;
        while(l<r){
            const s = nums[i]+nums[l]+nums[r];
            if(s>0){
                r--;
            } else if(s<0){
                l++;
            } else {
                arr.push([nums[i],nums[l],nums[r]]);
                while (nums[l] === nums[l+1]) l++;
                while (nums[r] === nums[r-1]) r--;
                l++;
                r--;
            }
        }
    }
    return arr;
}

斐波拉数列

示例: [1,1,2,3,5,8,13] //n=7 return 13

方法一(递归)

const fib1 = (n)=>{
    if(n=== 1|| n===2){
        return 1;
    }
    return fib1(n-1)+fib1(n-2);
}

时间复杂度为O(2^n),空间复杂度为O(n)

方法二(变量替换)

 const fib2 = (n)=>{
    let num = 1,
    pre = 0,
    next = 1;
    for(let i=1;i<n;i++){
        num = pre+next;
        pre = next;
        next = num;
    }
    return num;
}

时间复杂度为O(n),空间复杂度为O(1)

方法三(数组递进)

const fib3 = (n)=>{
    let arr = [1,1];
    for(let i=2;i<n;i++){
        arr[i] = arr[i-1]+arr[i-2];
    }
    return arr[n-1];
}

时间复杂度为O(n),空间复杂度为O(1)

查找id对应所有父级id

说明:已知一个层级的数组,根据数组中的一个数据id,找出id对应的所有父级数据的id

例如:

const data = [{
    id: '1',
    name: 'test1',
    children: [{
        id: '11',
        name: 'test11',
        children: [{
            id: '111',
            name: 'test111'
        }, {
            id: '112',
            name: 'test112'
        }]
    }, {
        id: '12',
        name: 'test12',
        children: [{
            id: '121',
            name: 'test121'
        }, {
            id: '122',
            name: 'test122'
        }]
    }]
}];
查找id'121'返回数组['1', '12']

思路:通过传入一个父级id集合递归添加,直到数组符合之后返回递归后的数组

const findIdFn = (data, id)=>{
    let res = [];
    const fn = (list, arr = [])=>{
        for(const item of list){
            if(item.id === id){
                res = arr;
                break;
            } else if(item.children){
                fn(item.children, arr.concat(item.id));
            }
        }
    }
    fn(data);
    return res;
}

最长公共前缀

说明: 编写一个函数来查找字符串数组中的最长公共前缀。如果不存在公共前缀,返回空字符串 ""。

示例:输入: ["flower","flow","flight"] 输出: "fl"

方法一(逐个比较)

解题思路: 从前往后一次比较字符串,获取公共前缀

const overLap = (arr) => {
    let str = '';
    if(!arr.length) return str;
    const firstItem = arr[0];
    const firstItemLen = firstItem.length;
    for(let i=firstItemLen;i>0;i--){
        let strItem = firstItem.substr(0,i);
        const isAllHasStr = arr.every(item=>{
            return item.indexOf(strItem) === 0;
        })
        if(isAllHasStr){
            str = strItem;
            break;
        }
    }
    return str;
}

方法二(仅需最大、最小字符串的最长公共前缀)

解题思路: 获取数组中的最大值及最小值字符串,最小字符串与最大字符串的最长公共前缀也为其他字符串的公共前缀,即为字符串数组的最长公共前缀

const overLap = (arr) => {
    let str = '';
    const len = arr.length;
    if(!len) return str;
    let min = 0,max = 0;
    for(let i=0;i<len;i++){
        if(arr[i]<arr[min])  min = i;
        if(arr[i]>arr[max])  max = i;
    }
    for(let i=arr[min].length;i>0;i--){
        if(arr[min].substr(0,i) === arr[max].substr(0,i)){
            str = arr[min].substr(0,i);
            break;
        }
    }
    return str;
}

简单实现vue LRU(使用最少次数的缓存数据删除)算法

说明: 实现一个类,提供set和get方法,当set时候添加缓存,不过超过容量时候删除最小使用次数的数据给新的数据留出空间;get时候获取缓存数据,没有返回-1

示例: let l = new LRU(2);

l.set(1,1); // [1] 1计数0

l.set(2,2); // [1,2] 1计数0,2计数0

l.get(1,1); // [1,2] 1计数1,2计数0

l.set(3,3); // [1,3] 1计数1,2删除,3计数0

思路:通过数组维护数据的key,使用次数多的排在最前面,每次get时候进行排序

class LRUCache{
  constructor(number){
      this.cacheArr = [];
      this.cacheObj = {};
      this.number = number;
  }
  get(key){
      const i = 1;
      const index = this.cacheArr.indexOf(key);
      if(index !== -1){
          console.log(this.cacheObj, key)
      this.cacheObj[key].num++;
      if(index === 0) return this.cacheObj[key].value;
          while (this.cacheObj[key].num > this.cacheObj[this.cacheArr[index-i]].num){
              i++;
          }
          this.cacheArr[index] = this.cacheArr[index-i];
          this.cacheArr[index-i] = key;
          return this.cacheObj[key].value;
      } else{
          return -1;
      }

  }
  set(key, value){
      const index = this.cacheArr.indexOf(key);

      if(index !== -1){
          this.cacheArr.splice(index, 1);
          this.cacheArr.push(key);
      } else {
          if(this.cacheArr.length === this.number) {
              delete this.cacheObj[this.cacheArr[this.number-1]];
              this.cacheArr.pop();
          };
          this.cacheArr.push(key);
      }

      this.cacheObj[key] = {
          value,
          num: 0
      };
  }
}