Skip to content

10.4   Hash optimization strategies

In algorithm problems, we often reduce the time complexity of algorithms by replacing linear search with hash search. Let's use an algorithm problem to deepen understanding.

Question

Given an integer array nums and a target element target, please search for two elements in the array whose "sum" equals target, and return their array indices. Any solution is acceptable.

10.4.1   Linear search: trading time for space

Consider traversing all possible combinations directly. As shown in Figure 10-9, we initiate a two-layer loop, and in each round, we determine whether the sum of the two integers equals target. If so, we return their indices.

Linear search solution for two-sum problem

Figure 10-9   Linear search solution for two-sum problem

The code is shown below:

two_sum.py
def two_sum_brute_force(nums: list[int], target: int) -> list[int]:
    """方法一:暴力枚举"""
    # 两层循环,时间复杂度为 O(n^2)
    for i in range(len(nums) - 1):
        for j in range(i + 1, len(nums)):
            if nums[i] + nums[j] == target:
                return [i, j]
    return []
two_sum.cpp
/* 方法一:暴力枚举 */
vector<int> twoSumBruteForce(vector<int> &nums, int target) {
    int size = nums.size();
    // 两层循环,时间复杂度为 O(n^2)
    for (int i = 0; i < size - 1; i++) {
        for (int j = i + 1; j < size; j++) {
            if (nums[i] + nums[j] == target)
                return {i, j};
        }
    }
    return {};
}
two_sum.java
/* 方法一:暴力枚举 */
int[] twoSumBruteForce(int[] nums, int target) {
    int size = nums.length;
    // 两层循环,时间复杂度为 O(n^2)
    for (int i = 0; i < size - 1; i++) {
        for (int j = i + 1; j < size; j++) {
            if (nums[i] + nums[j] == target)
                return new int[] { i, j };
        }
    }
    return new int[0];
}
two_sum.cs
/* 方法一:暴力枚举 */
int[] TwoSumBruteForce(int[] nums, int target) {
    int size = nums.Length;
    // 两层循环,时间复杂度为 O(n^2)
    for (int i = 0; i < size - 1; i++) {
        for (int j = i + 1; j < size; j++) {
            if (nums[i] + nums[j] == target)
                return [i, j];
        }
    }
    return [];
}
two_sum.go
/* 方法一:暴力枚举 */
func twoSumBruteForce(nums []int, target int) []int {
    size := len(nums)
    // 两层循环,时间复杂度为 O(n^2)
    for i := 0; i < size-1; i++ {
        for j := i + 1; j < size; j++ {
            if nums[i]+nums[j] == target {
                return []int{i, j}
            }
        }
    }
    return nil
}
two_sum.swift
/* 方法一:暴力枚举 */
func twoSumBruteForce(nums: [Int], target: Int) -> [Int] {
    // 两层循环,时间复杂度为 O(n^2)
    for i in nums.indices.dropLast() {
        for j in nums.indices.dropFirst(i + 1) {
            if nums[i] + nums[j] == target {
                return [i, j]
            }
        }
    }
    return [0]
}
two_sum.js
/* 方法一:暴力枚举 */
function twoSumBruteForce(nums, target) {
    const n = nums.length;
    // 两层循环,时间复杂度为 O(n^2)
    for (let i = 0; i < n; i++) {
        for (let j = i + 1; j < n; j++) {
            if (nums[i] + nums[j] === target) {
                return [i, j];
            }
        }
    }
    return [];
}
two_sum.ts
/* 方法一:暴力枚举 */
function twoSumBruteForce(nums: number[], target: number): number[] {
    const n = nums.length;
    // 两层循环,时间复杂度为 O(n^2)
    for (let i = 0; i < n; i++) {
        for (let j = i + 1; j < n; j++) {
            if (nums[i] + nums[j] === target) {
                return [i, j];
            }
        }
    }
    return [];
}
two_sum.dart
/* 方法一: 暴力枚举 */
List<int> twoSumBruteForce(List<int> nums, int target) {
  int size = nums.length;
  // 两层循环,时间复杂度为 O(n^2)
  for (var i = 0; i < size - 1; i++) {
    for (var j = i + 1; j < size; j++) {
      if (nums[i] + nums[j] == target) return [i, j];
    }
  }
  return [0];
}
two_sum.rs
/* 方法一:暴力枚举 */
pub fn two_sum_brute_force(nums: &Vec<i32>, target: i32) -> Option<Vec<i32>> {
    let size = nums.len();
    // 两层循环,时间复杂度为 O(n^2)
    for i in 0..size - 1 {
        for j in i + 1..size {
            if nums[i] + nums[j] == target {
                return Some(vec![i as i32, j as i32]);
            }
        }
    }
    None
}
two_sum.c
/* 方法一:暴力枚举 */
int *twoSumBruteForce(int *nums, int numsSize, int target, int *returnSize) {
    for (int i = 0; i < numsSize; ++i) {
        for (int j = i + 1; j < numsSize; ++j) {
            if (nums[i] + nums[j] == target) {
                int *res = malloc(sizeof(int) * 2);
                res[0] = i, res[1] = j;
                *returnSize = 2;
                return res;
            }
        }
    }
    *returnSize = 0;
    return NULL;
}
two_sum.kt
/* 方法一:暴力枚举 */
fun twoSumBruteForce(nums: IntArray, target: Int): IntArray {
    val size = nums.size
    // 两层循环,时间复杂度为 O(n^2)
    for (i in 0..<size - 1) {
        for (j in i + 1..<size) {
            if (nums[i] + nums[j] == target) return intArrayOf(i, j)
        }
    }
    return IntArray(0)
}
two_sum.rb
### 方法一:暴力枚举 ###
def two_sum_brute_force(nums, target)
  # 两层循环,时间复杂度为 O(n^2)
  for i in 0...(nums.length - 1)
    for j in (i + 1)...nums.length
      return [i, j] if nums[i] + nums[j] == target
    end
  end

  []
