【前端面试】七、算法-Math、数组、数据结构等

目录

1.Math

2.数组

3.常见数据结构

4.设计模式


1.Math

  • Math.abs:求绝对值;Math.sign:判断正负,求数值符号
  • Math.ceil:向上取整; Math.floor:向下取整; Math.round:四舍五入
  • Math.max:求最大值;Math.min:求最小值
  • Math.pow:求幂;Math.sqrt:求平方根;Math.random:生成0-1之间的随机数
    // 判断素数
    const isPrime = num => {
      if(num <= 1) return false
      for(let i = 2; i <= Math.sqrt(num); i++) {
        if(num % i === 0) return false
      }
      return true
    }

    // n以内的素数
    function *sieve_primes(n) {
      let arr = Array.from({length: n - 2}, (item, index) => index + 2)
      while(arr.length > 0) {
        let prime = arr[0]
        yield prime
        arr = arr.filter(num => num % prime !== 0)
      }
    }
    const it = sieve_primes(100)
    // console.log('100以内的素数=================');
    // console.log([...it]);

2.数组

常用方法

  • Array.length:长度

  • Array.from:类数组转数组、创建数组

  • Array.isArray:是否为数组

  • Array.includes:是否包含某元素

  • Array.indexOf:获取元素序号

  • Array.concat:合并数组(元素),返回新数组

  • Array.slice:截取数组,返回新数组

  • Array.splice:删除、插入、替换,操作原数组

  • Array.push/pop/shift/unshift:入栈、出栈、出队、入队,操作原数组

  • Array.join:数组转字符串,返回字符串     

  • Array.reverse:数组反转,操作原数组

  • Array.sort:数组排序,操作原数组

  • Array.filter:创建一个新数组,筛选过滤出通过回调函数的所有元素。 

  • forEach:遍历数组,对数组的每个元素执行一次回调函数。

  • map:映射 1对1,创建一个新数组,该数组中每个元素是调用一次回调函数后的返回值。

  • reduce:累加聚合,多对1。数组中每个元素执行回调函数进行累加汇总为单个返回值。

  • Array.reduceRight:从右往左

  • Array.some:是否有满足

  • Array.every:是否全部满足

     // 数组替换
    const arr1 = [1,2,3,4,5,6,7] 
    // console.log('splice测试=================');
    // console.log(arr1.splice(2,2,'x')); // [3,4]
    // console.log(arr1); // [1,2,'x',5,6,7] 

    // console.log(arr1.splice(2,1)); // ['x']
    arr1.splice(2,0,'y')
    // console.log(arr1); // [1,2,'y',5,6,7]

    const students = [  
      {  
        id: 1,  
        name: "Alice",  
        score: 92,  
        groupId: 1,  
      },  
      {  
        id: 2,  
        name: "Bob",  
        score: 55,  
        groupId: 2,  
      },  
      {  
        id: 3,  
        name: "Charlie",  
        score: 88,  
        groupId: 1,  
      },  
      {  
        id: 4,  
        name: "David",  
        score: 76,  
        groupId: 3,  
      },  
      {  
        id: 5,  
        name: "Eva",  
        score: 62,  
        groupId: 2,  
      }  
    ];  
  
    const groups = [  
      { id: 1, name: "Math Group" },  
      { id: 2, name: "Science Group" },  
      { id: 3, name: "History Group" }  
    ];  

    // 投射
    const studentWithGrade = students.map(student => {
      return {
        ...student,
        grade: student.score >= 60 ? '及格' : '不及格'
      }
    })
    // console.log('投射测试=================');
    // console.log(studentWithGrade);

    // 过滤
    const passedStudents = students.filter(student => student.score >= 60)
    // console.log('passedStudents 测试=================');
    // console.log(passedStudents);
    const group1Students = students.filter(student => student.groupId === 1)
    // console.log('group1Students 测试=================');
    // console.log(group1Students);

    // 分组
    const studentInGroups = students.reduce((groups, student) =>{
      groups[student.groupId] = [...(groups[student.groupId] || []), student]
      return groups
    },{})
    // console.log('studentInGroups 测试=================');
    // console.log(studentInGroups);

    // 联合
    const studentWithGroupInfo = students.map(student => {
      return {
        ...student,
        groupInfo: groups.find(group => group.id === student.groupId)
      }
    })
    // console.log('studentWithGroupInfo 测试=================');
    // console.log(studentWithGroupInfo);

    // 排序 Array.prototype.sort 方法会对原数组进行排序
    const sortedByScoreAsc = students.slice().sort((a, b) => a.score - b.score);
    // console.log('sortedByScoreAsc 测试=================');
    // console.log(sortedByScoreAsc);

    const sortedByScoreDesc = students.slice().sort((a, b) => b.score - a.score);
    // console.log('sortedByScoreDesc 测试=================');
    // console.log(sortedByScoreDesc);

    const nameArr = ['张三','李四','王五'].sort((a,b) => a.localeCompare(b, 'zh'))
    // console.log('nameArr 测试=================');
    // console.log(nameArr);

