哈希:以时间换空间的算法实战

两数之和

读完题目后,最容易想到的就是暴力解法:遍历数组中的每一个元素 num,同时查找剩余元素中值为 target-num 的元素。

代码:

kotlin 复制代码
class Solution {
    fun twoSum(nums: IntArray, target: Int): IntArray {
        for (i in 0 until nums.size - 1) { // 最后一个元素无需遍历
            val num = nums[i]
            for (j in i + 1 until nums.size) {
                if (num + nums[j] == target) {
                    return intArrayOf(i, j)
                }
            }
        }

        return intArrayOf()
    }
}

因为 num 之前的元素已经相互配对过,所以每次都可以从 num 元素之后进行查找。

不过,线性查找 target-num 的过程非常耗时。我们可以使用哈希表(空间换时间)来快速查找:首先创建一个哈希表,接着遍历数组中的元素 num,如果哈希表中已经存在 target-num,就直接返回这两个元素的下标;如果不存在,就将当前元素插入到哈希表中保存起来。

代码:

kotlin 复制代码
class Solution {
    fun twoSum(nums: IntArray, target: Int): IntArray {
        val hashMap = HashMap<Int, Int>()
        for (i in 0..nums.size - 1) {
            val num = nums[i]
            if (hashMap.containsKey(target - num)) {
                return intArrayOf(hashMap[target - num]!!, i)
            } else {
                // 存放下标
                hashMap[num] = i
            }
        }

        return intArrayOf()
    }
}

字母异位词分组

这题同样运用了哈希的思想,关键在于如何让属于字母异位词的字符串拥有相同的键。

比较容易想到的是对字符串进行排序。这样一来,"eat" 和 "tea" 排序后得到的键都是 "aet",并且非字母异位词无法得到相同的键,例如 "beat" 和 "eat" 分别得到的是 "abet" 和 "aet"。

kotlin 复制代码
class Solution {
    fun groupAnagrams(strs: Array<String>): List<List<String>> {
        val hashMap = HashMap<String, MutableList<String>>()
        for (str in strs) {
            // 对字符串进行排序
            val array = str.toCharArray()
            array.sort()
            val key = String(chars = array)
            if (hashMap.contains(key)) {
                hashMap[key]!!.add(str)
            } else {
                // 字母异位词列表
                hashMap[key] = mutableListOf(str)
            }
        }

        return ArrayList(hashMap.values)
    }
}

最长连续序列

先说说暴力解法:遍历数组中的每个元素 num,并且不断查找 num+1, num+2... 是否存在,这样就能找到当前元素能构成的最长连续序列长度。

我们还能预先对数组进行排序,并跳过已遍历的相同元素,进一步降低时间复杂度。

代码:

kotlin 复制代码
class Solution {
    fun longestConsecutive(nums: IntArray): Int {
        if (nums.isEmpty()) {
            return 0
        }

        nums.sort()
        var max = 0
        var tmp = 1 // 临时保存连续元素个数
        for (i in 1..nums.size - 1) {
            if (nums[i] == nums[i - 1]) { // 去除重复元素
                continue
            } else if (nums[i] == nums[i - 1] + 1) { // 连续元素
                tmp++
            } else { // 连续中断
                max = max.coerceAtLeast(minimumValue = tmp)
                // 重置最大长度
                tmp = 1
            }
        }

        return max.coerceAtLeast(minimumValue = tmp)
    }
}

遇到需要频繁查找元素是否存在时,我们就可以联想到哈希表,将判断某个元素是否存在的时间复杂度直接降为 O(1)。

另外,和暴力算法中的优化一样,我们需要跳过不必要的元素遍历。当遇到一个 x, x+1, x+2, ..., x+y 的连续序列时,如果从 x+1x+y 开始向后匹配,得到的结果肯定不如从起点 x 开始长,所以我们可以跳过这些中间数。

跳过原则: 如果遇到元素 num,集合中存在 num-1,说明 num 只是某个序列的中间节点,直接跳过即可。

代码:

kotlin 复制代码
class Solution {
    fun longestConsecutive(nums: IntArray): Int {
        if (nums.isEmpty()) {
            return 0
        }
        
        // 去重并存入哈希表
        val set = HashSet<Int>()
        for (num in nums) {
            set.add(num)
        }

        var result = 1

        for (num in set) {
            // 跳过非序列起点的元素
            if (set.contains(num - 1)) {
                continue
            } else {
                // 临时连续序列长度
                var count = 1
                var next = num + 1
                while (set.contains(next)) {
                    count++
                    next++
                }
                result = result.coerceAtLeast(count)
            }
        }

        return result
    }
}

思路沉淀:关于哈希

遍历元素时,如果发现之前的元素与当前元素能产生某种联系(例如相加等于目标值),就要立刻想到哈希。

在遍历的过程中,将之前的元素状态进行保存,后续就能进行 O(1) 的快速查找和匹配,这就是空间换时间的核心艺术。

相关推荐
San813_LDD3 小时前
[数据结构]LeetCode学习
数据结构·算法·图论
x138702859573 小时前
c语言排雷游戏(基础版9*9)
c语言·算法·游戏
sheeta19984 小时前
LeetCode 每日一题笔记 日期:2026.06.06 题目:2196. 根据描述创建二叉树
笔记·算法·leetcode
小欣加油4 小时前
leetcode994 腐烂的橘子
数据结构·c++·算法·leetcode·bfs
QuZero5 小时前
Guava Cache Deep Dive
java·后端·算法·guava
随意起个昵称5 小时前
线性dp-LIS题目4(A Twisty Movement)
算法·动态规划
Felven5 小时前
B. Fair Numbers
数据结构·算法
人道领域5 小时前
【LeetCode刷题日记】93.复原IP地址
java·开发语言·算法·leetcode