题目:Range Sum Query - Immutable
Given an integer array nums, find the sum of the elements between indices i and j (i ≤ j), inclusive.
Example:
Given nums = [-2, 0, 3, -5, 2, -1] sumRange(0, 2) -> 1 sumRange(2, 5) -> -1 sumRange(0, 5) -> -3
Note:
- You may assume that the array does not change.
- There are many calls to sumRange function.
给定一个数组,设计一个类能得到任意范围内的元素和。
要求:
- 假设数组不会改变;
- sumRange会被频繁调用。
思路:
要尽量使得sumRange的时间复杂度较小;可以考虑提前求出元素的和,这样可以达到O(1)的时间复杂度;
因此在构造的时候求出[0,i](0 <= i <= n)的所有元素的和,这样可以通过加减求出任意范围[i,j]的和。
class NumArray { public: NumArray(vector<int> nums) { int sum = 0; for (auto i : nums){ sum += i; sums.push_back(sum); } } int sumRange(int i, int j) { if(i > 0)return sums.at(j) - sums.at(i - 1); return sums.at(j); } private: vector<int>sums;//[0,i]的元素和 };
题目:Range Sum Query 2D - Immutable
Given a 2D matrix matrix, find the sum of the elements inside the rectangle defined by its upper left corner (row1, col1) and lower right corner (row2, col2).
The above rectangle (with the red border) is defined by (row1, col1) = (2, 1) and (row2, col2) = (4, 3), which contains sum = 8.
Example:
Given matrix = [ [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] ] sumRegion(2, 1, 4, 3) -> 8 sumRegion(1, 1, 2, 2) -> 11 sumRegion(1, 2, 2, 4) -> 12
Note:
- You may assume that the matrix does not change.
- There are many calls to sumRegion function.
- You may assume that row1 ≤ row2 and col1 ≤ col2.
从一维上升为二维,给定一个二维矩阵,求出给定的小矩阵范围内元素的和,
要求:
- 假设数组不会改变;
- sumRange会被频繁调用。
- 给定的是左上角的点和右下角的点
思路:
提前求出一些小矩阵的和,具体来说Sum[i][j]表示从点(0,0)到点(i,j)(这里的点是指的左上角的点和右下角的点)所组成的矩阵的所有元素的和。
然后任意矩阵(row1,col1)->(row2,col2)的范围内元素的和为Sum(row2,col2) - Sum(row1 - 1,col2) - Sum(row2,col1 - 1) + Sum(row1 - 1,col1 - 1);
当然要判断左上角的点的坐标是否大于0。
class NumMatrix { public: NumMatrix(vector<vector<int>> matrix) { for (size_t i = 0; i < matrix.size(); ++i){ vector<int>line; int sum = 0; for (size_t j = 0; j < matrix.at(i).size(); ++j){ sum += matrix.at(i).at(j); if(i)line.push_back(sum + sums.at(i - 1).at(j)); else line.push_back(sum); } sums.push_back(line); } } int sumRegion(int row1, int col1, int row2, int col2) { if (row1 && col1){//左上角不靠边 return sums.at(row2).at(col2) - sums.at(row1 - 1).at(col2) - sums.at(row2).at(col1 - 1) + sums.at(row1 - 1).at(col1 - 1); } else if (row1){//左边靠边 return sums.at(row2).at(col2) - sums.at(row1 - 1).at(col2); } else if (col1){//上边靠边 return sums.at(row2).at(col2) - sums.at(row2).at(col1 - 1); } //左上角都贴边 return sums.at(row2).at(col2); } private: vector<vector<int>>sums; };
题目:Range Sum Query - Mutable
Given an integer array nums, find the sum of the elements between indices i and j (i ≤ j), inclusive.
The update(i, val) function modifies nums by updating the element at index i to val.
Example:
Given nums = [1, 3, 5] sumRange(0, 2) -> 9 update(1, 2) sumRange(0, 2) -> 8
Note:
- The array is only modifiable by the update function.
- You may assume the number of calls to update and sumRange function is distributed evenly.
与第一个题目不同,这题数组是可以修改的,但是这里没有说明是否可以添加新节点,实际上LeetCode的测试用例中没有添加新节点的,但是他的内部标准答案中添加新节点也可以成功。
我这里的方法,并没有完善到这一步,但是并没有问题,像是树的插入一样,可以支持添加新节点,只需要在下面的基础上完善一下;
但是需要注意的是,经常添加会使得树变得很不平衡。如果要解决这个问题可以采用平衡二叉树,更具体可以参照:
因为也是求范围内元素的和,所以必然需要一个范围,因此树节点结构如下:
struct SumTreeNode {//树节点 int sum; pair<int, int>range; SumTreeNode *left; SumTreeNode *right; SumTreeNode(int x, int i, int j) : sum(x), range(make_pair(i, j)), left(NULL), right(NULL) {} };
思路:
每个节点上存储当前节点的范围和该范围内的和,这样最大需要O(logn)的时间复杂度就能求出任意范围的和。
待求的范围包括三种情况:
- 只在左子树中;
- 只在右子树中;
- 横跨左右子树;
同时更新的时候也是递归查找,注意更新了对应的节点的值后,还要回溯来更新节点中存储的和;
class NumArray2 { public: NumArray2(vector<int> nums) { if (!nums.size())root = nullptr; else root = buildSumTree(nums, 0, nums.size() - 1); } void update(int i, int val) { if (root->range.first <= i && root->range.second >= i){ updateSumTree(root, i, val); } } int sumRange(int i, int j) { return querySumTree(root, i, j); } private: SumTreeNode* root; SumTreeNode* buildSumTree(vector<int>& num, int l, int r){ if (l == r){//到达叶节点 SumTreeNode* p = new SumTreeNode(num.at(l),l,r); return p; } SumTreeNode* pa = new SumTreeNode(0,l,r); pa->left = buildSumTree(num, l, (r + l) / 2);//递归建立左子树 pa->right = buildSumTree(num, (r + l) / 2 + 1, r);//递归建立右子树 pa->sum = pa->left->sum + pa->right->sum; return pa; } int updateSumTree(SumTreeNode* p, int i, int v){ int dif = 0; //到达叶节点,或者i == 0 if (p->range.first == p->range.second && (p->range.second == i || (!i && !p->range.second))){ dif = v - p->sum; p->sum = v; } else{ if (p->left->range.second >= i){//更新左子树 dif = updateSumTree(p->left, i, v); } else{//更新右子树 dif = updateSumTree(p->right, i, v); } p->sum += dif;//跟新当前节点的和 } return dif; } int querySumTree(SumTreeNode* p, int i, int j){ if (p->range.first == i && p->range.second == j)return p->sum;//叶节点 if (p->left->range.second >= j){//仅在左子树的范围内 return querySumTree(p->left, i, j); } else if (p->right->range.first <= i){//仅在右子树的范围内 return querySumTree(p->right, i, j); } int sum = 0;//横跨左右子树,要不和加起来 sum += querySumTree(p->left, i, p->left->range.second); sum += querySumTree(p->right, p->right->range.first, j); return sum; } };