标签5连续子数组的和(前缀和)和区间问题

标签5连续子数组的和(前缀和)

首先需要理清楚两个概念子序列和子数组

 子序列:数组中不连续的某一段数字,但是要保持原先的先后顺序(n>...>k>j>0)

A[i],A[i+j],A[i+k],...,A[i+n]

 子数组:数组中连续的某一段数字,例如

A[i],A[i+1],A[i+2],...,A[i+n]A[i],A[i+1],A[i+2]

这道题是对子数组求和,因此我们可以考虑用前缀和(preSum)来表示A[i],...,A[i+n]这段子数组的和
前缀和概念:

preSum[i]:表示的是A[0]+A[1]+A[2]+...+A[i]的和(这里定义是左闭右闭)

523. 连续的子数组和

解法:对于子数组 nums[i + 1:j] (不包含下标 j ),其区间和为 sum[j]−sum[i](其中 sum 为预处理得到的前缀和数组),

    我们要判断的是 (sum[j]−sum[i])%k是否等于 0。
    根据 mod 运算的性质,我们知道 (sum[j]−sum[i])%k=sum[j]%k−sum[i]%k
    故若想 (sum[j]−sum[i])%k=0,则必有 sum[j]%k=sum[i]%ksum[j]\%k = sum[i]\%k。

算法:每当我们计算出一个前缀和 sum[j] 时,我们判断哈希表中是否存在键值为 sum[j]%k,若存在则有 sum[j]%k=sum[i]%k,我们返回 True。

map<int, int> 保存 <前缀和对k的取余, 第一次出现 sum%k 的位置>

public boolean checkSubarraySum(int[] nums, int k) {
		if(nums.length == 0) {
			return false;
		}else {
			HashMap<Integer, Integer> map = new HashMap<>();
			//map的key是前缀和对k取余,value是前缀和的截止下标
			map.put(k == 0 ? nums[0] : nums[0] % k, 0);
			int[] array = new int[nums.length];
			array[0] = nums[0];
			for(int i = 1;i < nums.length;i ++){
				array[i] = array[i - 1] + nums[i];
				int key = k == 0 ? array[i] : array[i] % k;
				//1.如果array[i] - array[j] == 0并且i - j大于等于2符合题意,子数组是下标j+1到i
				//2.如果key == k符合题意,子数组是从下标0到i
				//3.key == 0,符合题意,子数组是从下标0到i
				if((map.containsKey(key) && i - map.get(key) >= 2) || key == k || key == 0){
					return true;
				}else if(! map.containsKey(key)){
					//贪心思想,如果有key就不put,也就是存储i最小的key
					map.put(key, i);
				}
			}
			return false;
		}
    }

525. 连续数组

解法:思路和上面的一致,把0看作-1,题目转化为连续子数组和为0的问题。

class Solution {
    //规定nums数组只能是0或者1
	public int findMaxLength(int[] nums) {
		if(nums.length <= 1) {
			return 0;
		}else {
			HashMap<Integer, Integer> map = new HashMap<>();
			int[] sum = new int[nums.length];
			sum[0] = nums[0] == 0 ? -1 : 1;
			map.put(sum[0], 0);
			int length = 0;
			for(int i = 1;i < nums.length;i ++){
				sum[i] = sum[i - 1] + (nums[i] == 0 ? -1 : nums[i]);
				if(sum[i] == 0){
					//[0,i]区间和为0
					length = i + 1 > length ? i + 1 : length;
				}
                if(map.containsKey(sum[i])){
					//[j+1,i]区间和为0
					length = (i - map.get(sum[i])) > length ? i - map.get(sum[i]) : length;
				}else if(! map.containsKey(sum[i])){
					//贪心思想,如果有key就不put,也就是存储i最小的key
					map.put(sum[i], i);
				}
			}
			return length;
		}
    }
}

560. 和为K的子数组

解法如下:

public int subarraySum(int[] nums, int k) {
		if(nums.length == 0) {
			return 0;
		}else {
			int[] sum = new int[nums.length];
			HashMap<Integer, LinkedList<Integer>> map = new HashMap<>();
			sum[0] = nums[0];
			int counter = 0;
			LinkedList<Integer> list = new LinkedList<>();
			list.add(0);
			map.put(sum[0], list);
            if(sum[0] == k){
                counter ++;
            }
			for(int i = 1;i < nums.length;i ++) {
				sum[i] = sum[i - 1] + nums[i];
				if(map.containsKey(sum[i] - k)) {
					counter += map.get(sum[i] - k).size();
				}
                //不是elseif,因为两种情况都可能发生
				if (sum[i] == k) {
					counter += 1;
				}
                //将sum[i]作为关键字加入map的list中
                if(map.containsKey(sum[i])) {
                    map.get(sum[i]).add(i);
                }else {
                    //LinkedList<Integer> list2 = new LinkedList<>();
                    map.put(sum[i], new LinkedList<Integer>());
                    map.get(sum[i]).add(i);
                }
			}
			return counter;
		}
    }

1248. 统计「优美子数组」

解法:前缀和对应的map的key是前缀和的值,value是该值出现的次数或者出现的下标都可以

public int numberOfSubarrays(int[] nums, int k) {
		if(nums.length == 0) {
			return 0;
		}else {
			int[] sum = new int[nums.length];
			//这里的map的key是前缀和,value存放的不是之前的i也就是前缀和对应的下标,而是前缀和出现的次数
			HashMap<Integer, Integer> map = new HashMap<>();
			sum[0] = nums[0] % 2 == 0 ? 0 : 1;
			map.put(sum[0], 1);
			int counter = 0;
			if(sum[0] == k){
				counter ++;
			}
			for(int i = 1;i < nums.length;i ++) {
				sum[i] = sum[i - 1] + (nums[i] % 2 == 0 ? 0 : 1);
				if(map.containsKey(sum[i] - k)) {
					counter += map.get(sum[i] - k);
				}
				//这里是ifif的关系而不是elseif
				if(sum[i] == k) {
					//只能counter ++,而不是加上sum[i]出现的次数,因为重复
					counter ++;
				}
				if(map.containsKey(sum[i])) {
					//put的key是sum[i]
					map.put(sum[i], map.get(sum[i]) + 1);
				}else {
					map.put(sum[i], 1);
				}
			}
			return counter;
		}
    }
	public int numberOfSubarrays1(int[] nums, int k) {
		if(nums.length == 0) {
			return 0;
		}else {
			int[] sum = new int[nums.length];
			HashMap<Integer, LinkedList<Integer>> map = new HashMap<>();
			sum[0] = nums[0] % 2 == 0 ? 0 : 1;
			LinkedList<Integer> tem = new LinkedList<>();
			tem.add(0);
			map.put(sum[0], tem);
			int counter = 0;
			if(sum[0] == k){
				counter ++;
			}
			for(int i = 1;i < nums.length;i ++) {
				sum[i] = sum[i - 1] + (nums[i] % 2 == 0 ? 0 : 1);
				if(map.containsKey(sum[i] - k)) {
					counter += map.get(sum[i] - k).size();
				}
				//这里是ifif的关系而不是elseif
				if(sum[i] == k) {
					counter ++;
				}
				if(map.containsKey(sum[i])) {
					map.get(sum[i]).add(i);
				}else {
					LinkedList<Integer> list = new LinkedList<>();
					list.add(i);
					map.put(sum[i], list);
				}
			}
			return counter;
		}
    }

53. 最大子序和

时间复杂度的要求是O(n)

解法1:动态规划的解法,空间复杂度不需要O(n),只需一个sum变量记录当前连续子数组的值即可。

class Solution {
    //动态规划大的解法
    public int maxSubArray(int[] nums) {
        if(nums.length == 0) {
            return 0;
        }else if(nums.length == 1){
            return nums[0];
        }else {
            //注意题目中的描述是连续子数组的和
            int answer = Integer.MIN_VALUE;
            //sum标识包含nums[i]的连续子数组的和的最大值
            int sum = 0;
            for(int i = 0;i < nums.length;i ++) {
                if(sum < 0) {
                    sum = nums[i];
                }else {
                    sum += nums[i];
                }
                answer = Math.max(answer,sum);
            }
            return answer;
        }
    }
}

