子数组的最大异或和&&Trie
前缀树
用途:自动补全,拼音检查,ip路由(最长前缀匹配),九宫格打字预测
还有其他的数据结构,如平衡树和哈希表,使我们能够在字符串数据集中搜索单词。为什么我们还需要 Trie 树呢?尽管哈希表可以在 O(1)O(1) 时间内寻找键值,却无法高效的完成以下操作:
- 找到具有同一前缀的全部键值。
- 按词典序枚举字符串的数据集。
Trie 树优于哈希表的另一个理由是,随着哈希表大小增加,会出现大量的冲突,时间复杂度可能增加到 O(n)O(n),其中 nn 是插入的键的数量。与哈希表相比,Trie 树在存储多个具有相同前缀的键时可以使用较少的空间。此时 Trie 树只需要 O(m)O(m) 的时间复杂度,其中 mm 为键长。而在平衡树中查找键值需要 O(m \log n)O(mlogn) 时间复杂度。
Trie 树是一个有根的树,其结点具有以下字段:。
- 最多 RR 个指向子结点的链接,其中每个链接对应字母表数据集中的一个字母。
- 本文中假定 RR 为 26,小写拉丁字母的数量。
- 布尔字段,以指定节点是对应键的结尾还是只是键前缀。
const int MAXN=26;//英文字符个数 class Trie { private: Trie *next[MAXN]; bool isEnd=false; public: /** Initialize your data structure here. */ Trie() { isEnd=false; memset(next,0,sizeof(next)); } /** Inserts a word into the trie. */ void insert(string word) { if(word.empty()) return ; Trie *cur=this;//cur初始化根节点 for(auto c:word) { if(cur->next[c-'a']==nullptr)//看当前结点在前缀树中是否存在 { Trie *node=new Trie(); cur->next[c-'a']=node; } cur=cur->next[c-'a'];//每个结点有个next和isEnd } cur->isEnd=true;//当前节点已经是一个完整的字符串 return ; } /** Returns if the word is in the trie. */ bool search(string word) { if(word.empty()) return false; Trie *cur=this; for(auto c:word) { if(cur) cur=cur->next[c-'a'];//若c在Trie中不存在,则cur->next[c-'a']为nullptr } return cur&&cur->isEnd?true:false;//cur不为空且cur指向的结点为一个完整的字符串,则为成功找到 } /** Returns if there is any word in the trie that starts with the given prefix. */ bool startsWith(string prefix) { if(prefix.empty()) return false; auto cur=this; for(auto c:prefix) { if(cur) cur=cur->next[c-'a']; } return cur?true:false; } };
异或的运算法则为:0⊕0=0,1⊕0=1,0⊕1=1,1⊕1=0(同为0,异为1),这些法则与加法是相同的,只是不带进位,所以异或常被认作不进位加法。
#include <iostream> #include <vector> #include <cmath> using namespace std; //1.暴力解O(n^3) int get_max_eor(const vector<int>& arr) { if(arr.empty()||arr.size()<0) return -1; int Max=-0x3f3f; for(unsigned int i=0;i<arr.size();++i)//以i结尾的每个子数组 { for(unsigned int start=0;start<=i;++start)//0..i/1..i/2..i等等,前两个for找所有的子数组 { int Eor=0; for(unsigned int k=start;k<=i;++k)//遍历每个区间:k=[start..i],求每个区间的最大异或和,arr.at(k) Eor^=(arr.at(k)); Max=max(Max,Eor); } } return Max; } //2.记忆化优化解O(n^2) //0...i异或结果为Sum //0...start-1异或结果为A //start...i的异或结果为Sum^A int get_max_eor1(const vector<int>& arr) { if(arr.size()<0||arr.empty()) return -1; int Eor=0; int Max=-0x3f3f; for(unsigned int i=0;i<arr.size();++i) { Eor^=arr.at(i);//遍历0...i,Xor保存每个以i结尾的最长数组的异或和 Max=max(Max,Eor);//一:可能是整个以i结尾的数组的异或和最大 int res=0; for(unsigned int start=0;start<=i;++start) { res^=arr.at(start);//每个以i结尾的数组的前半部分 Max=max(Max,res^Eor);//二:可能是某个子数组的异或和最大 } } return Max; } //2 O(n^2) int get_max_eor2(const vector<int>& arr) { if(arr.size()<0||arr.empty()) return -1; vector<int> dp(arr.size(),0); int Eor=0; int Max=-0x3f3f; for(unsigned int i=0;i<arr.size();++i) { Eor^=arr.at(i); Max=max(Max,Eor); for(unsigned int start=1;start<=i;++start) { int res=Eor^dp[start-1]; Max=max(Max,res); } dp.at(i)=Eor; } return Max; } //3 //0...i的异或结果Eor存 //0...0异或结果 0...1的异或结果 0...2的异或结果 直到0...i-1的异或结果(装入黑盒---前缀树) typedef struct Node { Node *next[2]; Node() { next[0]=nullptr; next[1]=nullptr; } }Node; class Trie { public: int get_max_eor3(const vector<int>& arr); ~Trie() { delete head; } private: int max_eor(const int &num); void add(const int &num); static Node *head; }; Node* Trie::head=new Node(); int Trie::get_max_eor3(const vector<int>& arr) { if(arr.size()<0||arr.empty()) return -1; int Max=-0x3f3f; int Eor=0; Trie trie; trie.add(0); for(unsigned int i=0;i<arr.size();++i) { Eor^=arr.at(i); Max=max(max_eor(Eor),Max); add(Eor); } return Max; } void Trie::add(const int& num)//把num添加到前缀树中 { Node *cur=head; int res=0; for(int move=31;move>=0;--move) { int path=((num>>move)&1);//取每一位 cur->next[path]=(cur->next[path]==nullptr?new Node():cur->next[path]); cur=cur->next[path]; } } //符号位尽量为0,后面的位是0走1,1走0,没得选就将就着走,尽量保持最大化值 //每次选最优,可以找到最大值 int Trie::max_eor(const int& num)//num和前缀树中的哪个值异或和最大 { int res=0; Node *cur=head; for(int move=31;move>=0;--move) { int path=((num>>move)&1); int best=(move==31?path:(path^1)); best=(cur->next[best]!=nullptr?best:(best^1)); res|=((path^best)<<move); cur=cur->next[best]; } return res; } int main() { vector<int> arr{1,2,3,5,6,8,9}; cout<<get_max_eor(arr)<<endl; cout<<get_max_eor1(arr)<<endl; cout<<get_max_eor2(arr)<<endl; Trie t; cout<<t.get_max_eor3(arr)<<endl; return 0; }