树状数组(Fenwick Tree 或 Binary Indexed Tree,简称 BIT),是一种特殊的二叉查找树,用于高效地进行区间求和和单点更新操作。它是一种线性时间复杂度的数据结构,特别适用于解决静态数组的区间查询问题。
树状数组的特点:
- 高效性:单点更新和区间求和操作都可以在 O(log n) 时间内完成。
- 空间优化:树状数组使用原始数组的一部分空间来存储索引信息,从而避免了额外的空间开销。
- 适用性:适合处理静态数组的区间求和问题,特别是当更新操作不如查询操作频繁时。
树状数组的基本操作:
- 单点更新(Update):更新数组中某个位置的值,并递增其在树状数组中的所有相关位置。
- 区间求和(Query):查询数组中某个区间的元素和,通过计算特定位置之前的累计和之差来得到结果。
树状数组的Java实现:
public class FenwickTree {
private int[] tree;
private int n;
public FenwickTree(int[] nums) {
n = nums.length;
tree = new int[n + 1];
for (int i = 0; i < n; i++) {
update(i, nums[i]);
}
}
private void update(int idx, int val) {
int diff = val - tree[idx];
while (idx <= n) {
tree[idx] += diff;
idx += idx & (-idx);
}
}
public int query(int l, int r) {
return query(r) - query(l - 1);
}
private int query(int idx) {
int sum = 0;
while (idx > 0) {
sum += tree[idx];
idx -= idx & (-idx);
}
return sum;
}
}
树状数组的应用场景:
- 区间求和:计算静态数组中任意区间的元素和。
- 动态数组的区间修改:虽然树状数组适用于静态数组,但可以通过额外的数组来模拟动态数组的区间更新操作。
面试大厂题示例:
-
公司员工的薪水:
描述:给定一个公司员工的薪水列表,每个员工有一个编号和薪水数额。请你编写一个程序,计算给定编号范围内员工的薪水总和。
示例:输入: employees = [5, 3, 10, 9, 7], salaries = [1000, 2000, 3000, 4000, 5000], range = [1, 3] 输出: 9000
-
数字范围按位异或:
描述:给定一个整数数组,计算所有数异或(XOR)的结果。对于一个给定的区间,找出异或结果的最小可能值。
示例:输入: nums = [8, 1, 2, 9, 3, 6, 7], range = [0, 4] 输出: 8
-
统计全为 1 的子矩阵个数:
描述:给定一个二维二进制矩阵,统计其中所有 1 的子矩阵的个数。
示例:输入: matrix = [[0, 0, 1], [1, 1, 1], [1, 0, 1]] 输出: 2
这些题目和源码展示了树状数组在解决实际问题中的应用。在面试中,能够根据问题的特点选择合适的算法并实现其解决方案是非常重要的。希望这些示例能够帮助你更好地准备面试!树状数组(Fenwick Tree)是一种高效的数据结构,特别适合处理静态数组的区间求和和单点更新问题。以下是三道可能出现在大厂面试中的与树状数组相关的编程题目,以及相应的Java源码实现。
题目 1:区间求和
描述:
给定一个整数数组和一个区间列表,对于每个区间,计算其内所有元素的和。
示例:
输入: nums = [1, 3, 5, 7, 9, 11], intervals = [[1, 3], [2, 4]]
输出: [16, 20]
Java 源码:
public class RangeSumQuery {
private int n;
private int[] tree;
public RangeSumQuery(int[] nums) {
n = nums.length;
tree = new int[n + 1];
for (int i = 0; i < n; i++) {
update(i, nums[i]);
}
}
public void update(int idx, int val) {
while (idx <= n) {
tree[idx] += val;
idx += idx & (-idx);
}
}
public int query(int left, int right) {
return query(right) - query(left - 1);
}
private int query(int idx) {
int sum = 0;
while (idx > 0) {
sum += tree[idx];
idx -= idx & (-idx);
}
return sum;
}
public static void main(String[] args) {
RangeSumQuery rangeSumQuery = new RangeSumQuery(new int[]{1, 3, 5, 7, 9, 11});
int[] intervals = {{1, 3}, {2, 4}};
int[] results = new int[intervals.length];
for (int i = 0; i < intervals.length; i++) {
results[i] = rangeSumQuery.query(intervals[i][0], intervals[i][1]);
System.out.println("Sum of interval " + Arrays.toString(intervals[i]) + ": " + results[i]);
}
}
}
题目 2:最小值查询
描述:
给定一个整数数组,找到包含所有查询区间的最小值。
示例:
输入: nums = [3, 1, 2, 4, 3], queries = [2, 3, 4]
输出: [1, 2, 3]
Java 源码:
public class MinValueQuery {
private int n;
private int[] treeMin;
public MinValueQuery(int[] nums) {
n = nums.length;
treeMin = new int[n + 1];
for (int i = 0; i < n; i++) {
update(i, nums[i], 1);
}
}
public void update(int idx, int val, int size) {
while (idx <= n) {
treeMin[idx] = Math.min(treeMin[idx], val);
idx += idx & (-idx);
}
}
public int query(int left, int right) {
return query(right) - query(left - 1);
}
private int query(int idx) {
int min = Integer.MAX_VALUE;
while (idx > 0) {
min = Math.min(min, treeMin[idx]);
idx -= idx & (-idx);
}
return min;
}
public static void main(String[] args) {
MinValueQuery minValueQuery = new MinValueQuery(new int[]{3, 1, 2, 4, 3});
int[] queries = {2, 3, 4};
int[] results = new int[queries.length];
for (int i = 0; i < queries.length; i++) {
results[i] = minValueQuery.query(queries[i], queries[i]);
System.out.println("Minimum value in interval " + queries[i] + ": " + results[i]);
}
}
}
题目 3:单点更新
描述:
给定一个整数数组,对数组中的某个位置进行更新操作。
示例:
输入: nums = [1, 3, 5, 7, 9, 11], idx = 2, val = 8
输出: [1, 3, 8, 7, 9, 11]
Java 源码:
public class SinglePointUpdate {
private int[] tree;
public SinglePointUpdate(int[] nums) {
tree = new int[nums.length + 1];
for (int num : nums) {
update(num, 1);
}
}
public void update(int idx, int val) {
update(val, 1, idx + 1);
}
private void update(int val, int size, int idx) {
while (idx < tree.length) {
tree[idx] += val * size;
idx += idx & (-idx);
}
}
public static void main(String[] args) {
SinglePointUpdate update = new SinglePointUpdate(new int[]{1, 3, 5, 7, 9, 11});
int idx = 2;
int val = 8;
update.update(idx, val);
System.out.println("Updated array: " + Arrays.toString(update.getArray()));
}
private int[] getArray() {
int[] nums = new int[tree.length - 1];
for (int i = 1; i < tree.length; i++) {
nums[i - 1] = tree[i] / (1 << (32 - Integer.numberOfLeadingZeros(i & -i)));
}
return nums;
}
}
这些题目和源码展示了树状数组在解决实际问题中的应用。在面试中,能够根据问题的特点选择合适的算法并实现其解决方案是非常重要的。希望这些示例能够帮助你更好地准备面试!