数据结构题小结

数据结构题小结


前言

对数据结构类型的题做一个总结。


一、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];
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值