前缀和思想就是可以把连续子数组的元素和转换成两个前缀和的差值;
1. 区域和检索-数组不可变
给定一个整数数组 nums
,处理以下类型的多个查询:
- 计算索引
left
和right
(包含left
和right
)之间的nums
元素的 和 ,其中left <= right
实现 NumArray
类:
NumArray(int[] nums)
使用数组nums
初始化对象int sumRange(int i, int j)
返回数组nums
中索引left
和right
之间的元素的 总和 ,包含left
和right
两点(也就是nums[left] + nums[left + 1] + ... + nums[right]
)
class NumArray {
private int[] s; // 前缀和数组
public NumArray(int[] nums) {
s = new int[nums.length + 1]; // 要多一个s[0]元素
for(int i=0; i< nums.length; i++) {
s[i+1] = s[i] + nums[i]; // 计算前缀和
}
}
// 计算元素和
public int sumRange(int left, int right) {
return s[right+1] - s[left]; // left -> right 的值就是s[right + 1] - s[left]
}
}
2. 统计范围内的元音字符串数
给你一个下标从 0 开始的字符串数组 words
以及一个二维整数数组 queries
。
每个查询 queries[i] = [li, ri]
会要求我们统计在 words
中下标在 li
到 ri
范围内(包含 这两个值)并且以元音开头和结尾的字符串的数目。
返回一个整数数组,其中数组的第 i
个元素对应第 i
个查询的答案。
注意:元音字母是 'a'
、'e'
、'i'
、'o'
和 'u'
。
// class Solution {
// // 题目要求:即给一个下标范围,需要words中的元素下标在这个范围内,且需要为元音开头和结尾
// public int[] vowelStrings(String[] words, int[][] queries) {
// // 如果 words[i] 符合要求,就视作 1,否则视作 0。
// // 1.统计前缀和
// int[] prefix = new int[words.length+1]; //要比words的长度大1
// // 2. 计算前缀和
// prefix[0] = 0; // prefix[n] 表示n之前有多少个元音字母
// for(int i = 0; i < words.length; i++) {
// // 判断是不是元音字母开头和结尾的
// if(words[i].matches("[aeiou](.*[aeiou])?")) {
// prefix[i+1] =prefix[i] + 1; //比如word[0]为元音字符串,则prefix[1] = 1;
// }else {
// prefix[i+1] = prefix[i];
// }
// }
// // 3. 根据前缀和计算查询的答案
// int[] ans = new int[queries.length]; //保存答案的数目
// for(int i=0 ; i<queries.length; i++) {
// // 每个query 是一个数组
// ans[i] = prefix[queries[i][1]+1] - prefix[queries[i][0]];
// }
// return ans;
// }
// }
// 下面的方式更快
class Solution {
public int[] vowelStrings(String[] words, int[][] queries) {
int n = words.length;
int m = queries.length;
int[] res = new int[m];
//构造前缀和,简化到O(n)
int[] prefix = new int[n+1];
for (int i = 1; i <= n; i++) { // 计算前缀和
prefix[i] = prefix[i - 1];
if (isVowelString(words[i - 1])) {
prefix[i] += 1;
}
}
// 得到最后的结果
for (int i = 0; i < m; i++) {
int[] curQuery = queries[i];
res[i] = prefix[curQuery[1]+1] - prefix[curQuery[0]];
}
return res;
}
// 判断是不是元音字符串:即以元音字母开头与结尾
public boolean isVowelString(String word) {
return isVowelLetter(word.charAt(0)) && isVowelLetter(word.charAt(word.length() - 1));
}
public boolean isVowelLetter(char c) {
return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';
}
}
3. 和有限的最长子序列
给你一个长度为 n
的整数数组 nums
,和一个长度为 m
的整数数组 queries
。
返回一个长度为 m
的数组 answer
,其中 answer[i]
是 nums
中 元素之和小于等于 queries[i]
的 子序列 的 最大 长度 。
子序列 是由一个数组删除某些元素(也可以不删除)但不改变剩余元素顺序得到的一个数组。
notice:
1. 对于子序列问题,如果元素的顺序没有影响,就可以先进行排序,一般排序之后可以更方便的操作,如可以在查找时使用二分法进行查找。
2. 前缀和数组prefix[i] 不一定表示的就是 a[0] ~ a[i-1] 元素之和;也可能表示的是a[0] ~ a[i] 元素之和;可以根据题目进行设置;如果题目中需要使用到两个前缀和相减,那为了能在某些情况减之后保留住a[0]元素,此时就可以把prefix[i]设置成a[0] ~ a[i-1];
class Solution {
public int[] answerQueries(int[] nums, int[] queries) {
// 特点:子序列
// 常规思想:对于一些无序的数组是不是可以先进行排序
// 排序之后再利用前缀和思想
// 1. 排序
Arrays.sort(nums); //改变原数组
// 2. 如果每次选择最小的元素,那最后得到的子序列长度会最长
// 2.1 求出前缀和
int[] prefix = new int[nums.length];
prefix[0] = nums[0];
for(int i=1; i<nums.length; i++) {
prefix[i] = prefix[i-1] + nums[i] ; //这里的前缀和 prefix[i] 就表示前i个元素相加,如nums[1] = nums[0] + nums[1]
}
// 前缀和单调增大 [可使用二分查找来找到大于目标元素的第一个下标]
int[] ans = new int[queries.length];
for(int i=0; i<queries.length; i++) {
ans[i] = binarySearch(prefix,queries[i]);
}
return ans;
}
private int binarySearch(int[] prefix, int target) {
int left = -1, right = prefix.length;
while(left + 1 < right) {
int mid = left + (right - left)/2;
if(prefix[mid] > target) {
right = mid;
} else {
left = mid;
}
}
return right; // 如果没有找到,返回右边值
}
}
// 前缀和可以直接记到 nums 中。
// 答案可以直接记到 queries 中。