今天进步一点点2023.3.24
@力扣
每日一题:
1032 字符流 (困难题)
题目说明:设计一个算法:接收一个字符流,并检查这些字符的后缀是否是字符串数组 words 中的一个字符串。
例如,words = [“abc”, “xyz”] 且字符流中逐个依次加入 4 个字符 ‘a’、‘x’、‘y’ 和 ‘z’ ,你所设计的算法应当可以检测到 “axyz” 的后缀 “xyz” 与 words 中的字符串 “xyz” 匹配。
按下述要求实现 StreamChecker 类:
StreamChecker(String[] words) :构造函数,用字符串数组 words 初始化数据结构。
boolean query(char letter):从字符流中接收一个新字符,如果字符流中的任一非空后缀能匹配 words 中的某一字符串,返回 true ;否则,返回 false。
输入:
["StreamChecker", "query", "query", "query", "query", "query", "query", "query", "query", "query", "query", "query", "query"]
[[["cd", "f", "kl"]], ["a"], ["b"], ["c"], ["d"], ["e"], ["f"], ["g"], ["h"], ["i"], ["j"], ["k"], ["l"]]
输出:
[null, false, false, false, true, false, true, false, false, false, false, false, true]
解释:
StreamChecker streamChecker = new StreamChecker(["cd", "f", "kl"]);
streamChecker.query("a"); // 返回 False
streamChecker.query("b"); // 返回 False
streamChecker.query("c"); // 返回n False
streamChecker.query("d"); // 返回 True ,因为 'cd' 在 words 中
streamChecker.query("e"); // 返回 False
streamChecker.query("f"); // 返回 True ,因为 'f' 在 words 中
streamChecker.query("g"); // 返回 False
streamChecker.query("h"); // 返回 False
streamChecker.query("i"); // 返回 False
streamChecker.query("j"); // 返回 False
streamChecker.query("k"); // 返回 False
streamChecker.query("l"); // 返回 True ,因为 'kl' 在 words 中
考虑直接存储words数组,将所有以letter结尾的后缀找出啦,然后对比words词组,结果超时。
class StreamChecker {
public:
vector<string> temp;
set<string> WordS;
StreamChecker(vector<string>& words) {
for(auto e:words) WordS.insert(e);
}
bool query(char letter) {
bool result=false;
for(int i=0;i<temp.size();i++){
temp[i]+=letter;
if(WordS.count(temp[i])>0) result=true;
}
string t(1,letter);
temp.push_back(t);
if(WordS.count(temp.back())>0) result=true;
return result;
}
};
运行结果:
显然会超时,困难题能这么解就不是困难题了吧。
前缀树
前缀树的核心是空间换时间,哈希树的变种。根据力扣上其他大佬的思路,我们先把words化为一颗前缀树。因为需要从尾巴开始比较,那么从word数组的每一个字符串都从末尾开始构建前缀树。以题目中给出的示例画出前缀树如下:
这里需要再每一个末尾字符做一个标记,判断是否是words数组里字符串的末尾字符。
class StreamChecker {
public:
struct Trie{
char val;
bool IsTail;
vector<struct Trie*> next;
Trie(char c,bool IsTails):val(c),IsTail(IsTails),next(26,nullptr){}
};
vector<Trie*> Words;
string res;
StreamChecker(vector<string>& words):Words(26,nullptr),res("") {
for(auto e:words) {
if(Words[e[e.length()-1]-'a']==nullptr){
Trie* node=new Trie(e[e.length()-1],false);
Words[e[e.length()-1]-'a']=node;
}
Trie* node=Words[e[e.length()-1]-'a'];
for(int i=e.length()-2;i>=0;i--) {
if(node->next[e[i]-'a']==nullptr) {
Trie* node_=new Trie(e[i],false);
node->next[e[i]-'a']=node_;
node=node_;
}else{
node=node->next[e[i]-'a'];
}
}
node->IsTail=true;
}
}
bool query(char letter) {
res+=letter;
Trie* h=Words[letter-'a'];
if(h==nullptr) return false;
if(h->IsTail==true) return true;
for(int i=res.length()-2;i>=0;i--) {
if(h->next[res[i]-'a']==nullptr) return false;
h=h->next[res[i]-'a'];
if(h->IsTail==true) return true;
}
return false;
}
};
个人首先是定义了树节点,进而每层用vector包装起来,方便查找字符。
结果:
剑指Offer题
Offer 07. 重建二叉树
输入某二叉树的前序遍历和中序遍历的结果,请构建该二叉树并返回其根节点。
假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
示例 1:
Input: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
Output: [3,9,20,null,null,15,7]
示例 2:
Input: preorder = [-1], inorder = [-1]
Output: [-1]
重建二叉树的思路就是不断找确定的根节点的位置,再根据另一个序的数组,划分左右子树的范围。是一个递归的过程。
class Solution {
public:
TreeNode* PreAndInorder(vector<int> &preorder,vector<int> &inorder,int prebegin,int preend,int inbegin,int inend) {
if(prebegin>preend) return nullptr;
TreeNode* node=new TreeNode(preorder[prebegin]);
if(prebegin==preend) return node;
int Root;
for(int i=inbegin;i<=inend;i++) {
if(inorder[i]==preorder[prebegin]) {Root=i;break;}
}
int Left_len=Root-inbegin;
node->left=PreAndInorder(preorder,inorder,prebegin+1,prebegin+Left_len,inbegin,inbegin+Left_len-1);
node->right=PreAndInorder(preorder,inorder,prebegin+Left_len+1,preend,Root+1,inend);
return node;
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
return PreAndInorder(preorder,inorder,0,preorder.size()-1,0,inorder.size()-1);
}
};
突然思考这种题是否还有迭代解法呢,之前室友面试的时候,被问到了快速排序算法手撕,舍友回答的是用递归方法,当时面试官就问可以用迭代算法做吗。
看了官方迭代法,直呼好难呀。
跟着官方解写一遍先:
class Solution {
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
if(preorder.empty()) return nullptr;
TreeNode* root=new TreeNode(preorder[0]);
stack<TreeNode*> stk;
stk.push(root);
int inorderIndex=0;
for(int i=1;i<preorder.size();i++) {
int preorderNum=preorder[i];
TreeNode* node=stk.top();
if(node->val!=inorder[inorderIndex]) {
node->left=new TreeNode(preorderNum);
stk.push(node->left);
}else{
while(!stk.empty()&&stk.top()->val==inorder[inorderIndex]) {
node=stk.top();
stk.pop();
++inorderIndex;
}
node->right=new TreeNode(preorderNum);
stk.push(node->right);
}
}
return root;
}
};
浅浅的解释就是说:把树想象为一颗只有左子树的树,那么中序(左根)与前序(根左)两个数组是相反的顺序。那么前序数组可以直接从根节点建立到最左节点,什么时候可以观测到右子树的出现呢,就是前序(根左右)遍历时发现中序数组对应值对应到了,这段子树到头了,根据栈保存的左节点,一个一个pop出来,找到对应的头节点位置,再将右节点保存到栈中,不断重复这个操作。(第一次接触,希望以后慢慢理解。)
运行结果:
@其他细碎知识
- 红黑树
Epoll实现的结构是红黑树,会被问到红黑树的特性,朋友被问到红黑树和平衡二叉树的区别。特地学一下,防止被问到。红黑树其实是一种特化的二叉查找树,二叉查找树是啥捏,二叉查找树就是对于每个根节点来说,左子树节点的值都小于根节点,右子数节点的值都大于根节点。二叉平衡树也是二叉查找树,是需要满足平衡条件的二叉查找树。下面记下两种结构需要满足的条件:
-
平衡二叉树
每个根节点左右子树的高度差不超过1 -
红黑树(红黑树需要满足的条件很多)
红黑树每个节点分为黑色或者红色,根节点一定是黑色,从每个叶节点到根节点的路线上不可能有两个连续的红色节点,叶子节点(Null指针)都是黑色的,对每个节点,从该节点到其后叶子节点的每一条路径上的黑色节点的数目是相同的。红黑树与平衡树插入、删除的效率都是O(log2n)的,红黑树和平衡树在插入、删除之后都可能会处于不满足自身规则的情况,平衡树可能需要旋转一个或多个节点来恢复到自身的平衡状态,红黑树能够在三个树旋转之内恢复状态,这也是红黑树与平衡树不同的地方。
-
Epoll系列调用(按照使用顺序)
epoll_create ( int size ) 产生一个文件描述符,唯一标识内核中的事件发生表,size给内核一个提示,告诉他事件表需要多大。epoll_ctl ( int epfd, int op, int fd, struct epoll_event* event) fd就是要操作的文件描述符,op参数指定操作类型,event参数指定事件
epoll_wait ( int fd, struct epoll_event* events,int maxevents, int timeout ) timeout为-1时,poll调用会阻塞,直到某个事件发生;当timeout为0时,poll调用将立即返回。在这个地方,events数组得到的是所有就绪事件(select和poll的数组参数都是既用于用户注册的事件,又用于输出检测到的就绪事件,这样比较,epoll就提高了程序索引就绪文件描述符的效率)