前端算法(持续更新)

1、最大的钻石

1楼到n楼的每层电梯口都放着一个钻石,钻石大小不一。你从电梯1楼到n楼,每层楼电梯门都会打开一次,只能拿一次钻石,问怎样才能最大的钻石?

解题思路:

这是一个经典的动态规划问题,可以使用贪心算法来解决。以下是解决这个问题的思路:

  • 定义问题

从 1 楼到 n 楼,每层楼电梯门都会打开一次,只能拿一次钻石,要找到最大的钻石。

  • 贪心算法思路

当在第 i 层楼时,如果当前钻石比之前看到的所有钻石都大,就选择当前钻石。

因为如果当前钻石是目前为止最大的,那么后面可能出现的钻石即使比当前钻石大,也不能选择了,因为只能拿一次钻石。而如果不选择当前最大的钻石,后面出现更大钻石的概率是不确定的,所以选择当前最大的钻石是一种贪心策略。

  • 具体实现步骤
  1. 初始化一个变量 max_diamond 为负无穷大,表示目前看到的最大钻石的大小。
  2. 从 1 楼开始,依次遍历每一层楼。
  3. 当到达第 i 层楼时,比较当前钻石的大小和 max_diamond 的大小。
  4. 如果当前钻石比 max_diamond 大,就更新 max_diamond 为当前钻石的大小。
  5. 最后,max_diamond 就是能拿到的最大钻石的大小。

代码实例

bash 复制代码
function maxDiamonds(n) {
    let num = -Infinity;
    for (let i = 0; i < n; i++) {
        num = Math.max(num, Math.floor(Math.random(i) * 100));
    }
    return num;
}
let n = 100;
console.log(maxDiamonds(n));

提示

题中包含一个隐藏条件:随机放置。说有的分析都是基于随机放置给出的。换句话说,如果放置钻石是人为干预的大小,那么本题所有分析都不成立。

2、举例说明你对尾递归的理解,已经哪些应用场景

一、对尾递归的理解

尾递归是指一个函数在执行的最后一步调用自身的递归形式。在尾递归中,递归调用是函数执行的最后一个操作,并且在调用之后不需要再进行其他操作。这使得编译器或解释器可以对尾递归进行优化,将递归调用转换为循环,从而避免了传统递归可能导致的栈溢出问题。

例如,计算阶乘的函数可以用递归和尾递归两种方式实现:

  1. 传统递归实现阶乘:
javascript 复制代码
function factorial(n) {
  if (n === 0) {
    return 1;
  }
  return n * factorial(n - 1);
}

在这个传统递归实现中,每次递归调用都需要保存当前的状态到栈中,当 n 较大时,可能会导致栈溢出。复杂度为O(n)

  1. 尾递归实现阶乘:
javascript 复制代码
function factorialTailRecursive(n, accumulator = 1) {
  if (n === 0) {
    return accumulator;
  }
  return factorialTailRecursive(n - 1, n * accumulator);
}

在尾递归版本中,每次递归调用时都将当前的部分结果(accumulator)传递给下一次调用,最后在 n 为 0 时返回结果。这样,编译器或解释器可以优化这种形式的递归,避免栈的不断增长。复杂度为O(1)

二、应用场景

  1. 一些数学计算和算法问题中,如果可以将问题分解为相似的子问题并且可以用尾递归形式解决,就可以使用尾递归。例如,计算斐波那契数列、求解汉诺塔问题等。
  • 数组求和
bash 复制代码
function sumArray(arr, total = 0) {
    if (arr.length === 0) {
      return total;
    }
    return sumArray(arr, total + arr.pop());
  }
  
  const array = [1, 2, 3, 4, 5];
  const result = sumArray(array);
  console.log(result); // 15
  • 计算斐波那契数列
javascript 复制代码
function factorial(n, start=1, total=1) {
    if (n <= 2) {
        return start;
    }
    return factorial(n - 1, total, start + total);   
}
  • 数组扁平化
javascript 复制代码
let arr = [1, [2, [3, [4, [5]]]]];
function flat(arr=[],result=[]) {
    arr.forEach(item=>{
        if(Array.isArray(item)){
            result = result.concat(flat(item,[]));
        }else{
            result.push(item);
        }
    })  
    return result;
}
console.log(flat(arr,[]))
  1. 在函数式编程语言中,尾递归被广泛应用,因为函数式编程强调无副作用的纯函数和递归作为主要的控制结构。例如在 Haskell、Scheme 等语言中,尾递归是一种常见的编程模式。

  2. 深度优先搜索(DFS)和广度优先搜索(BFS)算法实现中,如果需要递归遍历图或树结构,可以考虑使用尾递归优化,特别是在处理大规模数据时,以避免栈溢出。

