累加数组数字求子数组[前缀和]
*1.和为K的子数组【LC560】
给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的连续子数组的个数 。
- 思路:将数组的前i个数字之和记为sum,如果数组中从第i+1个数字开始到第j个数字结束的子数组之和为k,那么数组的前j个数字之和为sum-k。
- 实现:使用HashMap记录前i个数字之和以及每个和出现的次数。
class Solution { public int subarraySum(int[] nums, int k) { Map<Integer,Integer> map = new HashMap<>();// 记录前j个数字之和及对应的次数 map.put(0,1); int res = 0; int len = nums.length; int sum = 0;// 保存前i个数字之和 for (int i = 0; i < len; i++){ sum += nums[i]; res += map.getOrDefault(sum-k,0); map.put(sum,map.getOrDefault(sum,0)+1);// 更新次数 } return res; } }
- 复杂度
。时间复杂度:O(n)
。空间复杂度:O(n)
*2.连续数组【LC525】
给定一个二进制数组 nums , 找到含有相同数量的 0 和 1 的最长连续子数组,并返回该子数组的长度。
0->-1
- 思路:将数组中的0全部转化为-1->那么题目就变成寻找含有相同数量的 -1 和 1 的最长连续子数组的长度->该类子数组和为0->那么题目就变成和为0的最长连续子数组的长度
- 实现:与*1.和为K的子数组【LC560】类似,使用HashMap记录前i个数字之和以及此时的下标i。
- 代码
class Solution { public int findMaxLength(int[] nums) { Map<Integer,Integer> map = new HashMap<>();// 记录前j个数字之和及对应的下标 map.put(0,-1); int res = 0; int len = nums.length; int sum = 0;// 保存前i个数字之和 for (int i = 0; i < len; i++){ sum += nums[i] == 0 ? -1 : 1; if (map.containsKey(sum)){ res = Math.max(res,i-map.get(sum)); }else{ map.put(sum,i); } } return res; } }
- 复杂度
。时间复杂度:O(n)
。空间复杂度:O(n)
- 思路:如果子数组nums[i,j]含有相同数量的 0 和 1 ,那么它的和为固定值
。和为 (j-i+1)/2 = Sj-Si-1
。长度必为偶数
- 实现:时间超出限制
class Solution { public int findMaxLength(int[] nums) { int len = nums.length; int[] sum = new int[len]; sum[0] = nums[0]; int res = 0; for (int i = 1; i < len; i++){ sum[i] = sum[i-1] + nums[i]; } for (int i = len-1; i >= 0; i--){ for (int j = 0; j < len; j++){ if ( (i-j+1) % 2 == 0 ){ if (j == 0 && sum[i] * 2 == i-j+1 ){ res = Math.max(res,i-j+1); break; }else if( j > 0 && (sum[i] - sum[j-1]) * 2 == i-j+1){ res = Math.max(res,i-j+1); break; } } } } return res; } }
3.寻找数组的中心索引【LC724】
给你一个整数数组 nums ,请计算数组的 中心下标 。
数组 中心下标 是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。
如果中心下标位于数组最左端,那么左侧数之和视为 0 ,因为在下标的左侧不存在元素。这一点对于中心下标位于数组最右端同样适用。
如果数组有多个中心下标,应该返回 最靠近左边 的那一个。如果数组不存在中心下标,返回 -1 。
作者:力扣 (LeetCode)
链接:https://leetcode-cn.com/leetbook/read/array-and-string/yf47s/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
- 题解
求出元素左边元素之和元素右边元素之和,若相等,则返回下标i
class Solution { public int pivotIndex(int[] nums) { //下标为0时坐标的数之和 int sumLeft = 0; //求出下标为0时右边的数之和 int sumRight = 0; for (int i=1;i<nums.length;i++){ sumRight += nums[i]; } for (int i=0;i<nums.length;i++){ //相等则返回当前坐标 if (sumLeft == sumRight){ return i; } //坐标为nums.length-1时,不需要更新左右之和 if (i < nums.length-1){ //求下一坐标的左右之和 sumLeft = sumLeft + nums[i];//sumLeft加nums[当前坐标] sumRight = sumRight - nums[i+1];//sumRight减nums[当前坐标+1] } } //无符合的返回-1 return -1; } }
。复杂度
- 时间复杂度:O ( n )
- 空间复杂度:O ( 1 )
- 题解
思想:遍历数组,利用整个数组元素的和减去中心下标元素所得到的值等于中心下标左侧元素的和的两倍来寻找中心下标。
class Solution { public int pivotIndex(int[] nums) { int sum = 0; int len = nums.length; for (int i = 0; i < len; i++){ sum += nums[i]; } int sumLeft = 0; for (int i = 0; i < len; i++){ if (2 * sumLeft == sum - nums[i]){ return i; } n += nums[i]; } return -1; } }
。复杂度
- 时间复杂度:O ( n )
- 空间复杂度:O ( 1 )
4.二维子矩阵的数字之和【LC304】
给定一个二维矩阵 matrix,以下类型的多个请求:
- 计算其子矩形范围内元素的总和,该子矩阵的 左上角 为 (row1, col1) ,右下角 为 (row2, col2) 。
实现 NumMatrix 类:
- NumMatrix(int[][] matrix) 给定整数矩阵 matrix 进行初始化
- int sumRegion(int row1, int col1, int row2, int col2) 返回 左上角 (row1, col1) 、右下角 (row2, col2) 所描述的子矩阵的元素 总和 。
按行计算前缀和
- 思路
在类的构造函数中,使用二维矩阵sumRow记录输入矩阵matrix的前缀和
那么 左上角 (row1, col1) 、右下角 (row2, col2) 的矩阵的和,可以按行计算为
- 代码:
class NumMatrix { int m; int n; int[][] sumRow; public NumMatrix(int[][] matrix) { m = matrix.length; n = matrix[0].length; sumRow = new int[m][n]; for (int i = 0; i < m; i++){ for (int j = 0; j < n; j++){ if (j != 0){ sumRow[i][j] += sumRow[i][j-1]; } sumRow[i][j] += matrix[i][j]; } } } public int sumRegion(int row1, int col1, int row2, int col2) { int sumArea = 0; for (int i = row1; i <= row2; i++){ sumArea += sumRow[i][col2]; if (col1 > 0){ sumArea -= sumRow[i][col1-1]; } } return sumArea; } } /** * Your NumMatrix object will be instantiated and called as such: * NumMatrix obj = new NumMatrix(matrix); * int param_1 = obj.sumRegion(row1,col1,row2,col2); */
- 复杂度
。时间复杂度:O ( m n ) ,检索的时间复杂度为O ( m )
。空间复杂度:O ( m n )
- 优化:
col1-1可能是负数,因此可以在最左面增加一列
class NumMatrix { int m; int n; int[][] sumRow; public NumMatrix(int[][] matrix) { m = matrix.length; n = matrix[0].length; sumRow = new int[m][n+1]; for (int i = 0; i < m; i++){ for (int j = 0; j < n; j++){ sumRow[i][j+1] = sumRow[i][j] + matrix[i][j]; } } } public int sumRegion(int row1, int col1, int row2, int col2) { int sumArea = 0; for (int i = row1; i <= row2; i++){ sumArea += sumRow[i][col2+1] - sumRow[i][col1]; } return sumArea; } } /** * Your NumMatrix object will be instantiated and called as such: * NumMatrix obj = new NumMatrix(matrix); * int param_1 = obj.sumRegion(row1,col1,row2,col2); */
二维前缀和
- 思路
在类的构造函数中,使用二维矩阵sum记录从左上角坐标(0,0)到每个右下角坐标的子矩阵的数字之和
那么 左上角 (row1, col1) 、右下角 (row2, col2) 的矩阵的和,可以计算为
sum=sum[row2][col2]−sum[row1−1][clo2]−sum[row2][col1−1]+sum[row1−1][col1−1]
- 实现
将sum矩阵最左面增加一列,最上面增加一行,避免负数下标
- 代码:
class NumMatrix { int[][] sum; int m; int n; public NumMatrix(int[][] matrix) { m = matrix.length; n = matrix[0].length; sum = new int[m + 1][n + 1]; for (int i = 0; i < m; i++) { int rowSum = 0; for (int j = 0; j < n; j++) { rowSum += matrix[i][j]; sum[i+1][j+1] = sum[i][j+1] + rowSum; // sum[i + 1][j + 1] = sum[i][j + 1] + sum[i + 1][j] - sum[i][j] + matrix[i][j]; } } } public int sumRegion(int row1, int col1, int row2, int col2) { return sum[row2 + 1][col2 + 1] - sum[row1][col2 + 1] - sum[row2 + 1][col1] + sum[row1][col1]; } }
- 复杂度
。时间复杂度:O ( m n ),检索的时间复杂度为O ( 1 )
。空间复杂度:O ( m n )
*5.和至少为K的最短子数组【LC862】
给你一个整数数组 nums 和一个整数 k ,找出 nums 中和至少为 k 的 最短非空子数组 ,并返回该子数组的长度。如果不存在这样的 子数组 ,返回 -1 。
子数组 是数组中 连续 的一部分。
- 思路:将数组的前i个数字之和记为sum,如果数组中从第i+1个数字开始到第j个数字结束的子数组之和大于等于k,那么数组的前i个数字之和小于等于为sum-k。
。如何快速搜索是否有前j个数字之和小于等于sum-k以及下标?
- 使用hashmap记录前j个数字之和以及下标,然后通过for循环遍历查找map中是否存在[minSum,sum-k]【超时】
- 单调栈+二分查找?->不能通过下标形式查找所需值,因此不能够实现
- 双端队列
前缀和+暴力搜索【超时】
class Solution { public int shortestSubarray(int[] nums, int k) { int len = nums.length; int[] sums = new int[len+1];// 记录nums[0,i-1]的子数组和 for (int i = 0; i < len; i++){ sums[i+1] = sums[i] + nums[i]; } int minLen = Integer.MAX_VALUE; for (int i = 0; i < len; i++){ for (int j = i; j < len;j++){ int subSum = sums[j+1] - sums[i]; if (subSum >= k){ minLen = Math.min(minLen,j - i + 1); } } } return minLen == Integer.MAX_VALUE ? -1 :minLen; } }
- 复杂度
。时间复杂度:最差O(n2)
。空间复杂度:O(n)
前缀和+HashMap【超时】
- 实现:使用hashmap记录前j个数字之和以及下标,然后通过for循环遍历查找map中是否存在[minSum,sum-k]【超时】
。代码
class Solution { public int shortestSubarray(int[] nums, int k) { Map<Integer,Integer> sumToIndex = new HashMap<>(); sumToIndex.put(0,-1); int len = nums.length; int sum = 0;// 记录子数组nums[0,i]的和 int minSum = 0;// 记录前i个子数组nums[0,i]和的最小值 int minLen = Integer.MAX_VALUE; for (int i = 0; i < len; i++){ sum += nums[i]; int target = sum - k; if (minSum <= target){// 存在前j个数字之和小于等于sum-k for (int n = minSum; n <= target; n++){ if (sumToIndex.containsKey(n)){ minLen = Math.min(minLen,i - sumToIndex.get(n)); } } } sumToIndex.put(sum,i); minSum = Math.min(minSum,sum); } return minLen == Integer.MAX_VALUE ? -1 :minLen; } }
。复杂度
- 时间复杂度:最差O(n2)
- 空间复杂度:O(n)
前缀和+双端队列
- 实现:双端队列存储元素下标
。从头部到尾部下标对应的前缀和的顺序为递增
。当头部元素存储的下标i对应的前缀和sum[i]小于等于当前遍历的元素sums[j]-k时,更新minLen=min(minLen,j-i),并将其弹出
- 为什么弹出不影响后续结果?因为后续节点的j‘-i一定大于j-i,因此一定不会影响结果
。当尾部元素存储的前缀和大于当前遍历的元素sums[j]时,将其弹出
- 我们需要保持队列递增
。把sums[j]压入队列尾部,保证sums[j]是队列最大的元素
。代码
class Solution { public int shortestSubarray(int[] nums, int k) { int len = nums.length; long[] sums = new long[len + 1]; for (int i = 0; i < len; i++) { sums[i + 1] = sums[i] + nums[i]; } int minLen = Integer.MAX_VALUE; Deque<Integer> st = new LinkedList<>(); for (int i = 0; i <= len; i++){ long curSum = sums[i]; while (!st.isEmpty() && sums[st.peekFirst()] <= curSum - k ){ minLen = Math.min(minLen,i-st.pollFirst()); } while (!st.isEmpty() && curSum <= sums[st.peekLast()]){ st.pollLast(); } st.addLast(i); } return minLen == Integer.MAX_VALUE ? -1 :minLen; } }
。复杂度
- 时间复杂度:O(n)
- 空间复杂度:O(n)