数组去重

  • 使用Set:利用Set的自动去重特性,将数组转换为Set,再转换回数组。

     const unique = arr => [...new Set(arr)]
  • 使用indexOf、includes:遍历数组,检查当前元素是否已在结果数组中出现过。

  • 使用Map:利用Map对象的键是唯一的特性,遍历数组,将数组元素作为键存入Map,再从Map中获取所有键组成的数组。

笛卡尔积
 例如:[1,2] X ['a', 'b'] = [[1, 'a'], [1, 'b'], [2, 'a'], [2,'b']]

    function cartesianProduct(...Matrix) {
      if(Matrix.length ===0) return []
      if(Matrix.length === 1)return Matrix[0]

      return Matrix.reduce((A,B) => {
        const product=[]
        for(let i=0; i<A.length; i++){
          for(let j=0; j<B.length; j++){
            product.push(Array.isArray(A[i]) ? [...A[i], B[j]] : [A[i], B[j]])
          }
        }
        return product
      })
    }
    result = cartesianProduct([1, 2], ['a', 'b']);  
    console.log(result); // [[1, 'a'], [1, 'b'], [2, 'a'], [2, 'b']]

3.常见数据结构

集合 

ES6引入的新的数据结构,用于存储任何类型的唯一值,无论是原始值还是对象引用。

特性:

  • 唯一性:成员值唯一,无重复
  • 无序性:元素没有特定顺序,不能像数组那样通过索引访问
  • 类型敏感性:对于原始值,即使值相同但类型不同(如5和'5'),也会被视为不同元素存储

常用方法

  • new Set([...arr])
  • add(value):向Set中添加一个新元素,如果该元素已存在,则不添加。
  • delete(value):删除Set中某个值,删除成功返回true,否则返回false。
  • has(value):判断Set中是否存在某个值,存在返回true,否则返回false。
  • clear():清除Set中的所有元素,没有返回值。
  • size:返回Set中元素的个数。
  • keys():返回 Iterator 对象,该对象包含Set对象中每个元素的键。对于Set,键和值相同。
  • values():返回 Iterator 对象,该对象包含Set对象中每个元素的值。与keys()相同。
  • entries():返回 Iterator 对象,该对象包含Set对象中每个元素的[value, value]数组。

应用场景

  • 数组去重:利用Set的唯一性特性,可以快速去除数组中的重复元素。
  • 数据重组:在处理数据时,可以使用Set来存储唯一值,简化后续数据处理逻辑。
  • 数据储存:在某些场景下,需要存储一系列不重复的值时,可以使用Set来实现。

注意事项

  • 类型转换:向Set添加值时,不会发生类型转换,如5和'5'被视为两个不同的值。
  • NaN处理:在Set中只能存储一个NaN。
  • 对象引用:如果Set中存储的是对象,那么比较的是对象的引用而非对象的内容。

与其他数据结构的比较

  • 与Array比较:Set是无序的,且不允许重复值;数组则是有序的,可以包含重复值。
  • 与Map比较:Set只存储值,而Map存储键值对,且键可以是任意类型。
    // 子数组和整除
    // 写一个函数solve(arr, N),判断数组arr中某一项,或任意多项的和,是否被另一个整数N整除
    // 例如:solve([1,2,3,4,5], 7) => true

    // 求数组全部组合后求余数
    const solve = (arr, N) => {
      const s = new Set()
      for(let i = 0; i <= arr.length; i++) {
        for (let j = i + 1; j <= arr.length; j++) {
          const remain = arr.slice(i, j).reduce((a,b) => a + b, 0) % N
          s.add(remain)
        }
      }
      return s.has(0)
    }

    // 子问题结构思想
    const solve2 = (arr, N) => {
      const s = new Set()
      while (arr.length > 0) {
        const ak = arr.pop()
        s.add(ak)
        s.forEach(item => {
          s.add((item + ak) % N)
        })
      }
      return s.has(0)
    }

    // console.log('子数组和整除 测试=================');
    // console.log(solve2([1,2,3,4,5], 7));
    // console.log(solve2([3,9], 9));

