题目四:给定一个数组,值可以为正、负和0,请返回累加和小于等于k的最长子数组长度。 时间复杂度:O(n)

import java.util.HashMap;

/**
 * 
 * 3、给定一个数组,值可以为正、负和0,请返回累加和小于等于k的最长子数组长度。 时间复杂度:O(n)
 *  
 *  这里需要分为两步,第一步是获取,以每个位置开头最小和的长度。第二步,从0到N逐一判断刚才最小长度是否可以合并在一起达到小于等于k的效果。
 *     
 *   第一步:   获取以每个位置开头的最小和的长度。
 *         可以从后向前计算,把问题变成以某个位置结尾和的最小子串的长度。
 *         例如:   index = 7 , value  = -2 ;   min_sum = -2 ; min_index = 7;
 *              index = 6 , value  = -1 ;   min_sum = -3 ; min_index = 6;
 *              index = 5 , value  = -3 ;   min_sum = -6 ; min_index = 5;
 *              index = 4 , value  =  7 ;   min_sum =  1 ; min_index = 4;
 *              从后向前计算的时候,当发现i+1的min_sum值为正数的时候。则把当前位置i设置为min_index = i ,min_sum = value.
 *                因为这样就是计算以每个位置开头的最小和的长度。-----------------这很重要,也是一种思想。
 *              index = 3 , value  =  6 ;   min_sum =  6 ; min_index = 3;
 *              index = 2 , value  = -2 ;   min_sum = -2 ; min_index = 2;
 *               
 *   index  :       0	     1	      2		  3		  4		  5		   6     7       
 *   value  :       4		 3       -2       6       7      -3       -1     -2  
 *   
 *   
 *   min_index: 	0		 2		  2	      3		  7		  7		   7	  7
 *   min_sum :      4        1       -2       6       1      -6       -3     -2  
 *   			
 *  
 *  
 *  第二步:从0到N逐一判断刚才最小长度是否可以合并在一起达到小于等于k的效果。
 *  	  注意:1,从0-N开始位置index,一个一个遍历。
 *           2,每当遇到一个开始位置index = i时, end = min_index[i],
 *             判断当前sum+=min_sum的大小是否<=K,如果<=K则尝试着往右继续扩展加上  end = min_index[i] + 1,获得其sum += min_sum[i+1]
 *             如果最终的结果继续<=k;则继续向右扩展。如果,>=k则跳转到第3步,
 *           3,如果 sum>=k;则右边不动,左边缩一位,也就是index = i+1;sum-=value[i];再次判断其sum是否<=k;
 *             如果继续<=k,则到第二步。如果不是则继续第三步。
 * 
 * @author xiaodong
 *
 */
public class Problem_02_LongestSubarrayLessSumAwesomeSolution
{

	public static int maxLengthAwesome(int[] arr, int k)
	{
		if (arr == null || arr.length == 0)
		{
			return 0;
		}
		int[] sums = new int[arr.length];
		HashMap<Integer, Integer> ends = new HashMap<Integer, Integer>();
		sums[arr.length - 1] = arr[arr.length - 1];// 以某个位置开头,得到的最小和。
		ends.put(arr.length - 1, arr.length - 1);// 当前位置下,最长子序列的最小和
		for (int i = arr.length - 2; i >= 0; i--)
		{
			if (sums[i + 1] < 0)
			{
				sums[i] = arr[i] + sums[i + 1];
				ends.put(i, ends.get(i + 1));
			} else
			{
				sums[i] = arr[i];
				ends.put(i, i);
			}
		}
		int end = 0;
		int sum = 0;
		int res = 0;
		for (int i = 0; i < arr.length; i++)
		{
			while (end < arr.length && sum + sums[end] <= k)
			{
				sum += sums[end];
				end = ends.get(end) + 1;
			}
			sum -= end > i ? arr[i] : 0;//这里是为了防止刚开始的时候end==i,就不满足,这样sum没有被加过,所以一直为0,所以不需要减去任何值

			res = Math.max(res, end - i);
			end = Math.max(end, i + 1);//这里是为了防止刚开始的时候end==i,需要把end值往后移动一个位置,向右扩展。i是自增的
		}
		return res;
	}

	// 这是第二种方法
	public static int maxLength(int[] arr, int k)
	{
		int[] h = new int[arr.length + 1];
		int sum = 0;
		h[0] = sum;
		for (int i = 0; i != arr.length; i++)
		{
			sum += arr[i];
			h[i + 1] = Math.max(sum, h[i]);
		}
		sum = 0;
		int res = 0;
		int pre = 0;
		int len = 0;
		for (int i = 0; i != arr.length; i++)
		{
			sum += arr[i];
			pre = getLessIndex(h, sum - k);
			len = pre == -1 ? 0 : i - pre + 1;
			res = Math.max(res, len);
		}
		return res;
	}

	public static int getLessIndex(int[] arr, int num)
	{
		int low = 0;
		int high = arr.length - 1;
		int mid = 0;
		int res = -1;
		while (low <= high)
		{
			mid = (low + high) / 2;
			if (arr[mid] >= num)
			{
				res = mid;
				high = mid - 1;
			} else
			{
				low = mid + 1;
			}
		}
		return res;
	}

	// for test
	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;
	}

	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("oops!we made a mistake");
			} else
			{
				// System.out.println(maxLengthAwesome(arr, k) );
			}
		}

	}

}

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Terry_dong

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值