【LeetCode】Sama的个人记录_20

 

 

【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

你可能感兴趣的:(Leetcode)