Java前缀和算法题目练习

前缀和

前缀和

题目解析 :在一个数组中查询起对应区间的和,会查询多次
算法思想 :暴力解法:每次查询都进行一次遍历,时间复杂度O(n*m)
前缀和解法:新定义一个数组,每一个下标存放的值是要查询数组的前下标对应值的和,这样我们在访问起某一个区间的时候,直接利用这个数组就非常快速


java 复制代码
import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        int m = in.nextInt();
        int[] arr = new int[n+1];//此时我们让其下标从1开始,这样可以省略边界问题考虑
        arr[0] = 0;
        for(int i = 1;i <= n;i++){
            arr[i] = in.nextInt();
        }
       //题目意思就是给了我们长度是n的数组,但是会有m查询
       //1.首先定义一个新的数组,其每一个下标对应的值是其从arr1开始到这个下标值的和
       long[] dp = new long[n+1];
       for(int i = 1;i <= n;i++){
        dp[i] = dp[i - 1] + arr[i];
       }
       while(m > 0){
        int l = in.nextInt();
        int r = in.nextInt();
        System.out.println(dp[r] - dp[l - 1]);
        m--;
       }
    }
}

时间复杂度:O(n + m)

空间复杂度:O(n)

二维前缀和

题目解析 :和上一题一样,只不过这里变成了二维数组,要求的是对应子矩阵元素之和

前缀和:先定义一个新数组dp,其下标对应的值是原arr 数组[1,1] ~ [i, j]下标的和,此时还是让其下标从1开始这样就不用考虑下标越界情况


java 复制代码
import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        // 注意 hasNext 和 hasNextLine 的区别
        int n = in.nextInt();
        int m = in.nextInt();
        int q = in.nextInt();
        //下标从1开始
        int[][] arr = new int[n+1][m+1];
        for(int i = 1;i <= n;i++){
            for(int j = 1;j<=m;j++){
                   arr[i][j] = in.nextInt();           
            }
        }
        //前缀和数组
        long[][] dp = new long[n+1][m+1];
        for(int i = 1;i<=n;i++){
            for(int j = 1;j<=m;j++){         
                    dp[i][j] = dp[i-1][j] + dp[i][j-1] + arr[i][j] - dp[i-1][j-1];
            }
        }
        //使用前缀和数组
        while(q > 0){
            int x1 = in.nextInt();
            int y1 = in.nextInt();
            int x2 = in.nextInt();
            int y2 = in.nextInt();
            System.out.println(dp[x2][y2] -dp[x1-1][y2]-dp[x2][y1-1] + dp[x1-1][y1-1]);
            q--;
        }
    }
}

时间复杂度:O(n*m + q)

空间复杂度:O(n * m)

寻找数组的中心下标

题目解析 :找到一个下标,让其数组左边元素的和等于其右边元素的和

前缀和算法:由于暴力解法每次都要进行遍历多次,这样时间复杂度高

因此我们可以使用f和g两个数组,一个每一个下标用来放其前缀和,一个用来放后缀和,最后遍历一遍判断其f[i] == g[i]即可

java 复制代码
class Solution {
    public int pivotIndex(int[] nums) {
        int n = nums.length;
        int[] f = new int[n];
        int[] g = new int[n];

        //此时f要从左向右填写,因为后一个要根据前一个填写
        for(int i = 1;i < n;i++){
            f[i] = f[i-1] + nums[i-1];
        }

        for(int i = n-2;i >= 0;i--){
            g[i] = g[i+1] + nums[i+1];
        }
        
        for(int i = 0;i< n;i++){
            if(f[i] == g[i]){
                return i;
            }
        }
        return -1;
    }
}

时间复杂度:O(n)

空间复杂度:O(n)

除自身以外数组的乘积

题目解析:返回一个answer数组,里面answer[i]表示的是nums数组中除了num[i]下标的值,其他所有元素的积,返回这个answer数组

前缀积:f表示前缀积,g表示后缀积

java 复制代码
class Solution {
    public int[] productExceptSelf(int[] nums) {
        int n = nums.length;
        //一个前缀积和一个后缀积
        int[] f = new int[n];
        int[] g = new int[n];
        int[] ret = new int[n];
        
        f[0] = g[n-1] = 1;

        for(int i = 1;i < n;i++){
            f[i] = f[i-1] * nums[i-1];
        }

        for(int i = n - 2;i >= 0;i--){
            g[i] = g[i+1] * nums[i+1];
        }
        for(int i = 0 ; i<n;i++){
            ret[i] = f[i]*g[i];
        }
        return ret;
    }
}

和为k的子数组

题目解析 :数组中找子数组和为k的个数
前缀和 :有一个前缀和数组,这样每次我们只要在[ 0 , i-1]找出和为nums[i] - k即可,

但是如果sum[i] == k,此时就要从 [0,-1]中找0,因此可以先将<0,1>放入hash中

java 复制代码
class Solution {
    public int subarraySum(int[] nums, int k) {
        Map<Integer,Integer> hash = new HashMap<>();
        hash.put(0,1);

        int sum = 0;//表示前缀和
        int ret = 0;
        for(int num : nums){
            sum += num;
            ret += hash.getOrDefault(sum - k, 0);//更新结果
            hash.put(sum,hash.getOrDefault(sum,0)+1);//将其放入hash中
        }
        return ret;
    }
}

时间复杂度:O(n)

