算法学习(30)-最大总和数组问题

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));
    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值