每日打卡:转变数组后最接近目标值的数组和

打卡: 转变数组后最接近目标值的数组和

心情

周末刷完了《十日游戏》,关于影子与公主的爱情故事,看到了爱情、欺骗、谎言、赎罪。特别喜欢东野圭吾的悬疑小说,还有"于海" 简直是技术宅中的神~。

读题

leetcode: 1300. 转变数组后最接近目标值的数组和

描述:
给你一个整数数组 arr 和一个目标值 target ,请你返回一个整数 value ,使得将数组中所有大于 value 的值变成 value 后,数组的和最接近 target (最接近表示两者之差的绝对值最小)。
如果有多种使得和最接近 target 的方案,请你返回这些整数中的最小值。
请注意,答案不一定是 arr 中的数字。

测试用例:
输入:arr = [4,9,3], target = 10
输出:3
输入:arr = [2,3,5], target = 10
输出:5
输入:arr = [60864,25176,27249,21296,20204], target = 56803
输出:11361

1 <= arr.length <= 10^4
1 <= arr[i], target <= 10^5

虽说标签中提到了二分查找,但是感觉完全没有必要呀。

思路

1:先排序,从小到大
2:找到临界的那个数
3:将临界的那个数减去超出的部分
4:判断符合条件的最小值

实现

public int findBestValue(int[] arr, int target) {
    //先排序
    Arrays.sort(arr);
    int sum = 0, temp = 0, i;
    for (i = 0; i < arr.length; i++) {
        temp = sum + (arr.length - i) * arr[i];
        //找到临界数据 arr[i]
        if (temp > target) {
            //若超过了target,则将超过的数值赋给temp
            temp = temp - target;
            break;
        } else if (temp == target || i == arr.length - 1) {
            //若是刚好相等或者最后一个值也无法达到target,直接返回
            return arr[i];
        }
        sum += arr[i];
    }
    //通过arr[i]-单个值超出的部分得到结果。考虑到小数,会有两种结果
    int left = arr[i] - (int) Math.ceil(temp * 1.0 / (arr.length - i));
    int right = arr[i] - (int) Math.floor(temp * 1.0 / (arr.length - i));
    if(left == right) return left;
    //right==left+1,此时比较那个结果距离target更近,若是一样近则取left
    return (sum * 2 + (left + right) * (arr.length - i)) / 2 < target ? right : left;
}

感觉还是很好理解的,考虑到前面的数字必须要求和,无法避免for循环。

提交

在这里插入图片描述
我试着优化了几次,按照这个思路已经优化到极限了。看下官方怎么使用二分法来处理的吧。

标答

public int findBestValue(int[] arr, int target) {
    Arrays.sort(arr);
    int len = arr.length;
    int curSum = 0;
    for (int i = 0; i < len; i++) {
    	//为了满足target,整数的解为curAve
        int curAve = (target - curSum) / (len - i);
        if (curAve <= arr[i]) {
            //到达了临界点,此时带小数的解为curAveDou
            double curAveDou = (target * 1.0 - curSum) / (len - i);
            if (curAveDou - curAve <= 0.5) {
            	//这个0.5比较难理解
                return curAve;
            } else {
                return curAve + 1;
            }
        }
        curSum += arr[i];
    }
    //找不到临界点则返回最大值
    return arr[len - 1];
}

标答取自评论区某大神
那个0.5我还是没办法理解,不过可以推导出来。
在我的解法中,结果为 (sum * 2 + (left + right) * (arr.length - i)) / 2 < target ? right : left 看平均值在target的左边还是右边,来判断哪个解更接近目标值。
取left的值需要满足:target >= (sum * 2 + (left + right) * (arr.length - i)) / 2
target 2 - sum2 >= (left + right) * (arr.length - i)
arr.length-i必然是大于0的,right=left+1,不然也不会走到这一步。
(target - sum)/(arr.length - i) >= (left+right)/2
不等书左侧公式=题解中的curAveDou
不等式右侧公式中left=题解中的curAve
curAveDou*2>=curAve+curAve+1
curAveDou-curAve>=0.5
由上述的推导过程,若需要取left,则需要满足curAveDou-curAve>=0.5
只能说大神还是厉害呀,这个0.5的判断我是想不到的!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值