空间复杂度:O(n)

和可被K整除的子数组

题目解析 :在一个数组中找出所有子数组和可以整除k的子数组个数

这个题目和上一个求所有和为子数组的个数一样,一些细节处理方式都差不多,唯一不一样的是

java 复制代码
class Solution {
    public int subarraysDivByK(int[] nums, int k) {
        Map<Integer,Integer> hash = new HashMap<>();
        //刚开始要先将0%k放入进去,也就是<0,1>,防止当
        hash.put(0 % k,1);

        int sum = 0;
        int ret  = 0;
        for(int num : nums){
            sum += num;
            int r = (sum % k + k)%k;
            ret += hash.getOrDefault( r, 0);
            hash.put(r,hash.getOrDefault(r,0)+1);
        }
        return ret;
    }
}

时间复杂度:O(n)

空间复杂度:O(n)

连续数组

题目解析 :找一个最长的子数组其和为0,并且此子数组区间中0和1的个数一样

题目转换,我们可以将其中的0看成-1,此时就变成了找出和为0的最长子数组 ,上面有一个题目是和为k的子数组,此时的k == 0
前缀和 + 哈希表

但是有很多细节不一样,

1.这里哈希表存放的是<前缀和,下标>

2.重复的哈希其不用存放,因为这里要求的是最长子数组

3.长度为i - j

4.<0,-1>,前缀和为0,存放的是-1

5.先使用,后存放入哈希表中

java 复制代码
class Solution {
    public int findMaxLength(int[] nums) {
        //此时可以将其0看成-1,此时就变成和为0的最长子数组的求解
        Map<Integer,Integer> hash = new HashMap<>();
        //当其[0,i]位置和为0时候,我们要从[0,-1]中找出和为0
        hash.put(0,-1);
        int len = 0;
        int sum = 0;
        for(int i = 0;i < nums.length ;i++){
            if(nums[i] == 0){
                sum -= 1;
            }else{
                sum += 1;
            }
            //从前面找和为sum,那后面和就为0,当然其j越靠前越好
           //因此如果已经有了和为sum,后面的就不用放进去了
           if(hash.containsKey(sum)){
            len = Math.max(len,i - hash.get(sum));
           }else{
            //放入hash中
            hash.put(sum,i);
           }
        }
        return len;
    }
}

时间复杂度:O(n)

空间复杂度:O(n)

矩阵区域和

题目解析 :给了一个二维数组,我们要返回一个相同的二维数组,每一个下标对应的值是其原本二维数组距离其为k的元素所围成的一个矩形中元素之和

前缀和矩阵,上面有一个题目是求(x1,y1) 到 (x2,y2)所围成矩阵的和就用到了前缀和矩阵

因此这里可以先求出前缀和数组,在使用的时候直接根据k求出其是那两个坐标 围成的矩形,有了坐标可以使用前缀和矩阵,这样就求出对应的值了

但是此时要注意其下标是不一样的,前缀和矩阵下标是从1开始



java 复制代码
class Solution {
    public int[][] matrixBlockSum(int[][] mat, int k) {
        int m = mat.length;
        int n = mat[0].length;
        int[][] dp = new int[m+1][n+1];
        //构建前缀和其下标从1开始
        for(int i = 1;i<= m;i++){
            for(int j = 1;j<= n;j++){
                dp[i][j] = dp[i-1][j] + dp[i][j-1] - dp[i-1][j-1] + mat[i-1][j-1];
            }
        }
        int[][] ret = new int[m][n];
        //使用的时候下标也要+1这样才能正常使用前缀和数组
        for(int i = 0;i<m;i++){
            for(int j = 0;j<n;j++){
                int x1 = Math.max(i-k,0) + 1;
                int y1 = Math.max(j-k,0) + 1;
                int x2 = Math.min(i+k, m -1) + 1;
                int y2 = Math.min(j+k, n - 1) + 1;
                ret[i][j] = dp[x2][y2] - dp[x1-1][y2] - dp[x2][y1-1] + dp[x1-1][y1-1];
            }
        }
        return ret;
    }
}

时间复杂度:O(m * n)

空间复杂度:O(m * n)

相关推荐
laplace01231 小时前
Java八股—MySQL
java·mysql·oracle
zhangyao9403302 小时前
关于js导入Excel时,Excel的(年/月/日)日期是五位数字的问题。以及对Excel日期存在的错误的分析和处理。
开发语言·javascript·excel
熙客2 小时前
TiDB:分布式关系型数据库
java·数据库·分布式·tidb
骑驴看星星a2 小时前
【Three.js--manual script】4.光照
android·开发语言·javascript
你想考研啊3 小时前
linux安装jdk和tomcat和并自启动
java·linux·tomcat
星释3 小时前
Rust 练习册 :Leap与日期计算
开发语言·后端·rust
智驱力人工智能4 小时前
基于视觉分析的人脸联动使用手机检测系统 智能安全管理新突破 人脸与手机行为联动检测 多模态融合人脸与手机行为分析模型
算法·安全·目标检测·计算机视觉·智能手机·视觉检测·边缘计算
悟能不能悟5 小时前
java的java.sql.Date和java.util.Date的区别,应该怎么使用
java·开发语言
2301_764441335 小时前
水星热演化核幔耦合数值模拟
python·算法·数学建模
循环过三天5 小时前
3.4、Python-集合
开发语言·笔记·python·学习·算法