数据结构题小结
- 前言
- 一、queue/stack
- 二、priority_queue
- 三、deque
- 四、map / set
- 五、链表
- 六、字符串
- 七、图
- 八、树
- 1. Leetcode 437 路径总和III
- 2. Leetcode 1110 删点成林
- 3.Leetcode 208 字典树
- 4. JZ27 二叉树的镜像
- 5. JZ82 二叉树中和为某一值的路径(一)
- 6. JZ82 二叉树中和为某一值的路径(二)
- 7. JZ54 二叉搜索树的第k个节点
- 8. JZ77 按之字形顺序打印二叉树
- 9. JZ86 在二叉树中找到两个节点的最近公共祖先
- 10. JZ7 重建二叉树
- 11. JZ26 树的子结构
- 12. JZ33 二叉搜索树的后序遍历序列
- 13. Leetcode 99 恢复二叉搜索树
- 14. Leetcode 95 不同的二叉搜索树 II
- 15. Leetcode 98 验证二叉搜索树
- 九、自定义类
前言
对数据结构类型的题做一个总结。
一、queue/stack
1 Leetcode 232 用栈实现队列
代码如下:
class MyQueue {
private:
stack<int> s1, s2;
public:
void tf() {
while (!s1.empty()) {
s2.push(s1.top());
s1.pop();
}
}
MyQueue() {}
void push(int x) {
s1.push(x);
}
int pop() {
if (s2.empty()) {
tf();
}
int tmp = s2.top();
s2.pop();
return tmp;
}
int peek() {
if (s2.empty()) {
tf();
}
return s2.top();
}
bool empty() {
return s1.empty() && s2.empty();
}
};
2 JZ9 用两个栈实现队列
代码如下:
class Solution
{
public:
void push(int node) {
stack1.push(node);
}
int pop() {
if (stack2.empty()) {
while (!stack1.empty()) {
stack2.push(stack1.top());
stack1.pop();
}
}
int ans = stack2.top();
stack2.pop();
return ans;
}
private:
stack<int> stack1; // stack1中存放着按顺序压入的节点
stack<int> stack2; // stack2中存放着逆序的节点
// 但是要注意的是,stack1和stack2中存放的都不是完整的节点,而是分开存放的,在执行一次pop之后,stack1中的值就会转到stack2中
};
3. Leetcode 225 用队列实现栈
class MyStack {
queue<int> q1;
queue<int> q2;
public:
MyStack() {
}
void push(int x) {
q2.push(x);
while(!q1.empty()) {
q2.push(q1.front());
q1.pop()
}
swap(q1, q2);
}
int pop() {
int ans = q1.front();
q1.pop();
return ans;
}
int top() {
return q1.front();
}
bool empty() {
return q1.empty();
}
};
4. JZ30 包含min函数的栈
这一题用两个栈,一个栈用来存储push进去的值,另一个栈存放截至到目前的最小值,具有一定的动态规划的思想。
class Solution {
private:
stack<int> s1, smin;
public:
void push(int value) {
s1.push(value);
if (smin.empty()) smin.push(value);
else smin.push(std::min(smin.top(), value));
}
void pop() {
smin.pop();
s1.pop();
}
int top() {
return s1.top();
}
int min() {
return smin.top();
}
};
5. JZ31 栈的压入、弹出序列
这一题可以模拟一下栈的压入与弹出,如果无法按照popV的顺序弹出的话,就返回false。
bool IsPopOrder(vector<int> pushV,vector<int> popV) {
int n = pushV.size();
stack<int> s;
int idx1 = 0, idx2 = 0; // idx1记录pushV中到第几个了,idx2记录popV中到第几个了
while (idx1 < n || idx2 < n) { // 只要还有一个数组没遍历完,就不退出循环
while (s.empty() || s.top() != popV[idx2]) {
if (idxl > n) return false; // 如果这时候已经把所有的压入了,但是和弹出的对应不上,那么就表示没办法按照这个顺序弹出,就返回false
s.push(pushV[idx1]);
idx1++;
}
// 如果对应上了,就按顺序弹出
s.pop();
idx2++;
}
// 如果能按照顺序压入和弹出,就返回true
return true;
}
二、priority_queue
1.Leetcode 23 合并K个有序链表
优先队列一般使用heap实现,其使用方法为:
priority_queue<Type, Container, Functional> q;
如存放链表节点的优先队列如下:
struct Comp{
bool operator() (ListNode* l1, ListNode* l2) {
return l1->val > l2->val;
}
};
priority_queue<ListNode*, vector<ListNode*>, Comp> q;
因此,本题代码如下:
int n = lists.size();
if (n == 0 || (count(lists.begin(), lists.end(), nullptr) == n) return nullptr; // 要么是空链表集,要么里面有链表但是链表都为空,这样都要返回空指针
priority_queue<ListNode*, vector<ListNode*>, Comp> q;
for (ListNode* list: lists) {
if (!list) continue; // 有些链表可能是空的,忽略它们
q.push(list);
}
ListNode* head = new ListNode(0), *cur = head;
while(!q.empty()) {
cur->next = q.top();
q.pop();
cur = cur->next;
if (cur->next) {
q.push(cur->next); // 要把新节点的下一个节点放到优先队列中
}
}
return head->next;
2.Leetcode 218 天际线问题
采用扫描线+优先队列的方法。逐个扫描建筑物的左端,扫描完存在互相重叠的建筑后,再从最高建筑物的右端开始检验,遇到高度发生变化的点就添加进入ans。
int len = buildings.size();
priority_queue<pair<int, int>> p; // 存放<高度,右侧>
int cur_x, cur_h; // 存放新建筑的x和h
int idx = 0; // 扫描到第idx个建筑物
vector<vector<int>> ans;
while (idx < len || !p.empty()) {
// 扫描完毕 且 所有建筑物右端也被排除
if (p.empty() || idx < len && buildings[idx][0] <= p.top().second) {
// 扫描第一个建筑 或者 有重叠建筑
cur_x = buildings[idx][0]; // 新建筑左侧
while (idx < len && cur_x == buildings[idx][0]){
// 第idx个建筑物的信息,顺便解决左端相同的各个障碍物
p.emplace(buildings[idx][2], buildings[idx][1]);
idx++;
}
}
else {
// 把有重叠的建筑部分处理一下
cur_x = p.top().second;
while (!p.empty() && cur_x >= p.top().second) {
// 把最高建筑物右端之前的建筑物都pop掉
p.pop();
}
}
cur_h = p.empty()? 0 : p.top().first; // 更新cur_x对应的高度
if (ans.empty() || cur_h != ans.back()[1]) {
// 如果该高度和之前高度不同,说明发生变化,需要存放进去
ans.push_back({cur_x, cur_h});
}
}
return ans;
另外,力扣上宫水三叶的解答特别好,大家也可以康康。
3.Leetcode 239 滑动最大窗口
采用扫描线+优先队列的方法。逐个扫描建筑物的左端,扫描完存在互相重叠的建筑后,再从最高建筑物的右端开始检验,遇到高度发生变化的点就添加进入ans。
struct Comp{
bool operator() (pair<int, int> p1, pair<int, int> p2) {
if (p1.first != p2.first) return p1.first < p2.first; // 大的放前面
else return p1.second > p2.second; // 大的放后面
}
};
priority_queue<pair<int, int>, vector<pair<int, int>>, Comp> pq;
int n = nums.size();
vector<int> ans;
for (int i = 0; i < k; ++i) {
pq.emplace(nums[i], i);
}
ans.push_back(pq.top().first);
for (int i = 1; i <= n - k; ++i) {
int idx = pq.top().second;
if (idx < i) {
while (!pq.empty() && idx >= pq.top().second) {
pq.pop();
}
}
pq.emplace(nums[i + k - 1], i + k - 1);
ans.push_back(pq.top().first);
}
return ans;
4.JZ40 最小的K个数
最小的K个数有很多方法可以做:
(1)堆排序,用priority_queue。
(2)集合排序,用set元素自动排序的思想。
(3)sort排序。
(4)快速排序,前k个排完了就可以停止了。
以下展示用priority_queue的方法:
vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
int n = input.size();
vector<int> ans;
if (n == 0) return ans;
priority_queue<int, vector<int>, greater<int>> q; // 注意greater<int>是从小到大排序,元素一个比一个大;如果要从大到小排序,要用less<int>
for (int i = 0; i < n; ++i) {
q.push(input[i]);
}
for (int i = 0; i < k; ++i) {
ans.push_back(q.top());
q.pop();
}
return ans;
}
三、deque
双向队列的头文件为:
#include<deque>
双向队列一般有如下操作:
deque<int> dq; // 定义
dq.push_back(1); // 尾部插入
dq.push_front(2); // 头部插入
int b = dq.back(); // 取尾值
int f = dq.front(); // 取头值
dq.clear(); // 清空双端队列
int s = dq.size(); // 取长度
1.Leetcode 239 滑动最大窗口
用双端队列维护滑动窗口内的递减的单调队列,则队首元素则为当前窗口内的最大值。以下分别展示从右边加入和从左边加入的算法。
deque<int> dq; // 存放窗口内的单调序列的下标
vector<int> ans;
for (int i = 0; i < nums.size(); ++i) {
// 在滑到下一个窗口的第一件事 是 把上一个窗口的头值给去掉
if (!dq.empty() && dq.front() <= i - k) {
dq.pop_front();
}
// 把尾值加进去之前先判断之前有没有更小值
while (!dq.empty() && nums[dq.back()] < nums[i]) {
dq.pop_back();
}
// 把尾值加进去
dq.push_back(i);
if (i >= k - 1) ans.push_back(nums[dq.front()]);
}
return ans;
int n = nums.size();
vector<int> ans;
if (n == 0) return ans;
deque<int> dq;
for (int i = 0; i < n; ++i) {
// 左边的加入与弹出:如果队列中有值,且头值小于新值,那么把小于新值的弹出,然后再把这个加入
while (!dq.empty() && nums[i] > nums[dq.front()]) dq.pop_front();
dq.push_front(i);
// 右边的弹出
while (i - dq.back() >= k) dq.pop_back();
if (i >= k - 1) ans.push_back(nums[dq.back()]);
}
return ans;
// 举例
// 1 3 -1 -3 5 3 6 7
// i = 0 : 1
// i = 1 : 3
// i = 2 : -1 3 : 3
// i = 3 : -3 -1 3 : 3 3
// i = 4 : 5 : 3 3 5
// i = 5 : 3 5 : 3 3 5 5
// i = 6 : 6 : 3 3 5 5 6
// i = 7 : 7 : 3 3 5 5 6 7
2.剑指Offer 59 队列的最大值
同时创建队列和双端队列,并用双端队列 是 队列范围内递减的单调队列。
class MaxQueue {
queue<int> q;
deque<int> dq;
public:
MaxQueue() {
}
int max_value() {
if (q.empty()) return -1;
return dq.front();
}
void push_back(int value) {
q.push(value);
while (!dq.empty() && dq.back() < value) {
dq.pop_back();
}
dq.push_back(value);
}
int pop_front() {
if (q.empty()) return -1;
if (q.front() == dq.front()) {
dq.pop_front();
}
int ans = q.front();
q.pop();
return ans;
}
};
四、map / set
map如何查找一个键是否存在,存在的话输出对应的值:
unordered_map<int, char> hash;
...
auto it = hash.find(2);
if (it != hash.end()) cout << it->second << endl;
map如何删除一个元素,要么直接用键大小,要么用对应的迭代器。
map<int, int> m;
for (int i = 0; i < 4; ++i) {
m[i] = i * 10;
}
m.erase(m.begin());
m.erase(1);
for (auto it = m.begin(); it != m.end(); ++it) {
cout << it->first << " " << it->second << endl;
}
// 剩下的是 2 20 3 30
set如果要遍历一遍,需要用*迭代器。
set<int> hash;
hash.insert(19);
hash.insert(20);
for (auto it = hash.begin(); it != hash.end(); ++it) {
cout << *it << endl;
}
set如何删除一个元素,要么直接用元素大小,要么用对应的迭代器。
set<int> s;
for (int i = 0; i < 4; ++i) {
s.insert(i);
}
s.erase(s.begin());
s.erase(1);
// 剩下的是 2 3
1. Leetcode 1 两数之和
unordered_map<int, int> hash;
for (int i = 0; i < nums.size(); ++i) {
if (!hash.empty() && hash.find(target - nums[i]) != hash.end()) {
return vector<int>{i, hash[target - nums[i]]};
}
hash[nums[i]] = i;
}
return vector<int>{-1, -1};
2. Leetcode 13 罗马数字转整数
这一题需要观察给出的字符串的特点,一般情况下字母对应的数字都在逐渐减小,如果有增大,那说明是IV这样的特殊情况,这时候就要做减法了。
int ns = s.size(), ans = 0;
unordered_map<char, int> hash;
hash['I'] = 1;
hash['V'] = 5;
hash['X'] = 10;
hash['L'] = 50;
hash['C'] = 100;
hash['D'] = 500;
hash['M'] = 1000;
for(int i = 0; i < ns; ++i){
if (i + 1 < ns && hash[s[i]] < hash[s[i + 1]]) {
ans -= hash[s[i]];
}
else ans += hash[s[i]];
}
return ans;
3. Leetcode 3 无重复字符的最长子串
int n = s.size();
if (n <= 1) return n;
int ans = 1;
unordered_set<char> us;
int l = 0, r = 0;
while (r < n) {
if (!us.count(s[r])) {
us.insert(s[r]);
ans = max(ans, r - l + 1);
r++;
}
else {
while (us.count(s[r])) {
us.erase(s[l]);
l++;
}
}
}
return ans;
4. Leetcode 128 最长连续序列
这一题将nums中的所有元素存储到unordered_set当中,然后取出里面的值,并分别向前向后查找是否存在连续的元素,若有,长度增加,并且将该元素去掉。
unordered_set<int> hash;
int ans = 0;
for (int n: nums) {
hash.insert(n);
}
while (!hash.empty()) {
int cur = *(hash.begin());
int prev = cur - 1, next = cur + 1;
hash.erase(cur);
while (hash.find(prev) != hash.end()) {
hash.erase(prev--);
}
while (hash.find(next) != hash.end()) {
hash.erase(next++);
}
ans = max(ans, next - prev - 1);
}
return ans;
这一题也可以用set
int longestConsecutive(vector<int>& nums) {
set<int> s;
for (int n: nums) s.insert(n);
int ans = 0;
while (!s.empty()) {
int cur = *(s.begin()), tmp = 0;
while (s.find(cur) != s.end()) {
s.erase(cur);
cur++;
tmp++;
}
ans = max(ans, tmp);
}
return ans;
}
5 Leetcode 159 直线上最多的点
这一题遍历每一个点,每一个点遍历之后的点,判断该点和后面的点:
1.是不是重复点
2.在不在同一列。因为这样的直线不能用y=kx+b表示;
3.斜率是多少。斜率相同的需要汇总起来,这个就要用到hash表。
int ans = 1;
for (int i = 0; i < points.size(); ++i) {
int ians = 0; // 记录第i点最多有多少个共线点
int same = 0; // 记录有多少个重复点
int liney = 0; // 记录有多少个在同一列的点
int lineo = 0; // 记录最多有多少个共线的点
unordered_mao<double, int> hash; // 记录对应斜率有多少个共线的点
for (int j = i + 1; j < points.size(); ++j) {
// 判断是不是重复点
if (points[i][0] == points[j][0] && points[i][1] == points[j][1]) {
same++;
continue;
}
// 判断是不是在同一列,即x坐标相同
if (points[i][0] == points[j][0]) {
liney++;
continue;
}
double dx = points[i][0] - points[j][0], dy = points[i][1] - points[j][1];
hash[dy / dx]++;
lineo = max(lineo, hash[dy / dx]);
}
ians = max(liney, lineo) + same + 1;
ans = max(ans, ians);
}
return ans;
}
6 Leetcode 332 重新安排行程
这一题要用到set的排序特性:对于set或multiset,在insert之后默认从小到大排序,也可以设置从大到小排序,如下所示:
set<int, greater<int>> s; // greater表示越靠前越大,即从大到小排序
set不仅可以排序int,也可以排序char和string。
因此,针对本题,我们希望优先搜索字母序更小的答案,因此采用multiset去存储,那么它的首位置对应的字符串即为字母序最小的。
vector<string> findItinerary(vector<vector<string>>& tickets) {
if (tickets.size() == 0) return vector<string>{};
vector<string> ans; // 存储尾结点,最后翻转回来
stack<string> st; // 存储搜索顺序
unordered_map<string, multiset<string>> mm;
for (auto t : tickets) {
mm[t[0]].insert(t[1]);
}
st.push("JFK");
while(!st.empty()) {
string s = st.top();
if (mm[s].size() == 0) { // 找到尾结点,有可能会提前找到尾节点,但是不要紧,先放到ans里去,比如如下例子:
// [["JFK","KUL"],["JFK","NRT"],["NRT","JFK"]]
ans.push_back(s); // 尾结点放入ans中
st.pop(); // 从st中弹出
} else { // 还没找到尾结点,还要接着搜
st.push(*mm[s].begin()); // 把第一个string放进去,第一个是字母序最小的
mm[s].erase(mm[s].begin()); // Hierholzer 算法,要把这条表移除
}
}
reverse(ans.begin(), ans.end());
return ans;
}
7 JZ 50 第一次只出现一次的字母
这一题用哈希表去存储字母和它对应的下标,如果重复了,那就从哈希表中删除,那么最后哈希表中剩下的就是只出现了一次的字符,然后对其遍历,找出下标最小的。
我在写这一题的时候,用到了哈希表的删除,这一点用的很少,hash.erase(xxx)。以后要记得,免得还需要上网查。
int FirstNotRepeatingChar(string str) {
int n = str.size()l
if (n == 0) return -1;
if (n == 1) return 0;
unordered_map<char, int> hash;
hash.insert({str[0], 0});
for (int i = 1; i < n; ++i) {
if (hash.find(str[i]) != hash.end()) hash.erase(str[i]);
else hash.insert({str[i], i});
}
if (hash.empty()) return -1;
int ans = n;
for (auto it = hash.begin(); it != hash.end(); ++it) {
ans = min(ans, it->second);
}
return ans;
}
8 Leetcode 146 LRU 缓存
class LRUCache {
unordered_map<int, pair<int, list<int>::iterator>> hash; // 通过key去找value和对应的迭代器
list<int> l; // 存储key
int cap;
void insert(int key, int value) {
l.push_back(key);
hash[key] = make_pair(value, --l.end());
}
public:
LRUCache(int capacity) {
cap = capacity;
}
int get(int key) {
if (hash.find(key) != hash.end()) {
// 把它放到list最前面(尾巴的地方),用的方法是先通过迭代器删除,再补充到rbegin()处
l.erase(hash[key].second);
l.push_back(key);
hash[key].second = --l.end();
return hash[key].first;
}
return -1;
}
void put(int key, int value) {
// 判断这里面有没有,有就更新value,没有就插入
if (get(key) == -1) { // 没有
// 要判断容量是不是够
if (l.size() < cap) {
// 容量够
insert(key, value);
} else {
// 容量不够
hash.erase(*l.begin());
l.pop_front();
insert(key, value);
}
} else {
hash[key].first = value;
}
}
};
五、链表
1. HJ 51 输出单向链表中倒数第k个节点
用双指针,把右指针右移k次,然后左右指针同时右移,当右指针为空时,左指针就到倒数第k个了。
#include<iostream>
using namespace std;
struct ListNode
{
int m_nKey;
ListNode* m_pNext;
};
int main() {
int n;
while (cin >> n) {
ListNode *head = new ListNode(), *cur = head;
int val, k;
while(n--) {
cur->m_pNext = new ListNode(); // 先往后取一个节点,这样做是为了保证每次创建的新节点都有用到
cur = cur->m_pNext;
cin >> val;
cur->m_nKey = val;
}
cin >> k;
if (k == 0) cout << 0 << endl;
else {
ListNode *c1 = head->m_pNext, *c2 = c1;
while (k--) {
c2 = c2->m_pNext; // 先让c2往后挪k个
if (!c2) cout << 0;
}
while (1) {
c1 = c1->m_pNext;
c2 = c2->m_pNext;
if (!c2) {
cout << c1->m_nKey << endl; // 当c2指向空节点时,c1就指向了目标节点
break;
}
}
}
}
return 0;
}
2. Leetcode 206 翻转链表
方法1,新建一个链表,每读到一个节点,就在新链表的前面追加一个节点。
if (head == nullptr) return head;
ListNode *ans = nullptr, *cur = nullptr;
while (head) {
ListNode* temp = new ListNode(head->val);
ans = temp;
temp->next = cur;
cur = temp;
head = head->next;
}
return ans;
方法2,将原节点的指针更改方向
ListNode* reverseList(ListNode* head) {
if (!head) return head;
ListNode* prev = nullptr; // 这个用来记录当前head节点的前一个节点
return rev(head, prev);
}
ListNode* rev(ListNode* head, ListNode* prev) {
// 递归的出口条件是 当head是空节点时,即读到了最后,那么返回链表最后一个节点,即prev
if (!head) return prev;
// 更改链表方向
ListNode* next = head->next;
head->next = prev;
return rev(next, head);
}
方法3,不用递归。
ListNode* reverseList(ListNode* head) {
ListNode *cur = head, *prev = nullptr;
while (cur) {
ListNode* next = cur->next;
cur->next = prev;
prev = cur;
if (!next) break;
cur = next;
}
return cur;
}
3. Leetcode 92 反转链表II
比官方答案简单一些。
ListNode* reverseBetween(ListNode* head, int left, int right) {
if (left == right) return head; // left=right,说明不需要反转
ListNode* dummy = new ListNode(0, head), *cur = dummy;
int tmp = left;
while (tmp > 1) {
cur = cur->next; // 找到待反转第一个节点的前一个节点
tmp--;
}
cur->next = rev(cur->next, right- left);
return dummy->next;
}
//反转链表len次,并把头结点和尾巴接上,返回最后一个被反转的节点
ListNode* rev(ListNode* head, int len) {
ListNode* prev = head, *cur = head->next;
while (len--) {
ListNode* next = cur->next;
cur->next = prev;
prev = cur;
cur = cur->next;
}
head->next = cur; // 头结点和尾巴接上
return prev; // 返回最后一个被反转的节点
}
4. Leetcode 24 两两交换链表的节点
方法1,非递归,用循环来做
ListNode* swapPairs(ListNode* head) {
if (!head || !head->next) return head;
ListNode* cur = head;
while (cur && cur->next) {
int temp = cur->val;
cur->val = cur->next->val;
cur->next->val = temp;
cur = cur->next->next;
}
return head;
}
5.删除链表的倒数第 N 个结点
这种题要注意,因为它可能删除头结点,所以在头结点之前加一个dummy哑结点,可以大大简化这个过程。
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummy = new ListNode(), *l = dummy, *r = dummy;
while (n) {
r = r->next;
n--;
}
while (r->next) {
r = r->next;
l = l->next;
} // 循环结束后,l所在位置即为目标节点的前一个节点
l->next = l->next->next; // 删除目标节点
return dummy->next; // 如果head被删除了就不能返回head
}
方法2,用递归来做,递归函数的作用是返回交换值后的节点
ListNode* swapPairs(ListNode* head) {
if (!head || !head->next) return head;
int temp = head->val;
head->val = head->next->val;
head->next->val = temp;
head->next->next = swapPairs(head->next->next);
return head;
}
6. 移除链表元素
很简单,创建一个dummy节点来简化一下算法。
ListNode* removeElements(ListNode* head, int val) {
if (!head) return head;
ListNode* dummy = new ListNode(0, head), *l = dummy, *r = head;
while (r) {
if (r->val == val) {
l->next = r->next;
r = r->next;
} else {
l = r;
r = r->next;
}
}
return dummy->next;
}
7. Leetcode 21 合并两个有序链表
要设置dummy节点。
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
if (!list1) return list2;
if (!list2) return list1;
ListNode* dummy = new ListNode(), *cur = dummy;
while (list1 && list2) {
if (list1->val <= list2->val) {
cur->next = list1;
list1 = list1->next;
} else {
cur->next = list2;
list2 = list2->next;
}
cur = cur->next;
}
if (list1) cur->next = list1;
else if (list2) cur->next = list2;
return dummy->next;
}
六、字符串
1. HJ 106 字符逆序
法1:将字符串放入栈中,并弹出,则为逆序
#include<bits/stdc++.h>
using namespace std;
int main() {
string s;
stack<char> st;
getline(cin, s);
for (int i = 0; i < s.size(); ++i) {
st.push(s[i]);
}
while(!st.empty()) {
cout << st.top();
st.pop();
}
return 0;
}
法2:将字符串翻转
reverse(s.begin(), s.end());
cout << s;
2. JZ73 翻转单词序列
这一题根据空格或末尾判断一个单词的结束,然后把该单词截取出来substr放入答案中。
string ReverseSentence(string str) {
int n = str.size();
string ans;
if (n == 0) return ans; // 如果是空字符串,那么就输出空字符串
int idxl = 0, idxr = 0;
for (;idxr <= n; ++idxr) {
if (str[idxr] == ' ' || idxr == n) {
if (idxl == 0) ans = str.substr(idxl, idxr - idxl); // 如果还没有赋值,那么就只赋值这段单词,否则还需要加上空格
else ans = str.substr(idxl, idxr - idxl) + " " + ans; // 要加上空格
}
}
return ans;
}
七、图
图中节点在力扣中通常以如下方式进行定义:
class Node {
public:
int val;
vector<Node*> neighbors;
// 以下是很有用的构造函数
Node() {
val = 0;
neighbors = vector<Node*>();
}
Node(int _val) {
val = _val;
neighbors = vector<Node*>();
}
Node(int _val, vector<Node*> _neighbors) {
val = _val;
neighbors = _neighbors;
}
};
1. Leetcode 133 克隆图
采用深度优先搜索对图的节点进行克隆,并采用hash表去存储原图和新图对应节点。
Node* cloneGraph(Node* node) {
if (node == nullptr) return node;
Node* ans = new Node(node->val);
if (node->neighbors.size() == 0) return ans;
unordered_map<Node*, Node*> hash;
hash[node] = ans;
dfs(node, ans, hash);
return ans;
}
// node是原图的节点,ans是新图对应的节点
void dfs(Node* node, Node* ans, unordered_map<Node*, Node*>& hash) {
// 不用设置出口条件,因为在如下代码中设置了入口条件
// 将node中所有的邻接点遍历,如果该点曾经遍历过,则直接添加在ans的邻接点中
// 如果没有被遍历,不仅需要创建该点,添加在ans的邻接点中,还需要以该点继续遍历
for (int i = 0; i < node->neighbors.size(); ++i) {
if (hash.find(node->neighbors[i]) == hash.end()) { // 没有被遍历过
Node* cur = new Node(node->neighbors[i]->val);
ans->neighbors.push_back(cur);
hash[node->neighbors[i]] = cur;
dfs(node->neighbors[i],cur, hash);
} else ans->neighbors.push_back(hash[node->neighbors[i]]);
}
}
2. Leetcode 207 课程表
用哈希表去存先修课程与其对应的后修课程,用数组去存各个课程的先修课程的数量,用队列去存当前情况下有哪些课程能修,用一个int去存总共能修完多少课。
对队列中的课程遍历,在数组中查看是否有新的入度为0的idx,这些idx继续放入队列,继续循环,直到队列为空。
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
unordered_map<int, vector<int>> hash;
vector<int> indegree(numCourses);
queue<int> seq;
int total = 0;
for (auto p : prerequisites) {
if (p[1] == p[0]) return false;
hash[p[1]].push_back(p[0]);
indegree[p[0]]++;
}
for (int i = 0; i < numCourses; ++i){
if (indegree[i] == 0) {
seq.push(i);
total++;
}
}
while (!seq.empty()) {
int cur = seq.front(); // cur为当前修的课程
seq.pop();
for (int i: hash[cur]) { // 找出修完cur才能修的课程
indegree[i]--;
if (indegree[i] == 0) {
seq.push(i);
total++;
}
}
}
return total == numCourses;
}
3. Leetcode 310 最小高度树
用数组去存每个点的度,用哈希表去存储边。
从度为1的节点同时开始向内搜索,当最终汇聚到最后一圈的点即为答案。
vector<int> findMinHeightTrees(int n, vector<vector<int>>& edges) {
if (n == 1) return vector<int>{0};
unordered_map<int, vector<int>> hash;
vector<int> degree(n, 0);
vector<int> ans;
queue<int> seq;
int total = 0;
for (auto e: edges) {
hash[e[0]].push_back(e[1]);
hash[e[1]].push_back(e[0]);
degree[e[0]]++;
degree[e[1]]++;
}
for (int i = 0; i < n; ++i) {
if (degree[i] == 1) {
seq.push(i);
total++;
}
}
while (1) {
if (total == n) break;
int sz = seq.size();
for (int i = 0; i < sz; ++i) {
int cur = seq.front();
degree[cur]--;
seq.pop();
for (int h: hash[cur]) {
if (degree[h] == 0) continue;
degree[h]--;
if (degree[h] == 1) {
seq.push(h);
total++;
}
if (degree[h] == 0) return vector<int>{h};
}
}
}
for (int i = 0; i < n; ++i) {
if (degree[i] == 1) ans.push_back(i);
}
return ans;
4. Leetcode 210 课程表2
vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
// 用ind存储每个节点的入度,入度为0时,表示先修课程已完成
// 用hash存储每个节点指向的节点,是一个vector
// 要q去存储当前入度为0的节点
// 用ans存储搜索顺序
// 如果无法按照要求先后修,应当出现ind剩余的都不为0,这时返回空数组
vector<int> ind(numCourses, 0), ans;
map<int, vector<int>> hash;
queue<int> q;
// 填ind和hash
for (auto v: prerequisites) {
// v[0]入 v[1]出
ind[v[0]]++;
hash[v[1]].push_back(v[0]);
}
for (int i = 0; i < numCourses; ++i) {
if (ind[i] == 0) q.push(i);
}
while (!q.empty()) {
int cur = q.front();
ans.push_back(cur);
q.pop();
// 把对应入度--,若入度为0,则加入q
vector<int> cour = hash[cur];
for (int c: cour) {
ind[c]--;
if (ind[c] == 0) q.push(c);
}
}
for (int i = 0; i < numCourses; ++i) {
if (ind[i] != 0) return vector<int>{};
}
return ans;
}
5. Leetcode 684 并查集
class Solution {
vector<int> anc; // 用于存放各点的祖先节点
public:
int find(x) { // 找到该节点的祖父节点
if (anc[i] == i) return i;
else anc[i] = find(anc[i]);
return anc[i];
}
void unionn(int i , int j) {
int anci = find(i), ancj = find(j);
anc[anci] = ancj;
}
vector<int> findRedundantConnection(vector<vector<int>>& edges) {
int n = edges.size(); // n为节点数量
for (int i = 0; i <= n; ++i) anc.push_back(i); // 初始化每个节点的祖先节点为自己
for (auto e: edges) {
if (find(e[0]) == find(e[1])) return e; // 如果两个节点的祖父节点相同,那么这两个节点就不需要再连接
unionn(e[0], e[1]);
}
return vector<int>{};
}
};
八、树
1. Leetcode 437 路径总和III
int pathSum(TreeNode* root, int targetSum) {
if(!root) return 0;
// 要考虑三种情况:
// root点的值被取,之后一直要取,所以用另外的函数rootSum,表示一定要取当前节点
// root值不被取,那么左右都有可能,则有两种情况
return rootSum(root, targetSum - root->val)
+ pathSum(root->left, targetSum)
+ pathSum(root->right, targetSum);
}
int rootSum(TreeNode* root, int targetSum){
if (!root) return 0;
int ans = 0;
if (root->val == targetSum) ans++;
ans += rootSum(root->left, targetSum - root->val);
ans += rootSum(root->right, targetSum - root->val);
return ans;
}
2. Leetcode 1110 删点成林
这一题采用深度优先搜索,如果搜索到的值在hash表中,则将其非空的左右节点放入ans中,并将其置为null。
vector<TreeNode*> delNodes(TreeNode* root, vector<int>& to_delete) {
if (!root) return vector<TreeNode*>{nullptr};
unordered_set<int> hash;
vector<TreeNode*> ans;
for (int i : to_delete) hash.insert(i);
root = dfs(root, hash, ans);
if (root) ans.push_back(root);
return ans;
}
TreeNode* dfs(TreeNode* root, unordered_set<int> hash, vector<TreeNode*>& ans) {
if (!root) return root;
root->left = dfs(root->left, hash, ans);
root->right = dfs(root->right, hash, ans);
if (hash.count(root->val)) {
if (root->left) ans.push_back(root->left);
if (root->right) ans.push_back(root->right);
root = nullptr;
}
return root;
}
3.Leetcode 208 字典树
class Trie {
struct TreeNode {
bool finish = false;
TreeNode* next[26] = {nullptr};
};
TreeNode* root = new TreeNode();
public:
Trie() {
}
void insert(string word) {
int n = word.length();
TreeNode* cur = root;
for (int i = 0; i < n; ++i) {
if (cur->next[word[i] - 'a'] == nullptr) {
TreeNode* tmp = new TreeNode();
cur->next[word[i] - 'a'] = tmp;
}
cur = cur->next[word[i] - 'a'];
}
cur->finish = true;
}
bool search(string word) {
int n = word.length();
TreeNode* cur = root;
for (int i = 0; i < n; ++i) {
if (cur->next[word[i] - 'a'] == nullptr) {
return false;
}
cur = cur->next[word[i] - 'a'];
}
return cur->finish;
}
bool startsWith(string prefix) {
int n = prefix.length();
TreeNode* cur = root;
for (int i = 0; i < n; ++i) {
if (cur->next[prefix[i] - 'a'] == nullptr) {
return false;
}
cur = cur->next[prefix[i] - 'a'];
}
return true;
}
};
4. JZ27 二叉树的镜像
这一题可以直接在原二叉树上进行镜像,比较简单,但是遇到的次数比较多,如下所示:
TreeNode* Mirror(TreeNode* pRoot) {
if (!pRoot || (!pRoot->left && !pRoot->right)) return pRoot; // 节点为空 或者 节点无子节点
TreeNode* temp = pRoot->left;
pRoot->left = Mirror(pRoot->right);
pRoot->right = Mirror(temp);
return pRoot;
}
5. JZ82 二叉树中和为某一值的路径(一)
这一题要求从根节点到叶子节点,叶子节点没有节点了。遇到的次数比较多。
bool hasPathSum(TreeNode* root, int sum) {
if (!root) return false;
if (root->val == sum && !root->left && !root->right) {
return true;
}
return hasPathSum(root->left, sum - root->val) || hasPathSum(root->right, sum - root->val);
}
6. JZ82 二叉树中和为某一值的路径(二)
这一题要求从根节点到叶子节点,叶子节点没有节点了。要求输出路径,遇到的次数比较多。
vector<vector<int>> FindPath(TreeNode* root,int expectNumber) {
vector<vector<int>> ans;
if (!root) return ans;
vector<int> temp;
dfs(toor, expectNumber, ans, temp);
return ans;
}
void dfs(TreeNode* root,int expectNumber, vector<vector<int>>& ans, vector<int>& temp) {
if (!root) return;
temp.push_back(root->val);
if (root->val == expectNumber && !root->left && !root->right) {
ans.push_back(temp);
temp.pop_back(); // 退出该函数时一定要把这一节点的值pop出去
return;
}
dfs(root->left, expectNumber, ans, temp);
dfs(root->right, expectNumber, ans, temp);
temp.pop_back(); // 退出该函数时一定要把这一节点的值pop出去
}
7. JZ54 二叉搜索树的第k个节点
这一题如果全部遍历再排序的话,太复杂了,但是看到排名第一的做法是这样,不知为何。
这一题的二叉树是排序二叉树,也就是说二叉树是按照规律排过序的,只要采用中序遍历,那么就可以从小到大一个一个取出来,取到第k个就停止。
int KthNode(TreeNode* proot, int k) {
// write code here
if (!proot || k <= 0) return -1;
int idx = 0, ans = -1;
midOrder(proot, k, idx, ans);
return ans;
}
void midOrder(TreeNode* proot, int k, int& idx, int& ans) {
if (!proot || idx >= k) return; // 如果搜到空节点或者已经搜到目标节点,就不搜了,直接返回
midOrder(proot->left, k, idx, ans);
idx++;
if (idx > k) return; // 如果已经搜到了,那么就不搜了
if (idx == k) {
ans = proot->val;
}
midOrder(proot->right, k, idx, ans);
}
8. JZ77 按之字形顺序打印二叉树
这一题按照之字形打印,也就是说这一行从左到右输出,下一行就要从右往左输出,那么我采用双向队列的方式,存储每一行的节点,那么就可以实现从左侧或者右侧不断取值,实现反向输出。
vector<vector<int> > Print(TreeNode* pRoot) {
if (!pRoot) return vector<vector<int> >{};
bool l2r = true;
vector<vector<int> > ans;
deque<TreeNode*> q;
q.push_front(pRoot);
TreeNode* temp = nullptr;
while (!q.empty()) {
vector<int> tp;
int n = q.size();
while (n--) {
if (l2r) { // 奇数层,是从左往右遍历
temp = q.front();
q.pop_front();
if (temp->left) q.push_back(temp->left); // 从左往右遍历的话,要把下一层的节点添加在双向队列的右侧,按顺序先添加左节点,然后右节点
if (temp->right) q.push_back(temp->right);
} else { // 偶数层,是从右往左遍历
temp = q.back(); // 注意此处要从最右侧取值
q.pop_back();
if (temp->right) q.push_front(temp->right); // 从右往左遍历的话,要把下一层的节点添加在双向队列的左侧,按顺序先添加右节点,然后左节点
if (temp->left) q.push_front(temp->left);
}
tp.push_back(temp->val);
}
ans.push_back(tp);
}
return ans;
}
9. JZ86 在二叉树中找到两个节点的最近公共祖先
这一题的二叉树不是查找二叉树,所以树的节点没有排序,因此我用以下三种方法解题:
1.分别找出到o1和o2的路径,然后根据路径的重合部分找出最近公共祖先;
int lowestCommonAncestor(TreeNode* root, int o1, int o2) {
// write code here
vector<int> r1, r2;
dfs(root, o1, r1);
dfs(root, o2, r2);
int idx = 0;
for (; idx < r1.size(); ++idx) {
if (r1[idx] != r2[idx]) break;
}
return r1[idx - 1];
}
void dfs(TreeNode* root, int o, vector<int>& r) {
if (!root || !r.empty() && r[r.size() - 1] == o) return; // 如果是空节点,或者已经找到路径了,就不找了
r.push_back(root->val);
if (root->val == o) return;
dfs(root->left, o, r);
dfs(root->right, o, r);
if (!r.empty() && r[r.size() - 1] == o) return;
r.pop_back();
}
2.用递归去寻找目标节点,并将目标节点返回。这个算法的好处是不用判断两个目标节点的关系。两个目标节点可能是同公共祖先,也可能其中一个就是公共祖先。
int lowestCommonAncestor(TreeNode* root, int o1, int o2) {
// write code here
return flca(root, o1, o2)->val;
}
TreeNode* flca(TreeNode* root, int o1, int o2) {
if (!root || root->val == o1 || root->val == o2) return root; // 如果root是空节点,或者找到了其中一个,那么就返回该节点
TreeNode* l = flca(root->left, o1, o2), *r = flca(root->right, o2, o2);
if (!l && !r) return nullptr; // 两侧都没找到任何一个目标节点,返回空
if (l && !r) return l; // 其中一侧找到了,就返回该侧
if (!l && r) return r; // 其中一侧找到了,就返回该侧
return root; // 两侧都找到了,返回公共节点
}
3.用递归,返回true或false。如果找到一个目标节点就返回true,两侧都是true则找到了公共祖先,一侧是true且本节点找到了另外一个目标节点,也就找到了公共祖先。
int lowestCommonAncestor(TreeNode* root, int o1, int o2) {
// write code here
flca(root, o1, o2);
return ans;
}
bool flca(TreeNode* root, int o1, int o2) {
if (!root) return false;
bool l = flca(root->left, o1, o2), r = flca(root->right, o1, o2);
if (l && r || (root->val == o1 || root->val == o2) && (l || r)) ans = root->val;
return l || r || root->val == o1 || root->val == o2;
}
10. JZ7 重建二叉树
这一题通过前序遍历和中序遍历得到的数组重建二叉树,我有两种方法:
1.第一种方法是建立两个哈希表,分别把前序和中序遍历得到的数和下标存入哈希表中,这样查找起来很快。
TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
int n = pre.size();
if (n == 0) return nullptr;
unordered_map<int, int> um1, um2;
for (int i = 0; i < n; ++i) {
um1[pre[i]] = i;
um2[vin[i]] = i;
}
return recon(pre, 0, n - 1, vin, 0, n - 1, um1, um2);
}
TreeNode* recon(vector<int> pre, int l1, int r1, vector<int> vin, int l2, int r2, unordered_map<int, int> um1, unordered_map<int, int> um2) {
// 这里一定要注意的是,要分别把待递归的数组的边界确定下来。我这里分别把边界的下标命名为l1 r1 l2 r2
TreeNode* head = new TreeNode(pre[l1]);
int m2 = um2[pre[l1]]; // 找到待递归部分的头节点在中序遍历中的下标
if (l2 < m2) { // 说明左子树存在
head->left = recon(pre, l1 + 1, m2 + l1 - l2, vin, l2, m2 - 1, um1, um2);
}
if (m2 < r2) { // 说明右子树存在
head->right = recon(pre, m2 + l1 -l2 + 1, r1, vin, m2 + 1, r2, um1, um2);
}
return head;
2.第一个方法用到了哈希表来存放数和下标,目的是找到待递归的数组段,那么我们也可以用数组直接把待递归的数组段取出来。
TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
int n = pre.size();
if (n == 0) return nullptr;
TreeNode* head = new TreeNode(pre[0]);
int m2 = 0;
for (; m2 < n; ++m2) {
if (vin[m2] == pre[0]) break;
}
vector<int> prel, prer, vinl, vinr;
for (int i = 1; i <= m2; ++i) {
prel.push_back(pre[i]);
vinl.push_back(vin[i - 1]);
}
for (int i = m2 + 1; i < n; ++i) {
prer.push_back(pre[i]);
vinl.push_back(vin[i]);
}
head->left = reConstructBinaryTree(prel, vinl);
head->right = reConstructBinaryTree(prer, vinr);
return head;
}
11. JZ26 树的子结构
这一题很简单,遍历找到值相同的头节点,以该节点判断是否是子树。
bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2) {
if (!pRoot1 || !pRoot2) return false;
queue<TreeNode*> q; // 准备采用广度优先遍历
q.push(pRoot1);
bool ans = false;
while (!q.empty()) {
if (ans) break; // 如果找到了,那么就不搜了
int n = q.size();
while (n--) {
TreeNode* temp = q.front();
q.pop();
if (temp->val == pRoot2->val) {
ans = ans || issame(temp, pRoot2); // 开始判断
if (ans) break;
}
if (temp->left) q.push(temp->left);
if (temp->right) q.push(temp->right);
}
}
return ans;
}
bool issame(TreeNode* r1, TreeNode* r2) {
if (!r1 && !r2) return true;
if (!r1 && r2 || r1 && !r2) return false;
if (r1->val != r2->val) return false;
bool ans = true;
if (r2->left) ans = ans && issame(r1->left, r2->left); // 如果小树有左子树,才判断大树有没有左子树
if (r2->right) ans = ans && issame(r1->right, r2->right); // 如果小树有右子树,才判断大树有没有右子树
return ans;
}
12. JZ33 二叉搜索树的后序遍历序列
这一题是根据数组判断能不能恢复成一颗二叉搜索树,代码如下:
bool VerifySquenceOfBST(vector<int> sequence) {
int n = sequence.size();
if (n == 0) return false;
if (n <= 2) return true;
int mid = sequence[n - 1]; // 取出头节点的值
bool ans = false;
if (sequence[0] > mid && sequence[n - 2] < mid) return false; // 如果左节点大于头节点且右节点小于头节点,那么就不是二叉搜索树
if (sequence[0] < mid && sequence[n - 2] > mid) { // 有左子树和右子树
int i = 0;
for (; i < n - 2; ++i) {
if (sequence[i] < mid && sequence[i + 1] > mid) {
break;
}
}
for (int j = i + 1; j < n - 1; ++j) {
if (sequence[j] < mid) return false;
} // 要判断后面是不是都大于头节点
vector<int> s1, s2;
for (int idx = 0; idx <= i; ++idx) {
s1.push_back(sequence[idx]);
}
for (int idx = i + 1; idx <= n - 2; ++idx) {
s2.push_back(sequence[idx]);
}
ans = VerifySquenceOfBST(s1) && VerifySquenceOfBST(s2);
} else { // 只有一侧子树
if (sequence[0] < mid) {
for (int i = 0; i < n - 1; ++i) {
if (sequence[i] >= mid) return false;
} // 判断一侧是不是都小于或者大于头节点
} else {
for (int i = 0; i < n - 1; ++i) {
if (sequence[i] <= mid) return false;
}
}
vector<int> s1;
for (int i = 0; i <= n - 2; ++i) {
s1.push_back(sequence[i]);
}
ans = VerifySquenceOfBST(s1);
}
return ans;
}
13. Leetcode 99 恢复二叉搜索树
这一题需要三步:(1)中序遍历;(2)通过中序遍历结果找到位置被互换的两个点;(3)遍历二叉树,将两点换回来。
void inorder(TreeNode* root, vector<int>& nums) { // 中序遍历
if (!root) return;
inorder(root->left, nums);
nums.push_back(root->val);
inorder(root->right, nums);
}
pair<int, int> find2(vector<int> nums) { // 找到被互换的两个点
int n = nums.size();
bool hasone = false;
int l = 0, r = 0;
for (int i = 0; i < n - 1; ++i) {
if (nums[i] > nums[i + 1]) {
if (hasone) {
r = nums[i + 1];
} else {
l = nums[i];
r = nums[i + 1];
hasone = true;
}
}
}
return pair<int, int>{l, r};
}
void recover(TreeNode* root, int v1, int v2) { // 互换两个点
if (!root) return;
if (root->val == v1) root->val = v2;
else if (root->val == v2) root->val = v1;
recover(root->left, v1, v2);
recover(root->right, v1, v2);
}
void recoverTree(TreeNode* root) {
vector<int> nums;
inorder(root, nums);
pair<int, int> idxs = find2(nums);
recover(root, idxs.first, idxs.second);
}
14. Leetcode 95 不同的二叉搜索树 II
class Solution {
public:
vector<TreeNode*> generateTrees(int n) {
return generateTrees(n, 0); // 0为给定的偏置,即返回n个节点的二叉树,每个节点偏置0+1
}
vector<TreeNode*> generateTrees(int n, int idx) {
vector<TreeNode*> ans;
if (n == 0) return vector<TreeNode*>{nullptr}; // 不能返回为空,必须返回空指针,要不然其长度为0的话导致没办法开始循环
if (n == 1) {
TreeNode* tmp = new TreeNode(idx + 1); // 偏置idx+1
return vector<TreeNode*>{tmp};
}
for (int i = 1; i <= n; ++i) {
auto l = generateTrees(i - 1, idx), r = generateTrees(n - i, i + idx);
// 左边和右边都有一定的组合方式,然后将它们拼起来
int nl = l.size(), nr = r.size();
for (int j = 0; j < nl; ++j) {
for (int k = 0; k < nr; ++k) {
TreeNode* root = new TreeNode(idx + i); // 偏置要注意改成idx+i,因为它通过循环改变了root的位置
root->left = l[j];
root->right = r[k];
ans.push_back(root);
}
}
}
return ans;
}
};
15. Leetcode 98 验证二叉搜索树
bool isValidBST(TreeNode* root) {
if (!root) return false;
return isValidBST(root, INT_MIN, INT_MAX);
}
bool isValidBST(TreeNode* root, long l, long r) {
if (!root) return true;
if (root->val < l || root->val > r) return false;
if (root->val == INT_MIN) return !root->left && isValidBST(root->right, root->val + 1, r); // 如果是最小值,那么其左边不能再-1了,只能为空
if (root->val == INT_MAX) return isValidBST(root->left, l, root->val - 1) && !root->right; // 如果是最大值,那么其右边不能再+1了,只能为空
return isValidBST(root->left, l, root->val - 1) && isValidBST(root->right, root->val + 1, r);
}
九、自定义类
力扣中有些题目需要自己完成一个类中的函数。
1. Leetcode 303 区域与检索-数组不可变
class NumArray {
vector<int> sum;
public:
NumArray(vector<int>& nums) {
sum = vector<int>(nums.size() + 1, 0); // 在设计上让sum的多一维,那样可以写出递推通项,而且在求前缀和的时候更方便
for (int i = 0; i < nums.size(); ++i) {
sum[i + 1] = sum[i] + nums[i];
}
}
int sumRange(int left, int right) {
return sum[right + 1] - sum[left];
}
};
2. Leetcode 304 区域与检索-矩阵不可变
class NumMatrix {
vector<vector<int>> Sum;
public:
NumMatrix(vector<vector<int>>& matrix) {
Sum = vector<vector<int>>(matrix.size() + 1, vector<int>(matrix[0].size() + 1, 0)); // 行和列都增加一维
for (int i = 0; i < matrix.size(); ++i) {
for (int j = 0; j < matrix[0].size(); ++j) {
Sum[i + 1][j + 1] = Sum[i + 1][j] + Sum[i][j +1] - Sum[i][j] + matrix[i][j];
}
}
}
int sumRegion(int row1, int col1, int row2, int col2) {
return Sum[row2 + 1][col2 + 1] - Sum[row1][col2 + 1] - Sum[row2 + 1][col1] + Sum[row1][col1];
}
};