feat: 面试常见算法题26,27,80,88,168,189

This commit is contained in:
LouisFonda 2025-03-25 16:49:27 +08:00
parent 456e2bbccf
commit dfe0072916
Signed by: yigencong
GPG Key ID: 29CE877CED00E966
7 changed files with 427 additions and 0 deletions

103
recursion/n皇后.js Normal file
View File

@ -0,0 +1,103 @@
/**
* 解决N皇后问题
* @param {number} n - 棋盘大小和皇后数量
* @return {string[][]} - 所有可能的解决方案
*/
function solveNQueens(n) {
const result = [];
// 创建一个表示棋盘的数组,初始化为空
const board = Array(n).fill().map(() => Array(n).fill('.'));
// 开始回溯
backtrack(0);
return result;
/**
* 回溯函数尝试在每一行放置皇后
* @param {number} row - 当前处理的行
*/
function backtrack(row) {
// 如果已经放置了n个皇后找到一个解决方案
if (row === n) {
// 将当前棋盘状态转换为所需的格式并添加到结果中
const solution = board.map((row) => row.join(''));
result.push(solution);
return;
}
// 尝试在当前行的每一列放置皇后
for (let col = 0; col < n; col++) {
// 检查当前位置是否可以放置皇后
if (isValid(row, col)) {
// 放置皇后
board[row][col] = 'Q';
// 递归到下一行
backtrack(row + 1);
// 回溯,移除皇后
board[row][col] = '.';
}
}
}
/**
* 检查在[row, col]位置放置皇后是否有效
* @param {number} row - 行索引
* @param {number} col - 列索引
* @return {boolean} - 如果位置有效返回true否则返回false
*/
function isValid(row, col) {
// 检查同一列是否有皇后
for (let i = 0; i < row; i++) {
if (board[i][col] === 'Q') {
return false;
}
}
// 检查左上对角线是否有皇后
for (let i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
if (board[i][j] === 'Q') {
return false;
}
}
// 检查右上对角线是否有皇后
for (let i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
if (board[i][j] === 'Q') {
return false;
}
}
return true;
}
}
/**
* 打印棋盘
* @param {string[]} board - 表示棋盘的字符串数组
*/
function printBoard(board) {
for (const row of board) {
console.log(row);
}
console.log('---');
}
// 测试代码
function testNQueens(n) {
console.log(`求解${n}皇后问题:`);
const solutions = solveNQueens(n);
console.log(`找到${solutions.length}个解决方案`);
// 打印前3个解决方案
for (let i = 0; i < Math.min(3, solutions.length); i++) {
console.log(`解决方案 ${i + 1}:`);
printBoard(solutions[i]);
}
}
// 测试8皇后问题
testNQueens(8);

View File

