diff --git a/README.md b/README.md index a0bb0a8..4af871f 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@
- + diff --git a/assets/data/categories.json b/assets/data/categories.json index 1b0ba92..72be66c 100644 --- a/assets/data/categories.json +++ b/assets/data/categories.json @@ -336,6 +336,12 @@ "path": "./problemset/count-vowel-strings-in-ranges/README.md", "difficulty": "中等" }, + { + "id": "2740", + "title": "找出分区值", + "path": "./problemset/find-the-value-of-the-partition/README.md", + "difficulty": "中等" + }, { "id": "剑指 Offer II 076", "title": "数组中的第 k 大的数字", @@ -697,6 +703,12 @@ "path": "./problemset/largest-values-from-labels/README.md", "difficulty": "中等" }, + { + "id": "2740", + "title": "找出分区值", + "path": "./problemset/find-the-value-of-the-partition/README.md", + "difficulty": "中等" + }, { "id": "剑指 Offer II 076", "title": "数组中的第 k 大的数字", diff --git a/assets/data/topics.json b/assets/data/topics.json index 697daf0..b85db6f 100644 --- a/assets/data/topics.json +++ b/assets/data/topics.json @@ -1729,6 +1729,16 @@ "url": "https://leetcode.cn/problems/modify-graph-edge-weights/", "path": "./problemset/modify-graph-edge-weights/README.md" }, + { + "id": "2740", + "title": { + "cn": "找出分区值", + "en": "find-the-value-of-the-partition" + }, + "difficulty": "中等", + "url": "https://leetcode.cn/problems/find-the-value-of-the-partition/description/?envType=daily-question&envId=2024-07-26", + "path": "./problemset/find-the-value-of-the-partition/README.md" + }, { "id": "剑指 Offer II 076", "title": { diff --git a/assets/docs/CATEGORIES.md b/assets/docs/CATEGORIES.md index c63de1e..6e4be61 100644 --- a/assets/docs/CATEGORIES.md +++ b/assets/docs/CATEGORIES.md @@ -68,6 +68,7 @@ | 2460. [对数组执行操作](../../problemset/apply-operations-to-an-array/README.md) | 简单 | | 2475. [数组中不等三元组的数目](../../problemset/number-of-unequal-triplets-in-array/README.md) | 简单 | | 2559. [统计范围内的元音字符串数](../../problemset/count-vowel-strings-in-ranges/README.md) | 中等 | +| 2740. [找出分区值](../../problemset/find-the-value-of-the-partition/README.md) | 中等 | | 剑指 Offer II 076. [数组中的第 k 大的数字](../../problemset/xx4gt2/README.md) | 中等 | ## 字符串 @@ -149,6 +150,7 @@ | 215. [数组中的第K个最大元素](../../problemset/kth-largest-element-in-an-array/README.md) | 中等 | | 524. [通过删除字母匹配到字典里最长单词](../../problemset/longest-word-in-dictionary-through-deleting/README.md) | 中等 | | 1090. [受标签影响的最大值](../../problemset/largest-values-from-labels/README.md) | 中等 | +| 2740. [找出分区值](../../problemset/find-the-value-of-the-partition/README.md) | 中等 | | 剑指 Offer II 076. [数组中的第 k 大的数字](../../problemset/xx4gt2/README.md) | 中等 | ## 数学 diff --git a/assets/docs/TOPICS.md b/assets/docs/TOPICS.md index c585e45..52e0e9b 100644 --- a/assets/docs/TOPICS.md +++ b/assets/docs/TOPICS.md @@ -346,6 +346,8 @@ [2699. 修改图中的边权](../../problemset/modify-graph-edge-weights/README.md) +[2740. 找出分区值](../../problemset/find-the-value-of-the-partition/README.md) + [剑指 Offer II 076. 数组中的第 k 大的数字](../../problemset/xx4gt2/README.md) [剑指 Offer II 088. 爬楼梯的最少成本](../../problemset/gzcjip/README.md) diff --git a/problemset/find-the-value-of-the-partition/README.md b/problemset/find-the-value-of-the-partition/README.md new file mode 100644 index 0000000..67c8795 --- /dev/null +++ b/problemset/find-the-value-of-the-partition/README.md @@ -0,0 +1,44 @@ +# 找出分区值 + +> 难度:中等 +> +> https://leetcode.cn/problems/find-the-value-of-the-partition/description/?envType=daily-question&envId=2024-07-26 + +## 题目 + +给你一个 正 整数数组 `nums` 。 + +将 `nums` 分成两个数组:`nums1` 和 `nums2` ,并满足下述条件: + +- 数组 `nums` 中的每个元素都属于数组 `nums1` 或数组 `nums2` 。 +- 两个数组都 非空 。 +- 分区值 最小 。 +分区值的计算方法是 `|max(nums1) - min(nums2)|` 。 + +其中,`max(nums1)` 表示数组 `nums1` 中的最大元素,`min(nums2)` 表示数组 `nums2` 中的最小元素。 + +返回表示分区值的整数。 + + + +### 示例 1: +``` +输入:nums = [1,3,2,4] +输出:1 +解释:可以将数组 nums 分成 nums1 = [1,2] 和 nums2 = [3,4] 。 +- 数组 nums1 的最大值等于 2 。 +- 数组 nums2 的最小值等于 3 。 +分区值等于 |2 - 3| = 1 。 +可以证明 1 是所有分区方案的最小值。 +``` + +### 示例 2: +``` +输入:nums = [100,1,10] +输出:9 +解释:可以将数组 nums 分成 nums1 = [10] 和 nums2 = [100,1] 。 +- 数组 nums1 的最大值等于 10 。 +- 数组 nums2 的最小值等于 1 。 +分区值等于 |10 - 1| = 9 。 +可以证明 9 是所有分区方案的最小值。 +``` diff --git a/problemset/find-the-value-of-the-partition/index.spec.ts b/problemset/find-the-value-of-the-partition/index.spec.ts new file mode 100644 index 0000000..fd93c1b --- /dev/null +++ b/problemset/find-the-value-of-the-partition/index.spec.ts @@ -0,0 +1,16 @@ +import { describe, expect, it } from 'vitest' +import { findValueOfPartition } from '.' + +describe('找出分区值', () => { + testCase(findValueOfPartition) +}) + +function testCase(fn: (nums: number[]) => number) { + it.each([ + // test cases + [[1, 3, 2, 4], 1], + [[100, 1, 10], 9], + ])('示例%#', (nums, expected) => { + expect(fn(nums)).toBe(expected) + }) +} diff --git a/problemset/find-the-value-of-the-partition/index.ts b/problemset/find-the-value-of-the-partition/index.ts new file mode 100644 index 0000000..efe3237 --- /dev/null +++ b/problemset/find-the-value-of-the-partition/index.ts @@ -0,0 +1,12 @@ +import { arraySort } from 'utils/array/array' +export function findValueOfPartition(nums: number[]): number { + const sortNums = arraySort.quickSort(nums); + let min = -1; // 差值 + for (let i = 0; i < sortNums.length - 1; i++) { + const x = sortNums[i + 1] - sortNums[i]; + if (min === -1 || x < min) { + min = x; + } + } + return min +} diff --git a/utils/array/array.ts b/utils/array/array.ts new file mode 100644 index 0000000..e9f45a4 --- /dev/null +++ b/utils/array/array.ts @@ -0,0 +1,19 @@ +import { + bubbleSort, + heapSort, + insertionSort, + mergeSort, + quickSort, + selectionSort, + shellSort, +} from './arraySort'; + +export const arraySort = { + quickSort, + mergeSort, + heapSort, + shellSort, + insertionSort, + selectionSort, + bubbleSort, +} diff --git a/utils/array/arraySort.ts b/utils/array/arraySort.ts new file mode 100644 index 0000000..22e05a3 --- /dev/null +++ b/utils/array/arraySort.ts @@ -0,0 +1,253 @@ +/** + * NO.1 数组快速排序 + * 平均时间复杂度:O(n log n) + * 最坏时间复杂度:O(n²) + * 空间复杂度:O(log n)(递归调用栈) + * 特点: + * 基于分治策略,选择一个“枢轴”元素,将数组分为小于和大于枢轴的两部分,然后递归排序。 + * 最坏情况下(如每次选择的枢轴是最大或最小元素),时间复杂度为 O(n²)。 + * 通常表现非常好,是实际中常用的排序算法之一。 + * @param arr + * @returns + */ +export function quickSort(arr: number[]): number[] { + if (arr.length <= 1) { + return arr; + } + + const pivot = arr[Math.floor(arr.length / 2)]; + const left: number[] = []; + const right: number[] = []; + const equal: number[] = []; + + for (const num of arr) { + if (num < pivot) { + left.push(num); + } else if (num > pivot) { + right.push(num); + } else { + equal.push(num); + } + } + + return [...quickSort(left), ...equal, ...quickSort(right)]; +} + +/** + * NO.2 归并排序 + * 平均时间复杂度:O(n log n) + * 最坏时间复杂度:O(n log n) + * 空间复杂度:O(n) + * 特点: + * 基于分治策略,将数组分成两半,递归排序每一半,然后合并两个已排序的部分。 + * 稳定排序算法(即相同值的元素保持原来的顺序)。 + * 无论什么情况下,时间复杂度都是 O(n log n)。 + * @param arr + * @returns + */ +export function mergeSort(arr: number[]): number[] { + if (arr.length <= 1) { + return arr; + } + + const middle = Math.floor(arr.length / 2); + const left = arr.slice(0, middle); + const right = arr.slice(middle); + + return merge(mergeSort(left), mergeSort(right)); +} + +function merge(left: number[], right: number[]): number[] { + const result: number[] = []; + 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++; + } + } + + // Concat remaining elements (if any) + return result.concat(left.slice(leftIndex)).concat(right.slice(rightIndex)); +} + +/** + * NO.3 堆排序(Heap Sort) + * 平均时间复杂度:O(n log n) + * 最坏时间复杂度:O(n log n) + * 空间复杂度:O(1) + * 特点: + * 使用堆数据结构(通常是二叉堆)来排序元素。 + * 在数组内部进行排序,不需要额外的存储空间。 + * 不稳定排序算法。 + * @param arr + */ +export const heapSort = (arr: number[]): number[] => { + const n = arr.length; + + // Build max heap + for (let i = Math.floor(n / 2) - 1; i >= 0; i--) { + heapify(arr, n, i); + } + + // Extract elements from heap one by one + for (let i = n - 1; i > 0; i--) { + // Move current root to end + [arr[0], arr[i]] = [arr[i], arr[0]]; + + // Call heapify on the reduced heap + heapify(arr, i, 0); + } + + return arr; +} +function heapify(arr: number[], n: number, i: number) { + let largest = i; + const left = 2 * i + 1; + const right = 2 * i + 2; + + // If left child is larger than root + if (left < n && arr[left] > arr[largest]) { + largest = left; + } + + // If right child is larger than largest so far + if (right < n && arr[right] > arr[largest]) { + largest = right; + } + + // If largest is not root + if (largest !== i) { + [arr[i], arr[largest]] = [arr[largest], arr[i]]; + + // Recursively heapify the affected sub-tree + heapify(arr, n, largest); + } +} + +/** + * NO.4 希尔排序(Shell Sort) + * 平均时间复杂度:介于 O(n log n) 和 O(n²) 之间 + * 最坏时间复杂度:O(n²) + * 空间复杂度:O(1) + * 特点: + * 基于插入排序的改进,通过比较距离较远的元素来进行排序。 + * 性能依赖于选择的增量序列,最坏情况下复杂度为 O(n²)。 + * @param arr + */ +export const shellSort = (arr: number[]): number[] => { + const n = arr.length; + + // Start with a large gap, then reduce the gap + for (let gap = Math.floor(n / 2); gap > 0; gap = Math.floor(gap / 2)) { + // Perform a gapped insertion sort for this gap size + for (let i = gap; i < n; i++) { + // Save arr[i] in temp and make a hole at position i + const temp = arr[i]; + + // Shift earlier gap-sorted elements up until the correct location for arr[i] is found + let j: number; + for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) { + arr[j] = arr[j - gap]; + } + + // Put temp (the original arr[i]) in its correct location + arr[j] = temp; + } + } + + return arr; +} + +/** + * NO.5 插入排序(Insertion Sort) + * 平均时间复杂度:O(n²) + * 最坏时间复杂度:O(n²) + * 空间复杂度:O(1) + * 特点: + * 每次将一个元素插入到已经排序的部分。 + * 对于小规模数组或几乎有序的数组,表现很好。 + * 稳定排序算法。 + * @param arr + */ +export const insertionSort = (arr: number[]): number[] => { + for (let i = 1; i < arr.length; i++) { + const key = arr[i]; + let j = i - 1; + + // Move elements of arr[0..i-1], that are greater than key, + // to one position ahead of their current position + while (j >= 0 && arr[j] > key) { + arr[j + 1] = arr[j]; + j = j - 1; + } + arr[j + 1] = key; + } + return arr; +} + +/** + * NO.6 选择排序(Selection Sort) + * 平均时间复杂度:O(n²) + * 最坏时间复杂度:O(n²) + * 空间复杂度:O(1) + * 特点: + * 每次选择最小(或最大)的元素,放到数组的起始位置。 + * 不稳定排序算法。 + * @param arr + */ +export const selectionSort = (arr: number[]): number[] => { + const n = arr.length; + + for (let i = 0; i < n - 1; i++) { + // Find the minimum element in the unsorted part of the array + let minIndex = i; + for (let j = i + 1; j < n; j++) { + if (arr[j] < arr[minIndex]) { + minIndex = j; + } + } + + // Swap the found minimum element with the first element of the unsorted part + if (minIndex !== i) { + [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]]; + } + } + + return arr; +} + +/** + * NO.7 冒泡排序(Bubble Sort) + * 平均时间复杂度:O(n²) + * 最坏时间复杂度:O(n²) + * 空间复杂度:O(1) + * 特点: + * 通过相邻元素的比较和交换,使较大(或较小)的元素逐渐从后面移动到前面。 + * 稳定排序算法。 + * @param arr + */ +export const bubbleSort = (arr: number[]): number[] => { + let n = arr.length; + let swapped: boolean; + + do { + swapped = false; + for (let i = 0; i < n - 1; i++) { + if (arr[i] > arr[i + 1]) { + // Swap arr[i] and arr[i + 1] + [arr[i], arr[i + 1]] = [arr[i + 1], arr[i]]; + swapped = true; + } + } + // Each pass through the array, the largest element "bubbles" to the end + n--; // Reduce the array length by one for the next pass + } while (swapped); + + return arr; +}