
以下是 LeetCode 3934. Smallest Unique Subarray最短唯一子数组 的 JavaScript 实现。解题思路核心性质如果存在一个长度为 l 的唯一子数组在整个数组中只出现一次那么对于任意长度 L l也一定存在唯一子数组因为可以把那个长度为 l 的唯一子数组向左右扩展。因此答案具有单调性可以使用二分查找。算法二分查找最小可行长度 len对于每个 len用双哈希滚动哈希Rolling Hash在 O(n) 时间内统计所有长度为 len 的子数组出现次数判断是否存在出现恰好一次的子数组。- 时间复杂度O(n \log n)- 空间复杂度O(n)javascript/*** param {number[]} nums* return {number}*/var smallestUniqueSubarray function(nums) {const n nums.length;// 快速剪枝如果有某个元素只出现一次答案直接为 1const freq new Map();for (const x of nums) {freq.set(x, (freq.get(x) || 0) 1);}for (const v of freq.values()) {if (v 1) return 1;}// 双哈希参数const BASE1 911382323;const MOD1 1_000_000_007;const BASE2 972663749;const MOD2 1_000_000_009;const b1 BigInt(BASE1), m1 BigInt(MOD1);const b2 BigInt(BASE2), m2 BigInt(MOD2);const bigNums nums.map(x BigInt(x));// 预处理幂次const pow1 new Array(n 1);const pow2 new Array(n 1);pow1[0] 1n;pow2[0] 1n;for (let i 1; i n; i) {pow1[i] (pow1[i - 1] * b1) % m1;pow2[i] (pow2[i - 1] * b2) % m2;}// 检查是否存在长度为 len 的唯一子数组const check (len) {const cnt new Map(); // key 合并后的双哈希值// 计算第一个窗口的哈希let h1 0n, h2 0n;for (let i 0; i len; i) {h1 (h1 * b1 bigNums[i]) % m1;h2 (h2 * b2 bigNums[i]) % m2;}let key h1 * m2 h2;cnt.set(key, 1);// 滑动窗口for (let i 1; i n - len; i) {// 移除左边元素的贡献h1 (h1 - bigNums[i - 1] * pow1[len - 1]) % m1;h2 (h2 - bigNums[i - 1] * pow2[len - 1]) % m2;if (h1 0n) h1 m1;if (h2 0n) h2 m2;// 左移并加上右边新元素h1 (h1 * b1 bigNums[i len - 1]) % m1;h2 (h2 * b2 bigNums[i len - 1]) % m2;key h1 * m2 h2;cnt.set(key, (cnt.get(key) || 0) 1);}// 检查是否有出现恰好一次的子数组for (const v of cnt.values()) {if (v 1) return true;}return false;};// 二分查找最小可行长度let lo 1, hi n, ans n;while (lo hi) {const mid (lo hi) 1;if (check(mid)) {ans mid;hi mid - 1;} else {lo mid 1;}}return ans;};关键点说明1. 双哈希使用两个独立的哈希函数将结果合并为一个 BigInt 作为 Map 的 key极大降低哈希碰撞概率。2. 滚动哈希递推高位在前的多项式哈希滑动时先减去最左元素的贡献再整体左移乘 base最后加入新元素。3. BigIntJavaScript 的 Number 最大安全整数约为 9 \times 10^{15}而哈希中间结果可能溢出因此使用 BigInt 保证精度。4. 快速剪枝若数组中存在只出现一次的元素直接返回 1避免不必要的二分过程。