end
two_sum.zig
// 方法一:暴力枚举
fn twoSumBruteForce(nums: []i32, target: i32) ?[2]i32 {
    var size: usize = nums.len;
    var i: usize = 0;
    // 两层循环,时间复杂度为 O(n^2)
    while (i < size - 1) : (i += 1) {
        var j = i + 1;
        while (j < size) : (j += 1) {
            if (nums[i] + nums[j] == target) {
                return [_]i32{@intCast(i), @intCast(j)};
            }
        }
    }
    return null;
}
Code Visualization

This method has a time complexity of \(O(n^2)\) and a space complexity of \(O(1)\), which is very time-consuming with large data volumes.

10.4.2   Hash search: trading space for time

Consider using a hash table, with key-value pairs being the array elements and their indices, respectively. Loop through the array, performing the steps shown in the figures below each round.

  1. Check if the number target - nums[i] is in the hash table. If so, directly return the indices of these two elements.
  2. Add the key-value pair nums[i] and index i to the hash table.

Help hash table solve two-sum

two_sum_hashtable_step2

two_sum_hashtable_step3

Figure 10-10   Help hash table solve two-sum

The implementation code is shown below, requiring only a single loop:

two_sum.py
def two_sum_hash_table(nums: list[int], target: int) -> list[int]:
    """方法二:辅助哈希表"""
    # 辅助哈希表,空间复杂度为 O(n)
    dic = {}
    # 单层循环,时间复杂度为 O(n)
    for i in range(len(nums)):
        if target - nums[i] in dic:
            return [dic[target - nums[i]], i]
        dic[nums[i]] = i
    return []
two_sum.cpp
/* 方法二:辅助哈希表 */
vector<int> twoSumHashTable(vector<int> &nums, int target) {
    int size = nums.size();
    // 辅助哈希表,空间复杂度为 O(n)
    unordered_map<int, int> dic;
    // 单层循环,时间复杂度为 O(n)
    for (int i = 0; i < size; i++) {
        if (dic.find(target - nums[i]) != dic.end()) {
            return {dic[target - nums[i]], i};
        }
        dic.emplace(nums[i], i);
    }
    return {};
}
two_sum.java
/* 方法二:辅助哈希表 */
int[] twoSumHashTable(int[] nums, int target) {
    int size = nums.length;
    // 辅助哈希表,空间复杂度为 O(n)
    Map<Integer, Integer> dic = new HashMap<>();
    // 单层循环,时间复杂度为 O(n)
    for (int i = 0; i < size; i++) {
        if (dic.containsKey(target - nums[i])) {
            return new int[] { dic.get(target - nums[i]), i };
        }
        dic.put(nums[i], i);
    }
    return new int[0];
}
two_sum.cs
/* 方法二:辅助哈希表 */
int[] TwoSumHashTable(int[] nums, int target) {
    int size = nums.Length;
    // 辅助哈希表,空间复杂度为 O(n)
    Dictionary<int, int> dic = [];
    // 单层循环,时间复杂度为 O(n)
    for (int i = 0; i < size; i++) {
        if (dic.ContainsKey(target - nums[i])) {
            return [dic[target - nums[i]], i];
        }
        dic.Add(nums[i], i);
    }
    return [];
}
two_sum.go
/* 方法二:辅助哈希表 */
func twoSumHashTable(nums []int, target int) []int {
    // 辅助哈希表,空间复杂度为 O(n)
    hashTable := map[int]int{}
    // 单层循环,时间复杂度为 O(n)
    for idx, val := range nums {
        if preIdx, ok := hashTable[target-val]; ok {
            return []int{preIdx, idx}
        }
        hashTable[val] = idx
    }
    return nil
}
two_sum.swift
/* 方法二:辅助哈希表 */
func twoSumHashTable(nums: [Int], target: Int) -> [Int] {
    // 辅助哈希表,空间复杂度为 O(n)
    var dic: [Int: Int] = [:]
    // 单层循环,时间复杂度为 O(n)
    for i in nums.indices {
        if let j = dic[target - nums[i]] {
            return [j, i]
        }
        dic[nums[i]] = i
    }
    return [0]
}
two_sum.js
/* 方法二:辅助哈希表 */
function twoSumHashTable(nums, target) {
    // 辅助哈希表,空间复杂度为 O(n)
    let m = {};
    // 单层循环,时间复杂度为 O(n)
    for (let i = 0; i < nums.length; i++) {
        if (m[target - nums[i]] !== undefined) {
            return [m[target - nums[i]], i];
        } else {
            m[nums[i]] = i;
        }
    }
    return [];
}
two_sum.ts
/* 方法二:辅助哈希表 */
function twoSumHashTable(nums: number[], target: number): number[] {
    // 辅助哈希表,空间复杂度为 O(n)
    let m: Map<number, number> = new Map();
    // 单层循环,时间复杂度为 O(n)
    for (let i = 0; i < nums.length; i++) {
        let index = m.get(target - nums[i]);
        if (index !== undefined) {
            return [index, i];
        } else {
            m.set(nums[i], i);
        }
    }
    return [];
}
two_sum.dart
/* 方法二: 辅助哈希表 */
List<int> twoSumHashTable(List<int> nums, int target) {
  int size = nums.length;
  // 辅助哈希表,空间复杂度为 O(n)
  Map<int, int> dic = HashMap();
  // 单层循环,时间复杂度为 O(n)
  for (var i = 0; i < size; i++) {
    if (dic.containsKey(target - nums[i])) {
      return [dic[target - nums[i]]!, i];
    }
    dic.putIfAbsent(nums[i], () => i);
  }
  return [0];
}
two_sum.rs
/* 方法二:辅助哈希表 */
pub fn two_sum_hash_table(nums: &Vec<i32>, target: i32) -> Option<Vec<i32>> {
    // 辅助哈希表,空间复杂度为 O(n)
    let mut dic = HashMap::new();
    // 单层循环,时间复杂度为 O(n)
    for (i, num) in nums.iter().enumerate() {
        match dic.get(&(target - num)) {
            Some(v) => return Some(vec![*v as i32, i as i32]),
            None => dic.insert(num, i as i32),
        };
    }
    None
}
two_sum.c
/* 哈希表 */
typedef struct {
    int key;
    int val;
    UT_hash_handle hh; // 基于 uthash.h 实现
} HashTable;

