1. Two Sum
Description
Given an array of integers, return indices of the two numbers such that they add up to a specific target.
You may assume that each input would have exactly one solution, and you may not use the same element twice.
Example:
Given nums = [2, 7, 11, 15], target = 9,
Because nums[0] + nums[1] = 2 + 7 = 9,
return [0, 1].
Sample Input
[2,7,11,15]
9
Sample Output
[0,1]
基本题意
给定一组整数,找到两个数,使它们加起来等于目标数,返回这两个数在数组中的下标。并且如果有解的话,总是只有一个解,而同一个数只能使用一次。
解题思路
本题没有给出数据范围,我们可以发散想不同的做法。
暴力
从数组 a_n 中遍历一个数 X ,再从数组遍历一个除 X 以外的数 Y ,测试 X + Y = Target 是否成立,由于只有一个解,只要成立即可返回。由于需要遍历两轮整个数组,最坏时间复杂度 O(N^2) 。
排序 + 二分
对原数组 a_n 进行排序后,先从有序的数组 a'_n 中遍历一个数 X ,再从中查找 Y = Target - X 是否存在,若存在即找到了符合题目要求的数 X 和 Y ,还原它们在原数组中的下标即为答案。由于还要存储它们的原下标,额外的空间复杂度为 O(N) 。
如果给定的数都为正数,我们还可以做一个小优化。显然当 X \leq Y 时总满足 X \leq \frac12Target \leq Y ,因此我们可以从不小于 \frac12Target 的最小下界开始查找数 Y ,在大于 \frac12Target 的最小上界之前查找数 X ,即:
X \in [\min(a_n), \inf\{a_i : a_i \ge \frac12Target\}]
 且
Y \in [\sup\{a_i : a_i \ge \frac12Target\}, \max(a_n)]
但由于题目没有特别说明数据范围,那么也可能在数组中存在负数,于是这个优化在极端情况中并没有用,最坏时间复杂度 O(NlogN) 。注意由于有排序、找上下界等操作,常数还是较大的。
哈希表
我们也可以对数组 a_n 中所有元素做一个到下标的映射 a_i \xrightarrow{f} i 。由于只是简单查找某个键是否存在并取出其值,而一个元素在原数组中的下标又是唯一的,我们不需要让哈希表维护键之间的序关系,也不需要允许键名或键值重复。
现在对数组遍历一遍,设当前遍历到数 X ,只要从表中查找 Y = Target - X 是否存在即可。若找到,即可返回它们的下标;若没找到,则将 X \xrightarrow{a_i^{-1}} i 存进表中以供下次查询。
建表 O(N) 每次查询 O(1) ,理论平均时间复杂度 O(N) ,理论空间复杂度 O(N) 。
解答代码
排序 + 二分做法
这种做法怎么写都没什么差别,复杂度就是这么高,常数就是那么大。。
C++ 版本(只排序索引 + 手写二分)
Runtime: 12 ms
Memory Usage: 9.5 MB
enum class BSearchType: int {
    LowerBound,
    UpperBound,
    Match
};
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        int N = nums.size();
        vector<int> indexes = this->transformIndexes(nums, N);
        int lowerHalf = this->bSearchPos(nums, indexes, 0, N, target >> 1, BSearchType::LowerBound);
        int upperHalf = this->bSearchPos(nums, indexes, 0, N, target >> 1, BSearchType::UpperBound);
        for (int i = upperHalf - 1; i < N; ++i) {
            int temp = target - nums[indexes[i]];
            int j = this->bSearchPos(nums, indexes, 0, lowerHalf + 1, temp, BSearchType::Match);
            if (~j && j != i && nums[indexes[j]] == temp) {
                return {indexes[j], indexes[i]};
            }
        }
        return {};
    }
private:
    vector<int> transformIndexes(vector<int>& nums, int size) {
        vector<int> indexes(size);
        for (int i = 0; i < size; ++i) {
            indexes[i] = i;
        }
        sort(
            indexes.begin(),
            indexes.end(),
            [&](int i, int j) -> bool { return nums[i] < nums[j]; }
        );
        return indexes;
    }
    int bSearchPos(vector<int>& nums, vector<int>& indexes, int first, int last, int target, BSearchType type) {
        while (first < last) {
            int mid = (first + last) >> 1;
            int num = nums[indexes[mid]];
            if (target < num) {
                last = mid;
            } else if (target > num) {
                first = mid + 1;
            } else {
                if (type == BSearchType::LowerBound) {
                    last = mid;
                } else if (type == BSearchType::UpperBound) {
                    first = mid + 1;
                } else {
                    return mid;
                }
            }
        }
        return type == BSearchType::Match ? -1 : first;
    }
};
C++ 版本(整体排序 + STL)
Runtime: 8 ms
Memory Usage: 9.7 MB
typedef pair<int, int> NumWithIndex;
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        size_t size = nums.size();
        const auto compareFn = [](const NumWithIndex& i, const NumWithIndex& j) { return i.first < j.first; };
        vector<NumWithIndex> orderedNums(size);
        for (size_t i = 0; i < size; ++i) {
            orderedNums[i] = {nums[i], i};
        }
        sort(orderedNums.begin(), orderedNums.end(), compareFn);
        NumWithIndex halfTarget = {target >> 1, -1};
        auto lowerHalf = lower_bound(orderedNums.begin(), orderedNums.end(), halfTarget, compareFn);
        if (lowerHalf->first == halfTarget.first) {
            lowerHalf += 1;
        };
        auto upperHalf = upper_bound(orderedNums.begin(), orderedNums.end(), halfTarget, compareFn) - 1;
        if (upperHalf < lowerHalf) {
            upperHalf += 1;
        };
        
        for (auto i = upperHalf; i != orderedNums.end(); ++i) {
            NumWithIndex temp = {target - i->first, -1};
            auto j = lower_bound(orderedNums.begin(), lowerHalf, temp, compareFn);
            if (j != lowerHalf && j->first == temp.first) {
                return {j->second, i->second};
            }
        }
        return {};
    }
};
哈希表做法
C++ 版本
由于 LeetCode 支持 C++11 ,偷懒就用 unordered_map 了。以前打 ACM 比赛都是必备手写哈希表模板的。
Runtime: 4 ms
Memory Usage: 10.1 MB
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        size_t count = nums.size();
        unordered_map<int, int> numToIndex({{nums[0], 0}}, count);
        for (size_t i = 1; i < count; ++i) {
            int current = nums[i];
            const auto iter = numToIndex.find(target - current);
            if (iter != numToIndex.end()) {
                return {iter->second, i};
            }
            numToIndex.insert({{current, i}});
        }
        
        return {};
    }
};
JavaScript 版本
Runtime: 52 ms
Memory Usage: 34.7 MB
/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
const twoSum = function(nums, target) {
    const numsToIndex = Object.create(null);
    const count = nums.length;
    
    for (let i = 0; i < count; ++i) {
        const numForTest = target - nums[i];
        if (numForTest in numsToIndex) {
            return [numsToIndex[numForTest], i];
        }
        numsToIndex[nums[i]] = i;
    }
    
    return [];
};