792. 匹配子序列的单词数


给定字符串 S 和单词字典 words, 求 words[i] 中是 S 的子序列的单词个数。

示例:
输入:
S = “abcde”
words = [“a”, “bb”, “acd”, “ace”]
输出: 3
解释: 有三个是 S 的子序列的单词: “a”, “acd”, “ace”。
注意:

  1. 所有在words和 S 里的单词都只由小写字母组成。
  2. S 的长度在 [1, 50000]。
  3. words 的长度在 [1, 5000]。
  4. words[i]的长度在[1, 50]。

思路:两个字符串是否满足子序列,我们使用双指针来判断,
这里借用了这个思路,将每个子序列的指针变量pos保存到hash中,和words中的子序列构成一个元组 value, key为当前比对到的字符。

  1. /**
  2. * @param {string} s
  3. * @param {string[]} words
  4. * @return {number}
  5. */
  6. var numMatchingSubseq = function(s, words) {
  7. // 初始化字符-子串hash表,value格式为:[w, pos]
  8. const r = words.reduce((r, w) => {
  9. const ch = w.charAt(0);
  10. if (r.has(ch)) {
  11. r.get(ch).push([w, 0]);
  12. } else {
  13. r.set(ch, [[w, 0]]);
  14. }
  15. return r;
  16. }, new Map());
  17. let ans = 0;
  18. for (let i = 0; i < s.length; i++) {
  19. const ch = s.charAt(i);
  20. const list = r.get(ch);
  21. // 查找hash表中是否存在该字符开头的子串。
  22. if (list && list.length) {
  23. let len = list.length;
  24. while (len--) {
  25. // 找到子串,将pos向后移动,若pos大于子序列t的长度,说明是子序列,结果集加一
  26. let [t, pos] = list.shift();
  27. pos ++;
  28. if (pos >= t.length) {
  29. // 结果集计数
  30. ans ++;
  31. continue;
  32. }
  33. // 更新hash表,将当前字符和pos位置存储到hash表中。
  34. const tch = t.charAt(pos);
  35. if (!r.has(tch)) {
  36. r.set(tch, []);
  37. }
  38. r.get(tch).push([t, pos]);
  39. }
  40. }
  41. }
  42. return ans;
  43. };

1804. 实现 Trie (前缀树) II

/**
 * Initialize your data structure here.
 */
var Trie = function() {
  this.root = new Array(27).fill(undefined);
};
/**
 * Inserts a word into the trie. 
 * @param {string} word
 * @return {void}
 */
Trie.prototype.insert = function(word) {
  let node = this.root;
  for (let i = 0; i < word.length; i++) {
    let ch = word.charCodeAt(i) - 97;
    if (node[ch] === undefined) {
      node[ch] = new Array(27).fill(undefined);
    }
    node = node[ch];
  }
  node[26] = word;
};
Trie.prototype.__find = function(key) {
  let node = this.root;
  for (let i = 0; i < key.length; i++) {
    let ch = key.charCodeAt(i) - 97;
    if (node[ch] !== undefined) {
      node = node[ch];
    } else {
      return;
    }
  }
  return node;
}
/**
 * Returns if the word is in the trie. 
 * @param {string} word
 * @return {boolean}
 */
Trie.prototype.search = function(word) {
  let node = this.__find(word);
  if (node) {
    return node[26] !== undefined;
  }
  return false;
};

/**
 * Returns if there is any word in the trie that starts with the given prefix. 
 * @param {string} prefix
 * @return {boolean}
 */
Trie.prototype.startsWith = function(prefix) {
  let results = [];
  const _startWith = (node, key) => {
    if (!node) {
      return;
    }
    if (node[26] !== undefined) {
      results.push(key.join(''));
    }
    for (let ch = 0; ch < 26; ch++) {
      key.push(ch);
      _startWith(node[ch], key);
      key.pop();
    };
  };
  _startWith(this.__find(prefix), Array.from(prefix));
  return results.length > 0;
};

/**
 * Your Trie object will be instantiated and called as such:
 * var obj = new Trie()
 * obj.insert(word)
 * var param_2 = obj.search(word)
 * var param_3 = obj.startsWith(prefix)
 */

421. 数组中两个数的最大异或值

/**
 * @param {number[]} nums
 * @return {number}
 */
var findMaximumXOR = function(nums) {
  if (nums.length < 2) return 0;
  const tr = [undefined, undefined];
  for (let i = 0; i < nums.length; i++) {
    insert(tr, nums[i]);
  }
  let ans = Number.MIN_SAFE_INTEGER;
  for (let i = 0; i < nums.length; i++) {
    ans = Math.max(ans, nums[i] ^ srearch(tr, nums[i]));
  }  
  return ans;
};

