前缀和
- LeetCode724. 寻找数组的中心下标
724. 寻找数组的中心下标
给你一个整数数组 nums ,请计算数组的 中心下标 。
数组 中心下标 是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。
如果中心下标位于数组最左端,那么左侧数之和视为 0 ,因为在下标的左侧不存在元素。这一点对于中心下标位于数组最右端同样适用。
如果数组有多个中心下标,应该返回 最靠近左边 的那一个。如果数组不存在中心下标,返回 -1 。
输入:nums = [1, 7, 3, 6, 5, 6]
输出:3
解释:
中心下标是 3 。
左侧数之和 sum = nums[0] + nums[1] + nums[2] = 1 + 7 + 3 = 11 ,
右侧数之和 sum = nums[4] + nums[5] = 5 + 6 = 11 ,二者相等。
两次遍历,分别求第i个数的前缀和以及后缀和,再判断是否存在一个数的前缀和等于后缀和。
public static int pivotIndex(int[] nums) {
//前缀
int[] pre = new int[nums.length];
pre[0] = 0;
for (int i = 1; i < nums.length; i++) {
pre[i] = pre[i - 1] + nums[i - 1];
}
//后缀
int[] suf = new int[nums.length];
suf[nums.length - 1] = 0;
for (int i = nums.length - 2; i >= 0; i--) {
suf[i] = suf[i + 1] + nums[i + 1];
}
for (int i = 0; i < nums.length; i++) {
if(pre[i] == suf[i]){
return i;
}
}
return -1;
}
优化:存在数学规律:数组总和为total,当一个数的左右和sum相等时,存在关系式:2*sum+num[i] == total。
并且中心索引左侧或右侧没有元素时,即为零个项相加,也成立。
public static int pivotIndex(int[] nums) {
//数学规律:总和为total,左右和sum相等时,存在2*sum+num[i] == total
//中心索引左侧或右侧没有元素时,即为零个项相加,也成立
int total = Arrays.stream(nums).sum();
int sum = 0;
for (int i = 0; i < nums.length; i++) {
if(2 * sum + nums[i] == total){
return i;
}
sum += nums[i];
}
- LeetCode560. 和为 K 的子数组
560. 和为 K 的子数组
给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的连续子数组的个数 。
子数组是数组中元素的连续非空序列。
示例1:
输入:nums = [1,1,1], k = 2
输出:2示例 2:
输入:nums = [1,2,3], k = 3
输出:2
双指针遍历子数组,找到和为k的子数组的个数
int count = 0;
for (int i = 0; i < nums.length; i++) {
int sum = 0;
for (int j = i; j < nums.length; j++) {
sum += nums[j];
if(sum == k){
count++;
}
}
}
使用前缀和,找到所有i ,j前缀和的差等于k的组合个数
int[] sum = new int[nums.length + 1];
sum[0] = 0;
for (int i = 0; i < nums.length; i++) {
sum[i + 1] = sum[i] + nums[i];
}
int count = 0;
for (int i = 0; i < nums.length + 1; i++) {
for (int j = i + 1; j < nums.length + 1; j++) {
if(sum[j] - sum[i] == k){
count++;
}
}
}
优化,不需要把前缀和计算出来之后再两次遍历。使用map存储前缀和的种类以及个数。
HashMap<Integer, Integer> map = new HashMap<>();
map.put(0, 1);//-1处的前缀和是0
int sum = 0;
int count = 0;
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
int key = sum - k;
if(map.containsKey(key)){
count += map.get(key);
}
if(map.containsKey(sum)){
map.put(sum, map.get(sum) + 1);
}else{
map.put(sum, 1);
}
}
return count;
- LeetCode437 路径总和 III
437. 路径总和 III
给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。
路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
输入:root = [10,5,-3,3,2,null,11,3,-2,null,1], targetSum = 8
输出:3
解释:和等于 8 的路径有 3 条,如图所示。
=
使用前缀和+map
从根节点开始计算前缀和,需要把初始处:key=0 value=1 先写入。
注意sum会溢出的问题!!!
class Solution {
//前缀和+map
//注意sum不能溢出
HashMap<Long, Integer> map = new HashMap<>();
int count = 0;
public int pathSum(TreeNode root, int targetSum) {
map.put(0L, 1);
dfs(root, 0L, targetSum);
return count;
}
public void dfs(TreeNode root, Long sum, int targetSum) {
if(root == null){
return;
}
sum += root.val;
if(map.containsKey(sum - targetSum) && map.get(sum - targetSum) >= 1){
count += map.get(sum - targetSum);
}
map.put(sum, map.getOrDefault(sum, 0) + 1);
dfs(root.left, sum, targetSum);
dfs(root.right, sum, targetSum);
map.put(sum, map.get(sum) - 1);
}
}
- LeetCode1248. 统计「优美子数组」
统计「优美子数组」
给你一个整数数组 nums 和一个整数 k。如果某个连续子数组中恰好有 k 个奇数数字,我们就认为这个子数组是「优美子数组」。
请返回这个数组中 「优美子数组」 的数目。
示例 1:
输入:nums = [1,1,2,1,1], k = 3
输出:2
解释:包含 3 个奇数的子数组是 [1,1,2,1] 和 [1,2,1,1] 。
示例 2:
输入:nums = [2,4,6], k = 1
输出:0
解释:数列中不包含任何奇数,所以不存在优美子数组。
示例 3:
输入:nums = [2,2,2,1,2,2,1,2,2,2], k = 2
输出:16
计算前缀含有的素数的个数+map
public int numberOfSubarrays(int[] nums, int k) {
//前缀素数和+map
HashMap<Integer, Integer> map = new HashMap<>();
map.put(0, 1);//-1出的素数个数为0
int count = 0;
int res = 0;
for (int i = 0; i < nums.length; i++) {
if(nums[i] % 2 == 1){
count++;
}
if(map.containsKey(count - k)){
res += map.get(count - k);
}
map.put(count, map.getOrDefault(count, 0) + 1);
}
return res;
}
- 美团0826 平均数为k的最长连续子数组
平均数为k的最长连续子数组
前缀和+map
给定n个正整数组成的数组,求平均数正好等于k 的最长连续子数组的长度。
输入描述:
第一行输入两个正整数n和k,用空格隔开。
第二行输入n个正整数ai,用来表示数组。
范围:1<=n<=200000 1<=k,ai<=10^9输出描述:
如果不存在任何一个连续子数组的平均数等于k,则输出-1。
否则输出平均数正好等于k的最长连续子数组的长度。示例
输入:
5 2
1 3 2 4 1
输出 3
思路:平均数比较难处理,我们不妨将原数组中每个元素都-k,这样问题转换成找到和为0的最长子数组。
求每个元素的前缀和,如果map中存在和当前元素的前缀和相等,就表示这两个前缀和之前的元素的和是0。
为了求出最长的子数组的长度,map key保存前缀和,value保存key出现的最左边的下标。
public static void main(String[] args) {
//所有元素都-k 找和为0的最长的子数组的长度
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int k = scanner.nextInt();
int[] nums = new int[n];
for (int i = 0; i < n; i++) {
nums[i] = scanner.nextInt() - k;
}
HashMap<Long, Integer> map = new HashMap<>();
int maxLen = 0;
long sum = 0;
map.put(0L, -1);
for (int i = 0; i < n; i++) {
sum += nums[i];
if(map.containsKey(sum)){
maxLen = Math.max(maxLen, i - map.get(sum));
}else{
map.put(sum, i);
}
}
if(maxLen == 0){
System.out.println("-1");
return;
}
System.out.println(maxLen);
}
- 美团0909第二题
小美有一个数组 a,对这个数组的元素求和:a1+a2+a3+…+an。现在,她想把其中一个加号变成减号,但小美是小学生,不会负数的加减法,因此计算过程中不能出现负数。
小美想知道政变符号后含三的最小值是多少,如果个能改变行号,则输出-1。
输入描述:
第一行输入一个整数n(1<n<10^5)表示数组长度
第二行输入n个整数表示数组a(1<=ai<=10^9)。
输出描述:
输出改变符号后的答案,若无法攻变,则输出 -1
样例:
输入:
3
3 2 1
输出2
:3 - 2 = 1 + 1 = 2
:3 + 2 = 5 - 1 = 4
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int[] nums = new int[n];
long sum = 0;//元素的总和
for (int i = 0; i < n; i++) {
nums[i] = scanner.nextInt();
sum += nums[i];
}
long preSum = 0;//前缀和
long res = Integer.MAX_VALUE;
for (int i = 0; i < n - 1; i++) {
preSum += nums[i];
if(preSum < nums[i + 1]){//当前数据的之前数据的和(前缀和)是否大于当前元素
continue;//< 则当前元素不能改成-
}
//> 当前元素可以改成-
//判断改减后和是否是正的
if(sum - 2*nums[i + 1] >= 0){
res = Math.min(res, sum - 2*nums[i + 1]);
}
}
if(res == Integer.MAX_VALUE){
System.out.println("-1");
}else {
System.out.println(res);
}
}