Map

  • 定义:Map 是一种集合(Collection),它存储了键值对(key-value pairs),其中每个键都映射到其对应的值。
  • 特点
    • Map中的键可以是任意类型(对象、函数等),不仅仅是字符串或数字。
    • Map记住了键的原始插入顺序。
    • Map中的键是唯一的。

Map的实现原理

  • 内部实现:不同的JavaScript引擎(如V8)可能以不同的方式实现Map,但一般来说,Map是基于哈希表实现的,这允许快速的插入、删除和查找操作。
  • 哈希冲突解决:当两个键的哈希值相同时,Map内部会采用某种策略(如链表法、红黑树等)来解决冲突。

与类似数据结构的比较

  • 与Object的比较
    • Object的键只能是字符串或Symbol,而Map的键可以是任意类型。
    • Object的键是无序的,而Map保持插入顺序。
    • Object的大小(属性数量)不能通过length或size属性直接获取,而Map可以通过size属性获取。
  • 与Set的比较
    • Set仅存储唯一的值,不存储键值对。
    • Set的迭代顺序是元素被添加到集合中的顺序。

使用场景

  • 需要保持键值对插入顺序时
  • 键类型非字符串或数字时
  • 需要频繁进行插入、删除和查找操作时

特定方法的理解

  • set(key, value):向Map中添加或更新键值对。
  • get(key):根据键获取对应的值,如果不存在则返回undefined。
  • has(key):检查Map中是否存在某个键。
  • delete(key):从Map中删除指定的键及其对应的值。
  • clear():清除Map中的所有键值对。
  • size:返回Map中键值对的数量。

高级话题

  • Map与WeakMap的区别
    • WeakMap的键只能是对象或函数,且是弱引用,如果没有其他引用指向键的对象,则该对象可以被垃圾回收。
    • WeakMap没有size属性,因为它不保证能随时提供准确的大小。
    • WeakMap不可迭代,没有keys()values()forEach()方法。
    • WeakMap主要用于避免内存泄漏,适用于缓存场景。
  • HashMap、TreeMap、LinkedHashMap等(Java)
    • 在Java中,Map接口有多种实现,如HashMap(基于哈希表)、TreeMap(基于红黑树,可以自然排序或自定义排序)、LinkedHashMap(保持插入顺序)。
    • 这些实现各有特点,HashMap适合快速查找,TreeMap适合需要排序的场景,LinkedHashMap适合需要保持插入顺序的场景。

线性表:数组、链表(单向链表、双向链表、循环链表)。

栈与队列:后进先出(LIFO)和先进先出(FIFO)的数据结构。

    // 括号匹配
    const is_balance = str => {
      const [first, ...others] = str
      const stack = [first]
      while (others.length > 0) {
        const n = others.shift()
        const c = stack[stack.length - 1]
        const ifMatch =  (c === '(' && n === ')') || (c === '[' && n === ']') || (c === '{' && n === '}')
        ifMatch ? stack.pop() : stack.push(n)
      }
      return stack.length === 0
    }

树与二叉树:二叉查找树、平衡二叉树(如AVL树、红黑树)、堆等。

:用于表示多对多关系的数据结构,包括有向图和无向图。

4.设计模式

  1. 单例模式:确保一个类仅有一个实例,并提供一个全局访问点。

  2. 工厂模式:创建对象时不直接指定具体类,而是通过调用共同的接口来指定创建哪种类的实例。

  3. 观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

Promise.all的实现

function PromiseAll(promises) {  
  return new Promise((resolve, reject) => {  
    const results = [];  
    let count = 0;  

    promises.forEach((promise, index) => {  
      Promise.resolve(promise).then(  
        value => {  
          results[index] = value;  
          if (++count === promises.length) {  
            resolve(results);  
          }  
        },  
        error => {  
          reject(error);  
        }  
      );  
    });  
  });  
}

Event Emitter的实现(简化版):

class EventEmitter {  
  constructor() {  
    this.listeners = {};  
  }  

  on(event, callback) {  
    if (!this.listeners[event]) {  
      this.listeners[event] = [];  
    }  
    this.listeners[event].push(callback);  
  }  

  emit(event, ...args) {  
    if (this.listeners[event]) {  
      this.listeners[event].forEach(callback => callback(...args));  
    }  
  }  
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值