【Q1300】(md) 转变数组后最接近目标值的数组和
给你一个整数数组 arr 和一个目标值 target ,请你返回一个整数 value ,使得将数组中所有大于 value 的值变成 value 后,数组的和最接近 target (最接近表示两者之差的绝对值最小)。
如果有多种使得和最接近 target 的方案,请你返回这些整数中的最小值。
请注意,答案不一定是 arr 中的数字。
示例 1:
输入:arr = [4,9,3], target = 10
输出:3
解释:当选择 value 为 3 时,数组会变成 [3, 3, 3],和为 9 ,这是最接近 target 的方案。
示例 2:
输入:arr = [2,3,5], target = 10
输出:5
示例 3:
输入:arr = [60864,25176,27249,21296,20204], target = 56803
输出:11361
注:
1 <= arr.length <= 10^4
1 <= arr[i], target <= 10^5
class Solution {
/*
* 1. 思路的本质是:最终结果可能是[0, 数组最大值]中的任何一个数字,我们就对这些数字做一次遍历,看看究竟谁对应currentSum更接近target
* O(n)遍历过去显然有些暴力,因此想到了优化的方法:【二分法】—— 不一个一个的取试探,而是用mid去试探
* 2. 但是,怎样根据题目给的规则得到currentSum也需要算法:【数组排序 + 前缀和 + 二分查找(这里是找某个数字的位置)】
*
* 因此,这道题是分两部分的:二分取数字;依据该数字计算数组和。
*
*【二分法】
* 关键:二分的不是下标,而是值;再根据该值去算currentSum
*
* 先把数组排序
* 这个value下限为0,上限为数组的最大值
* 以此为l和r进行二分
*
* 【数组转化求和】
* 根据每次二分得到的mid的值,计算此时的和————这里使用到了两个技巧:
* 一是通过前缀和减少重复计算
* 二是通过binarySearch二分查找到此时mid值在arr中的位置,以此来计算和
*/
public int findBestValue(int[] arr, int target) {
Arrays.sort(arr);
int len = arr.length;
int[] arrSum = new int[len + 1];
for(int i = 1; i < len + 1; i++) {
arrSum[i] = arrSum[i - 1] + arr[i - 1];
}
int l = 0;
int r = arr[len - 1];
int res = -1;
while(l <= r) {
int mid = (l + r) / 2;
int index = Arrays.binarySearch(arr, mid);
if(index < 0) {
index = (-index) - 1;
}
int currentSum = arrSum[index] + mid * (len - index);
if(currentSum <= target) {
res = mid;
l = mid + 1;
}else {
r = mid - 1;
}
}
int d1 = Math.abs(countSum(arr, res) - target);
int d2 = Math.abs(countSum(arr, res + 1) - target);
// 为什么我们要对 res 和 res+1 进行一次特判,而不是res-1呢?
// 这么理解:
// 上面在二分时,res能被修改的条件,被限制在currentSum <= target;也就是说,只有当此时的和小于等于目标值,res才允许被修改
// [res是满足小于等于targer条件的最大值]
// 可是,其实,这个最终结果又可能出现在currentSum刚好比target大一点点的时候;因此,res又可能还要多出1,需要进行特判
return d1 <= d2 ? res : res + 1;
}
private int countSum(int[] arr, int num) { // 这里用不用countSum函数无所谓,和上面一样用【前缀和】也行
int sum = 0;
for(int n : arr) {
if(n > num) {
sum += num;
}else {
sum += n;
}
}
return sum;
}
}
【Q5438】(md) 制作m束花需要的最小天数
给你一个整数数组 bloomDay,以及两个整数 m 和 k 。
现需要制作 m 束花。制作花束时,需要使用花园中 相邻的 k 朵花 。
花园中有 n 朵花,第 i 朵花会在 bloomDay[i] 时盛开,恰好 可以用于 一束 花中。
请你返回从花园中摘 m 束花需要等待的最少的天数。如果不能摘到 m 束花则返回 -1 。
示例 1:
输入:bloomDay = [1,10,3,10,2], m = 3, k = 1
输出:3
示例 2:
输入:bloomDay = [1,10,3,10,2], m = 3, k = 2
输出:-1
示例 3:
输入:bloomDay = [7,7,7,7,12,7,7], m = 2, k = 3
输出:12
示例 4:
输入:bloomDay = [1000000000,1000000000], m = 1, k = 1
输出:1000000000
示例 5:
输入:bloomDay = [1,10,2,9,3,8,4,7,5,6], m = 4, k = 2
输出:9
class Solution {
/*
*【二分法】
*
* 问题拆成两部分:
* 1.天数可取值为[1, 数组最大值],直接遍历过于暴力,因此优化为【二分法】(本质都一样,就是不断用数字试探)
* 2.已知某个天数,需要计算有多少个"相邻的k朵花",该值作为二分的依据
*
* 第一部分的【二分法】比较套路化;第二部分的计数也不要想复杂;
*
* 【二分法】的套路化写法中,有一点需要重点理解:
* 这句代码:if(countM_2(bloomDay, k, mid) >= m) { res = mid;}
* 关键在于:res的赋值写在了"大于等于"的下方,那么【可以保证res天时可以摘大于等于m束花】;不断的二分逼近,又【保证res是满足条件(也就是>=)的最小天数】
* 如果res的赋值写在"小于等于"下时,就能【保证res是满足小于等于束花的最大天数】 ———— 这就是我所说的"套路化写法"
*/
public int minDays(int[] bloomDay, int m, int k) {
int res = -1;
int l = 1;
int r = getMax(bloomDay);
while(l <= r) {
int mid = (l + r) / 2;
if(countM(bloomDay, k, mid) >= m) {
res = mid;
r = mid - 1;
}else {
l = mid + 1;
}
}
return res;
}
private int getMax(int[] arr) { // 获取数组最大值
int[] tempArr = Arrays.copyOf(arr, arr.length);
Arrays.sort(tempArr);
return tempArr[tempArr.length - 1];
}
private int countM_2(int[] bloomDay, int k, int mid) { // 根据此时的天数,计算可以做几束花
int count = 0;
int len = 0;
for(int i = 0; i < bloomDay.length; i++) {
if(bloomDay[i] <= mid) {
len++;
if(len == k) {
count++;
len = 0;
}
}else {
len = 0;
}
}
return count;
}
}
/*
* 不知道是不是巧合,这道题和上一题分别是同一天的周赛题和每日一题,一模一样的思路:
* 都是把问题拆成两部分:如何取值(二分法)+取值后如何进行计算(自拟算法),并根据计算结果修改二分的边界。
*/
【Q43】(md) 字符串相乘
给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式。
示例 1:
输入: num1 = “2”, num2 = “3”
输出: “6”
示例 2:
输入: num1 = “123”, num2 = “456”
输出: “56088”
class Solution {
/*
* 核心:利用一个数组实现竖式计算
*
* 首先数组的大小,也就是结果串的长度,不会超过两个数长度的和(小学数学)
* for i for j 两层循环遍历,某两位相乘的结果加在下标 k = i+j+1 处,每次有加法操作,就用while操作来尝试进位
*
* 这个竖式也有奇怪的地方,比如"123"×"45":
* 1和4是最高位,但它们的下标都是0;也就是说,我们是先算高位再算低位,但我们是通过下标对应位数的,哪种顺序其实都无所谓
* 我们为什么使用 i+j+1 呢?因为最高位的i和j都是0,它们有可能会向前进位,我们要预留出来
*
*/
public String multiply(String num1, String num2) {
if(num1.equals("0") || num2.equals("0")) return "0";
// if(num1 == "0" || num2 == "0") return "0"; // 千万不要这样写,传入的字符串实参如果是new出来的,这样就会出错!!!
int len1 = num1.length();
int len2 = num2.length();
int[] resArr = new int[len1 + len2];
for(int i = 0; i < len1; i++) {
for(int j = 0; j < len2; j++) {
int k = i + j + 1;
resArr[k] += (num1.charAt(i) - '0') * (num2.charAt(j) - '0'); // 字符转化为数字的方法常用且巧妙:c - '0'
while(resArr[k] >= 10) {
resArr[k - 1] += (resArr[k] / 10);
resArr[k] %= 10;
k--;
}
}
}
return trans(resArr);
}
private String trans(int[] arr) { // 这个方法用来将数组转化为字符串
StringBuilder sb = new StringBuilder();
for(int n : arr) {
sb.append(String.valueOf(n));
}
return sb.toString().replaceAll("^(0+)", ""); // 去掉字符串前面的"0"
}
// 字符串大数的相加,也是同样的思路:一位一位的计算;再手动进位
// 目的很明确:防止溢出
}
Qs from https://leetcode-cn.com
♥ loli suki
♦ end