3、去除字符串中出现次数最少的字符,不改变原字符串的顺序

实现删除字符串中出现次数最少的字符,如果出现最少的字符有多个,则把出现次数最少的字符都删除。输出删除这些单词后的字符串,字符串中的顺序保持不变

javascript 复制代码
function removeLeastFrequent(str) {
    // 创建对象,保存key为字符,value为出现次数
    let obj = {};
    for (let i = 0; i < str.length; i++) {
      if (obj[str[i]]) {
        obj[str[i]]++;
      } else {
        obj[str[i]] = 1;
      }
    }
    // 找出出现次数最少的次数
    let minCount = Math.min(...Object.values(obj));
    // 删除对象obj中value为minCount的key
    for (let key in obj) {
      if (obj[key] === minCount) {
        delete obj[key];
      }
    }
    // 遍历原字符串,将出现次数最少的字符删除
    let result = '';
    for (let i = 0; i < str.length; i++) {
      if (obj[str[i]]) {
        result += str[i];
      }
    }
    return result;
}
console.log(removeLeastFrequent('aaabbbccddeeff'));

4、手写快速排序

以下是用 JavaScript 实现快速排序的代码:

javascript 复制代码
function quickSort(arr) {
    if (arr.length <= 1) {
      return arr;
    }
    // 设置数组的中位数middle,pivot是middle为索引的值
    let middle = Math.floor(arr.length / 2);
    let pivot = arr[middle];
    let left = [];
    let right = [];
    for (let i = 0; i < arr.length; i++) {
    	// 如果是中位数,跳过
        if (i === middle) {
            continue
        }
        // 把小于中位数的放到左边,大于的放进右边
        let item = arr[i];
        if (item < pivot) {
          left.push(item);
        } else {
          right.push(item);
        }      
    }
    // 左右分别递归转为更小的颗粒度
    return [...quickSort(left), pivot, ...quickSort(right)];
}
console.log(quickSort([3, 6, 8, 10, 1, 2, 1]));
}

快速排序的基本思想是:选择一个基准值(这里选择数组的第一个元素作为基准值),将数组分为两部分,小于基准值的元素放在左边,大于等于基准值的元素放在右边。然后对左右两部分分别递归地进行快速排序,最后将左、基准值、右三部分合并起来。

快速排序的时间复杂度在平均情况下为 O ( n l o g n ) O(n log n) O(nlogn),但在最坏情况下(例如数组已经有序时)为 O ( n 2 ) O(n^2) O(n2)。空间复杂度在平均情况下为 O ( l o g n ) O(log n) O(logn)(递归调用栈的深度),最坏情况下为 O ( n ) O(n) O(n)。

5、洗牌算法

洗牌算法是将原数组打散,使得原数组在新数组中的位置等概率相等,也叫乱序算法

javascript 复制代码
function shuffle(arr) {
    let len = arr.length;
    for (let i = len - 1; i >= 0; i--) {
        let randomIndex = Math.floor(Math.random() * (i + 1));
        [arr[i], arr[randomIndex]] = [arr[randomIndex], arr[i]];
    }
    return arr;
}

6、手写一个LRU函数

javascript 复制代码
class LRU {
    constructor(capacity) {
      // 缓存的最大容量
      this.capacity = capacity;
      // 缓存的数据
      this.cache = new Map();
    }
    // 获取缓存数据
    get(key) {
      // 如果缓存中没有该数据,则返回false
      if (this.cache.has(key)) {
        // 将缓存中的数据删除,再重新插入,保证最近使用的数据在最后
        let val = this.cache.get(key);
        this.cache.delete(key);
        this.cache.set(key, val);
        return val;
      }
      return false;
    }
    put(key, value) {
      // 如果缓存中有该数据,则删除,再重新插入
      if (this.cache.has(key)) {
        this.cache.delete(key);
      } else if (this.cache.size >= this.capacity) {
        // 另一种写法:this.cache.delete([...this.cache.keys()][0]);
        this.cache.delete(this.cache.keys().next().value);
      }
      this.cache.set(key, value);
    }
}

7、判断回文串

