如何判断JavaScript数组是否包含连续顺序一致的子数组?
替代JSON.stringify的数组连续子数组匹配方案
针对你需要判断目标数组是否包含元素顺序完全连续一致的子数组的需求,这里提供两种更高效、安全的替代方案,解决JSON.stringify方法存在的局限性:
方案一:深度相等校验 + 滑动窗口遍历
这种方案通过自定义深度相等函数,配合滑动窗口遍历目标数组的所有可能起始位置,逐个验证子数组是否匹配,避免了JSON.stringify的序列化问题。
实现代码
// 深度相等比较函数,支持数组、对象及基本类型 function deepEqual(a, b) { // 基本类型与null/undefined直接比较 if (a === b) return true; // 数组类型校验 if (Array.isArray(a) && Array.isArray(b)) { if (a.length !== b.length) return false; for (let i = 0; i < a.length; i++) { if (!deepEqual(a[i], b[i])) return false; } return true; } // 对象类型校验(按键排序避免属性顺序影响) if (typeof a === 'object' && a !== null && typeof b === 'object' && b !== null) { const keysA = Object.keys(a).sort(); const keysB = Object.keys(b).sort(); if (keysA.length !== keysB.length) return false; for (const key of keysA) { if (!keysB.includes(key) || !deepEqual(a[key], b[key])) return false; } return true; } // 其余情况均不相等 return false; } function test(target, arr) { const targetLen = target.length; const arrLen = arr.length; // 待匹配数组更长,直接返回false if (arrLen > targetLen) return false; // 滑动窗口遍历所有可能的起始位置 for (let i = 0; i <= targetLen - arrLen; i++) { let isMatch = true; for (let j = 0; j < arrLen; j++) { if (!deepEqual(target[i + j], arr[j])) { isMatch = false; break; // 元素不匹配,提前终止当前窗口校验 } } if (isMatch) return true; } return false; } // 测试示例 const target = [1,2,3,4,5,"abc", "def", [10,1000]]; console.log(test(target, [2,3,4])); // true console.log(test(target, [4,5,"abc"])); // true console.log(test(target, ["def", [10, 1000]])); // true console.log(test(target, [0,1,2])); // false console.log(test(target, ["abc",5,4])); // false console.log(test(target, [2,4])); // false console.log(test(target, ["def", [1000, 10]])); // false
优势
- 避免
JSON.stringify的局限性:能正确处理BigInt、undefined、函数(可根据需求调整逻辑),不会因循环引用抛出错误; - 提前终止机制:遇到不匹配元素立即跳出当前窗口校验,减少不必要的计算;
- 逻辑清晰,符合JS规范,可维护性强。
方案二:滚动哈希(Rabin-Karp算法)优化
针对大数组场景,滚动哈希可以将时间复杂度优化到接近O(n+m)。通过为每个元素生成唯一哈希值,快速比较子数组哈希,仅在哈希匹配时做深度相等校验(避免哈希碰撞)。
实现代码
// 复用方案一中的deepEqual函数 // 生成元素的唯一哈希字符串 function getElementHash(value) { if (value === null) return 'null'; if (typeof value !== 'object') return String(value); if (Array.isArray(value)) { return `[${value.map(getElementHash).join(',')}]`; } // 对象按键排序,避免属性顺序影响哈希值 const sortedKeys = Object.keys(value).sort(); return `{${sortedKeys.map(key => `${key}:${getElementHash(value[key])}`).join(',')}}`; } function testWithRollingHash(target, arr) { const targetLen = target.length; const arrLen = arr.length; if (arrLen > targetLen) return false; // 计算待匹配数组的哈希串 const arrHash = arr.map(getElementHash).join('|'); // 初始化第一个窗口的哈希串 let windowHash = target.slice(0, arrLen).map(getElementHash).join('|'); // 先校验第一个窗口 if (windowHash === arrHash && deepEqual(target.slice(0, arrLen), arr)) { return true; } // 滚动遍历后续窗口 for (let i = arrLen; i < targetLen; i++) { // 更新窗口哈希:移除最左侧元素的哈希,添加新元素的哈希 const leftElementHash = getElementHash(target[i - arrLen]); windowHash = windowHash.replace(`${leftElementHash}|`, '') + `|${getElementHash(target[i])}`; // 哈希匹配时再做深度校验,避免碰撞 if (windowHash === arrHash && deepEqual(target.slice(i - arrLen + 1, i + 1), arr)) { return true; } } return false; } // 测试示例 console.log(testWithRollingHash(target, [2,3,4])); // true
优势
- 高效性:大数组场景下,哈希比较比逐个元素深度相等更快,减少计算量;
- 安全性:通过深度校验避免哈希碰撞导致的误判;
- 可扩展性:可根据需求调整哈希生成逻辑,适配更多类型。
对比原JSON.stringify方案的问题
原方案依赖字符串序列化,存在以下隐患:
- 类型兼容性差:无法处理
BigInt(需额外replacer)、循环引用(直接报错),会忽略undefined和函数; - 误匹配风险:若元素本身包含
[、]、,等字符,可能导致错误的子串匹配; - 性能问题:需要序列化整个数组,大数组场景下效率低下。
内容的提问来源于stack exchange,提问作者hellopeach
相关产品推荐
相关产品推荐

