树状数组

406. 根据身高重建队列 方法一:贪心+插入
方法二:树状数组
类似于AcWing 244. 谜一样的牛
前者已知身高和排在前面的数量求位置
后者已知位置和排在前面的数量求身高
315. 计算右侧小于当前元素的个数 方法一:树状数组
方法二:归并
类似于剑指 Offer 51. 数组中的逆序对
区别在于后者是整体逆序对数量,前者是针对具体某个元素而言的,归并实现时细节有所区别
327. 区间和的个数 方法一:离散化+树状数组
方法二:归并
又是一道利用归并的中间过程的题!
493. 翻转对 方法一:离散化+树状数组
方法二:归并

AcWing 1215. 小朋友排队 方法一:树状数组
方法二:归并
从单个元素着手

406. 根据身高重建队列

思路:
方法1:从高到低考虑
重新排序,按照身高从大到小,相同身高的按照排在前面的人数从小到大排。
考虑到总人数只有2000,可以在O(n2)时间复杂度内暴力插入解决。
从前往后根据相对关系将每个人插入到队列中
方法2:从低到高考虑
重新排序,按照身高从小到大,相同身高按照排在前面的人数从大到小排。
从前往后将每个人置于他应该在的位置
实现1:暴力寻找每个人应在的位置
实现2:使用二分+ 树状数组优化求解每个人应在的位置

  1. // 从高到低考虑
  2. class Solution {
  3. public int[][] reconstructQueue(int[][] people) {
  4. List<Integer> list = new LinkedList<>();
  5. Arrays.sort(people, (o1, o2) -> (o1[0] == o2[0] ? o1[1] - o2[1] : o2[0] - o1[0]));
  6. int n = people.length;
  7. for (int i = 0; i < n; i++) {
  8. int idx = people[i][1];
  9. list.add(idx, i);
  10. }
  11. int[][] res = new int[n][2];
  12. int i = 0;
  13. for (int idx : list) {
  14. res[i++] = people[idx];
  15. }
  16. return res;
  17. }
  18. }
  1. // 从低到高考虑,使用暴力插入
  2. class Solution {
  3. public int[][] reconstructQueue(int[][] people) {
  4. int n = people.length;
  5. Arrays.sort(people, (o1, o2) -> (o1[0] == o2[0] ? o2[1] - o1[1] : o1[0] - o2[0]));
  6. int[] a = new int[n];
  7. Arrays.fill(a, -1);
  8. int idx = 0;
  9. for (int[] p : people) {
  10. int h = p[0], c = p[1];
  11. int cnt = 0;
  12. int i = 0;
  13. while (cnt < c) {
  14. if (a[i] == -1)
  15. cnt++;
  16. i++;
  17. }
  18. while (a[i] != -1)
  19. i++;
  20. a[i] = idx++;
  21. }
  22. int[][] res = new int[n][];
  23. for (int i = 0; i < n; i++) {
  24. res[i] = people[a[i]];
  25. }
  26. return res;
  27. }
  28. }
  1. // 从低到高考虑,使用二分 + 树状数组优化
  2. class Solution {
  3. public int[][] reconstructQueue(int[][] people) {
  4. int n = people.length;
  5. Arrays.sort(people, (o1, o2) -> (o1[0] == o2[0] ? o2[1] - o1[1] : o1[0] - o2[0]));
  6. BIT bit = new BIT(n);
  7. bit.init();
  8. int[][] res = new int[n][2];
  9. for (int i = 0; i < n; i++) {
  10. int h = people[i][0], k = people[i][1];
  11. int t = k + 1;
  12. int l = 1, r = n;
  13. while (l < r) {
  14. int mid = l + r >> 1;
  15. if (bit.sum(mid) < t)
  16. l = mid + 1;
  17. else
  18. r = mid;
  19. }
  20. res[l - 1] = people[i];
  21. bit.add(l, -1);
  22. }
  23. return res;
  24. }
  25. }
  26. class BIT {
  27. int n;
  28. int[] a;
  29. BIT(int n) {
  30. this.n = n;
  31. a = new int[n + 1];
  32. }
  33. void init() {
  34. for (int i = 1; i <= n; i++)
  35. a[i] = i & (-i);
  36. }
  37. void add(int idx, int x) {
  38. for (int i = idx ; i <= n; i += i & (-i))
  39. a[i] += x;
  40. }
  41. int sum(int idx) {
  42. int res = 0;
  43. for (int i = idx; i > 0; i -= i & (-i))
  44. res += a[i];
  45. return res;
  46. }
  47. }

