layout: post
title: leetcode重点题目分类别记录(一)数据结构类
description: leetcode重点题目分类别记录(一)数据结构类
tag: 数据结构与算法
算法题分类别记录
数组
排序
归并排序
合并两有序数组
合并两个升序有序数组
这里借助了赋值空间help数组,是归并排序中使用的手段,因此归并排序的空间复杂度为O(n),此外归并时,可以保证稳定性,主要体现在当元素相等时,直接赋值左边数组元素。
vector<int> mergeTwoArr(vector<int> &nums1, vector<int> &nums2) {
int m = nums1.size(), n = nums2.size();
vector<int> help(m + n, 0);
int i = 0, j = 0, k = 0;
while (i < m && j < n) help[k++] = nums1[i] > nums2[j] ? nums2[j++] : nums1[i++];
while (i < m) help[k++] = nums1[i++];
while (j < n) help[k++] = nums2[j++];
nums1.resize(help.size());
copy(help.begin(), help.end(), nums1.begin());
}
归并排序
void merge(vector<int> &nums, int left, int mid, int right) {
vector<int> help();
help.resize(right - left + 1);
int p1 = left, p2 = mid + 1, index = 0;
while (p1 <= mid && p2 <= right) help[index++] = nums[p2] < nums[p1] ? nums[p2++] : nums[p1++];
while (p1 <= mid) help[index++] = nums[p1++];
while (p2 <= right) help[index++] = nums[p2++];
copy(help.begin(), help.end(), nums.begin() + left);
}
void mergeRecur(vector<int> &nums, int left, int right) {
if (left < right) {
int mid = left + ((right - left) >> 1);
mergeRecur(nums, left, mid);
mergeRecur(nums, mid + 1, right);
merge(nums, left, mid, right);
}
}
void mergeSort(vector<int> &nums) {
mergeRecur(nums, 0, nums.size() - 1);
}
快速排序
荷兰旗问题
75、颜色分类
两头partition
void sortColors(vector<int>& nums) {
int left = 0, right = nums.size(), index = 0;
while (index < right) {
if (nums[index] == 0) swap(nums[index++], nums[left++]);
else if (nums[index] == 1) index++;
else swap(nums[index], nums[--right]);
}
}
荷兰旗问题2
2161. 根据给定数字划分数组
快排的partition不是相邻交换,是无法做到题目要求的稳定性的,因此这题使用了额外的空间,从两头额外寻找小于和大于目标值,直接填入,默认赋值为pivot目标值,因此不需要再处理等于目标值的情况。
vector<int> pivotArray(vector<int>& nums, int pivot) {
int n = nums.size();
vector<int> ans(n, pivot);
int p1 = 0, p2 = n - 1;
for (int i = 0; i < n; ++i) {
if (nums[i] < pivot) ans[p1++] = nums[i];
if (nums[n - 1 - i] > pivot) ans[p2--] = nums[n - 1 - i];
}
return ans;
}
快速排序
注意:
- 采用右端点值作为目标值来partition,为避免最坏情况发生,将随机生成一个索引,将其与右端点值交换;
- partition结束后,p1在小于区的下一个,p2在大于区的前一个,即两个都在等于区,因此返回边缘的时候p1 - 1,p2 + 1;
pair<int, int> fastPut(vector<int> &nums, int left, int right) {
int idx = rand() % (right - left + 1) + left;
swap(nums[idx], nums[right]);
int p1 = left, p2 = right, index = left;
while (index < p2) {
if (nums[index] < nums[right]) swap(nums[p1++], nums[index++]);
else if (nums[index] == nums[right]) index++;
else swap(nums[index], nums[--p2]);
}
swap(nums[p2], nums[right]);
return {p1 - 1, p2 + 1};
}
void fastRecur(vector<int> &nums, int left, int right) {
if (left < right) {
auto edge = fastPut(nums, left, right);
fastRecur(nums, left, edge.first);
fastRecur(nums, edge.second, right);
}
}
堆排序
void heapInsert(vector<int> &nums, int index) {
while (nums[index] > nums[(index - 1) / 2]) {
swap(nums[index], nums[(index - 1) / 2]);
index = (index - 1) / 2;
}
}
void heapfy(vector<int> &nums, int index, int heapSize) {
int leftIndex = 2 * index + 1;
while (leftIndex < heapSize) {
int bigIndex = leftIndex + 1 < heapSize && nums[leftIndex + 1] > nums[leftIndex] ? leftIndex + 1 : leftIndex;
if (nums[index] > nums[bigIndex]) break;
swap(nums[index], nums[bigIndex]);
index = bigIndex;
leftIndex = 2 * index + 1;
}
}
void heapSort(vector<int> &nums) {
int heapSize = 0;
while (heapSize < nums.size()) heapInsert(nums, heapSize++);
while (heapSize) {
swap(nums[0], nums[--heapSize]);
heapfy(nums, 0, heapSize);
}
}
基数排序
滑动窗口/双指针
N数之和
15三数之和
18四数之和
四数相加
链表
环形链表
142环形链表
1、快慢指针法, 如果无环一定快指针先到末尾
2、如果有环,快慢指针在某个时刻相等
3、如果相等,将快指针调回头部,快慢指针都一次一步,两者在入口相遇
ListNode* detectCircle(ListNode* head) {
ListNode *slow = head, *fast = head;
// 如果无环一定快指针先到末尾
while (fast && fast->next) {
// 快指针一次两步,慢指针一次一步,如果有环,某个时刻,快指针与慢指针相等
slow = slow->next;
fast = fast->next->next;
// 如果相等,将快指针调回头部,快慢指针都一次一步,两者在入口相遇
if (slow == fast) {
fast = head;
while (slow != fast) {
slow = slow->next;
fast = fast->next;
}
return slow;
}
}
return nullptr;
}
重排链表
143重排链表
反转后半条链表,然后重排。
注意利用快慢指针获取链表中间靠左的位置。
ListNode* reverseList(ListNode* head) {
ListNode* cur = head, *pre = nullptr;
while (cur) {
ListNode* next = cur->next;
cur->next = pre;
pre = cur;
cur = next;
}
return pre;
}
void reorderList(ListNode* head) {
ListNode* slow = head, *fast = head;
// 注意:快慢指针遍历,条件为 while(fast && fast->next),假定节点长为偶数个,最终慢指针停在中间靠右位置
// 而条件为 while (fast->next && fast->next->next) 慢指针最终停在中间靠左位置。假设节点长为奇数,都停中间
while (fast->next && fast->next->next) {
slow = slow->next;
fast = fast->next->next;
}
ListNode* l1 = head, *l2 = slow->next;
// 前后半条链表断开,返回后半条链表
slow->next = nullptr;
l2 = reverseList(l2);
// 奇数时,前半条比后半条多一个节点,偶数时长度相同
// 将l2的节点每间隔一个串到l1上
while (l2) {
ListNode* next1 = l1->next, *next2 = l2->next;
l1->next = l2;
l2->next = next1;
l1 = next1;
l2 = next2;
}
}
LRU缓存
146LRU缓存
需要构建一种结构,支持随机访问缓存内容,而又便于删除过期缓存和添加新的缓存。
因此构建如下图所示的由双向链表与哈希表组成的特殊数据结构:
编写时的注意点:
1、头尾使用虚拟伪节点,不计入哈希表结构
2、push新的缓存时,假如哈希表中已有key,还需要将key对应的value更新为最新的value,随后再添加到链表头部
3、先编写在头部添加节点addToHead()
和删除某个位置的节点 remove()
这样的基础函数。据此构建移动到头部的函数moveToHead
。
4、基础的remove
函数,仅仅是在链表的某个位置将该节点脱离下来,并没有真正删除节点的指针,因此当容量满了,需要删除过期节点时,需要先将尾结点获取到,随后在链表中remove
它,然后再在哈希中erase
,最后执行delete
操作。
class HashLinkedNode {
public:
int key, val;
HashLinkedNode *pre, *next;
HashLinkedNode() : key(0), val(0), pre(nullptr), next(nullptr) {}
HashLinkedNode(int _key, int _val) : key(_key), val(_val), pre(nullptr), next(nullptr) {}
};
class LRUCache {
public:
LRUCache(int capacity) : capacity(capacity), size(0) {
head = new HashLinkedNode();
tail = new HashLinkedNode();
head->next = tail;
tail->pre = head;
}
int get(int key) {
if (mp.count(key)) {
HashLinkedNode* node = mp[key];
moveToHead(node);
return node->val;
}
return -1;
}
void put(int key, int value) {
if (mp.count(key)) { // 如果表中已经有key,直接修改key对应的val值并移动到队列头
HashLinkedNode* node = mp[key];
node->val = value;
moveToHead(node);
} else {
HashLinkedNode* node = new HashLinkedNode(key, value);
mp.insert({key, node});
addToHead(node);
++size;
if (size > capacity) {
HashLinkedNode* outNode = removedTail();
mp.erase(outNode->key);
delete outNode;
--size;
}
}
}
void addToHead(HashLinkedNode* node) {
node->next = head->next;
head->next->pre = node;
head->next = node;
node->pre = head;
}
void removedNode(HashLinkedNode* node) {
node->pre->next = node->next;
node->next->pre = node->pre;
}
void moveToHead(HashLinkedNode* node) {
removedNode(node);
addToHead(node);
}
HashLinkedNode* removedTail() {
HashLinkedNode* node = tail->pre;
removedNode(node);
return node;
}
private:
HashLinkedNode *head, *tail;
unordered_map<int, HashLinkedNode*> mp;
int size, capacity;
};
栈与队列
栈的应用
逆波兰表达式
每当遇到“±*/”符合时,弹出栈顶的两个元素,做相应运算。
注意:
1、switch的参数只能是char或者int,不能是str,此外switch注意使用break;退出,否则会执行下边的switch。
2、运算时,栈先进后出,因此后边的元素b是先弹出的,前边的元素a是后弹出的。
int evalRPN(vector<string>& tokens) {
stack<int> stk;
for (string& str : tokens) {
if (!(str == "+" || str == "-" || str == "*" || str == "/")) {
stk.push(stoi(str));
} else {
int b = stk.top();
stk.pop();
int a = stk.top();
stk.pop();
switch (str[0]) {
case '+' : {
stk.push(a + b);
break;
}
case '-' : {
stk.push(a - b);
break;
}
case '*' : {
stk.push(a * b);
break;
}
case '/' : {
stk.push(a / b);
break;
}
}
}
}
return stk.top();
}
栈实现队列/队列实现栈
最小栈/最小队列
1、最小栈
实现一个栈,带有出栈(pop),入栈(push),取最小元素(getMin)三个方法。这三个方法的时间复杂度都是O(1)。
思路:一个数据栈,一个记录最小元素的辅助栈。对于栈来讲,后入栈的元素先过期,因此后入栈的元素如果比当前最小元素还要小,则需要把他压入最小栈,否则最小栈再压入一个栈顶元素。
class MinStack {
public:
MinStack() {
minStk.push(INT_MAX);
}
void push(int val) {
dataStk.push(val);
if (val < minStk.top()) {
minStk.push(val);
} else {
minStk.push(minStk.top());
}
}
void pop() {
dataStk.pop();
minStk.pop();
}
int top() {
return dataStk.top();
}
int getMin() {
return minStk.top();
}
stack<int> dataStk, minStk;
};
2、最小队列
实现一个队列,带有出队(deQueue),入队(enQueue),取最小元素(getMin)三个方法。要保证这三个方法的时间复杂度都尽量小。
思路:最小队列实际上就是下边的单调队列的做法,只需要将维护双端队列从队头到队尾单增即可。
单调队列
单调队列用于快速求取一个数组区间的最值!
1、假定求需要求最大值,维护一个双端队列,保持从队头到队尾单调减小,每次取队头即为最大值。
2、由于先来的元素先出队(过期),因此,每次入队时,将入队元素与队尾元素比较,直至队列为空,或者队尾元素大于入队元素,将元素入队。这样做的理由是,有新的更大的元素入队,它前面比它小的元素一定比它先过期(出队),所以不需要保存它们。
3、单调队列出队时pop()函数必须指定pop的是哪个元素!!
当pop的元素为队头元素时,从队头弹出它。
class MonoQueue {
public:
deque<int> dq;
MonoQueue() : {}
void push(int _val) {
while (!dq.empty() && dq.back() <= _val) dq.pop_back();
dq.push_back(_val);
}
int getMax() {
return dq.front();
}
void pop(int _val) {
if (!dq.empty() && dq.front() == _val) dq.pop_front();
}
};
单调栈
- 柱状图中最大的矩形
- 最大矩形
最大矩形可能出现的情况是:
对于某一个高度为height的矩形,找它左右第一个比它小的矩形,记为left,right,在此开区间范围内,所有矩形的高度至少为该矩形的高度height。因此 area = (right - left - 1) * height。
为了使得每个高度,都能在左右找到比它小的矩形,在原始height数组头尾插入0
int largestRectangleArea(vector<int>& heights) {
heights.insert(heights.begin(), 0);
heights.push_back(0);
stack<int> stk;
int ans = 0;
for (int i = 0; i < heights.size(); ++i) {
while (!stk.empty() && heights[stk.top()] > heights[i]) {
int cur = stk.top();
stk.pop();
int width = i - stk.top() - 1;
ans = max(ans, width * heights[cur]);
}
stk.push(i);
}
return ans;
}
以某一行的底边作为x轴,将累计的1叠加,就会发现该题与上边求最大矩形如出一辙。
基于此,可以直接利用根据heights数组求最大矩形的思路,在主函数中我们只需要构建出heighs数组即可。而heights数组的构建方法是,如果当前遍历为字符1,累计结果,否则置为0.
为了避免反复对heights数组头尾插入0,我们在构建heights数组时直接将其大小构建为n + 2,从1开始构建。
int maximalRectangle(vector<vector<char>>& matrix) {
int m = matrix.size(), n = matrix[0].size();
vector<int> heights(n + 2, 0);
int ans = 0;
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
heights[j + 1] = matrix[i][j] == '0' ? 0 : (heights[j + 1] + 1);
}
ans = max(ans, largestRectangleArea(heights));
}
return ans;
}
int largestRectangleArea(vector<int>& heights) {
// heights.insert(heights.begin(), 0);
// heights.push_back(0);
stack<int> stk;
int ans = 0;
for (int i = 0; i < heights.size(); ++i) {
while (!stk.empty() && heights[stk.top()] > heights[i]) {
int cur = stk.top();
stk.pop();
int width = i - stk.top() - 1;
ans = max(ans, width * heights[cur]);
}
stk.push(i);
}
return ans;
}
哈希表
原地哈希应用
- 数组中重复的数据
原地哈希:使用nums数组本身作为哈希表,对出现的元素取反标记,使用绝对值可以不受标记影响得到元素实际上的索引位置,如果该位置为正说明第一次出现,否则说明是重复元素。
vector<int> findDuplicates(vector<int>& nums) {
vector<int> ans;
for (int i = 0; i < nums.size(); ++i) {
int x = abs(nums[i]) - 1; // 取绝对值,不受取反影响,元素值与索引差1
if (nums[x] > 0) nums[x] = -nums[x]; // 如果该位置元素大于0,第一次遍历,取反标记
else ans.push_back(x + 1); // 否则说明这个位置已经标记过,是重复元素
}
return ans;
}
哈希+前缀和
- 和为 K 的子数组
- 和可被 K 整除的子数组
使用前缀和,可以很方便的计算子数组和。在构建前缀和数组时,常常使用左闭右开区间,并从空数组开始累加前缀和,举个例子: nums[1,2,3]的前缀和数组为prefixSum[0, 1, 3, 6]; 这里对于原nums数组中[left, right)区间的子数组和就可以表示为prefixSum[right] - prefixSum[left];
这里求子数组和为K的个数,即求prefixSum[right] - prefixSum[left] = K,即在prefixSum数组中应用两数之和的哈希表套路:
int subarraySum(vector<int>& nums, int k) {
int n = nums.size();
vector<int> prefixSum(n + 1, 0);
for (int i = 0; i < n; ++i) prefixSum[i + 1] = prefixSum[i] + nums[i];
// 应用两数之和的套路
unordered_map<int, int> umap;
int ans = 0;
for (int i = 0; i <= n; ++i) {
if (umap.count(prefixSum[i])) ans += umap[prefixSum[i]];
++umap[prefixSum[i] + k];
}
return ans;
}
实际上两次for循环没有必要,可以边统计前缀,边查询哈希:
int subarraySum(vector<int>& nums, int k) {
unordered_map<int, int> umap = {{k, 1}}; // 往哈希表存的prefix[i] + k
int sum = 0, ans = 0;
for (int &n : nums) {
sum += n;
if (umap.count(sum)) ans += umap[sum];
++umap[sum + k];
}
return ans;
}
跟求子数组之和为K是一个套路:
设前缀和数组为S(~),这里即求(S(right) - S(left)) % K = 0;即S(right)% K = S(left)% K;
那么直接求对K取余的前缀和,再应用哈希表查询即可。
注意:C++中,取余的结果 = 被除数的符号×(被除数的 绝对值 % 除数的绝对值),为避免S(X) % K取余结果为负数, 可以令S(X) = (S(X) % K + K) % K
int subarraysDivByK(vector<int>& nums, int k) {
unordered_map<int, int> umap = {{0, 1}};
int sum = 0, ans = 0;
for (int &n : nums) {
sum += n;
int m = (sum % k + k) % k;
if (umap.count(m)) ans += umap[m];
++umap[m];
}
return ans;
}
字符串
字符串处理
字符串匹配KMP
注意pre已经是前一个位置处的最长的前缀和后缀匹配长度,当不匹配且pre没有后退到0时,则pre后撤 pre = ans[pre];
vector<int> getNext(string str) {
if (str.size() == 1) return {-1};
vector<int> ans(str.size(), 0);
ans[0] = -1;
ans[1] = 0;
// 从i为2开始, pre是ans[i - 1], 即前一个位置最长公共前后缀
int i = 2, pre = 0;
while (i < str.size()) {
if (str[i- 1] == str[pre]) ans[i++] = ++pre;
else if (pre > 0) pre = ans[pre];
else ans[i++] = 0;
}
return ans;
}
int strStr(string haystack, string needle) {
int m = haystack.size(), n = needle.size();
if (m < n) return -1;
vector<int> next = getNext(needle);
int p1 = 0, p2 = 0;
while (p1 < m && p2 < n) {
if (haystack[p1] == needle[p2]) {++p1; ++p2;}
else if (p2 == 0) ++p1;
else p2 = next[p2];
}
return p2 == n ? p1 - p2 : -1;
}
子串
二叉树
二叉树的遍历方式
递归法
略
迭代法
以N叉树为例:
先序
vector<int> preorder(Node* root) {
vector<int> ans;
stack<Node*> stk;
if (root) stk.push(root);
while (!stk.empty()) {
Node* cur = stk.top();
stk.pop();
ans.push_back(cur->val);
int size = cur->children.size();
for (int i = size - 1; i >= 0; --i) {
if (cur->children[i]) stk.push(cur->children[i]);
}
}
return ans;
}
后序
先序遍历得到头左右
后序遍历是左右头
在先序遍历时,颠倒子节点入栈顺序得到头右左
,再将结果反转得到左右头
的后序结果。
vector<int> postorder(Node* root) {
vector<int> ans;
stack<Node*> stk;
if (root) stk.push(root);
while (!stk.empty()) {
Node* cur = stk.top();
stk.pop();
ans.push_back(cur->val);
int size = cur->children.size();
for (int i = 0; i < size; ++i) {
if (cur->children[i]) stk.push(cur->children[i]); // 入栈顺序颠倒
}
}
reverse(ans.begin(), ans.end()); // 反转结果
return ans;
}
中序
vector<int> inorderTraversal(TreeNode* root) {
vector<int> ans;
stack<TreeNode*> stk;
TreeNode* cur = root;
while (cur || !stk.empty()) {
if (cur) {
stk.push(cur);
cur = cur->left;
} else {
cur = stk.top();
stk.pop();
ans.push_back(cur->val);
cur = cur->right;
}
}
return ans;
}
二叉树的最近公共祖先
235. 二叉搜索树的最近公共祖先
236. 二叉树的最近公共祖先
第一个满足节点值在p和q之间的节点即为所求。
如果节点值大于p和q,则p和q都在左子树,递归左子树
如果节点值小于p和q,则p和q都在右子树,递归右子树
否则说明在区间内,返回该节点
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (root->val > p->val && root->val > q->val) return lowestCommonAncestor(root->left, p, q);
if (root->val < p->val && root->val < q->val) return lowestCommonAncestor(root->right, p, q);
return root;
}
向左右子树要节点p和节点q,如果有其中之一则返回,如果孩子节点为空,则返回空
如果某个节点左子树和右子树索要p或q的结果都不为空,说明该节点即为所求,否则将非空的结果返回。
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (!root || root == p || root == q) return root;
TreeNode* left = lowestCommonAncestor(root->left, p, q);
TreeNode* right= lowestCommonAncestor(root->right, p, q);
if (left && right) return root;
return left ? left : right;
}
二叉搜索树
插入节点
一种最简单的思路是按照搜索节点的方式,将搜索遇到的第一个空节点,赋值为要插入的值。
如果空节点,返回要插入的节点;
如果非空且当前节点值大于val,则说明val应该插入到左子树;
否则插入到右子树;
返回root;
TreeNode* insertIntoBST(TreeNode* root, int val) {
if (!root) return new TreeNode(val);
if (root->val > val) root->left = insertIntoBST(root->left, val);
if (root->val < val) root->right = insertIntoBST(root->right, val);
return root;
}
删除节点
删除节点包含三种情况:
1、待删除的节点是叶子节点;
2、待删除的节点只有一个孩子节点;
3、待删除的节点有两个孩子节点。
1和2情况比较简单,1直接删除,2直接让待删除的节点的父节点指向它的孩子节点即可。
情况3就比较麻烦,因为删除的同时我们还要保证有序。
因此二叉搜索树的中序遍历是有序的,因此我们可以找待删除节点的左子树最右,那么它一定是一个叶子节点,保存它的值,删除它,将它的值赋给我们待删除节点的位置。这样就保证了删除后依旧有序。
TreeNode* deleteNode(TreeNode* root, int key) {
if (!root) return root;
if (root->val > key) root->left = deleteNode(root->left, key);
else if (root->val < key) root->right = deleteNode(root->right, key);
else {
if (!root->left || !root->right) return root->left ? root->left : root->right;
TreeNode* mostRight = root->left;
while (mostRight->right) {
mostRight = mostRight->right;
}
int val = mostRight->val;
root->left = deleteNode(root->left, val);
root->val = val;
}
return root;
}
树的重建
中序与后序遍历构造二叉树
为减少拷贝,用左闭右开区间来表示一个数组。
后序遍历,最后一个节点必定是根,根据根节点的值,找到它在中序的位置index,该index即可将中序数组一分为二,代表了左右子树的中序数组,根据左子树的中序数组的长度,判断左子树后序数组的长度,递归。
vector<int> inorder, postorder;
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
this->inorder = inorder;
this->postorder = postorder;
return buildRecur(0, inorder.size(), 0, postorder.size());
}
TreeNode* buildRecur(int inB, int inE, int postB, int postE) {
if (postE - postB == 0) return nullptr;
int val = postorder[postE - 1];
TreeNode* node = new TreeNode(val);
if (postE - postB == 1) return node;
int index = inB;
while (index < inE && inorder[index] != val) ++index;
// 此时index是中序根节点
node->left = buildRecur(inB, index, postB, postB + index - inB);
node->right = buildRecur(index + 1, inE, postB + index - inB, postE - 1);
return node;
}
中序与前序遍历构造二叉树
略
二叉树的序列化和反序列化
使用唯一的记法来表示一颗二叉树,根据这个记法可以重写构建出这颗二叉树。
序列化:
如果非空节点,转为字符串;如果空节点,用“None”标记
递归左右孩子节点;
例如图示的二叉树将被序列化为:
“1, 2, none, none, 3, 4, none, none, 5, none, none,”
string serialize(TreeNode* root) {
string ans;
serializeRecur(root, ans);
return ans;
}
void serializeRecur(TreeNode* root, string &ans) {
if (!root) {ans += "None,"; return;}
ans += (to_string(root->val) + ",");
serializeRecur(root->left, ans);
serializeRecur(root->right, ans);
}
反序列化:
首先需要将每个节点的值取出来,然后构建树,如果是none返回nullptr,否则返回一个树节点,递归左右子树。
对于序列化的结果,我们会用一个节点值便丢一个,所以采用链表,便于删除。
// 先将序列转为节点值链表
TreeNode* deserialize(string data) {
list<string> dataArray;
string str;
for (auto &ch : data) {
if (ch == ',') {
dataArray.push_back(str);
str.clear();
} else {
str.push_back(ch);
}
}
return deserializeRecur(dataArray);
}
// 根据节点值链表构建树
TreeNode* deserializeRecur(list<string> &dataArray) {
if (dataArray.front() == "None") {
dataArray.erase(dataArray.begin());
return nullptr;
}
TreeNode* root = new TreeNode(stoi(dataArray.front()));
dataArray.erase(dataArray.begin());
root->left = deserializeRecur(dataArray);
root->right = deserializeRecur(dataArray);
return root;
}
前缀树
使用后缀树结构存储words(words逆序插入),然后搜索字符流后缀是否在后缀树中即可。
注意点:
1、insert中,每个word逆序插入
2、搜索字符流的后缀,如果到空节点,返回false,如果非空,且为末尾,返回true;
class Trie {
public:
vector<Trie*> children;
bool isEnd;
Trie() : children(26), isEnd(false) {}
void insert(const string &word) {
Trie* cur = this;
for (int i = word.size() - 1; i >= 0; --i) {
char c = word[i];
if (!cur->children[c - 'a']) cur->children[c - 'a'] = new Trie();
cur = cur->children[c - 'a'];
}
cur->isEnd = true;
}
bool search(string &s) {
Trie* cur = this;
for (int i = s.size() - 1; i >= 0; --i) {
char c = s[i];
if (!cur->children[c - 'a']) return false;
cur = cur->children[c - 'a'];
if (cur->isEnd) return true;
}
return false;
}
};
class StreamChecker {
public:
Trie* trie = new Trie();
string s;
StreamChecker(vector<string>& words) {
for (auto& w : words) {
trie->insert(w);
}
}
bool query(char letter) {
s += letter;
return trie->search(s);
}
};