面试所遇算法题目小结
1. 括号字符串匹配:
js
// 对应 LeetCode 第 20 题
// 实现一个 isMatch 函数,输入字符只有 '()[]{}' 三种括号字符之中的若干项,
// 要求括号成对匹配且左右顺序不可颠倒
// 不允许括号有交叉的情况,即 '[(])' 为 false
// 示例及预期结果:
// '(([]))' // true
// '()[]{}(())' // true
// '())([]()' // false
// 实现方案如下:
const isMatch = (str) => {
if (typeof str !== "string" || str.length % 2 === 1) {
return false;
}
// 直接用普通 对象 也可以
const brackets = new Map([
["(", 1],
["[", 1],
["{", 1],
[")", "("],
["]", "["],
["}", "{"],
]);
const stack = [];
for (const item of str) {
const matchedItem = brackets.get(item);
// 左半括号 => 入栈
if (matchedItem === 1) {
stack.unshift(item);
continue;
}
const top = stack[0]; // 栈顶项 ['(', '[']
// 右半括号 => 栈内无左括号 || 栈顶项与右括号不匹配
if (top === undefined || matchedItem !== top) {
return false;
}
// 否则,左括号出栈
stack.shift();
}
return stack.length === 0;
};2. 数字对称判定
js
// 如数字 3,121,12321 成左右对称(在不转换类型的情况下,实现数字的对称判定)
// 初始拿到这几个举例数字时,就直接想的是个位数单独判定,
// 另外的开方再判定结果是不是整数不就行了吗?
// 但比如 11, 123321 这种就不适用了
// 回文数,实现如下(TypeScript):
const isPalindrome = (n: number) => {
// 负数或个位为 0 的数字不可能对称
if (n < 0 || (n % 10 === 0 && n !== 0)) return false
let revertedNumber: number = 0
while (n > revertedNumber) {
revertedNumber = revertedNumber * 10 + (n % 10)
n = ~~(n / 10) // 取整
}
return n === revertedNumber || n === ~~(revertedNumber / 10)
}3. 字符串解析编译
js
// 描述:给定字符串如:'a[2]bc[3](def)[4]((g[2]h)[1]i)[2]'
// 通过函数实现编译解析,输出:'aabcccdefdefdefdefgghigghi',不可使用正则表达式
// 即:[] 中数字表示其重复次数,小括号表示一个整体,存在多层嵌套的情况
// 提升:[] 中的数字可以是计算表达式,如:'a[1+2*10]'
// 初步实现方案如下 (部分实现):
const compileStr = (str: string) => {
// TODO ...
if (typeof str !== 'string' || str.length === 0) return ''
const stack = []
let res = '' /* 最终结果字符串 */,
cache = '' /* 中间缓存字串 */,
num = 0 /* 重复次数 */,
level = 0 /* 记录成组数 */
for (const char of str) {
if (char === '(') {
cache = ''
level++
} else if (char === ')') {
// 一个组结束
level--
} else if (char === '[') {
// 后续是重复次数
stack.push(cache)
cache = ''
} else if (char === ']') {
// 一个组的重复次数结束
if (level === 0) {
res += stack.join('').repeat(num)
} else {
cache += stack.join('').repeat(num)
}
stack.length = 0
num = 0 // 计数置0
} else if (!isNaN(char)) {
// 是数字字符
num = num * 10 + Number(char) // 数字可能不只是个位数重复
} else {
// 普通字符
if (cache !== '') {
// 前一个是普通字符
if (level === 0) {
// 没有分组
res += cache
cache = char
} else {
// 已有分组
cache += char
}
} else {
cache = char
}
// 有成组的字符
}
}
return res
}
const str1 = 'a[2]bc[3](def)[4]((g[2]h)[1]i)[2]' // √
console.log(`${str1}: `, compileStr(str1))
const str2 = 'a[2]bc[3]' // √
console.log(`${str2}: `, compileStr(str2))
const str3 = 'a[2]bc[3](def)[4]' // √
console.log(`${str3}: `, compileStr(str3))
const str4 = '(a[2]bc[3])[3](def)[4]' // aabcccaabcccaabcccdefdefdefdef // ×
console.log(`${str4}: `, compileStr(str4)) // 此结果尚还不对,待做......4. 斐波那契数列
js
// 传入一个非负整数,获取斐波那契数列该项的值,并优化
// 方案一(简单递归实现,存在调用栈内存溢出问题):
const recursionFib = (n: number) => {
if (n < 0) {
throw new Error('The required parameters must be non-negative integers.')
}
if (n === 0 || n === 1) return n
return recursionFib(n - 1) + recursionFib(n - 2)
}
// 方案二(缓存优化):
const cacheFib = (function () {
const cacheArr: number[] = [0, 1]
return function (n: number) {
if (n < 0) {
throw new Error('The required parameters must be non-negative integers.')
}
if (cacheArr[n] !== undefined) {
return cacheArr[n]
}
for (let i = 2; i <= n; i++) {
cacheArr[i] = cacheArr[i - 1] + cacheArr[i - 2]
}
return cacheArr[n]
}
})()
// 方案三(动态规划):
const dynamicFib = (n: number) => {
if (n < 0) {
throw new Error('The required parameters must be non-negative integers.')
}
let current = 0
let next = 1
while (n-- > 0) {
;[current, next] = [next, current + next]
}
return current
}
// 方案四(ES6尾调用优化):
;('use strict') // 必须开启严格模式
const tailCallFib = (n: number, current = 0, next = 1) => {
if (n < 0) {
throw new Error('The required parameters must be non-negative integers.')
}
if (n === 0) return 0
if (n === 1) return next
return tailCallFib(n - 1, next, current + next)
}5. 遍历二叉树
js
// 例如,遍历二叉树,示例结构如下:
const testData = {
value: 1,
left: {
value: 2,
right: {
value: 4
}
},
right: {
value: 3,
left: {
value: 5
},
right: {
value: 6
}
}
}
// 输出根节点到叶子节点:[[1, 2, 4], [1, 3, 5], [1, 3, 6]]
interface TreeNode {
value: number;
left?: TreeNode | null;
right?: TreeNode | null;
}
// 递归方案
function pathToLeaves(root: TreeNode | null): number[][] {
const result: number[][] = [];
function dfs(node: TreeNode | null, path: number[]) {
if (!node) return;
// 添加当前节点值
path.push(node.value);
// 如果是叶子节点,保存路径
if (!node.left && !node.right) {
result.push([...path]);
} else {
// 继续遍历左右子树
dfs(node.left, path);
dfs(node.right, path);
}
// 回溯
path.pop();
}
dfs(root, []);
return result;
}
// 迭代方案(使用栈)
function pathToLeaves(root: TreeNode | null): number[][] {
if (!root) return [];
const result: number[][] = [];
const stack: [TreeNode, number[]][] = [[root, [root.value]]];
while (stack.length > 0) {
const [node, path] = stack.pop()!;
// 叶子节点
if (!node.left && !node.right) {
result.push(path);
continue;
}
// 子节点
if (node.right) {
stack.push([node.right, [...path, node.right.value]]);
}
if (node.left) {
stack.push([node.left, [...path, node.left.value]]);
}
}
return result;
}6. 找出数组中第 k 大的和第 m 大的数字相加之和
js
/* 描述:
* 找出数组中第k大和第m大的数字相加之和
* 说明:实现一个方法,找出数组中第k大的和第m大的数字相加之和
* 示例:
* let arr = [1,2,4,4,3,5], k = 2, m = 4
* findTopSum(arr, k, m);
* 第2大的数是4,出现2次,第4大的是2,出现1次,所以结果为10
* */
function findTopSum(arr, k, m) {
const temp = Array.from(new Set(arr)); // 数组去重
temp.sort((a, b) => b - a); // 去重之后的数组从大到小进行排序
const len = temp.length;
if (m > len || k > len) return;
const max = temp[k - 1]; // 第 k 大 = 1 => temp[0]
const min = temp[m - 1]; // 第 m 大 = 3 => temp[2]
let num = 0;
// 遍历原数组
arr.forEach((item) => {
if (item === max || item === min) {
num += item;
}
});
return num;
}7. 实现一个方法 addJoin
js
/**
* 描述:
* 实现一个方法 addJoin(arr, condition)
* 合并满足条件的项
* 示例:
* let arr = [1, 2, 3, 4, 5]
* addJoin(arr, item => item !== 3); // [[1, 2], 3, [4, 5]]
* addJoin(arr, item => item < 3); // [[1, 2], 3, 4, 5]
* */
function addJoin(arr, condition) {
const len = arr.length;
if (len === 0) return [];
arr.sort((a, b) => a - b); // 升序排序
const res = [];
const tempArr = [];
for (let i = 0; i < len; i++) {
const item = arr[i];
if (condition(item)) {
tempArr.push(item);
continue;
}
if (tempArr.length > 0) {
res.push([...tempArr]);
tempArr.length = 0;
}
res.push(item);
}
if (tempArr.length > 0) {
res.push([...tempArr]);
tempArr.length = 0;
}
return res;
}8. 实现一个方法 EatMan
js
// 分别输出如下:
// 1. EatMan('Hank')
// Hi! This is Hank!
// 2. EatMan('Hank').eat('dinner').eat('supper')
// Hi! This is Hank!
// Eat dinner~
// Eat supper~
// 3. EatMan('Hank').eat('dinner').eatFirst('lunch')
// Eat lunch~
// Hi! This is Hank!
// Eat dinner~
// 4. EatMan('Hank').eat('dinner').eatFirst('lunch').eatFirst('breakfast')
// Eat breakfast~
// Eat lunch~
// Hi! This is Hank!
// Eat dinner~
function EatMan(name) {
return new _EatMan(name); // 返回一个实例
}
class _EatMan {
constructor(name) {
this.tasks = [];
this.tasks.push({
type: "EatMan",
name,
});
this.next();
}
eat(thing) {
this.tasks.push({
type: "eat",
name: thing,
});
return this; // 要实现链式调用的效果,则必须返回当前实例
}
eatFirst(thing) {
this.tasks.unshift({
type: "eatFirst",
name: thing,
});
return this;
}
next() {
if (this.timer) clearTimeout(this.timer);
this.timer = setTimeout(() => {
this.runTask();
}, 0);
}
runTask() {
this.tasks.forEach((task) => {
switch (task.type) {
case "EatMan":
console.log(`Hi! This is ${task.name}!`);
break;
default:
console.log(`Eat ${task.name}~`);
break;
}
});
}
}9. 找出趋近 limit 的值
ts
/*
给定一个数组 arr,里面只含 0~9 不重复的数字
给定一个 limit 值
实现一个方法,返回用数组中数字可以组成的小于 limit 值的最大值
例:
arr = [2, 9, 4, 5]
limit = 22221 => 返回:9999
limit = 22212 => 返回:9999
limit = 59996 => 返回:59995
limit = 25218 => 返回:24999
*/
TODO~