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);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值