javascript 复制代码
function isPalindrome(s) {
  if (typeof s !== 'string') return false;
  return s === s.split('').reverse().join('');
}

8、写出一个程序,接受一个由字母、数字和空格组成的字符串,和一个字符,然后输出输入字符串中该字符的出现次数。(不区分大小写字母)

牛客网的答题格式:

javascript 复制代码
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;

void async function () {
    // Write your code here
   
    let a  = await readline();
    let b  = await readline();
    a = a.toLocaleLowerCase()
    b = b.toLocaleLowerCase()
    console.log(a.split(b).length-1)
    
}()

9、输入一个字符串,请按长度为8拆分每个输入字符串并进行输出;长度不是8整数倍的字符串请在后面补数字0,空字符串不处理。

javascript 复制代码
function fun8(str) {
  let len = str.length;
  if(len===8){
      console.log(str)
  } else if(len<8){
      console.log(padEnd(str))
  } else {
      let list = str.split('');
      let newList = [];
      while(list.length>0){
          if(list.length<8){
              let end = list.splice(0,list.length);
              newList.push(padEnd(end));
          } else {
               newList.push(list.splice(0,8).join(''));
          }
      }
      for(let i in newList){
          console.log(newList[i])
      }
  }
  function padEnd(msg){
      let n = 8-msg.length;
      let end = "0".repeat(n);
      return msg+end;
  }
}

10、质数因子

功能:输入一个正整数,按照从小到大的顺序输出它的所有质因子(重复的也要列举)(如180的质因子为2 2 3 3 5 )

javascript 复制代码
function getZhiYinZi(str){
  let num = parseInt(str);
  let list = [];
  let i = 2;
  while (i * i <= num) {
    while (num % i === 0) {
      list.push(i);
      num /= i;
    }
    i++;
}
if (num > 1) {
    list.push(num);
}
  console.log(list.join(' '));
}

11、写出一个程序,接受一个十六进制的数,输出该数值的十进制表示。

javascript 复制代码
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;

async function hexToDecimal() {
    while (line = await readline()) {
        let hexNumber = line;
        console.log(parseInt(hexNumber, 16));
    }
}

hexToDecimal();

12、合并表记录

数据表记录包含表索引index和数值value(int范围的正整数),请对表索引相同的记录进行合并,即将相同索引的数值进行求和运算,输出按照index值升序进行输出。

javascript 复制代码
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;

void (async function () {
    let n = await readline();
    let sets = new Map();
    while (n > 0) {
        n--;
        let arr = await readline();
        let list = arr.split(' ');
        let key = parseInt(list[0]),
            value = parseInt(list[1]);
        if (sets.has(key)) {
            let lastValue = sets.get(key);
            sets.set(key, lastValue + value);
        } else {
            sets.set(key, value);
        }
    }
    // 根据key排序
    const sortedMapByKey = new Map([...sets].sort((a, b) => a[0]-b[0]));


    for (const [key, value] of sortedMapByKey) {
        console.log(key, value);
    }
})();

13、相交链表

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。

图示两个链表在节点 c1 开始相交:

题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构 。

你能否设计一个时间复杂度 O(m + n) 、仅用 O(1) 内存的解决方案?

javascript 复制代码
function getIntersectionNode(headA, headB) {
	let lenA = 0, lenB = 0;
	let pa = headA;
	let pb = headB;
	while(pa){
		lenA++;
		pa = pa.next;
	}
	while(pb){
		lenA++;
		pb = pb.next;
	}
	pa = headA;
	pb = headB;
	if(lenA>lenB){
		let diff = lenA - lenB;
		while(diff>0){
			diff--;
			pa = pa.next;
		}
	} else (lenB>lenA){
		let diff = lenB - lenA;
		while(diff>0){
			diff--;
			pb = pb.next;
		}
	}
	while(pa && pb){
		if(pa === pb){
			return pa
		}
		pa = pa.next;
		pb = pb.next;
	}
	return null;
}

首先通过两个while循环分别计算headA和headB链表的长度lenA和lenB。

然后重新将pa指向headA,pb指向headB。根据lenA和lenB的大小关系,让较长的链表先走|lenA - lenB|步。

最后通过一个while循环让两个链表同时走,当pa和pb相等时,说明找到了相交节点,返回这个节点;如果循环结束都没有找到相等的节点,说明两个链表不相交,返回null。

相关推荐
腾讯TNTWeb前端团队3 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰6 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪6 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪7 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy7 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom8 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom8 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom8 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom8 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom8 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试