leetcode重点题目分类别记录(一)数据结构类


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;
    }
快速排序

注意:

  1. 采用右端点值作为目标值来partition,为避免最坏情况发生,将随机生成一个索引,将其与右端点值交换;
  2. 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四数之和

四数相加

454. 四数相加 II

链表

环形链表

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();
		}

};

单调栈

  1. 柱状图中最大的矩形
  2. 最大矩形
    在这里插入图片描述
    最大矩形可能出现的情况是:
    对于某一个高度为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;
    }

哈希表

原地哈希应用

  1. 数组中重复的数据
    在这里插入图片描述
    原地哈希:使用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;
    }

哈希+前缀和

  1. 和为 K 的子数组
  2. 和可被 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);
    }
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值