实现排序算法之冒泡排序
从数组的起始位置开始,依次比较相邻的两个元素,如果顺序不对就交换位置,直到整个数组排序完成。
function bubbleSort(arr) {
const n = arr.length;
for (let i = 0; i < n - 1; i++) {
for (let j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
}
}
}
return arr;
}
// 时间复杂度:最好情况O(n),平均和最坏情况O(n^2)
实现排序算法之插入排序
从数组的第二个元素开始,依次将元素插入到已排序的部分中,直到整个数组排序完成。
function insertionSort(arr) {
const n = arr.length;
for (let i = 1; i < n; i++) {
let current = arr[i];
let j = i - 1;
while (j >= 0 && arr[j] > current) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = current;
}
return arr;
}
// 时间复杂度:最好情况O(n),平均和最坏情况O(n^2)
实现排序算法之选择排序
在未排序的部分中选择最小(或最大)的元素,然后放到已排序部分的末尾。
function selectionSort(arr) {
const n = arr.length;
for (let i = 0; i < n - 1; i++) {
let minIndex = i;
for (let j = i + 1; j < n; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
[arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
}
return arr;
}
// 时间复杂度:最好、平均和最坏情况均为O(n^2)
实现排序算法之快速排序
通过选择一个基准元素,将数组分为小于基准的部分和大于基准的部分,然后递归地对这两部分进行排序。
function quickSort(arr) {
if (arr.length <= 1) {
return arr;
}
const pivot = arr[0];
const left = [];
const right = [];
for (let i = 1; i < arr.length; i++) {
if (arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return quickSort(left).concat(pivot, quickSort(right));
}
// 时间复杂度:平均情况O(n log n),最坏情况O(n^2)
实现排序算法之归并排序
将数组分成两半,分别对每半进行排序,然后再合并两个有序的子数组。
function mergeSort(arr) {
if (arr.length <= 1) {
return arr;
}
const mid = Math.floor(arr.length / 2);
const left = arr.slice(0, mid);
const right = arr.slice(mid);
return merge(mergeSort(left), mergeSort(right));
}
function merge(left, right) {
let result = [];
let leftIndex = 0;
let rightIndex = 0;
while (leftIndex < left.length && rightIndex < right.length) {
if (left[leftIndex] < right[rightIndex]) {
result.push(left[leftIndex]);
leftIndex++;
} else {
result.push(right[rightIndex]);
rightIndex++;
}
}
return result.concat(left.slice(leftIndex), right.slice(rightIndex));
}
// 时间复杂度:平均、最好和最坏情况均为O(n log n)
实现排序算法之堆排序
将数组看成一个二叉堆,构建最大堆或最小堆,然后不断地从堆顶取出元素。
function heapSort(arr) {
const n = arr.length;
for (let i = Math.floor(n / 2) - 1; i >= 0; i--) {
heapify(arr, n, i);
}
for (let i = n - 1; i > 0; i--) {
[arr[0], arr[i]] = [arr[i], arr[0]];
heapify(arr, i, 0);
}
return arr;
}
function heapify(arr, n, i) {
let largest = i;
const left = 2 * i + 1;
const right = 2 * i + 2;
if (left < n && arr[left] > arr[largest]) {
largest = left;
}
if (right < n && arr[right] > arr[largest]) {
largest = right;
}
if (largest !== i) {
[arr[i], arr[largest]] = [arr[largest], arr[i]];
heapify(arr, n, largest);
}
}
// 时间复杂度:平均、最好和最坏情况均为O(n log n)
实现排序算法之基数排序
将数字按照位数切分,然后分别对各个位进行排序。
function radixSort(arr) {
const maxDigits = Math.max(...arr).toString().length;
for (let i = 0; i < maxDigits; i++) {
const buckets = Array.from({ length: 10 }, () => []);
for (let j = 0; j < arr.length; j++) {
const digit = getDigit(arr[j], i);
buckets[digit].push(arr[j]);
}
arr = buckets.flat();
}
return arr;
}
function getDigit(num, place) {
return Math.floor(Math.abs(num) / Math.pow(10, place)) % 10;
}
// 时间复杂度:最好、平均和最坏情况均为O(nk),其中 k 为最大位数
实现一个将列表转成树的方法
将一个列表(数组)转换为树形结构可以使用递归来实现。每个元素代表树中的一个节点,元素中的某个属性(例如父节点 ID 或者层级信息)用来建立节点之间的关系。
const dataList = [
{ id: 1, name: 'Node 1', parentId: null },
{ id: 2, name: 'Node 1.1', parentId: 1 },
{ id: 3, name: 'Node 1.2', parentId: 1 },
{ id: 4, name: 'Node 2', parentId: null },
{ id: 5, name: 'Node 2.1', parentId: 4 },
{ id: 6, name: 'Node 2.1.1', parentId: 5 },
];
function listToTree(list, parentId = null) {
const tree = [];
for (const item of list) {
if (item.parentId === parentId) {
const children = listToTree(list, item.id);
if (children.length) {
item.children = children;
}
tree.push(item);
}
}
return tree;
}
const tree = listToTree(dataList);
console.log(tree);
实现判断节点是否在二叉树中
二叉查找树(Binary Search Tree,BST)是一种特殊的二叉树,其中每个节点的左子树的值都小于该节点的值,而右子树的值都大于该节点的值。
class TreeNode {
constructor(value) {
this.value = value;
this.left = null;
this.right = null;
}
}
class BinarySearchTree {
constructor() {
this.root = null;
}
insert(value) {
const newNode = new TreeNode(value);
if (!this.root) {
this.root = newNode;
return this;
}
let current = this.root;
while (true) {
if (value === current.value) return undefined;
if (value < current.value) {
if (!current.left) {
current.left = newNode;
return this;
}
current = current.left;
} else {
if (!current.right) {
current.right = newNode;
return this;
}
current = current.right;
}
}
}
search(value) {
if (!this.root) return false;
let current = this.root;
while (current) {
if (value === current.value) return true;
if (value < current.value) {
current = current.left;
} else {
current = current.right;
}
}
return false;
}
}
const bst = new BinarySearchTree();
bst.insert(10);
bst.insert(5);
bst.insert(15);
bst.insert(3);
bst.insert(7);
bst.insert(12);
bst.insert(18);
console.log(bst.search(7)); // 输出: true
console.log(bst.search(9)); // 输出: false
实现获取二叉树的路径
class TreeNode {
constructor(value) {
this.value = value;
this.left = null;
this.right = null;
}
}
class BinarySearchTree {
constructor() {
this.root = null;
}
insert(value) {
const newNode = new TreeNode(value);
if (!this.root) {
this.root = newNode;
return this;
}
let current = this.root;
while (true) {
if (value === current.value) return undefined;
if (value < current.value) {
if (!current.left) {
current.left = newNode;
return this;
}
current = current.left;
} else {
if (!current.right) {
current.right = newNode;
return this;
}
current = current.right;
}
}
}
findPath(value) {
const path = [];
this._findPathRecursive(this.root, value, path);
return path;
}
_findPathRecursive(node, value, path) {
if (!node) return false;
path.push(node.value);
if (value === node.value) {
return true;
} else if (value < node.value) {
if (this._findPathRecursive(node.left, value, path)) {
return true;
}
} else {
if (this._findPathRecursive(node.right, value, path)) {
return true;
}
}
path.pop();
return false;
}
}
const bst = new BinarySearchTree();
bst.insert(10);
bst.insert(5);
bst.insert(15);
bst.insert(3);
bst.insert(7);
bst.insert(12);
bst.insert(18);
console.log(bst.findPath(7)); // 输出: [10, 5, 7]
console.log(bst.findPath(12)); // 输出: [10, 15, 12]
console.log(bst.findPath(9)); // 输出: []
实现斐波那契数列
斐波那契数列是一个经典的递归数列,其中每个数都是前两个数之和。通常情况下,斐波那契数列的前两个数是 0 和 1 递归实现
function fibonacciRecursive(n) {
if (n <= 0) return 0;
if (n === 1) return 1;
return fibonacciRecursive(n - 1) + fibonacciRecursive(n - 2);
}
迭代实现
function fibonacciIterative(n) {
if (n <= 0) return 0;
if (n === 1) return 1;
let prevPrev = 0;
let prev = 1;
let current = 0;
for (let i = 2; i <= n; i++) {
current = prev + prevPrev;
prevPrev = prev;
prev = current;
}
return current;
}
使用数组缓存实现
function fibonacciWithCache(n, cache = {}) {
if (n in cache) return cache[n];
if (n <= 0) return 0;
if (n === 1) return 1;
cache[n] = fibonacciWithCache(n - 1, cache) + fibonacciWithCache(n - 2, cache);
return cache[n];
}
实现一个求最长递增子序列的值
求解最长递增子序列(Longest Increasing Subsequence,LIS)是一个经典的动态规划问题。最长递增子序列是指在一个序列中找到一个递增的子序列,使得该子序列的长度最大
function longestIncreasingSubsequence(arr) {
const n = arr.length;
const dp = new Array(n).fill(1); // 初始化每个元素为长度为 1 的子序列
for (let i = 1; i < n; i++) {
for (let j = 0; j < i; j++) {
if (arr[i] > arr[j]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
}
return Math.max(...dp);
}
const arr = [10, 22, 9, 33, 21, 50, 41, 60, 80];
console.log(longestIncreasingSubsequence(arr));
// 输出: 6(最长递增子序列是 [10, 22, 33, 50, 60, 80])
在上述代码中,dp 数组用于存储以每个元素结尾的最长递增子序列的长度。外层循环遍历每个元素,内层循环遍历所有比当前元素小的元素,如果当前元素能够接在前面的元素后面形成递增子序列,就更新 dp 数组的值。
实现获取字符串的所有排列组合
获取字符串的所有排列组合可以使用递归方法来实现
function getAllPermutations(input) {
const result = [];
const inputArr = input.split('');
function permute(arr, currentIndex) {
if (currentIndex === arr.length - 1) {
result.push(arr.join(''));
return;
}
for (let i = currentIndex; i < arr.length; i++) {
[arr[currentIndex], arr[i]] = [arr[i], arr[currentIndex]];
permute([...arr], currentIndex + 1);
[arr[currentIndex], arr[i]] = [arr[i], arr[currentIndex]]; // 回溯
}
}
permute(inputArr, 0);
return result;
}
const inputString = 'abc';
const permutations = getAllPermutations(inputString);
console.log(permutations);
// ['abc', 'acb', 'bac', 'bca', 'cab', 'cba']
实现版本号排序
版本号排序是一个常见的问题,可以将版本号拆分成数字部分,然后按照每个数字进行比较和排序
function compareVersion(version1, version2) {
const v1 = version1.split('.').map(Number);
const v2 = version2.split('.').map(Number);
const maxLength = Math.max(v1.length, v2.length);
for (let i = 0; i < maxLength; i++) {
const num1 = i < v1.length ? v1[i] : 0;
const num2 = i < v2.length ? v2[i] : 0;
if (num1 < num2) {
return -1;
} else if (num1 > num2) {
return 1;
}
}
return 0;
}
function sortVersions(versions) {
return versions.sort(compareVersion);
}
const versions = ['1.3.0', '1.1.2', '1.2.1', '1.4.2'];
const sortedVersions = sortVersions(versions);
console.log(sortedVersions); // 输出: ['1.1.2', '1.2.1', '1.3.0', '1.4.2']
实现数组中n个数之和等于目标值,输出这n个数
要在数组中找到 n 个数之和等于目标值,可以使用递归回溯的方法来实现
function findNNumbersWithSum(nums, targetSum, n) {
const result = [];
backtrack(nums, targetSum, n, 0, [], result);
return result;
}
function backtrack(nums, targetSum, n, startIndex, currentCombination, result) {
if (n === 0 && targetSum === 0) {
result.push([...currentCombination]);
return;
}
for (let i = startIndex; i < nums.length; i++) {
if (nums[i] > targetSum) {
continue; // 剪枝:当前数大于剩余目标值,跳过
}
currentCombination.push(nums[i]);
backtrack(nums, targetSum - nums[i], n - 1, i + 1, currentCombination, result);
currentCombination.pop();
}
}
const nums = [2, 3, 7, 4, 8, 5];
const targetSum = 12;
const n = 3;
const result = findNNumbersWithSum(nums, targetSum, n);
console.log(result);
// [[2, 3, 7], [3, 4, 5]]
findNNumbersWithSum 函数用于寻找数组中 n 个数之和等于目标值的组合。backtrack 函数用于进行递归回溯,尝试所有可能的组合。在满足条件时,将当前组合添加到结果数组中。
实现一个判断输入是不是回文字符串的函数
回文字符串是指从前往后读和从后往前读都相同的字符串。换句话说,就是一个字符串在忽略非字母和数字字符、忽略大小写的情况下,正着读和倒着读都是一样的。例如,"level"、"deified"、"madam" 都是回文字符串
function isPalindrome(str) {
str = str.toLowerCase().replace(/[^a-z0-9]/g, ''); // 将字符串转为小写并去除非字母和数字字符
const length = str.length;
for (let i = 0; i < Math.floor(length / 2); i++) {
if (str[i] !== str[length - 1 - i]) {
return false;
}
}
return true;
}
console.log(isPalindrome("A man, a plan, a canal, Panama")); // 输出: true
console.log(isPalindrome("racecar")); // 输出: true
console.log(isPalindrome("hello")); // 输出: false
实现获取字符串中无重复字符的最长子串
要获取字符串中无重复字符的最长子串,可以使用滑动窗口的方法。滑动窗口是一个用于维护子串的窗口,通过移动窗口的左右边界来寻找满足条件的子串
function lengthOfLongestSubstring(s) {
const charIndexMap = new Map(); // 用于存储字符和其最近出现的索引
let maxLength = 0;
let left = 0; // 滑动窗口的左边界
for (let right = 0; right < s.length; right++) {
const char = s[right];
if (charIndexMap.has(char) && charIndexMap.get(char) >= left) {
left = charIndexMap.get(char) + 1; // 更新左边界,避免重复字符
}
maxLength = Math.max(maxLength, right - left + 1);
charIndexMap.set(char, right);
}
return maxLength;
}
console.log(lengthOfLongestSubstring("abcabcbb")); // 输出: 3 ("abc")
console.log(lengthOfLongestSubstring("bbbbb")); // 输出: 1 ("b")
console.log(lengthOfLongestSubstring("pwwkew")); // 输出: 3 ("wke")
lengthOfLongestSubstring 函数使用了滑动窗口的思想,通过维护一个字符到其最近出现索引的映射,以及左边界来计算最长子串的长度。
实现删除字符串中的所有相邻重复项
要删除字符串中的所有相邻重复项,可以使用栈来实现。遍历字符串,如果当前字符与栈顶字符相同,则弹出栈顶字符,否则将当前字符入栈。最终,栈中的字符就是删除相邻重复项后的结果
function removeAdjacentDuplicates(str) {
const stack = [];
for (const char of str) {
if (stack.length && stack[stack.length - 1] === char) {
stack.pop();
} else {
stack.push(char);
}
}
return stack.join('');
}
console.log(removeAdjacentDuplicates("abbaca")); // 输出: "ca"
console.log(removeAdjacentDuplicates("azxxzy")); // 输出: "ay"