feat: 面试常见算法题26,27,80,88,168,189
This commit is contained in:
parent
456e2bbccf
commit
dfe0072916
103
recursion/n皇后.js
Normal file
103
recursion/n皇后.js
Normal 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);
|
115
top-interview-leetcode150/numbers-and-strings/169多数元素.js
Normal file
115
top-interview-leetcode150/numbers-and-strings/169多数元素.js
Normal 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]);
|
54
top-interview-leetcode150/numbers-and-strings/189轮转数组.js
Normal file
54
top-interview-leetcode150/numbers-and-strings/189轮转数组.js
Normal 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);
|
@ -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表示个数
|
||||
};
|
41
top-interview-leetcode150/numbers-and-strings/27移除元素.js
Normal file
41
top-interview-leetcode150/numbers-and-strings/27移除元素.js
Normal 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即可
|
||||
};
|
||||
|
||||
// 上面
|
@ -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);
|
38
top-interview-leetcode150/numbers-and-strings/88合并两个有序数组.js
Normal file
38
top-interview-leetcode150/numbers-and-strings/88合并两个有序数组.js
Normal 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 的情况
|
||||
|
||||
// 其他方法,直接合并两个数组,再对其排序,简单粗暴
|
Loading…
x
Reference in New Issue
Block a user