/* 哈希表查询 */
HashTable *find(HashTable *h, int key) {
    HashTable *tmp;
    HASH_FIND_INT(h, &key, tmp);
    return tmp;
}

/* 哈希表元素插入 */
void insert(HashTable *h, int key, int val) {
    HashTable *t = find(h, key);
    if (t == NULL) {
        HashTable *tmp = malloc(sizeof(HashTable));
        tmp->key = key, tmp->val = val;
        HASH_ADD_INT(h, key, tmp);
    } else {
        t->val = val;
    }
}

/* 方法二:辅助哈希表 */
int *twoSumHashTable(int *nums, int numsSize, int target, int *returnSize) {
    HashTable *hashtable = NULL;
    for (int i = 0; i < numsSize; i++) {
        HashTable *t = find(hashtable, target - nums[i]);
        if (t != NULL) {
            int *res = malloc(sizeof(int) * 2);
            res[0] = t->val, res[1] = i;
            *returnSize = 2;
            return res;
        }
        insert(hashtable, nums[i], i);
    }
    *returnSize = 0;
    return NULL;
}
two_sum.kt
/* 方法二:辅助哈希表 */
fun twoSumHashTable(nums: IntArray, target: Int): IntArray {
    val size = nums.size
    // 辅助哈希表,空间复杂度为 O(n)
    val dic = HashMap<Int, Int>()
    // 单层循环,时间复杂度为 O(n)
    for (i in 0..<size) {
        if (dic.containsKey(target - nums[i])) {
            return intArrayOf(dic[target - nums[i]]!!, i)
        }
        dic[nums[i]] = i
    }
    return IntArray(0)
}
two_sum.rb
### 方法二:辅助哈希表 ###
def two_sum_hash_table(nums, target)
  # 辅助哈希表,空间复杂度为 O(n)
  dic = {}
  # 单层循环,时间复杂度为 O(n)
  for i in 0...nums.length
    return [dic[target - nums[i]], i] if dic.has_key?(target - nums[i])

    dic[nums[i]] = i
  end

  []
end
two_sum.zig
// 方法二:辅助哈希表
fn twoSumHashTable(nums: []i32, target: i32) !?[2]i32 {
    var size: usize = nums.len;
    // 辅助哈希表,空间复杂度为 O(n)
    var dic = std.AutoHashMap(i32, i32).init(std.heap.page_allocator);
    defer dic.deinit();
    var i: usize = 0;
    // 单层循环,时间复杂度为 O(n)
    while (i < size) : (i += 1) {
        if (dic.contains(target - nums[i])) {
            return [_]i32{dic.get(target - nums[i]).?, @intCast(i)};
        }
        try dic.put(nums[i], @intCast(i));
    }
    return null;
}
Code Visualization

This method reduces the time complexity from \(O(n^2)\) to \(O(n)\) by using hash search, greatly improving the running efficiency.

As it requires maintaining an additional hash table, the space complexity is \(O(n)\). Nevertheless, this method has a more balanced time-space efficiency overall, making it the optimal solution for this problem.

Feel free to drop your insights, questions or suggestions