前端算法题

1. 两个数组的交集

解题思路

这题重点:

复制代码
交集元素出现几次,就返回几次

例如:

复制代码
[1,2,2,1]
[2,2]

结果:

复制代码
[2,2]

因此:

复制代码
不能直接用 Set

因为 Set 会去重。


方法1:Map 计数(推荐)

时间复杂度:

复制代码
O(n)

答案

复制代码
function intersect(nums1, nums2) {
  const map = new Map();
  const result = [];

  // 统计 nums1 出现次数
  for (const num of nums1) {
    map.set(num, (map.get(num) || 0) + 1);
  }

  // 遍历 nums2
  for (const num of nums2) {
    const count = map.get(num);

    if (count > 0) {
      result.push(num);

      map.set(num, count - 1);
    }
  }

  return result;
}

// 测试
console.log(intersect([1,2,2,1], [2,2]));
// [2,2]

console.log(intersect([4,9,5], [9,4,9,8,4]));
// [9,4]

2. 滑动窗口最大值


解题思路

普通解法:

复制代码
每次窗口都重新求最大值

复杂度:

复制代码
O(n*k)

太慢。


正确做法:单调队列

核心思想:

复制代码
队列里只保留"可能成为最大值"的元素下标

保持:

复制代码
从大到小

答案(高频面试题)

复制代码
function maxSlidingWindow(nums, k) {
  const deque = [];
  const result = [];

  for (let i = 0; i < nums.length; i++) {

    // 1. 移除窗口外元素
    if (deque.length && deque[0] <= i - k) {
      deque.shift();
    }

    // 2. 维护单调递减队列
    while (
      deque.length &&
      nums[deque[deque.length - 1]] < nums[i]
    ) {
      deque.pop();
    }

    // 3. 当前元素加入队列
    deque.push(i);

    // 4. 形成窗口后开始记录结果
    if (i >= k - 1) {
      result.push(nums[deque[0]]);
    }
  }

  return result;
}

// 测试
console.log(
  maxSlidingWindow(
    [1,3,-1,-3,5,3,6,7],
    3
  )
);

// [3,3,5,5,6,7]

3. Bug 修复与调试


原代码问题分析

原代码:

复制代码
function filterAndWrapProducts(products, keyword, onResult) {
  const kw = keyword.trim();
  const lowerKw = kw.toLowerCase();
  const out = [];
  for (let i = 0; i <= products.length; i++) {
    const p = products[i];
    const title = p.title;
    if (title.indexOf(lowerKw) !== -1) {
      out.push(p);
      continue;
    }
    const start = title.toLowerCase().indexOf(lowerKw);
    const wrapped =
      title.slice(0, start) +
      "<mark>" +
      title.slice(start, start + kw.length) +
      "</mark>" +
      title.slice(start + kw.length);
    p.displayTitle = wrapped;
    out.push(p);
  }
  document.getElementById("hint").innerHTML =
    "共匹配 <b>" + out.length + "</b> 条(关键词:" + kw + ")";
  setTimeout(function () {
    onResult(out);
  }, 0);
  return out;
}

至少 6 处问题


问题1:循环越界

错误:

复制代码
i <= products.length

最后一次:

复制代码
products[products.length]

是:

复制代码
undefined

导致:

复制代码
p.title 报错

修复

复制代码
i < products.length

问题2:未校验 products

如果:

复制代码
products = null

会报错。


修复

复制代码
if (!Array.isArray(products)) {
  return [];
}

问题3:keyword 可能为空

如果:

复制代码
keyword = null

则:

复制代码
keyword.trim()

直接报错。


修复

复制代码
const kw = (keyword || '').trim();

问题4:匹配逻辑大小写错误

代码:

复制代码
title.indexOf(lowerKw)

title 没转小写。

例如:

复制代码
title = "Apple"
keyword = "apple"

匹配失败。


修复

复制代码
title.toLowerCase().indexOf(lowerKw)

问题5:逻辑反了

原代码:

复制代码
if (title.indexOf(lowerKw) !== -1) {
  out.push(p);
  continue;
}

已经匹配到了:

复制代码
却直接 continue

导致:

复制代码
永远不会执行高亮包装

正确逻辑

应该:

复制代码
没匹配到才 continue

修复

复制代码
if (start === -1) {
  continue;
}

问题6:可能读取 undefined.title

复制代码
const p = products[i];
const title = p.title;

若:

复制代码
p 为 undefined

会崩。


修复

复制代码
if (!p || typeof p.title !== 'string') {
  continue;
}

问题7:XSS 风险

危险:

复制代码
innerHTML

如果 keyword:

复制代码
<script>alert(1)</script>

可能造成 XSS。


修复

推荐:

复制代码
textContent

或转义。


问题8:直接修改原对象

复制代码
p.displayTitle = wrapped

属于:

