休息了两天,开始继续写题。 记得完成自适应光学、信息论和智能图像处理的作业。
[x] 自适应光学 [ ] 信息论 [ ] 智能图像处理 读题 给定整数数组 nums 和整数 k,请返回数组中第 **k** 个最大的元素。 请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。 你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。
要求有二: 一是需要满足条件找到指定的数,二是对时间复杂度有要求。
思路 第一思路 给了 vector,于是想到对数组排序后直接取数;但是要求 O(n) 时间复杂度,也就是如果要遍历的话只能遍历一遍或有限遍。 如果遍历 k 遍,每遍得到一个最大值,那么复杂度是 O(kn)(大概)。 于是过程:
循环 k 次;创建一个等长的全 0 空数组第 i 次循环 跳过空数组对应位置不为 0 的位置 遇到的最大值为 max_num,记录对应位置,将空数组对应位置标记为 i 继续 考虑到 k 可能会比较大,如果 k 大于 size_nums 的一半,则改为找最小值。
于是有代码:
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 class Solution {public : int findKthLargest (vector<int >& nums, int k) { int size_nums = nums.size (); if (k<size_nums-k){ vector<int > max_nums (size_nums) ; int max_num = 0 ; int pos = -1 ; for (int i=0 ;i<=k;i++){ max_num = 0 ; pos = -1 ; for (int j=0 ;j<size_nums;j++){ if (max_nums[j]!=0 ) continue ; max_num = max_num>nums[j]?max_num:nums[j]; pos = max_num>nums[j]?pos:j; } max_nums[pos]=i; } return max_num; }else { vector<int > min_nums (size_nums); k = size_nums-k; int min_num = 0 ; int pos = -1 ; for (int i=0 ;i<=k;i++){ min_num=114514 ; pos = -1 ; for (int j=0 ;j<size_nums;j++){ if (min_nums[j]!=0 ) continue ; min_num = min_num<nums[j]?min_num:nums[j]; pos = min_num<nums[j]?pos:j; } min_nums[pos]=i; } return min_num; } } };
没超时,不过在一个小数列处出错了;于是把
k<size_nums-k 改成了
k<=size_nums-k。
现在在包含负数的地方出错了,应该是 max_num 初始化的问题?测试用例是
[-1,2,0],k 是 2,结果是 0,我输出是 -1。
哦,不是,是我在 k>size_nums-k 时这部分的判断问题。第 k 大应当对应第 size_nums-k+1 小(比方说大小为 3 的数组中,第 2 大应该是第 2 小),而不是第 size_nums-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 class Solution {public : int findKthLargest (vector<int >& nums, int k) { int size_nums = nums.size (); if (k<=size_nums-k){ vector<int > max_nums (size_nums) ; int max_num; int pos; for (int i=0 ;i<=k;i++){ max_num = -114514 ; pos = -1 ; for (int j=0 ;j<size_nums;j++){ if (max_nums[j]!=0 ) continue ; max_num = max_num>nums[j]?max_num:nums[j]; pos = max_num>nums[j]?pos:j; } max_nums[pos]=i; } return max_num; }else { vector<int > min_nums (size_nums); k = size_nums-k+1 ; int min_num; int pos; for (int i=0 ;i<=k;i++){ min_num=114514 ; pos = -1 ; for (int j=0 ;j<size_nums;j++){ if (min_nums[j]!=0 ) continue ; min_num = min_num<nums[j]?min_num:nums[j]; pos = min_num<nums[j]?pos:j; } min_nums[pos]=i; } return min_num; } } };
提交,然后不出所料地超时了。这个测试用例的 nums 巨大一坨,size 是 35107,k 是 15991,也就是在中间,刚好是我最怕的那种。 这题 k 是 n/2,那么我的复杂度就成了 O(n^2) 了。
改变思路 需要 O(n),我的直觉告诉我用之前遇到过的单调栈来解。但是怎么解? 维护一个容量为 k 的单调栈,栈顶最小,栈底最大。
num 大于栈顶的数字吗?是。栈顶出栈,重新判断,直到 num 小于栈顶元素。 否。栈是否已满?是继续前进。 否num 入栈。 于是有代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution {public : int findKthLargest (vector<int >& nums, int k) { stack<int > s; int size_nums = nums.size (); for (int i = 0 ; i < size_nums; i++) { if (s.empty ()) s.push (nums[i]); if (nums[i] > s.top ()) { i--; s.pop (); continue ; } else { if (s.size () >= k) continue ; else { s.pop (); s.push (nums[i]); } } } return s.top (); } };
但是发现无法满足“筛选出最大的 k 的元素要求”,因为只能看到栈顶元素;如果一直出栈,那就会导致之前的元素都被丢掉。如果记录并重新入栈,则会导致时间复杂度飙升。
再次改变思路 首先,明白我需要什么。不能强行使用单调栈这种不适合的数据结构。我需要的是一个能存储包含 k 个数据的单调数据结构。假如有一个数据结构叫 ASD,有最大大小 k,那么我需要的是:
ASD 是否为满?不为满当前元素是最大的/最小的吗?是:直接放在开头/结尾 否:插入 为满:插入 但是插入到正确位置这个过程最大难免会有 O(k) 复杂度。有什么数据结构能满足我的要求?还是说我的思路是错误的?
官方题解 不是我能做出来的题,直接看题解。 官方题解的都是 O(nlogn) 的时间复杂度,题干里却要求 O(n), 评论直接骂说左右脑互博。还有评论说直接秒解:
1 2 3 4 public int findKthLargest (int [] nums, int k) { Arrays.sort(nums); return nums[nums.length - k]; }
难绷。难道他是天才?
方法一:快速排序 类似于我的第一反应:先排序,后定位。但是使用的是快速排序,所以单次的时间复杂度是 O(logn) 而不是 O(n)。上面那个 Arrays.sort(nums) 应该就是 java 的快速排序库函数。
有官方代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution {public : int quickselect (vector<int > &nums, int l, int r, int k) { if (l == r) return nums[k]; int partition = nums[l], i = l - 1 , j = r + 1 ; while (i < j) { do i++; while (nums[i] < partition); do j--; while (nums[j] > partition); if (i < j) swap (nums[i], nums[j]); } if (k <= j)return quickselect (nums, l, j, k); else return quickselect (nums, j + 1 , r, k); } int findKthLargest (vector<int > &nums, int k) { int n = nums.size (); return quickselect (nums, 0 , n - 1 , n - k); } };
不是我说,这几乎就是自己搓了一个 qsort 吧?不过 cpp 中似乎没有 qsort。搜了一下发现 qsort 用的也是快速排序。
方法二:基于堆排序的选择方法 见过但是不记得的数据结构:堆。有定义: 堆(英语:heap) 是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:
堆中某个节点的值总是不大于或不小于其父节点的值; 堆总是一棵完全二叉树。 将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。常见的堆有二叉堆、斐波那契堆等。 确实是自带排序、方便维护和搜索的数据结构。
有官方代码:
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 class Solution {public : void maxHeapify (vector<int >& a, int i, int heapSize) { int l = i * 2 + 1 , r = i * 2 + 2 , largest = i; if (l < heapSize && a[l] > a[largest]) { largest = l; } if (r < heapSize && a[r] > a[largest]) { largest = r; } if (largest != i) { swap (a[i], a[largest]); maxHeapify (a, largest, heapSize); } } void buildMaxHeap (vector<int >& a, int heapSize) { for (int i = heapSize / 2 - 1 ; i >= 0 ; --i) { maxHeapify (a, i, heapSize); } } int findKthLargest (vector<int >& nums, int k) { int heapSize = nums.size (); buildMaxHeap (nums, heapSize); for (int i = nums.size () - 1 ; i >= nums.size () - k + 1 ; --i) { swap (nums[0 ], nums[i]); --heapSize; maxHeapify (nums, 0 , heapSize); } return nums[0 ]; } };
收获 堆 快速排序 复习了单调栈(虽然没有用上)