- 三数之和 题目:给定一个数组nums,判断 nums 中是否存在三个元素a,b,c,使得 a + b + c = target,找出所有满足条件且不重复的三元组合 输入:nums: [5, 2, 1, 1, 3, 4, 6] ;target:8 输出:[[1, 1, 6], [1, 2, 5], [1, 3, 4]]
function threeSum(nums, target) {
// 结果数组
const result = [];
// 对数组进行排序,便于去重和双指针操作
nums.sort((a, b) => a - b);
const n = nums.length;
// 遍历每个元素作为三元组的第一个元素
for (let i = 0; i < n - 2; i++) {
// 跳过重复的第一个元素
if (i > 0 && nums[i] === nums[i - 1]) {
continue;
}
// 左右双指针
let left = i + 1;
let right = n - 1;
while (left < right) {
const sum = nums[i] + nums[left] + nums[right];
if (sum === target) {
// 找到符合条件的三元组,加入结果
result.push([nums[i], nums[left], nums[right]]);
// 跳过重复的左指针元素
while (left < right && nums[left] === nums[left + 1]) {
left++;
}
// 跳过重复的右指针元素
while (left < right && nums[right] === nums[right - 1]) {
right--;
}
// 移动双指针
left++;
right--;
} else if (sum < target) {
// 和小于目标值,移动左指针增大和
left++;
} else {
// 和大于目标值,移动右指针减小和
right--;
}
}
}
return result;
}
// 测试
const nums = [5, 2, 1, 1, 3, 4, 6];
const target = 8;
console.log(threeSum(nums, target));
// 输出: [[1, 1, 6], [1, 2, 5], [1, 3, 4]]
- 当青蛙要跳上 n 级台阶时,它可以从 n-1 级台阶跳 1 级上去,也可以从 n-2 级台阶跳 2 级上去,依此类推,甚至可以直接从 0 级台阶跳 n 级上去。
// 方法1:使用递归,直接根据递推公式计算
function jumpWays1(n) {
if (n <= 1) {
return 1;
}
let result = 0;
for (let i = 0; i < n; i++) {
result += jumpWays1(i);
}
return result;
}
// 方法2:利用数学规律 f(n) = 2^(n-1)
function jumpWays2(n) {
if (n === 0) return 1;
return Math.pow(2, n - 1);
}
// 测试
console.log("跳1级台阶的方法数:", jumpWays1(1)); // 1
console.log("跳2级台阶的方法数:", jumpWays1(2)); // 2
console.log("跳3级台阶的方法数:", jumpWays1(3)); // 4
console.log("跳4级台阶的方法数:", jumpWays1(4)); // 8
console.log("跳5级台阶的方法数:", jumpWays2(5)); // 16
console.log("跳10级台阶的方法数:", jumpWays2(10)); // 512
- 查找字符串中最长无重复字符子串的长度
function lengthOfLongestSubstring(s) {
// 存储字符最后出现的位置
const charMap = new Map();
// 最长子串长度
let maxLength = 0;
// 滑动窗口的起始位置
let start = 0;
for (let end = 0; end < s.length; end++) {
const currentChar = s[end];
// 如果字符已存在于当前窗口中,则移动窗口起始位置
if (charMap.has(currentChar) && charMap.get(currentChar) >= start) {
start = charMap.get(currentChar) + 1;
}
// 更新字符最后出现的位置
charMap.set(currentChar, end);
// 计算当前窗口长度并更新最大长度
maxLength = Math.max(maxLength, end - start + 1);
}
return maxLength;
}
// 测试示例
console.log(lengthOfLongestSubstring("abcabcbb")); // 输出: 3 ("abc")
console.log(lengthOfLongestSubstring("bbbbb")); // 输出: 1 ("b")
console.log(lengthOfLongestSubstring("pwwkew")); // 输出: 3 ("wke")
console.log(lengthOfLongestSubstring("")); // 输出: 0
console.log(lengthOfLongestSubstring("au")); // 输出: 2
- 实现一个全排列
function permute(nums) {
// 存储所有排列结果
const result = [];
// 回溯函数:current表示当前已选元素,remaining表示剩余可选元素
function backtrack(current, remaining) {
// 终止条件:如果没有剩余元素,说明当前排列已完成
if (remaining.length === 0) {
result.push([...current]);
return;
}
// 遍历剩余元素,尝试每种可能的选择
for (let i = 0; i < remaining.length; i++) {
// 选择当前元素
current.push(remaining[i]);
// 递归处理剩余元素(排除已选择的元素)
const newRemaining = [...remaining.slice(0, i), ...remaining.slice(i + 1)];
backtrack(current, newRemaining);
// 回溯:撤销选择,尝试下一种可能
current.pop();
}
}
// 从空数组和完整nums开始递归
backtrack([], nums);
return result;
}
// 测试示例
console.log(permute([1, 2, 3]));
// 输出: [
// [1,2,3], [1,3,2],
// [2,1,3], [2,3,1],
// [3,1,2], [3,2,1]
// ]
console.log(permute(['a', 'b']));
// 输出: [ ['a','b'], ['b','a'] ]
- 实现一个快速排序
function quickSort(arr) {
// 基本情况:如果数组长度小于等于1,则已经是排序好的
if (arr.length <= 1) {
return arr;
}
// 选择基准元素(这里选择中间的元素)
const pivotIndex = Math.floor(arr.length / 2);
const pivot = arr.splice(pivotIndex, 1)[0];
// 小于基准的元素放左边,大于基准的元素放右边
const left = [];
const right = [];
// 遍历剩余元素,分配到左右数组
for (let i = 0; i < arr.length; i++) {
if (arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
// 递归排序左右数组,并与基准元素合并
return quickSort(left).concat([pivot], quickSort(right));
}
// 测试示例
const numbers = [64, 34, 25, 12, 22, 11, 90];
console.log("排序前:", numbers);
console.log("排序后:", quickSort(numbers)); // [11, 12, 22, 25, 34, 64, 90]
// 测试字符串数组
const words = ["banana", "apple", "orange", "grape"];
console.log("排序前:", words);
console.log("排序后:", quickSort(words)); // ["apple", "banana", "grape", "orange"]
- 买卖股票的最佳时机
/**
* 计算买卖股票的最佳时机(简洁版)
* @param {number[]} prices - 股票价格数组,prices[i]表示第i天的股票价格
* @return {number} 最大利润,如果不能获利则返回0
*/
function maxProfit(prices) {
// 初始化最小价格为无穷大,最大利润为0
let min = Infinity, max = 0;
// 遍历所有价格
for (const price of prices) {
// 更新到目前为止的最低价格
min = Math.min(min, price);
// 更新最大利润(当前价格 - 最低价格 与 已有最大利润 的较大值)
max = Math.max(max, price - min);
}
return max;
};
// 测试示例
console.log(maxProfit([7, 1, 5, 3, 6, 4])); // 输出: 5
console.log(maxProfit([7, 6, 4, 3, 1])); // 输出: 0
console.log(maxProfit([1, 2])); // 输出: 1
- 出1到10000之间的所有对称数字(回文数)
/**
* 找出1到10000之间的所有对称数字(回文数)
* @returns {number[]} 包含所有对称数字的数组
*/
const findPalindromes = () => {
const palindromes = [];
for (let i = 1; i <= 10000; i++) {
// 直接在循环中检查是否为回文数
const str = i.toString();
if (str === str.split('').reverse().join('')) {
palindromes.push(i);
}
}
return palindromes;
};
// 执行并输出结果
const result = findPalindromes();
console.log(`1到10000中的对称数字共有${result.length}个:`);
console.log(result);
- 接雨水 (要求计算在给定的高度数组中,雨后能接住的雨水总量)
/**
* 计算能接住的雨水总量
* @param {number[]} height - 高度数组,表示各个位置的高度
* @return {number} 能接住的雨水总量
*/
function trap(height) {
// 边界情况处理
if (height.length === 0) return 0;
let left = 0; // 左指针,从数组开头开始
let right = height.length - 1; // 右指针,从数组末尾开始
let leftMax = 0; // 左侧已遍历的最大高度
let rightMax = 0; // 右侧已遍历的最大高度
let water = 0; // 接住的雨水总量
// 当左右指针相遇时停止遍历
while (left < right) {
// 左侧高度小于右侧高度,处理左侧
if (height[left] < height[right]) {
// 如果当前左高度大于左侧最大高度,更新左侧最大高度
if (height[left] >= leftMax) {
leftMax = height[left];
} else {
// 否则,当前位置可以接住的雨水量为左侧最大高度减去当前高度
water += leftMax - height[left];
}
left++; // 左指针右移
} else {
// 右侧高度小于等于左侧高度,处理右侧
if (height[right] >= rightMax) {
rightMax = height[right];
} else {
water += rightMax - height[right];
}
right--; // 右指针左移
}
}
return water;
}
// 测试示例
console.log(trap([0,1,0,2,1,0,1,3,2,1,2,1])); // 输出: 6
console.log(trap([4,2,0,3,2,5])); // 输出: 9
console.log(trap([])); // 输出: 0
- 打家劫舍 你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。
/**
* 计算环形排列的房屋中能偷窃的最大金额
* @param {number[]} nums - 每个房屋中的金额数组
* @return {number} 不触动警报的情况下能偷窃的最大金额
*/
function rob(nums) {
// 边界情况处理
if (nums.length === 0) return 0;
if (nums.length === 1) return nums[0];
/**
* 计算线性排列的房屋中能偷窃的最大金额
* @param {number[]} arr - 线性排列的房屋金额数组
* @return {number} 最大偷窃金额
*/
function robLinear(arr) {
let prev = 0; // 前一个房屋的最大金额
let curr = 0; // 当前的最大金额
for (const num of arr) {
// 临时保存当前最大值
const temp = curr;
// 更新当前最大值:要么不偷当前房屋(保持原样),要么偷当前房屋(加上前前房屋的最大值)
curr = Math.max(curr, prev + num);
// 更新前一个房屋的最大值
prev = temp;
}
return curr;
}
// 情况1:不偷最后一个房屋,计算[0, n-2]范围的最大值
const case1 = robLinear(nums.slice(0, nums.length - 1));
// 情况2:不偷第一个房屋,计算[1, n-1]范围的最大值
const case2 = robLinear(nums.slice(1));
// 返回两种情况的最大值
return Math.max(case1, case2);
}
// 测试示例
console.log(rob([2,3,2])); // 输出: 3
console.log(rob([1,2,3,1])); // 输出: 4
console.log(rob([1,2,3])); // 输出: 3
console.log(rob([5])); // 输出: 5
console.log(rob([1,5,3,7])); // 输出: 12 (5+7)
- 有效的括号 题目:给定一个只包括 '(',')','{','}','[',']' 的字符串 s,判断字符串是否有效。有效字符串需满足: 左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。
function isValid(s) {
const stack = [];
const map = { ')': '(', '}': '{', ']': '[' };
for (const char of s) {
if (char in map) {
// 右括号:检查栈顶是否匹配
if (stack.pop() !== map[char]) return false;
} else {
// 左括号:入栈
stack.push(char);
}
}
return stack.length === 0; // 栈空则所有括号匹配
}