01 字典樹在面試中的妙用
簡介
\(\textbf{01trie}\) 就是把整數的二進位制表示式當作字串,按照從高位到低位的順序,掛載在字典樹上,每個節點有兩個孩子,分別是 \(0,\ 1\),從而形成一棵二叉樹,常用來處理異或問題
例如將一個數組 \([1,\ 2,\ 3]\) 掛在 \(\textbf{01trie}\) 上,便得到如下所示的一棵二叉樹
性質
由於 \(\textbf{01trie}\) 是一棵二叉樹,且最大深度取決於掛載值的大小,設掛載最大值為 \(n\),那麼一次查詢字首的過程便可以在 \(O(\log n)\) 時間完成
例題
力扣 421. 陣列中兩個數的最大異或值
給定 \(n\) 個正整數的陣列 \(a\)
資料規定
\(1\leq n\leq 2\cdot 10^4\)
\(1\leq a_{i}\leq 2^{31} - 1\)
題解
將陣列中所有正整數的二進位制表示,按照從高位到低位的順序,當作字串掛載在字典樹上,形成 \(01\) 字典樹,該字典樹為一棵二叉樹
對於正整數 \(t\),為了尋找陣列中的 \(s\),使得 \(x\oplus t\) 最大,我們只要每次貪心走與當前位相反的路即可
具體來講,如果當前位為 \(1\),我們走 \(0\) 子樹,反之走 \(1\) 子樹,當然,如果不存在對應的子樹,我們還是得走存在的子樹
這樣可以保證異或後的高位儘可能為 \(1\)
時間複雜度為 \(O(n\log L)\),其中 \(L = 2^{31} - 1\)
// cpp const int N = 2e4; const int M = 31; class Solution { public: int node[N * M][2], cnt = 0; void insert(int x) { int p = 0; for (int i = M; i >= 0; --i) { int idx = 1 & (x >> i); if (!node[p][idx]) node[p][idx] = ++cnt; p = node[p][idx]; } } int query(int x) { int p = 0; int ans = 0; for (int i = M; i >= 0; --i) { int idx = 1 & (x >> i); if (node[p][idx ^ 1]) { ans *= 2, ans += 1; p = node[p][idx ^ 1]; } else { ans *= 2, ans += 0; p = node[p][idx]; } } return ans; } int findMaximumXOR(vector<int>& nums) { for (auto &i: nums) insert(i); int ans = 0; for (auto &i: nums) ans = max(ans, query(i)); return ans; } };
力扣 1707. 與陣列中元素的最大異或值
給定 \(n\) 個正整數的陣列 \(a\),給定 \(q\) 個詢問,每個詢問包含兩個正整數 \(x_{i},\ m_{i}\)
對於每一個詢問,在 \(a\) 中所有不大於 \(m_{i}\) 的數中選一個 \(y\),使得 \(x_{i}\oplus y\) 最大,返回這個最大值
資料規定
\(1\leq n,\ q\leq 10^5\)
\(1\leq a_{i},\ x_{i},\ m_{i}\leq 10^9\)
題解
離線查詢,對 \(a\) 從小到大排序,對 \(q\) 按照 \(m_{i}\) 從小到大排序
根據單調性,使用雙指標,將 \(a\) 中符合條件的正整數 \(a_{i}\) 掛載到字典樹上,進行查詢即可
時間複雜度為 \(O((q + n)\cdot \log L + q\log q + n\log n)\),其中 \(L = 10^9\)
// cpp
#define pb push_back
const int N = 1e5;
const int M = 30;
class Solution {
public:
int node[N * M][2], cnt = 0;
void insert(int x)
{
int p = 0;
for (int i = M; i >= 0; --i) {
int idx = 1 & (x >> i);
if (!node[p][idx]) node[p][idx] = ++cnt;
p = node[p][idx];
}
}
int query(int x)
{
int p = 0;
int ans = 0;
for (int i = M; i >= 0; --i) {
int idx = 1 & (x >> i);
if (node[p][idx ^ 1]) {
ans *= 2, ans += 1;
p = node[p][idx ^ 1];
}
else {
ans *= 2, ans += 0;
p = node[p][idx];
}
}
return ans;
}
vector<int> maximizeXor(vector<int>& nums, vector<vector<int>>& q) {
int idx = 0;
for (auto &i: q) i.pb(idx++);
sort(q.begin(), q.end(), [&](const vector<int> &x, const vector<int> &y){
return x[1] < y[1];
});
sort(nums.begin(), nums.end());
int n = q.size();
vector<int> ans(n);
for (int i = 0, j = 0; i < n; ++i) {
while (j < nums.size() && nums[j] <= q[i][1])
insert(nums[j++]);
if (!j) ans[q[i][2]] = -1;
else ans[q[i][2]] = query(q[i][0]);
}
return ans;
}
};
力扣 1938. 查詢最大基因差
給定一棵 \(n\) 個節點的樹,每個節點的編號 \(i\) 即為其權值 \(v_{i}\)
給定 \(q\) 個查詢,每個查詢包含樹上一個點的編號 \(i\) 和目標值 \(t\)
對於每一個查詢,你需要選一個從根到 \(i\) 的節點 \(j\),要求使得 \(j \oplus i\) 值最大,返回這個最大值
資料規定
\(1\leq n\leq 10^5\)
\(1\leq q\leq 3\cdot 10^4\)
\(1\leq v_{i}\leq 2\cdot 10^5\)
題解
離線查詢,維護每個節點的所有查詢
我們需要維護一個從根到當前節點的路徑,因此考慮 dfs
具體來講,深搜到當前點 \(i\) 時,將 \(v_{i}\) 掛載在 01 trie
上,同時進行一次查詢,計算出最大的異或值,繼續向下深搜,等到回溯的時候,將當前節點的權值從字典樹上刪除
計算最大異或值時,每次貪心選擇與當前位相反的節點即可
時間複雜度為 \(O((n + q)\cdot \log L)\),其中 \(L = 2\cdot 10^5\)
// cpp
#define pii pair< int, int >
#define fi first
#define se second
#define pb push_back
const int N = 2e5;
const int M = 18;
int node[N * M][2], bucket[N * M][2], cnt = 0;
void insert(int x)
{
int p = 0;
for (int i = M; i >= 0; --i) {
int idx = 1 & (x >> i);
if (!node[p][idx]) node[p][idx] = ++cnt;
bucket[p][idx]++;
p = node[p][idx];
}
}
void del(int x)
{
int p = 0;
for (int i = M; i >= 0; --i) {
int idx = 1 & (x >> i);
int next = node[p][idx];
if (bucket[p][idx] == 1) node[p][idx] = 0;
bucket[p][idx]--;
p = next;
}
}
int query(int x)
{
int p = 0;
int ans = 0;
for (int i = M; i >= 0; --i) {
int idx = 1 & (x >> i);
if (node[p][idx ^ 1]) {
ans *= 2, ans += 1;
p = node[p][idx ^ 1];
}
else {
ans *= 2, ans += 0;
p = node[p][idx];
}
}
return ans;
}
class Solution {
public:
unordered_map< int, vector< pii > > mp;
vector< int > son[N];
void dfs(int u, vector< int > &ans)
{
insert(u);
for (auto &i : mp[u]) ans[i.se] = query(i.fi);
for (auto &i : son[u]) dfs(i, ans);
del(u);
}
vector< int > maxGeneticDifference(vector< int > &p, vector< vector< int > > &q)
{
int root = 0;
for (int i = 0; i < p.size(); ++i) {
if (p[i] != -1)
son[p[i]].push_back(i);
else
root = i;
}
int n = q.size();
for (int i = 0; i < n; ++i) {
q[i].pb(i);
mp[q[i][0]].pb({ q[i][1], q[i][2] });
}
vector< int > ans(n);
dfs(root, ans);
return ans;
}
};