@ -0,0 +1,115 @@
/**
*https://leetcode.cn/problems/majority-element/description/?envType=study-plan-v2&envId=top-interview-150
* @param {number[]} nums
* @return {number}
*/
const majorityElement = function (nums) {
f1(nums);
f2(nums);
f3(nums, 0, nums.length - 1);
f4(nums);
};
/*
暴力法遍历所有元素统计每一个元素出现的次数之后再遍历统计的结果统计结果中大于数组长度二分之一的就是我们要找
的多数元素
*/
function f1(nums) {
const countMap = new Map();
// 遍历数组,统计数组元素中每个元素出现的次数
for (const num of nums) {
countMap.set(num, (countMap.get(num) || 0) + 1);
}
// 遍历countMap 找到出现次数大于数组长度一半的元素就是多数元素
const majorityCount = nums.length / 2;
for (const [num, count] of countMap) {
if (count > majorityCount) return num;
}
// 如果没有就返回, 根据题目要求,不会发生这一情况
return null;
}
/*
排序法, 按照题目要求多数元素一定会大于数组长度的一半 所以只要把数组拍一个需那么在1/2位置的元素一定是多数
元素why? 我们可以这样思考想象大脑里面有一个进度条进度条现在是51%移动进度条的颜色部分无论怎么移动这个
颜色部分总是会覆盖中间位置 这里以数组长度3和4为例如果数组长度是3那么中间元素就是 3/2 = 1.5但在js中不会自动
丢弃小数位所以要Math.floor()向下取整再来看看四如果有多数元素这个多数元素一定有三个4/2 = 2,刚好也符合所以
奇偶无需特殊处理
*/
function f2(nums) {
// 先对数组排序
nums.sort((a, b) => a - b);
return nums[Math.floor(nums.length / 2)];
}
/*
分治法把大问题化成小问题把原数组分成左右两部分找出左右俩部分多数的数如果这两个数相同就返回这个数如果
不相同再比较谁多返回多的那个
*/
/**
*
* @param {number[]} nums 原数组
* @param {number} left 开始的部分
* @param {number} right 结束的部分
*/
function f3(nums, left, right) {
// 如果left和right相等表明只有一个元素直接返回即可
if (left === right) return nums[left];
// 递归计算左右两部分的多数元素
const mid = Math.floor((left + right) / 2);
const leftMajority = f3(nums, left, mid);
const rightMajority = f3(nums, mid + 1, right);
// 如果左右两边的元素相同,则返回这个元素
if (leftMajority === rightMajority) return leftMajority;
// 如果两个元素不相等,就比较它们谁出现的次数多
const leftMajorityCount = countInRange(nums, left, mid, leftMajority);
const rightMajorityCount = countInRange(nums, mid + 1, right, rightMajority);
return leftMajorityCount > rightMajorityCount ? leftMajority : rightMajority;
}
/**
*
* @param {number[]} nums 要处理的元素组
* @param {number} left 左边界
* @param {number} right 右边界
* @param {number} target 要统计的目标元素
* @description 统计目标元素出现的次数
*/
function countInRange(nums, left, right, target) {
let count = 0;
for (let i = left; i <= right; i++) {
if (nums[i] === target)count++;
}
return count;
}
/*
投票法假设数组中举行选择这样的话多数元素肯定会选举胜利如果当前选择的元素和自己是一样的那么就投票不是就反对
是对手票减少一个
*/
function f4(nums) {
if (nums.length === 1) return nums[0];
let count = 1;
let num = nums[0]; // 候选的
for (let i = 1; i < nums.length; i++) {
if (nums[i] === num) {
count++;
} else if (count > 0) {
count--;
} else {
num = nums[i];
count = 1;
}
}
return num;
}
majorityElement([2, 2, 1, 1, 1, 2, 2]);

View File

@ -0,0 +1,54 @@
/**
*https://leetcode.cn/problems/rotate-array/description/?envType=study-plan-v2&envId=top-interview-150
* @param {number[]} nums
* @param {number} k
* @return {void} Do not return anything, modify nums in-place instead.
*/
const rotate = function (nums, k) {
f1(nums, k);
// f2(nums, k);
};
/*
暴力法直接copy整个数组然后遍历复制后的数组从k位开始填充原数组
*/
function f1(nums, k) {
const copyNums = [...nums];
const size = nums.length; // 数组大小
for (let i = 0; i < size; i++) {
nums[(i + k) % size] = copyNums[i];
}
}
/*
反转法观察发现只要把数组选中之后再选择前k个元素和剩下的n-k个元素即可实现题目要求的效果
*/
function f2(nums, k) {
const size = nums.length;
// 如果k等于0直接结束即可反转是由性能损耗的
if (k === 0 || k === size) return;
k %= size; // 预防k>size
// 1. 反转整个数组
reverse(nums, 0, size - 1);
// 反转前面k个部分
reverse(nums, 0, k - 1);
// 反转剩下的n-k个部分
reverse(nums, k, size - 1);
}
// 反转数组的一个部分
function reverse(arr, start, end) {
while (start < end) {
// [arr[start], arr[end]] = [arr[end], arr[start]]; 交换效率低
const temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
start++;
end--;
}
}
const arr = [1, 2, 3, 4];
rotate(arr, 3);
console.log(arr);

View File

@ -0,0 +1,30 @@
/**
* https://leetcode.cn/problems/remove-duplicates-from-sorted-array/?envType=study-plan-v2&envId=top-interview-150
* @param {number[]} nums
* @return {number} 不重复元素的数量
*/
const removeDuplicates = function (nums) {
// 直接利用Set的去重特性hash
const numSet = new Set(nums);
let i = 0;
for (const num of numSet) {
nums[i] = num;
i++;
}
return numSet.size;
};
// 上面的方法虽然好理解但是需要遍历整个数组才能转换成Set加上hash操作并不是最优解。因为提供的nums已经是一个“非严格递增的数组”
// 只需利用双指针处理即可首先定义两个指针p1,p2,p1指向第一个元素p2指向第二个元素如果有的话p2开始遍历如果遍历到得元素和nums[p1]
// 不相等p1++ 并把这个值赋值到p1的位置最后返回p1+1
const removeDuplicates2 = function (nums) {
if (nums.length < 2) return 1;
let p1 = 0;
for (let p2 = 1; p2 < nums.length; p2++) {
if (nums[p1] !== nums[p2]) {
nums[++p1] = nums[p2];
}
}
return p1 + 1; // 下标加1表示个数
};