时间复杂度分析:
前两种都是O(n2)
用树状数组复杂度为O(nlognlogn)

AcWing 244. 谜一样的牛

思路:
倒着遍历,相当于已知当前奶牛在所有可选高度中属于第几小。
用树状数组维护当前剩余可选高度,边维护边求解
时间复杂度:O(nlognlogn)

  1. import java.util.*;
  2. public class Main {
  3. static final int N = 100010;
  4. static int n;
  5. static int[] a = new int[N], res = new int[N];
  6. static BIT bit = new BIT(N);
  7. public static void main(String[] args) {
  8. Scanner sc = new Scanner(System.in);
  9. bit.init();
  10. n = sc.nextInt();
  11. for (int i = 2; i <= n; i++)
  12. a[i] = sc.nextInt();
  13. for (int i = n; i >= 1; i--) {
  14. int x = a[i] + 1;
  15. int l = 1, r = n;
  16. while (l < r) {
  17. int mid = l + r >> 1;
  18. if (bit.sum(mid) < x)
  19. l = mid + 1;
  20. else
  21. r = mid;
  22. }
  23. res[i] = l;
  24. bit.add(l, -1);
  25. }
  26. for (int i = 1; i <= n; i++)
  27. System.out.println(res[i]);
  28. }
  29. }
  30. class BIT {
  31. int n;
  32. int[] a;
  33. BIT(int n) {
  34. this.n = n;
  35. a = new int[n + 1];
  36. }
  37. void init() {
  38. for (int i = 1; i <= n; i++)
  39. a[i] = lowbit(i);
  40. }
  41. void add(int idx, int x) {
  42. for (int i = idx; i <= n; i += lowbit(i))
  43. a[i] += x;
  44. }
  45. int sum(int idx) {
  46. int res = 0;
  47. for (int i = idx; i > 0; i -= lowbit(i))
  48. res += a[i];
  49. return res;
  50. }
  51. int lowbit(int x) {
  52. return x & (-x);
  53. }
  54. }

308. 二维区域和检索 - 可变

给你一个二维矩阵 matrix ,你需要处理下面两种类型的若干次查询:

  1. 更新:更新 matrix 中某个单元的值。
  2. 求和:计算矩阵 matrix 中某一矩形区域元素的 ,该区域由 左上角 (row1, col1) 和 右下角 (row2, col2) 界定。

image.png
实现 NumMatrix 类:

  • NumMatrix(int[][] matrix) 用整数矩阵 matrix 初始化对象。
  • void update(int row, int col, int val) 更新 matrix[row][col] 的值到 val 。
  • int sumRegion(int row1, int col1, int row2, int col2) 返回矩阵 matrix 中指定矩形区域元素的 ,该区域由 左上角 (row1, col1) 和 右下角 (row2, col2) 界定。

示例 1:
输入 [“NumMatrix”, “sumRegion”, “update”, “sumRegion”] [[[[3, 0, 1, 4, 2], [5, 6, 3, 2, 1], [1, 2, 0, 1, 5], [4, 1, 0, 1, 7], [1, 0, 3, 0, 5]]], [2, 1, 4, 3], [3, 2, 2], [2, 1, 4, 3]] 输出 [null, 8, null, 10] 解释 NumMatrix numMatrix = new NumMatrix([[3, 0, 1, 4, 2], [5, 6, 3, 2, 1], [1, 2, 0, 1, 5], [4, 1, 0, 1, 7], [1, 0, 3, 0, 5]]); numMatrix.sumRegion(2, 1, 4, 3); // 返回 8 (即, 左侧红色矩形的和) numMatrix.update(3, 2, 2); // 矩阵从左图变为右图 numMatrix.sumRegion(2, 1, 4, 3); // 返回 10 (即,右侧红色矩形的和)

提示:

  • m == matrix.length
  • n == matrix[i].length
  • 1 <= m, n <= 200
  • -105 <= matrix[i][j] <= 105
  • 0 <= row < m
  • 0 <= col < n
  • -105 <= val <= 105
  • 0 <= row1 <= row2 < m
  • 0 <= col1 <= col2 < n
  • 最多调用104 次 sumRegion 和 update 方法

