import java.util.HashMap;
/**
* @version 0.1
* @since 2021/10/25
* @author Void Bug
* 问题描述:
* 给定一个数组,值可以为正、负和0,请返回累加和小于等于k的最长子数组长度。
*/
public class Problem_02_LongestSubarrayLessSumAwesomeSolution {
/**
* 分两步来计算:先计算以每个数开头的最小累加和以及他们的右边界,再将累加和累加起来直至大于k。
* 首先计算以 i 开头往后的累加和,从数组尾部开始,举例如下:
*<br>
*<br>
* arr_index0 1 2 3 4 5 6 <br>
* arr_value 4 3 -2 6 7 -3 -1<br>
* min_value 4 1 -2 6 3 -4 -1<br>
*<br>
* min_index 0 2 2 3 6 6 6<br>
* arr_index 0 1 2 3 4 5 6<br>
* arr_value 4 3 -2 6 7 -3 -1<br>
*<br>
* min_value 4 1 -2 6 3 -4 -1<br>
* min_index 0 2 2 3 6 6 6<br>
* arr_index 0 1 2 3 4 5 6<br>
*<br>
* arr_value 4 3 -2 6 7 -3 -1<br>
* min_value 4 1 -2 6 3 -4 -1<br>
* min_index 0 2 2 3 6 6 6<br>
*
*
* 其中min_value是最小累加和,min_index是对应的右边界。从右往左遍历数组,如果要求出 i 位置的最小累加和,那么就要看 i+1 位置的最小累加和,如果min_value[ i+1 ]<0,那么min_value[ i ]=arr[ i ]+min_value[ i+1 ],min_index[ i ]=min_index[ i+1 ],否则min_value[ i ]=arr[ i ],min_index[ i ]=i。
* 接下来再从左往右遍历数组,如果以 i 开头的最小和>k,那么再往后面累加的累加和都>k。
*
* <img src="https://img-blog.csdn.net/20170726155238873?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXFfMjY5MTYzNTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描述" title="">
* 上图中的sum1,sum2,sum3,sum4等块中包含至少一个数,从l=0位置开始遍历,如果s+sum1<=k那么s+=sum1,记录下右边界,继续往后遍历,如果s+sum>k,记录下此时s对应的长度。然后 l 右移,此时在s中减去arr[ l ]的值,继续向后遍历,直至结束
* @param array 传递的 int[] 数组
* @param kValue 定值k
* @return 返回累加和不大于k的最长子数组长度
*/
public static int maxLengthAwesome(int[] array, int kValue) {
if (array == null || array.length == 0) {
return 0;
}
int[] sums = new int[array.length];//以i开头的最小值
HashMap<Integer, Integer> ends = new HashMap<>();//以i开头的右边界(i,右边界)
sums[array.length - 1] = array[array.length - 1];
ends.put(array.length - 1, array.length - 1);
for (int i = array.length - 2; i >= 0; i--) {
if (sums[i + 1] < 0) {
sums[i] = array[i] + sums[i + 1];
ends.put(i, ends.get(i + 1));
} else {
sums[i] = array[i];
ends.put(i, i);
}
}
int end = 0;//右边界
int sum = 0;//最长的和
int res = 0;//左边界
for (int i = 0; i < array.length; i++) {
while (end < array.length && sum + sums[end] <= kValue) {
//逐步向后加直至到结尾或者大于k
sum += sums[end];
end = ends.get(end) + 1;
}
//下一轮循环应该从i+1开始,但是不需要重新计算,直接将s中去掉arr[i]即可
sum -= end > i ? array[i] : 0;
res = Math.max(res, end - i);
end = Math.max(end, i + 1);
}
return res;
}
/**
* 测试方法,也可以说是方法二
* 1.我们首先算出到每个元素时候的元素之和
* 2.我们计算到达每个元素时候的返回的长度,与我们先前的最长值进行比较
* @param array 传递的 int[] 数组
* @param kValue 定值k
* @return 返回累加和不大于k的最长子数组长度
*/
public static int maxLength(int[] array, int kValue) {
int[] helpArray = new int[array.length + 1];
int sum = 0;
helpArray[0] = sum;
//计算出到每个元素时候的元素之和,将值记录在 helpArray 数组中
for (int i = 0; i != array.length; i++) {
sum += array[i];
helpArray[i + 1] = Math.max(sum, helpArray[i]);
}
sum = 0; //计算出到每个元素时候的元素之和,方便继续比较
int res = 0,pre = 0,len = 0; //pre 大于k的长度,res到每个元素时候返回的最大值,len表示到达某个位置时候返回的长度
for (int i = 0; i != array.length; i++) {
sum += array[i];
pre = getLessIndex(helpArray, sum - kValue);//得到 >k 时候得到的长度值
len = pre == -1 ? 0 : i - pre + 1;
res = Math.max(res, len);
}
return res;
}
/**
* 得到 >k 时候得到的长度值
* @param array 传递的 int[] 数组
* @param kValue 定值k@return
* @return 时候得到的长度值
*/
public static int getLessIndex(int[] array, int kValue) {
int low = 0;
int high = array.length - 1;
int mid = 0;
int res = -1;
while (low <= high) {
mid = (low + high) / 2;
if (array[mid] >= kValue) {
res = mid;
high = mid - 1;
} else {
low = mid + 1;
}
}
return res;
}
/**
* 生成 len 个元素,并且 最大的值是 maxValue 的数组
* @param len 维度
* @param maxValue 产生的最大值
* @return 返回 len 个元素,并且 最大的值是 maxValue 的数组
*/
public static int[] generateRandomArray(int len, int maxValue) {
int[] res = new int[len];
for (int i = 0; i != res.length; i++) {
res[i] = (int) (Math.random() * maxValue) - (maxValue / 3);
}
return res;
}
/**
* 主方法
* @param args 输入的信息
* 测试方法
*/
public static void main(String[] args) {
for (int i = 0; i < 1000000; i++) {
int[] arr = generateRandomArray(10, 20);
int k = (int) (Math.random() * 20) - 5;
if (maxLengthAwesome(arr, k) != maxLength(arr, k)) {
System.out.println("error!");
}
}
}
}
import java.util.HashMap;
/**
* 问题描述
* 给定一个数组,值全是正数,请返回累加和为给定值k的最长子数组长度。
* 给定一个数组,值可以为正、负和0,请返回累加和为给定值k的最长子数组长度。
*/
public class Problem_02_LongestSumSubArrayLength {
/**
* 返回累加和为给定值k的最长子数组长度。
* map 存储的是我们生成的 Map<总和值,元素下标>
* 解法思路:
* 我们进行一下反推,
* 1.我们现在求的是等于k的最长子数组长度,及我们只要求出 从 n到 m的数组是最大的等于k 的值的数组长度即可
* 2.我们要求出 从 n到 m的数组是最大的等于k ,我们可以发现其实我们可以把他看成
* Sum(下标为0到m的元素列表) - Sum(下标为0到n的元素列表) = Sum(m+1到n元素列表)
* 3.那么我们让一个`Map`去存储到`m`个元素时候我们的值和它的下表,存储关系就变成了map(sumValue,index)的存储关系,但是在这里我们要注意一点,
* 那么就是我们在插入的时候一定要在之前插入到一个(0,-1),为什么呢,因为我们如果不插入的话,假设我们的第一项极为所求,那么我们就会错过0到n之间的所有数组
* 我们来举一个例子 array=[4,12,1,-2];k=4 , 假设我们不加这个的话,我们 sum=4 时,及4-4=0,(0,-1)又不存在,我们会忽视掉这个元素的值
* 0 代表元素之和,-1 代表元素下标,及其中没有元素时的下标
*
* 推导完毕,现在我们再来总结一个正推规律
* 1.我们使用Map或者其他数据类型将值进行存储
* 2.我们在Map中插入(0,-1) 0 代表元素之和,-1 代表元素下标,及其中没有元素时的下标
* 3.Sum(array[0~j])-Sum(array[0~i])=k ==> k=array[i+1~j]
* 4.如果我们的 k=array[i+1~j] 找到了 返回 j-(i+1) 即可,否则我们继续加入我们的 Map 即可
*
* 参数详解
* @param array 传递的 int[] 数组
* @param k 定值k
* @return 返回累加和为给定值k的最长子数组长度。
*/
public static int maxLengthArray(int[] array, int k) {
if (array == null || array.length == 0) {
return 0;//判断是否为空的数组,如果为空的数组则返回0即可
}
HashMap<Integer, Integer> map = new HashMap<>(); // 存储
map.put(0, -1); // important
int len = 0;
int sum = 0;
for (int i = 0; i < array.length; i++) {
sum += array[i];//0到i
// 包含这个值
if (map.containsKey(sum - k)) {
// 求的 sum-k 的值已经找到了,记录全局最大
len = Math.max(i - map.get(sum - k), len);
}
//第一次没有出现
if (!map.containsKey(sum)) {
map.put(sum, i);
}
}
return len;
}
/**
* 对数器,生成一个随机的数组
* @param size 产生数组的维度
* @return 返回产生的随机数组
*/
public static int[] generateArray(int size) {
int[] result = new int[size];
for (int i = 0; i != size; i++) {
result[i] = (int) (Math.random() * 11) - 5;
}
return result;
}
/**
* 输出 array 数组
* @param arr 数组维度
*/
public static void printArray(int[] arr) {
for (int i = 0; i != arr.length; i++) {
System.out.print(arr[i] + ",");}
System.out.println();
}
/**
* 主方法
* @param args 输入的信息
* 测试方法
*/
public static void main(String[] args) {
int[] arr = generateArray(20);
printArray(arr);
System.out.println(maxLengthArray(arr, 10));
}
}