跳到主要内容Java 前缀和算法题目练习 | 极客日志Javajava算法
Java 前缀和算法题目练习
Java 前缀和算法在多个经典题目中的应用,涵盖一维与二维前缀和、中心下标、子数组乘积及和为 K 等问题。通过构建前缀和数组或利用哈希表优化,实现了区间查询的高效处理,显著降低了时间复杂度。文中包含完整代码示例及复杂度分析,适合算法学习者参考。
宁静1 浏览 前缀和
题目解析:在一个数组中查询对应区间的和,会查询多次。
算法思想:
- 暴力解法:每次查询都进行一次遍历,时间复杂度 O(n*m)。
- 前缀和解法:新定义一个数组,每一个下标存放的值是要查询数组的前下标对应值的和。这样我们在访问某一个区间的时候,直接利用这个数组就非常快速。
import java.util.Scanner;
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];
arr[0] = 0;
for (int i = 1; i <= n; i++) {
arr[i] = in.nextInt();
}
long[] dp = new long[n + 1];
for (int i = 1; i <= n; i++) {
dp[i] = dp[i - ] + arr[i];
}
(m > ) {
in.nextInt();
in.nextInt();
System.out.println(dp[r] - dp[l - ]);
m--;
}
}
}
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
1
while
0
int
l
=
int
r
=
1
时间复杂度:O(n + m)
空间复杂度:O(n)
二维前缀和
题目解析:和上一题一样,只不过这里变成了二维数组,要求的是对应子矩阵元素之和。
前缀和:先定义一个新数组 dp,其下标对应的值是原 arr 数组 [1,1] ~ [i, j] 下标的和,此时还是让其下标从 1 开始这样就不用考虑下标越界情况。
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int m = in.nextInt();
int q = in.nextInt();
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] 即可。
class Solution {
public int pivotIndex(int[] nums) {
int n = nums.length;
int[] f = new int[n];
int[] g = new int[n];
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;
}
}
除自身以外数组的乘积
题目解析:返回一个 answer 数组,里面 answer[i] 表示的是 nums 数组中除了 num[i] 下标的值,其他所有元素的积,返回这个 answer 数组。
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 的子数组
前缀和:有一个前缀和数组,这样每次我们只要在 [0, i-1] 找出和为 nums[i] - k 即可。但是如果 sum[i] == k,此时就要从 [0,-1] 中找 0,因此可以先将<0,1>放入 hash 中。
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);
}
return ret;
}
}
和可被 K 整除的子数组
题目解析:在一个数组中找出所有子数组和可以整除 k 的子数组个数。
这个题目和上一个求所有和为子数组的个数一样,一些细节处理方式都差不多,唯一不一样的是需要处理负数取模的情况。
class Solution {
public int subarraysDivByK(int[] nums, int k) {
Map<Integer, Integer> hash = new HashMap<>();
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;
}
}
连续数组
题目解析:找一个最长的子数组其和为 0,并且此子数组区间中 0 和 1 的个数一样。
题目转换,我们可以将其中的 0 看成 -1,此时就变成了找出和为 0 的最长子数组。上面有一个题目是和为 k 的子数组,此时的 k == 0。
- 这里哈希表存放的是<前缀和,下标>。
- 重复的哈希其不用存放,因为这里要求的是最长子数组。
- 长度为 i - j。
- <0,-1>,前缀和为 0,存放的是 -1。
- 先使用,后存放入哈希表中。
class Solution {
public int findMaxLength(int[] nums) {
Map<Integer, Integer> hash = new HashMap<>();
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;
}
if (hash.containsKey(sum)) {
len = Math.max(len, i - hash.get(sum));
} else {
hash.put(sum, i);
}
}
return len;
}
}
矩阵区域和
题目解析:给了一个二维数组,我们要返回一个相同的二维数组,每一个下标对应的值是其原本二维数组距离其为 k 的元素所围成的一个矩形中元素之和。
前缀和矩阵,上面有一个题目是求 (x1,y1) 到 (x2,y2) 所围成矩阵的和就用到了前缀和矩阵。因此这里可以先求出前缀和数组,在使用的时候直接根据 k 求出其是那两个坐标围成的矩形,有了坐标可以使用前缀和矩阵,这样就求出对应的值了。但是此时要注意其下标是不一样的,前缀和矩阵下标是从 1 开始。
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];
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];
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)