思路:
二维树状数组

  1. class NumMatrix {
  2. int n, m;
  3. int[][] a;
  4. private int lowbit(int x) {
  5. return x & (-x);
  6. }
  7. public NumMatrix(int[][] g) {
  8. this.n = g.length;
  9. this.m = g[0].length;
  10. a = new int[n + 1][m + 1];
  11. // 注意初始化,先列后行,不能同时更新
  12. for (int i = 1; i <= n; i++) {
  13. for (int j = 1; j <= m; j++) {
  14. a[i][j] += g[i - 1][j - 1];
  15. int j2 = j + lowbit(j), i2 = i + lowbit(i);
  16. if (j2 <= m)
  17. a[i][j2] += a[i][j];
  18. }
  19. }
  20. for (int i = 1; i <= n; i++) {
  21. int i2 = i + lowbit(i);
  22. if (i2 > n) continue;
  23. for (int j = 1; j <= m; j++) {
  24. a[i2][j] += a[i][j];
  25. }
  26. }
  27. }
  28. public void update(int row, int col, int val) {
  29. val -= sumRegion(row, col, row, col);
  30. row++;
  31. col++;
  32. add(row, col, val);
  33. }
  34. private void add(int row, int col, int val) {
  35. for (int i = row; i <= n; i += lowbit(i)) {
  36. for (int j = col; j <= m; j += lowbit(j)) {
  37. a[i][j] += val;
  38. }
  39. }
  40. }
  41. private int sum(int row, int col) {
  42. int res = 0;
  43. for (int i = row; i > 0; i -= lowbit(i)) {
  44. for (int j = col; j > 0; j -= lowbit(j)) {
  45. res += a[i][j];
  46. }
  47. }
  48. return res;
  49. }
  50. public int sumRegion(int row1, int col1, int row2, int col2) {
  51. row1++;
  52. row2++;
  53. col1++;
  54. col2++;
  55. return sum(row2, col2) - sum(row2, col1 - 1) - sum(row1 - 1, col2) + sum(row1 - 1, col1 - 1);
  56. }
  57. }
  58. /**
  59. * Your NumMatrix object will be instantiated and called as such:
  60. * NumMatrix obj = new NumMatrix(matrix);
  61. * obj.update(row,col,val);
  62. * int param_2 = obj.sumRegion(row1,col1,row2,col2);
  63. */
  1. class NumMatrix {
  2. int n, m;
  3. int[][] a;
  4. private int lowbit(int x) {
  5. return x & (-x);
  6. }
  7. public NumMatrix(int[][] g) {
  8. this.n = g.length;
  9. this.m = g[0].length;
  10. a = new int[n + 1][m + 1];
  11. // 用更新的方式初始化
  12. for (int i = 1; i <= n; i++) {
  13. for (int j = 1; j <= m; j++) {
  14. add(i, j, g[i - 1][j - 1]);
  15. }
  16. }
  17. // for (int i = 1; i <= n; i++) {
  18. // int i2 = i + lowbit(i);
  19. // if (i2 > n) continue;
  20. // for (int j = 1; j <= m; j++) {
  21. // a[i2][j] += a[i][j];
  22. // }
  23. // }
  24. }
  25. public void update(int row, int col, int val) {
  26. val -= sumRegion(row, col, row, col);
  27. row++;
  28. col++;
  29. add(row, col, val);
  30. }
  31. private void add(int row, int col, int val) {
  32. for (int i = row; i <= n; i += lowbit(i)) {
  33. for (int j = col; j <= m; j += lowbit(j)) {
  34. a[i][j] += val;
  35. }
  36. }
  37. }
  38. private int sum(int row, int col) {
  39. int res = 0;
  40. for (int i = row; i > 0; i -= lowbit(i)) {
  41. for (int j = col; j > 0; j -= lowbit(j)) {
  42. res += a[i][j];
  43. }
  44. }
  45. return res;
  46. }
  47. public int sumRegion(int row1, int col1, int row2, int col2) {
  48. row1++;
  49. row2++;
  50. col1++;
  51. col2++;
  52. return sum(row2, col2) - sum(row2, col1 - 1) - sum(row1 - 1, col2) + sum(row1 - 1, col1 - 1);
  53. }
  54. }
  55. /**
  56. * Your NumMatrix object will be instantiated and called as such:
  57. * NumMatrix obj = new NumMatrix(matrix);
  58. * obj.update(row,col,val);
  59. * int param_2 = obj.sumRegion(row1,col1,row2,col2);
  60. */