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>