数组与哈希表算法题的技巧总结

2 阅读4分钟

一、解题思维总结

1. 何时使用哈希表?

场景解法
判断元素是否存在Set
统计元素频次Map/Object
查找满足条件的元素对Map + 差值技巧
分组(异位词等)Map + 排序键
需要 O(1) 查询哈希表替代数组遍历

2. 复杂度分析

  • 哈希表操作:O(1) 平均时间复杂度
  • 排序:O(n log n)
  • 空间换时间:用额外 O(n) 空间换取 O(n) 时间优化

3. 常用技巧

  1. 对象初始化count[char] = (count[char] || 0) + 1
  2. 快速去重[...new Set(array)]Array.from(new Set(array))
  3. 对象遍历Object.entries()Object.keys()Object.values()
  4. 数组填充new Array(n).fill(0)

二、哈希表基础与应用

技巧一:使用 Set 进行快速查找

适用场景:需要判断元素是否已存在、去重等场景

核心优势

  • Set.has()Set.add() 都是 O(1) 时间复杂度
  • 相比数组的 includes()indexOf()(O(n))效率更高

典型例题存在重复元素

function containsDuplicate(nums) {
    const seen = new Set();
    for (const num of nums) {
        if (seen.has(num)) {
            return true;
        }
        seen.add(num);
    }
    return false;
}

Map vs Set 对比

操作MapSet
添加set(key, value)add(key)
查找has(key)has(key)
获取get(key)-
删除delete(key)delete(key)
清空clear()clear()
大小sizesize

技巧二:哈希表记录频次

适用场景:统计字符/数字出现次数、比较两个集合的元素频次

核心要点

  1. 遍历数组时,计算目标值与当前元素的差值
  2. 在哈希表中查找差值是否存在
  3. 将当前元素存入哈希表供后续查找
  4. 用空间换时间,将 O(n²) 降为 O(n)

典型例题1有效的字母异位词

function isAnagram(s, t) {
    if (s.length !== t.length) return false;

    const count = {};
    for (let char of s) {
        count[char] = (count[char] || 0) + 1;
    }
    for (let char of t) {
        if (!count[char]) return false;
        count[char]--;
    }
    return true;
}

进阶技巧 - 使用数组优化(固定字符集)

function isAnagram(s, t) {
    if (s.length !== t.length) return false;

    const counter = new Array(26).fill(0);
    for (let char of s) {
        counter[char.charCodeAt(0) - 'a'.charCodeAt(0)]++;
    }
    for (let char of t) {
        const index = char.charCodeAt(0) - 'a'.charCodeAt(0);
        if (--counter[index] < 0) return false;
    }
    return true;
}

典型例题2前 K 个高频元素

function topKFrequent(nums, k) {
    const frequencyMap = {};
    nums.forEach(num => {
        frequencyMap[num] = (frequencyMap[num] || 0) + 1;
    });

    const freqArray = Object.entries(frequencyMap)
        .map(([num, freq]) => [parseInt(num), freq]);

    freqArray.sort((a, b) => b[1] - a[1]);

    return freqArray.slice(0, k).map(item => item[0]);
}

技巧三:哈希表实现两数之和模式

适用场景:在数组中查找满足特定条件的元素对

典型例题两数之和

function twoSum(nums, target) {
    const hashTable = {};

    for (let i = 0; i < nums.length; i++) {
        const complement = target - nums[i];
        if (complement in hashTable) {
            return [hashTable[complement], i];
        }
        hashTable[nums[i]] = i;
    }
    return null;
}

技巧四:排序后作为哈希键

适用场景:需要将变位词/异位词分组

核心思路:将字符串排序后,异位词会得到相同的结果,以此作为哈希键

典型例题字母异位词分组

function groupAnagrams(strs) {
    const map = {};

    strs.forEach(str => {
        const sortedStr = str.split('').sort().join('');
        if (!map[sortedStr]) {
            map[sortedStr] = [];
        }
        map[sortedStr].push(str);
    });

    return Object.values(map);
}

三、数组技巧

技巧一:前缀积与后缀积

适用场景:需要计算"除自身以外"的累积结果

核心要点

  1. 从左到右计算前缀积
  2. 从右到左计算后缀积,同时与前缀积相乘得到结果
  3. 初始值为 1(1 与任何数相乘等于原数)

典型例题除自身以外数组的乘积

function productExceptSelf(nums) {
    const n = nums.length;
    const result = [];

    let prefix = 1;
    for (let i = 0; i < n; i++) {
        result[i] = prefix;
        prefix *= nums[i];
    }

    let suffix = 1;
    for (let i = n - 1; i >= 0; i--) {
        result[i] *= suffix;
        suffix *= nums[i];
    }

    return result;
}

技巧二:排序 + 遍历

适用场景:需要找出连续序列、区间问题等

典型例题最长连续序列

function longestConsecutive(nums) {
    if (nums.length === 0) return 0;

    const arr = Array.from(new Set(nums)).sort((a, b) => a - b);

    let maxLen = 0;
    let curLen = 1;

    for (let i = 1; i < arr.length; i++) {
        if (arr[i] === arr[i - 1] + 1) {
            curLen++;
        } else {
            maxLen = Math.max(maxLen, curLen);
            curLen = 1;
        }
    }

    return Math.max(maxLen, curLen);
}

四、易错点提醒

  1. sort() 默认排序:默认按字符串 Unicode 排序,数字需要传入比较函数

    [10, 2, 1].sort();              // [1, 10, 2] ❌
    [10, 2, 1].sort((a, b) => a - b); // [1, 2, 10] ✅
    
  2. 哈希表 vs 数组选择

    • 字符集固定且小(如26个字母)→ 用数组
    • 字符集不固定或很大 → 用哈希表
  3. Object 与 Map 的选择

    • 键为字符串/数字 → Object 更简洁
    • 键为对象/需要保持插入顺序 → 用 Map
  4. 遍历对象的安全检查

    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            // 处理逻辑
        }
    }