438. 找到字符串中所有字母异位词

/**
 * 给定两个字符串 s 和 p,找到 s 中所有 p 的异位词的起始索引。
 *
 * @param {string} s 主字符串
 * @param {string} p 目标异位词
 * @returns {number[]} 包含所有异位词起始索引的数组
 */
 // 辅助函数:比较两个 Map 是否相等
    function mapsEqual(map1, map2) {
        if (map1.size !== map2.size) {
            return false;
        }
        for (const [key, value] of map1) {
            if (map2.get(key) !== value) {
                return false;
            }
        }
        return true;
    }

function findAnagrams(s, p) {
    const sLen = s.length;
    const pLen = p.length;

    // 检查边界条件,如果 s 的长度小于 p,则不可能存在异位词
    if (sLen < pLen) {
        return [];
    }

    // 使用 Map 作为哈希表,来存储字符的频率
    const pMap = new Map();
    const sMap = new Map();
    const result = [];

    // 初始化 pMap
    for (const char of p) {
        pMap.set(char, (pMap.get(char) || 0) + 1);
    }

    // 初始化第一个滑动窗口的 sMap
    for (let i = 0; i < pLen; i++) {
        const char = s[i];
        sMap.set(char, (sMap.get(char) || 0) + 1);
    }

    if (mapsEqual(sMap, pMap)) {
        result.push(0);
    }

    // 滑动窗口
    for (let i = pLen; i < sLen; i++) {
        // 右边界滑动:新进入窗口的字符
        const newChar = s[i];
        sMap.set(newChar, (sMap.get(newChar) || 0) + 1);

        // 左边界滑动:离开窗口的字符
        const oldChar = s[i - pLen];
        sMap.set(oldChar, sMap.get(oldChar) - 1);

        // 如果某个字符频率减为0,则从Map中删除,以保证比较的准确性
        if (sMap.get(oldChar) === 0) {
            sMap.delete(oldChar);
        }

        // 检查当前窗口是否为异位词
        if (mapsEqual(sMap, pMap)) {
            result.push(i - pLen + 1);
        }
    }

    return result;
}

在 JavaScript 中,直接使用 ===== 比较对象或数组通常是行不通的,因为它们比较的是内存地址,而不是内容。即使两个对象或数组看起来内容完全相同,只要它们不是同一个引用,比较结果就是 false

如何比较数组是否相等?

比较数组是否相等和比较对象类似,但更简单一些,因为数组的键是数字索引。

  1. 长度相等: 两个数组必须有相同的长度。
  2. 元素值相等: 相同索引位置的元素值必须相等。

/**
 * 比较两个数组的内容是否相等
 * @param {Array} arr1
 * @param {Array} arr2
 * @returns {boolean}
 */
function arraysEqual(arr1, arr2) {
    // 检查是否为同一引用
    if (arr1 === arr2) {
        return true;
    }

    // 检查长度是否相等
    if (arr1.length !== arr2.length) {
        return false;
    }

    // 遍历元素并进行比较
    for (let i = 0; i < arr1.length; i++) {
        if (arr1[i] !== arr2[i]) {
            return false;
        }
    }

    return true;
}

如何比较对象是否相等?

要比较两个对象的内容是否相等,你需要遍历它们的属性,并确保以下条件都满足:

  1. 属性数量相等: 两个对象必须有相同数量的自有(own)属性。
  2. 属性键名相等: 两个对象必须有相同的属性键名。
  3. 属性值相等: 相同键名对应的属性值必须相等。

以下是一个实现 objectsEqual 函数的例子

/**
 * 比较两个普通对象的内容是否相等
 * @param {object} obj1
 * @param {object} obj2
 * @returns {boolean}
 */
function objectsEqual(obj1, obj2) {
    // 检查是否为同一引用,这是最快的判断
    if (obj1 === obj2) {
        return true;
    }

    // 检查是否为对象且不为 null
    if (typeof obj1 !== 'object' || obj1 === null || typeof obj2 !== 'object' || obj2 === null) {
        return false;
    }

    const keys1 = Object.keys(obj1);
    const keys2 = Object.keys(obj2);

    // 检查属性数量是否相等
    if (keys1.length !== keys2.length) {
        return false;
    }

    // 遍历所有属性并进行比较
    for (const key of keys1) {
        // 如果属性值不相等,或者obj2中没有这个属性,则返回 false
        if (!obj2.hasOwnProperty(key) || obj1[key] !== obj2[key]) {
            return false;
        }
    }

    return true;
}

⚠️ 注意: 上述函数只适用于浅层比较,即如果对象属性的值是另一个对象,它无法进行深层比较。要处理这种情况,你需要使用递归。

实现深层比较(Deep Equal)

这个 deepEqual 函数可以处理以下情况:

  • 两个值是同一引用(直接返回 true)。
  • 两个值的类型不同。
  • 两个值是基本数据类型(如字符串、数字、布尔值)。
  • 两个值是 null 或非对象类型。
  • 两个值都是对象或数组,并进行递归比较。
/**
 * 递归比较两个 JavaScript 值是否深度相等。
 * 支持普通对象、数组、基本类型等。
 * @param {*} val1 - 第一个要比较的值。
 * @param {*} val2 - 第二个要比较的值。
 * @returns {boolean} 如果两个值深度相等则返回 true,否则返回 false。
 */
function deepEqual(val1, val2) {
    // 1. 基本情况:检查是否为同一引用,这是最快的判断
    if (val1 === val2) {
        return true;
    }

    // 2. 基本情况:如果类型不同,或其中一个为 null,则不相等
    if (
        typeof val1 !== typeof val2 ||
        val1 === null ||
        val2 === null
    ) {
        return false;
    }
    
    // 3. 基本情况:处理非对象类型(基本类型,如数字、字符串)
    if (typeof val1 !== 'object') {
        return false; // 因为 val1 !== val2,所以如果它们不是对象,就一定不相等
    }
    
    // 4. 递归情况:处理数组
    if (Array.isArray(val1) && Array.isArray(val2)) {
        // 如果数组长度不同,则不相等
        if (val1.length !== val2.length) {
            return false;
        }
        // 递归比较数组的每一个元素
        for (let i = 0; i < val1.length; i++) {
            if (!deepEqual(val1[i], val2[i])) {
                return false;
            }
        }
        return true;
    }

    // 5. 递归情况:处理普通对象
    if (val1.constructor === Object && val2.constructor === Object) {
        const keys1 = Object.keys(val1);
        const keys2 = Object.keys(val2);

        // 如果属性数量不同,则不相等
        if (keys1.length !== keys2.length) {
            return false;
        }

        // 递归比较对象的每一个属性值
        for (const key of keys1) {
            // 确保 obj2 包含相同的属性,并且属性值深度相等
            if (!Object.prototype.hasOwnProperty.call(val2, key) || !deepEqual(val1[key], val2[key])) {
                return false;
            }
        }
        return true;
    }

    // 6. 如果是其他类型(如 Function, RegExp, Date等),简化处理为不相等
    // 这里的逻辑可以根据需要进行扩展
    return false;
}

// --- 示例用法 ---
const obj1 = {
    a: 1,
    b: [2, { c: 3 }],
};

const obj2 = {
    a: 1,
    b: [2, { c: 3 }],
};

const obj3 = {
    a: 1,
    b: [2, { c: 4 }],
};

console.log('obj1 和 obj2 深度相等吗?', deepEqual(obj1, obj2)); // 输出: true
console.log('obj1 和 obj3 深度相等吗?', deepEqual(obj1, obj3)); // 输出: false

全部评论

相关推荐

评论
3
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务