昨天组会,所以停更一天。
Leetcode42 接雨水
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
今天的每日一题,是一道hard题。分享一个官方题解下的评论“打开一看,困难题(眉头一皱),定睛一看,接雨水,那没事了”。
这一题首先难点在于,如果确定一个位置能装的水的最大高度。一个位置能装水的最大高度,取决于左右两侧的最大高度的最小值。比如该位置左侧最大高度3,右侧最大高度5,那么能装的水的最大高度就是3。
根据找个条件,就很容易想到,用两个数组分别保存左侧的最大高度以及右侧的最大高度。
因此,可以得到
class Solution {
public int trap(int[] height) {
int ans = 0;
int len = height.length;
if(len==0 || len==1){
return 0;
}
int[] max = new int[len];
max[len-1] = 0;
for(int i = len-2; i>=0; i--){
max[i] = height[i+1] > max[i+1] ? height[i+1] : max[i+1];
}
max[0] = 0;
for(int i=1; i<len; i++){
int leftmax = height[i-1] > max[i-1] ? height[i-1] : max[i-1];
max[i] = max[i] < leftmax ? max[i] : leftmax;
if(max[i]>height[i]) {
ans += max[i] - height[i];
}
}
return ans;
}
}
这里我用一个辅助数组代替了两个辅助数组,本以为应该够简洁。但是在题解中,看到了双指针的方法,将空间复杂度降为O(1)。这个方法有很多种解释,我觉得最好的就是下面这个,大家可以参考。
https://leetcode-cn.com/problems/volume-of-histogram-lcci/solution/shuang-zhi-zhen-an-xing-qiu-jie-xiang-xi-d162/
剑指offer40 最小的k个数
输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
示例 1:
输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]
这是一道十分经典的TopK题目,也是受益颇多。
方法一:排序
这也是最简单的一种方法了,直接进行排序,然后取出前k个数即可。Java中数组排序功能Arrays.sort(),可以直接完成。
但是一道亮评 “如果面试时用sort,有被面试官写成段子发到知乎接收精英阶层群嘲的风险哦” ,应该懂了吧,这题远没有这么简单。
方法二 堆方法
虽然学过数据结构,但是我对堆这个数据结构已经基本忘光了,这算是补补课。
堆就是用数组实现的二叉树,所以它没有使用父指针或者子指针。堆根据“堆属性”来排序,“堆属性”决定了树中节点的位置。
堆分为两种:最大堆和最小堆,两者的差别在于节点的排序方式。
在最大堆中,父节点的值比每一个子节点的值都要大。在最小堆中,父节点的值比每一个子节点的值都要小。这就是所谓的“堆属性”,并且这个属性对堆中的每一个节点都成立。
在Java中,提供了PriorityQueue类实现小顶堆的功能。可以直接使用PriorityQueue()构造该类,主要的方法包括add() ,poll(),peek(),size()。实现add()和poll()的时间复杂度为O(n)。
了解了堆以后,这一题就十分简单,因为要找最小的k个数,只需要用一个大顶堆,维护当前最小的k个数,依次添加元素,如果堆的大小大于k,则删除最大的元素。
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
int[] vec = new int[k];
if(k==0){
return vec;
}
PriorityQueue<Integer> queue = new PriorityQueue<Integer> ((o1, o2) -> o2 - o1); // 定义比较函数,实现大顶堆
for(int num: arr){
queue.add(num);
if(queue.size() > k){
queue.poll();
}
}
for(int i = 0; i< k; i++){
vec[i] = queue.poll();
}
return vec;
}
}
方法三 快排
这里利用快速排序的思想。快排的划分函数每次执行完后都能将数组分成两个部分,小于等于分界值 pivot 的元素的都会被放到数组的左边,大于的都会被放到数组的右边,然后返回分界值的下标。与快速排序不同的是,快速排序会根据分界值的下标递归处理划分的两侧,而这里我们只处理划分的一边。
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if(k==0){
return new int[0];
}else if(arr.length <= k){
return arr;
}
partitionArray(arr, 0, arr.length-1, k);
int[] res = new int[k];
for(int i =0; i < k; i++){
res[i] = arr[i];
}
return res;
}
public void partitionArray(int[] arr, int l, int h, int k){
int m = partition(arr, l, h);
if(k==m){
return;
}else if(k<m){
partitionArray(arr, l, m-1, k);
}else{
partitionArray(arr, m+1, h, k);
}
}
public int partition(int[] arr, int l, int r){
int i = l;
int j = r;
int v = arr[l];
while(i < j){
while(j > i && arr[j] > v){
j--;
}
if(i < j){
arr[i++] = arr[j];
}
while(i < j && arr[i] < v){
i++;
}
if(i < j){
arr[j--] = arr[i];
}
}
arr[i] = v;
return i;
}
}