解法2:分治的方法,https://leetcode-cn.com/problems/maximum-subarray/solution/dong-tai-gui-hua-fen-zhi-fa-python-dai-ma-java-dai/

class Solution {
    public int maxSubArray(int[] nums) {
        if(nums.length == 0) {
            return 0;
        }else if(nums.length == 1) {
            return nums[0];
        }else {
            return divideandrule(nums,0,nums.length - 1);
        }
    }
    public int divideandrule(int[] nums,int left,int right) {
        if(left == right) {
            return nums[left];
        }else {
            int mid = left + (right - left) / 2;
            //[left,mid]的连续子数组的最大值
            int leftnum = divideandrule(nums,left,mid);
            //[mid + 1,right]的连续子数组的最大值
            int rightnum = divideandrule(nums,mid + 1,right);
            //[left,right]区间,包括nums[mid],nums[mid + 1]在内的连续子数组的最大值
            int midnum = merge(nums,left,right,mid);
            return Math.max(midnum,Math.max(leftnum,rightnum));
        }
    }
    //nums的连续子数组必须要包含nums[mid]和nums[mid + 1]
    public int merge(int[] nums,int left,int right,int mid) {
        /*
        int leftnum = 0;
        int rightnum = 0;
        */
        int leftnum = Integer.MIN_VALUE;
        int rightnum = Integer.MIN_VALUE;
        int sum = 0;
        //
        for(int i = mid;i >= left;i --) {
            sum += nums[i];
            if(leftnum < sum) {
                leftnum = sum;
            }
        }
        sum = 0;
        for(int i = mid + 1;i <= right;i ++) {
            sum += nums[i];
            if(rightnum < sum) {
                rightnum = sum;
            }
        }
        return leftnum + rightnum;
    }
}

 1524. 和为奇数的子数组数目

解析:https://leetcode-cn.com/problems/number-of-sub-arrays-with-odd-sum/solution/5457-he-wei-qi-shu-de-zi-shu-zu-shu-mu-by-lin-miao/

class Solution {
    public int numOfSubarrays(int[] arr) {
        if(arr.length == 0) {
            return 0;
        }else {
            //o代表当前前缀和奇数的数目之和
            int o = 0;
            //0看作一个偶数
            //e代表当前前缀和偶数的数目之和
            int e = 1;
            int sum = 0;
            int answer = 0;
            //下面两个是一样的,Math.pow()得强制类型转换
            //int number = 1000000007;
            int number = (int)Math.pow(10,9) + 7;
            //奇数减去偶数等于奇数,偶数减去奇数等于奇数
            for(int i = 0;i < arr.length;i ++) {
                sum += arr[i];
                if(sum % 2 == 0) {
                    answer = (o + answer) % number;
                    e ++;
                }else {
                    answer = (e + answer) % number;
                    o ++;
                }
            }
            return answer;
        }
    }
}

区间问题

986. 区间列表的交集

解法:双指针合并区间

class Solution {
    public int[][] intervalIntersection(int[][] A, int[][] B) {
       List<int[]> list = new ArrayList<>();
       if(B.length == 0 && A.length == 0) {
           return list.toArray(new int[list.size()][2]);
       }else {
           int i = 0;
           int j = 0;
           int left;
           int right;
           //很明显循环进行的条件是i,j都合法
           while(i < A.length && j < B.length) {
               left = Math.max(A[i][0],B[j][0]);
               right = Math.min(A[i][1],B[j][1]);
               //能形成区间
               if(left <= right) {
                   list.add(new int[]{left,right});
               }
               //A[i]B[j]合并之后,A[i]B[j + 1]或者A[i + 1]B[j]都可能在进行合并
                //求完一个交集区间后,较早结束的子区间不可能再和别的子区间重叠了,它的指针要移动。
               if(A[i][1] < B[j][1]) {
                   i ++;
               }else {

                   j ++;
               }
           }
           return list.toArray(new int[list.size()][2]);
       }
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值