Leetcode刷题 2021.02.03
Leetcode1300 转变数组后最接近目标值的数组和
给你一个整数数组 arr 和一个目标值 target ,请你返回一个整数 value ,使得将数组中所有大于 value 的值变成 value 后,数组的和最接近 target (最接近表示两者之差的绝对值最小)。
如果有多种使得和最接近 target 的方案,请你返回这些整数中的最小值。
请注意,答案不一定是 arr 中的数字。
这种要求不是已知数据的值,并且是最小最大之类的题基本就是二分了。这题想到二分以后越想越复杂,还加上了前缀和等等,以为自己想错了,结果看了官方题解做法基本一致。官方题解用了Arrays.binarySearch()这个库函数,都不知道,于是自己又手写了一个,等于写了两道题了。话说字节就爱考这种,把几个问题合起来的综合题。
class Solution {
int[] help;
public int findBestValue(int[] arr, int target) {
//先排序,因为要对数组做一次二分,求大于某个值转变后的总和
Arrays.sort(arr);
int sum = 0;
//记录前缀和,方便后续求和
help = new int[arr.length];
for(int i = 0; i < arr.length; i++){
help[i] = sum + arr[i];
sum = help[i];
}
//如果求和小于target,直接返回就行了
if (sum < target) return arr[arr.length - 1];
//二分逼近
int i = 0, j = 100001;
while (i < j){
int pivot = i + (j - i) / 2;
//再做一次二分,这个函数表示第一个大于pivot值的下标
int index = binarySearch(arr, pivot);
int temp = sum;
if (index < arr.length){
//求出把index之后的值都替换成给定值的和,用上之前的前缀和
temp = help[index] + (arr.length - index) * pivot - arr[index];
}
//正常二分,这里是求出大于等于target的第一个值
if (temp < target){
i = pivot + 1;
}else{
j = pivot;
}
}
//因为小于这个值的元素有可能更接近,所以最后要再判断一下,本来想着这里也二分,然后想想不可能这么复杂吧,官方题解直接就遍历了。。
int chooseSmall = check(arr, i - 1);
int chooseBig = check(arr, i);
return Math.abs(chooseSmall - target) <= Math.abs(chooseBig - target) ? i - 1 : i;
}
public int check(int[] arr, int x) {
int ret = 0;
for (int num : arr) {
ret += Math.min(num, x);
}
return ret;
}
private int binarySearch(int[] arr, int pivot){
int i = 0, j = arr.length - 1;
while (i < j){
int mid = i + (j - i) / 2;
if (arr[mid] <= pivot){
i = mid + 1;
}else{
j = mid;
}
}
return arr[i] > pivot ? i : i + 1;
}
}
Leetcode1493 删掉一个元素以后全为 1 的最长子数组
给你一个二进制数组 nums ,你需要从中删掉一个元素。
请你在删掉元素的结果数组中,返回最长的且只包含 1 的非空子数组的长度。
如果不存在这样的子数组,请返回 0 。
本来想自己在好好整理下滑动窗口的,结果二月好像是滑动窗口月,那就等着LeetCode帮着整理吧。这题是昨天题解里给出的类似的题,基本一模一样的代码,没什么好讲的了。
class Solution {
public int longestSubarray(int[] nums) {
int i = 0, j = 0, res = 0, count = 0, n = nums.length;
while (j < n){
if (nums[j] == 1) count++;
while (j - i + 1 > count + 1){
if (nums[i] == 1) count--;
i++;
}
res = Math.max(res, j - i);
j++;
}
return res;
}
}
Leetcode480 滑动窗口中位数
中位数是有序序列最中间的那个数。如果序列的长度是偶数,则没有最中间的数;此时中位数是最中间的两个数的平均数。
例如:
[2,3,4],中位数是 3
[2,3],中位数是 (2 + 3) / 2 = 2.5
给你一个数组 nums,有一个长度为 k 的窗口从最左端滑动到最右端。窗口中有 k 个数,每次窗口向右移动 1 位。你的任务是找出每次窗口移动后得到的新窗口中元素的中位数,并输出由它们组成的数组。
今天的每日一题,其实也能想到和那道数据流中的中位数类似的方法。但是后面涉及到要删除的话就不太好想了,看了官方代码,其实也不难理解,但是要把代码优化成这个样子难度可就大了。好像C++有很便捷的方法,Java的话就看官方代码吧。这题就放着记录一下吧,估计面试的话也不会考这么复杂的代码。
class Solution {
public double[] medianSlidingWindow(int[] nums, int k) {
DualHeap dh = new DualHeap(k);
for (int i = 0; i < k; ++i) {
dh.insert(nums[i]);
}
double[] ans = new double[nums.length - k + 1];
ans[0] = dh.getMedian();
for (int i = k; i < nums.length; ++i) {
dh.insert(nums[i]);
dh.erase(nums[i - k]);
ans[i - k + 1] = dh.getMedian();
}
return ans;
}
}
class DualHeap {
// 大根堆,维护较小的一半元素
private PriorityQueue<Integer> small;
// 小根堆,维护较大的一半元素
private PriorityQueue<Integer> large;
// 哈希表,记录「延迟删除」的元素,key 为元素,value 为需要删除的次数
private Map<Integer, Integer> delayed;
private int k;
// small 和 large 当前包含的元素个数,需要扣除被「延迟删除」的元素
private int smallSize, largeSize;
public DualHeap(int k) {
this.small = new PriorityQueue<Integer>(new Comparator<Integer>() {
public int compare(Integer num1, Integer num2) {
return num2.compareTo(num1);
}
});
this.large = new PriorityQueue<Integer>();
this.delayed = new HashMap<Integer, Integer>();
this.k = k;
this.smallSize = 0;
this.largeSize = 0;
}
public double getMedian() {
return (k & 1) == 1 ? small.peek() : ((double) small.peek() + large.peek()) / 2;
}
public void insert(int num) {
if (small.isEmpty() || num <= small.peek()) {
small.offer(num);
++smallSize;
} else {
large.offer(num);
++largeSize;
}
makeBalance();
}
public void erase(int num) {
delayed.put(num, delayed.getOrDefault(num, 0) + 1);
if (num <= small.peek()) {
--smallSize;
if (num == small.peek()) {
prune(small);
}
} else {
--largeSize;
if (num == large.peek()) {
prune(large);
}
}
makeBalance();
}
// 不断地弹出 heap 的堆顶元素,并且更新哈希表
private void prune(PriorityQueue<Integer> heap) {
while (!heap.isEmpty()) {
int num = heap.peek();
if (delayed.containsKey(num)) {
delayed.put(num, delayed.get(num) - 1);
if (delayed.get(num) == 0) {
delayed.remove(num);
}
heap.poll();
} else {
break;
}
}
}
// 调整 small 和 large 中的元素个数,使得二者的元素个数满足要求
private void makeBalance() {
if (smallSize > largeSize + 1) {
// small 比 large 元素多 2 个
large.offer(small.poll());
--smallSize;
++largeSize;
// small 堆顶元素被移除,需要进行 prune
prune(small);
} else if (smallSize < largeSize) {
// large 比 small 元素多 1 个
small.offer(large.poll());
++smallSize;
--largeSize;
// large 堆顶元素被移除,需要进行 prune
prune(large);
}
}
}