**题目:**给定一个整数数组 nums,返回区间和在 [lower, upper] 之间的个数,包含 lower 和 upper。
区间和 S(i, j) 表示在 nums 中,位置从 i 到 j 的元素之和,包含 i 和 j (i ≤ j)。
说明:
最直观的算法复杂度是 O(n2) ,请在此基础上优化你的算法
示例:
方法一:动态规划
class Solution {
public int countRangeSum(int[] nums, int lower, int upper) {
int count = 0, n = nums.length;
long[] tmp=new long[n];
for (int i = 0; i < n; i++) {
tmp[i]=nums[i];
if(tmp[i]<=upper&&tmp[i]>=lower)count++;
}
for(int i=0;i<n;i++){
for(int j=i+1;j<n;j++){
tmp[i]+=tmp[j];
if(tmp[i]<=upper&&tmp[i]>=lower)count++;
}
}
return count;
}
}
方法二:归并排序
思路与算法:
class Solution {
public int countRangeSum(int[] nums, int lower, int upper) {
long s = 0;
long[] sum = new long[nums.length + 1];
for (int i = 0; i < nums.length; ++i) {
s += nums[i];
sum[i + 1] = s;
}
return countRangeSumRecursive(sum, lower, upper, 0, sum.length - 1);
}
public int countRangeSumRecursive(long[] sum, int lower, int upper, int left, int right) {
if (left == right) {
return 0;
} else {
int mid = (left + right) / 2;
int n1 = countRangeSumRecursive(sum, lower, upper, left, mid);
int n2 = countRangeSumRecursive(sum, lower, upper, mid + 1, right);
int ret = n1 + n2;
// 首先统计下标对的数量
int i = left;
int l = mid + 1;
int r = mid + 1;
while (i <= mid) {
while (l <= right && sum[l] - sum[i] < lower) {
l++;
}
while (r <= right && sum[r] - sum[i] <= upper) {
r++;
}
ret += r - l;
i++;
}
// 随后合并两个排序数组
int[] sorted = new int[right - left + 1];
int p1 = left, p2 = mid + 1;
int p = 0;
while (p1 <= mid || p2 <= right) {
if (p1 > mid) {
sorted[p++] = (int) sum[p2++];
} else if (p2 > right) {
sorted[p++] = (int) sum[p1++];
} else {
if (sum[p1] < sum[p2]) {
sorted[p++] = (int) sum[p1++];
} else {
sorted[p++] = (int) sum[p2++];
}
}
}
for (int j = 0; j < sorted.length; j++) {
sum[left + j] = sorted[j];
}
return ret;
}
}
}
方法三:线段树
思路与算法:
class Solution {
public int countRangeSum(int[] nums, int lower, int upper) {
long sum = 0;
long[] preSum = new long[nums.length + 1];
for (int i = 0; i < nums.length; ++i) {
sum += nums[i];
preSum[i + 1] = sum;
}
Set<Long> allNumbers = new TreeSet<Long>();
for (long x : preSum) {
allNumbers.add(x);
allNumbers.add(x - lower);
allNumbers.add(x - upper);
}
// 利用哈希表进行离散化
Map<Long, Integer> values = new HashMap<Long, Integer>();
int idx = 0;
for (long x : allNumbers) {
values.put(x, idx);
idx++;
}
SegNode root = build(0, values.size() - 1);
int ret = 0;
for (long x : preSum) {
int left = values.get(x - upper), right = values.get(x - lower);
ret += count(root, left, right);
insert(root, values.get(x));
}
return ret;
}
public SegNode build(int left, int right) {
SegNode node = new SegNode(left, right);
if (left == right) {
return node;
}
int mid = (left + right) / 2;
node.lchild = build(left, mid);
node.rchild = build(mid + 1, right);
return node;
}
public int count(SegNode root, int left, int right) {
if (left > root.hi || right < root.lo) {
return 0;
}
if (left <= root.lo && root.hi <= right) {
return root.add;
}
return count(root.lchild, left, right) + count(root.rchild, left, right);
}
public void insert(SegNode root, int val) {
root.add++;
if (root.lo == root.hi) {
return;
}
int mid = (root.lo + root.hi) / 2;
if (val <= mid) {
insert(root.lchild, val);
} else {
insert(root.rchild, val);
}
}
}
class SegNode {
int lo, hi, add;
SegNode lchild, rchild;
public SegNode(int left, int right) {
lo = left;
hi = right;
add = 0;
lchild = null;
rchild = null;
}
}
方法四:动态增加节点的线段树
思路与算法
与方法二类似,但我们可以不实用哈希表进行映射,而是只在线段树的插入操作过程中动态地增加树中的节点。而当我们进行查询操作时,如果到达一个空节点,那么说明对应的区间中暂时还没有值,就可以直接返回 0。
class Solution {
public int countRangeSum(int[] nums, int lower, int upper) {
long sum = 0;
long[] preSum = new long[nums.length + 1];
for (int i = 0; i < nums.length; ++i) {
sum += nums[i];
preSum[i + 1] = sum;
}
long lbound = Long.MAX_VALUE, rbound = Long.MIN_VALUE;
for (long x : preSum) {
lbound = Math.min(Math.min(lbound, x), Math.min(x - lower, x - upper));
rbound = Math.max(Math.max(rbound, x), Math.max(x - lower, x - upper));
}
SegNode root = new SegNode(lbound, rbound);
int ret = 0;
for (long x : preSum) {
ret += count(root, x - upper, x - lower);
insert(root, x);
}
return ret;
}
public int count(SegNode root, long left, long right) {
if (root == null) {
return 0;
}
if (left > root.hi || right < root.lo) {
return 0;
}
if (left <= root.lo && root.hi <= right) {
return root.add;
}
return count(root.lchild, left, right) + count(root.rchild, left, right);
}
public void insert(SegNode root, long val) {
root.add++;
if (root.lo == root.hi) {
return;
}
long mid = (root.lo + root.hi) >> 1;
if (val <= mid) {
if (root.lchild == null) {
root.lchild = new SegNode(root.lo, mid);
}
insert(root.lchild, val);
} else {
if (root.rchild == null) {
root.rchild = new SegNode(mid + 1, root.hi);
}
insert(root.rchild, val);
}
}
}
class SegNode {
long lo, hi;
int add;
SegNode lchild, rchild;
public SegNode(long left, long right) {
lo = left;
hi = right;
add = 0;
lchild = null;
rchild = null;
}
}
方法五:树状数组
class Solution {
public int countRangeSum(int[] nums, int lower, int upper) {
long sum = 0;
long[] preSum = new long[nums.length + 1];
for (int i = 0; i < nums.length; ++i) {
sum += nums[i];
preSum[i + 1] = sum;
}
Set<Long> allNumbers = new TreeSet<Long>();
for (long x : preSum) {
allNumbers.add(x);
allNumbers.add(x - lower);
allNumbers.add(x - upper);
}
// 利用哈希表进行离散化
Map<Long, Integer> values = new HashMap<Long, Integer>();
int idx = 0;
for (long x: allNumbers) {
values.put(x, idx);
idx++;
}
int ret = 0;
BIT bit = new BIT(values.size());
for (int i = 0; i < preSum.length; i++) {
int left = values.get(preSum[i] - upper), right = values.get(preSum[i] - lower);
ret += bit.query(right + 1) - bit.query(left);
bit.update(values.get(preSum[i]) + 1, 1);
}
return ret;
}
}
class BIT {
int[] tree;
int n;
public BIT(int n) {
this.n = n;
this.tree = new int[n + 1];
}
public static int lowbit(int x) {
return x & (-x);
}
public void update(int x, int d) {
while (x <= n) {
tree[x] += d;
x += lowbit(x);
}
}
public int query(int x) {
int ans = 0;
while (x != 0) {
ans += tree[x];
x -= lowbit(x);
}
return ans;
}
}