View File

@ -0,0 +1,41 @@
/**
*https://leetcode.cn/problems/remove-element/?envType=study-plan-v2&envId=top-interview-150
* @param {number[]} nums
* @param {number} val
* @return {number}
*/
/**
* @param {number[]} nums
* @param {number} val
* @return {number}
*/
const removeElement = function (nums, val) {
let p1 = 0; // 记录有效元素,在这个题目中我们需要把遍历到的所有有效元素依此放入这个位置
let k = 0; // 记录有效元素的个数
for (let p2 = 0; p2 < nums.length; p2++) {
if (nums[p2] !== val) {
// [nums[p1], nums[p2]] = [nums[p2], nums[p1]]; // 语法糖执行效率低
const temp = nums[p1];
nums[p1] = nums[p2];
nums[p2] = temp;
p1++;
k++;
}
}
return k;
};
const removeElement2 = function (nums, val) {
let p1 = 0; // 记录有效元素,在这个题目中我们需要把遍历到的所有有效元素依此放入这个位置
for (let p2 = 0; p2 < nums.length; p2++) {
if (nums[p2] !== val) {
const temp = nums[p1];
nums[p1] = nums[p2];
nums[p2] = temp;
p1++;
}
}
return p1; // p1记录的是有效元素的位置通过上面的循环可以发现p1和k的行为一致所以不需要k来记录只需返回p1即可
};
// 上面

View File

@ -0,0 +1,46 @@
/**
* https://leetcode.cn/problems/remove-duplicates-from-sorted-array-ii/?envType=study-plan-v2&envId=top-interview-150
* @param {number[]} nums
* @return {number}
*/
const removeDuplicates = function (nums) {
if (nums.length === 0) return 0;
let i = 1; // 慢指针初始化为1表示第一项是已经有效的
let count = 1; // 计数器,用于统计当前元素出现的次数
for (let j = 1; j < nums.length; j++) { // 快指针
if (nums[j] === nums[j - 1]) {
count++; // 当前元素和前一个元素相同计数器加1
} else {
count = 1; // 如果不同,重置计数器
}
if (count <= 2) {
nums[i] = nums[j]; // 如果当前元素不超过2次移动到有效位置
i++; // 慢指针向前移动
}
}
return i; // 最终慢指针的位置即为去重后的元素个数
};
// var removeDuplicates = function(nums) {
// const n = nums.length;
// if (n <= 2) {
// return n;
// }
// let slow = 2, fast = 2;
// while (fast < n) {
// if (nums[slow - 2] != nums[fast]) {
// nums[slow] = nums[fast];
// ++slow;
// }
// ++fast;
// }
// return slow;
// };
const numss = [0, 0, 1, 1, 1, 1, 2, 3, 3];
console.log(removeDuplicates(numss));
console.log(numss);

View File

@ -0,0 +1,38 @@
/**
* https://leetcode.cn/problems/merge-sorted-array/?envType=study-plan-v2&envId=top-interview-150
* @param {number[]} nums1
* @param {number} m
* @param {number[]} nums2
* @param {number} n
* @return {void} Do not return anything, modify nums1 in-place instead.
*/
const merge = function (nums1, m, nums2, n) {
let p1 = m - 1; // 指向nums1的末尾
let p2 = n - 1; // 指向nums2的末尾
let p = m + n - 1; // 指向nums1 填充部分的末尾
while (p1 >= 0 && p2 >= 0) {
if (nums1[p1] > nums2[p2]) {
nums1[p] = nums1[p1];
p1--;
} else {
nums1[p] = nums2[p2];
p2--;
}
p--;
}
// 如果nums2中有未移动的数据全部移动到nums1中
while (p2 >= 0) {
nums1[p] = nums2[p2];
p2--;
p--;
}
};
// 思考如果p1>=0,还需要全部移动一遍吗答案是否定的因为原本就是有序的如果nums2处理完毕之后就表示整个拼接好的nums1
// 就是有序的,只需考虑 p2>=0 的情况
// 其他方法,直接合并两个数组,再对其排序,简单粗暴