复制代码
副作用污染

修复

返回新对象:

复制代码
out.push({
  ...p,
  displayTitle: wrapped
});

问题9:onResult 未校验

如果:

复制代码
onResult 不是函数

会报错。


修复

复制代码
if (typeof onResult === 'function')

修复后的完整代码

复制代码
function filterAndWrapProducts(products, keyword, onResult) {

  if (!Array.isArray(products)) {
    return [];
  }

  const kw = (keyword || '').trim();

  if (!kw) {
    return [];
  }

  const lowerKw = kw.toLowerCase();

  const out = [];

  for (let i = 0; i < products.length; i++) {

    const p = products[i];

    if (!p || typeof p.title !== 'string') {
      continue;
    }

    const title = p.title;

    const start = title
      .toLowerCase()
      .indexOf(lowerKw);

    // 没匹配到
    if (start === -1) {
      continue;
    }

    const wrapped =
      title.slice(0, start) +
      "<mark>" +
      title.slice(start, start + kw.length) +
      "</mark>" +
      title.slice(start + kw.length);

    out.push({
      ...p,
      displayTitle: wrapped
    });
  }

  const hint = document.getElementById("hint");

  if (hint) {
    hint.textContent =
      `共匹配 ${out.length} 条(关键词:${kw})`;
  }

  setTimeout(function () {
    if (typeof onResult === 'function') {
      onResult(out);
    }
  }, 0);

  return out;
}

4. 前端组件题:简易待办


完整 index.html

复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />

  <meta
    name="viewport"
    content="width=device-width, initial-scale=1.0"
  />

  <title>简易待办</title>

  <style>

    body {
      font-family: Arial;
      background: #f5f5f5;
      padding: 30px;
    }

    main {
      width: 400px;
      margin: 0 auto;
      background: white;
      padding: 20px;
      border-radius: 8px;
    }

    h1 {
      text-align: center;
    }

    .input-wrap {
      display: flex;
      gap: 10px;
    }

    input {
      flex: 1;
      padding: 8px;
    }

    button {
      padding: 8px 12px;
      cursor: pointer;
    }

    ul {
      margin-top: 20px;
      padding: 0;
      list-style: none;
    }

    li {
      display: flex;
      justify-content: space-between;
      align-items: center;

      padding: 10px;
      border-bottom: 1px solid #ddd;
    }

    #msg {
      color: red;
      font-size: 14px;
    }

  </style>
</head>

<body>

  <main>

    <h1>待办列表</h1>

    <section aria-label="新增待办">

      <div class="input-wrap">

        <input
          type="text"
          id="todo-input"
          placeholder="请输入待办..."
        />

        <button id="btn-add">
          添加
        </button>

      </div>

      <p id="msg"></p>

    </section>

    <section aria-label="待办列表">

      <ul id="todo-list"></ul>

    </section>

  </main>

  <script>

    // 获取元素
    const input =
      document.getElementById('todo-input');

    const addBtn =
      document.getElementById('btn-add');

    const list =
      document.getElementById('todo-list');

    const msg =
      document.getElementById('msg');

    // 添加待办
    addBtn.addEventListener('click', function () {

      const text = input.value.trim();

      // 空校验
      if (!text) {

        msg.textContent = '请输入内容';

        return;
      }

      msg.textContent = '';

      // 创建 li
      const li =
        document.createElement('li');

      // 文本
      const span =
        document.createElement('span');

      span.textContent = text;

      // 删除按钮
      const delBtn =
        document.createElement('button');

      delBtn.textContent = '删除';

      delBtn.className = 'btn-delete';

      // 添加到 li
      li.appendChild(span);

      li.appendChild(delBtn);

      // 添加到列表
      list.appendChild(li);

      // 清空输入框
      input.value = '';

    });

    // 删除(事件委托)
    list.addEventListener('click', function (e) {

      if (
        e.target.classList.contains('btn-delete')
      ) {

        const li = e.target.parentNode;

        li.remove();
      }
    });

  </script>

</body>
</html>
相关推荐
Lee川1 小时前
从输入框到智能匹配:一文读懂搜索功能的完整实现
前端·后端
南宫萧幕1 小时前
基于 Simulink 与 Python 联合仿真的 eVTOL 强化学习全链路实战
开发语言·人工智能·python·算法·机器学习·控制
电魂泡哥1 小时前
CMS垃圾回收
java·jvm·算法
hkj88082 小时前
CRC-512算法输出64字节
算法
朝阳392 小时前
React【面试】
前端·react.js·面试
@我漫长的孤独流浪2 小时前
计算机系统核心概念与性能优化全解析
算法·计算机外设
如竟没有火炬2 小时前
接雨水22
数据结构·python·算法·leetcode·散列表
漓漾li2 小时前
每日面试题(2026-05-15)- 前端
前端·vue.js·react.js