按照算法和数据结构进行分类,一起来刷题,用于自己在面试前查漏补缺。我的意向岗位是前端,选择用javascript来刷题,优点是动态语言,语法简单,缺点是遇见复杂数据结构会出现较难的写法,如堆、并查集,每题对应leetcode的题号。本篇是排序算法
专题部分 排序算法 给定一个包含红色、白色和蓝色,一共 n 个元素的数组,**原地 **对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
示例 1:
1 2 输入:nums = [2,0,2,1,1,0] 输出:[0,0,1,1,2,2]
示例 2:
1 2 输入:nums = [2,0,1] 输出:[0,1,2]
示例 3:
示例 4:
提示:
n == nums.length
1 <= n <= 300
nums[i] 为 0、1 或 2
进阶:
你可以不使用代码库中的排序函数来解决这道题吗?
你能想出一个仅使用常数空间的一趟扫描算法吗?
利用快速排序的思想,用双指针控制0和2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 var sortColors = function (nums ) { let left = -1 , right = nums.length; let i = 0 ; while (i < right) { if (nums[i] === 0 ) { swap(nums, i++, ++left); } else if (nums[i] == 1 ) { i++; } else { swap(nums, i, --right); } } }; function swap (array, left, right ) { let temp = array[right]; array[right] = array[left]; array[left] = temp; }
给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。
进阶:
你可以在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序吗?
示例 1:
1 2 输入:head = [4,2,1,3] 输出:[1,2,3,4]
示例 2:
1 2 输入:head = [-1,5,3,4,0] 输出:[-1,0,3,4,5]
示例 3:
提示:
链表中节点的数目在范围 [0, 5 * 104] 内
-105 <= Node.val <= 105
归并排序法,自上而下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 var sortList = function (head ) { return toSortList(head, null ); }; function merge (head1, head2 ) { const dummyHead = new ListNode(0 ); let temp = dummyHead, temp1 = head1, temp2 = head2; while (temp1 !== null && temp2 !== null ) { if (temp1.val <= temp2.val) { temp.next = temp1; temp1 = temp1.next; } else { temp.next = temp2; temp2 = temp2.next; } temp = temp.next; } if (temp1 !== null ) { temp.next = temp1; } else if (temp2 !== null ) { temp.next = temp2; } return dummyHead.next; }; function toSortList (head, tail ) { if (head === null ) { return head; } if (head.next === tail) { head.next = null ; return head; } let slow = head, fast = head; while (fast !== tail) { slow = slow.next; fast = fast.next; if (fast !== tail) { fast = fast.next; } } const mid = slow; return merge(toSortList(head, mid), toSortList(mid, tail)); };
归并排序法,自下而上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 var sortList = function (head ) { if (!head || !head.next) return head; let slow = head, fast = head.next; while (fast && fast.next) { fast = fast.next.next; slow = slow.next; } let mid = slow.next; slow.next = null ; let left = sortList(head), right = sortList(mid); let h = new ListNode(); let res = h; while (left && right) { if (left.val < right.val) { h.next = left; left = left.next } else { h.next = right; right = right.next } h = h.next; } h.next = left ? left : right; return res.next; };
在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
示例 1:
1 2 输入: [3,2,1,5,6,4] 和 k = 2 输出: 5
示例 2:
1 2 输入: [3,2,3,1,2,4,5,5,6] 和 k = 4 输出: 4
说明:
你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。
方法一,直接用库函数 时间复杂度:O(nlogn),空间复杂度:O(logn)
1 2 3 4 var findKthLargest = function (nums, k ) { nums.sort((a, b ) => b - a); return nums[k - 1 ]; };
方法二,构造k个元素的小顶堆
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 var findKthLargest = function (nums, k ) { buildHeap(nums, k); for (let i = k; i < nums.length; i++) { if (nums[0 ] < nums[i]) { nums[0 ] = nums[i]; heapify(nums, k, 0 ); } } return nums[0 ]; }; let buildHeap = (arr, k ) => { if (k === 1 ) return ; for (let i = Math .floor((k - 1 ) / 2 ); i >= 0 ; i--) { heapify(arr, k, i); } } let heapify = (arr, k, i ) => { while (true ) { let minIndex = i; if (2 * i + 1 < k && arr[2 * i + 1 ] < arr[i]) { minIndex = 2 * i + 1 ; } if (2 * i + 2 < k && arr[2 * i + 2 ] < arr[minIndex]) { minIndex = 2 * i + 2 ; } if (minIndex !== i) { swap(arr, i, minIndex); i = minIndex; } else { break ; } } } let swap = (arr, i , j ) => { let temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; }
方法三:快速排序 时间复杂度:平均时间复杂度O(n),最坏情况时间复杂度为O(n2 ),空间复杂度:O(1)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 function partition (nums, left, right ) { let pivot = nums[left]; let j = left; for (let i = left + 1 ; i <= right; i++) { if (nums[i] < pivot) { j++; swap(nums, i, j); } } swap(nums, j, left); return j; }; function swap (nums, i, j ) { let temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; var findKthLargest = function (nums, k ) { let len = nums.length; let left = 0 ; let right = len - 1 ; let target = len - k; while (true ) { let index = partition(nums, left, right); if (index == target) { return nums[target]; } else if (index < target) { left = index + 1 ; } else { right = index - 1 ; } } };
给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
示例 1:
1 2 输入: nums = [1,1,1,2,2,3], k = 2 输出: [1,2]
示例 2:
1 2 输入: nums = [1], k = 1 输出: [1]
提示:
1 <= nums.length <= 105
k 的取值范围是 [1, 数组中不相同的元素的个数]
题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的
进阶: 你所设计算法的时间复杂度 必须 优于 O(n log n) ,其中 n 是数组大小。
无脑排序时间复杂度 O(n log n)
1 2 3 4 5 6 7 8 9 10 11 12 var topKFrequent = function (nums, k ) { let count = new Map (); for (let num of nums) { count.set(num, (count.get(num) || 0 ) + 1 ); } return Array .from(count).sort((a, b ) => b[1 ] - a[1 ]).slice(0 , k).map(item => item[0 ]); };
桶排序
首先使用哈希表统计频率,统计完成后,创建一个数组,将频率作为数组下标,对于出现频率不同的数字集合,存入对应的数组下标即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 var topKFrequent = function (nums, k ) { let map = new Map (); nums.map((num ) => { if (map.has(num)) map.set(num, map.get(num) + 1 ); else map.set(num, 1 ); }) if (map.size <= k) { return [...map.keys()]; } return bucketSort(map, k); }; function bucketSort (map, k ) { let arr = [], res = []; map.forEach((value, key ) => { if (!arr[value]) { arr[value] = [key]; } else { arr[value].push(key); } }) for (let i = arr.length - 1 ; i >= 0 && res.length < k; i--){ if (arr[i]) { res.push(...arr[i]); } } return res; }
堆排序 思路与算法
首先遍历整个数组,并使用哈希表记录每个数字出现的次数,并形成一个「出现次数数组」。找出原数组的前 kk个高频元素,就相当于找出「出现次数数组」的前 k 大的值。
最简单的做法是给「出现次数数组」排序。但由于可能有 O(N) 个不同的出现次数(其中 NN 为原数组长度),故总的算法复杂度会达O(NlogN),不满足题目的要求。
在这里,我们可以利用堆的思想:建立一个小顶堆,然后遍历「出现次数数组」:
如果堆的元素个数小于 k,就可以直接插入堆中。 如果堆的元素个数等于 k,则检查堆顶与当前出现次数的大小。如果堆顶更大,说明至少有 k 个数字的出现次数比当前值大,故舍弃当前值;否则,就弹出堆顶,并将当前值插入堆中。 遍历完成后,堆中的元素就代表了「出现次数数组」中前 k 大的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 var topKFrequent = function (nums, k ) { let map = new Map (), heap = [, ]; nums.map((num ) => { if (map.has(num)) map.set(num, map.get(num) + 1 ); else map.set(num, 1 ); }) if (map.size <= k) { return [...map.keys()]; } let i = 0 ; map.forEach((value, key ) => { if (i < k) { heap.push(key); if (i === k - 1 ) buildHeap(heap, map, k); } else if (map.get(heap[1 ]) < value) { heap[1 ] = key; heapify(heap, map, k, 1 ); } i++; }) heap.shift(); return heap; }; function buildHeap (heap, map, k ) { if (k === 1 ) return ; for (let i = Math .floor(k / 2 ); i >= 1 ; i--) { heapify(heap, map, k, i); } } function heapify (heap, map, k, i ) { while (true ) { let minIndex = i; if (2 * i <= k && map.get(heap[2 * i]) < map.get(heap[i])) { minIndex = 2 * i; } if (2 * i + 1 <= k && map.get(heap[2 * i + 1 ]) < map.get(heap[minIndex])) { minIndex = 2 * i + 1 ; } if (minIndex !== i) { swap(heap, i, minIndex); i = minIndex; } else { break ; } } } function swap (arr, i , j ) { let temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; }