function insert(root, x) {
  // 高位存储来Trie的前面,所以我们从左向右存储
  for(let i = 30; i >= 0; i--){
    // 取第i位的数字,this.len - 1...0
    let u = (x>>i) & 1;
    // 若第u位为空,则创建一个新节点,然后root移动到下一个节点
    if(root[u] === undefined) {
      root[u] = [undefined, undefined];
    }
    root = root[u];
  }
}

function srearch(root, x){  // 在前缀树中寻找 x 的最大异或值
  // res表示最大异或值,每次res*2表示左移一位,+u表示加上当前的最低位数字
  let res = 0;
  for(let i = 30; i >= 0; i--){
    let u = (x>>i) & 1;
    // 若 x 的第 u 位存在,取相反的方向搜索,因为异或总是|值|相反才取最大值的
    if (root[u ^ 1]) {
      u ^= 1;
    }
    res |= u << i;
    root = root[u];
  }
  return res;
}

1707. 与数组中元素的最大异或值

给你一个由非负整数组成的数组 nums 。另有一个查询数组 queries ,其中 queries[i] = [xi, mi] 。

第 i 个查询的答案是 xi 和任何 nums 数组中不超过 mi 的元素按位异或(XOR)得到的最大值。换句话说,答案是 max(nums[j] XOR xi) ,其中所有 j 均满足 nums[j] <= mi 。如果 nums 中的所有元素都大于 mi,最终答案就是 -1 。

返回一个整数数组 answer 作为查询的答案,其中 answer.length == queries.length 且 answer[i] 是第 i 个查询的答案。

示例 1:

输入:nums = [0,1,2,3,4], queries = [[3,1],[1,3],[5,6]]
输出:[3,3,7]
解释:
1) 0 和 1 是仅有的两个不超过 1 的整数。0 XOR 3 = 3 而 1 XOR 3 = 2 。二者中的更大值是 3 。
2) 1 XOR 2 = 3.
3) 5 XOR 2 = 7.
示例 2:

输入:nums = [5,2,4,6,6,3], queries = [[12,4],[8,1],[6,3]]
输出:[15,-1,5]

提示:

1 <= nums.length, queries.length <= 105
queries[i].length == 2
0 <= nums[j], xi, mi <= 109

思路:排序 + 暴力计算xor值

/**
 * @param {number[]} nums
 * @param {number[][]} queries
 * @return {number[]}
 */
var maximizeXor = function(nums, queries) {
  let ans = [];
  nums.sort((a, b) => a - b);
  for (let i = 0; i < queries.length; i++) {
    let [x, m] = queries[i];
    if (m < nums[0]) {
      ans.push(-1);
      continue;
    }
    let pos = search(nums, m, (p) => p - m);
    let s = Number.MIN_SAFE_INTEGER;
    for (let j = 0; j < pos; j++) {
      s = Math.max(s, nums[j] ^ x);
    }
    ans.push(s);
  }
  return ans;
};

function search(d, t, comp) {
  let l = 0; r = d.length;
  while (l < r) {
    let mid = l + ((r - l) >>> 1);
    let cp = comp(d[mid], t);
    if (cp === 0) {
      l = mid + 1;
    } else if (cp < 0) {
      l = mid + 1;
    } else if (cp > 0) {
      r = mid;
    }
  }
  return l;
}

解法二:Trie树 + 贪心选择不同位

/**
 * @param {number[]} nums
 * @param {number[][]} queries
 * @return {number[]}
 */
var maximizeXor = function(nums, queries) {
  const tr = [undefined, undefined, Number.MAX_SAFE_INTEGER];
  for (let v of nums) {
    insert(tr, v);
  }
  let ans = [];
  for (let i = 0; i < queries.length; i++) {
    let [s, m] = queries[i];
    ans[i] = search(tr, s, m);
  }
  return ans;
};

function insert(root, x) {
  // 在前缀树中插入值
  root[2] = Math.min(root[2], x);
  // 高位存储来Trie的前面,所以我们从左向右存储
  for(let i = 30; i >= 0; i--){
    // 取第i位的数字,this.len - 1...0
    let u = (x>>i) & 1;
    // 若第u位为空,则创建一个新节点,然后root移动到下一个节点
    if(root[u] === undefined) {
      root[u] = [undefined, undefined, Number.MAX_SAFE_INTEGER];
    }
    root = root[u];
    root[2] = Math.min(root[2], x);
  }
}

function search(root, x, limit) {  // 在前缀树中寻找 x 的最大异或值
  if (root[2] > limit) {
    return -1;
  }
  // res表示最大异或值,每次res*2表示左移一位,+u表示加上当前的最低位数字
  let res = 0;
  for(let i = 30; i >= 0; i--){
    let b = (x>>i) & 1;
    // 若 x 的第 b 位存在,取相反的方向搜索,因为异或总是|值|相反才取最大值的
    if (root[b ^ 1] && root[b ^ 1][2] <= limit) {
      res |= 1 << i;
      b ^= 1;
    }
    root = root[b];
  }
  return res;
}