twoSum問題的核心思想

讀完本文,你能夠去力扣拿下以下題目:java

1.兩數之和算法

170.兩數之和 III - 數據結構設計數組

-----------數據結構

Two Sum 系列問題在 LeetCode 上有好幾道,這篇文章就挑出有表明性的幾道,介紹一下這種問題怎麼解決。優化

TwoSum I

這個問題的最基本形式是這樣:給你一個數組和一個整數 target,能夠保證數組中存在兩個數的和爲 target,請你返回這兩個數的索引。設計

好比輸入 nums = [3,1,3,6], target = 6,算法應該返回數組 [0,2],由於 3 + 3 = 6。指針

這個問題如何解決呢?首先最簡單粗暴的辦法固然是窮舉了:code

int[] twoSum(int[] nums, int target) {

    for (int i = 0; i < nums.length; i++) 
        for (int j = i + 1; j < nums.length; j++) 
            if (nums[j] == target - nums[i]) 
                return new int[] { i, j };

    // 不存在這麼兩個數
    return new int[] {-1, -1};
}

這個解法很是直接,時間複雜度 O(N^2),空間複雜度 O(1)。排序

能夠經過一個哈希表減小時間複雜度:索引

int[] twoSum(int[] nums, int target) {
    int n = nums.length;
    index<Integer, Integer> index = new HashMap<>();
    // 構造一個哈希表:元素映射到相應的索引
    for (int i = 0; i < n; i++)
        index.put(nums[i], i);
    
    for (int i = 0; i < n; i++) {
        int other = target - nums[i];
        // 若是 other 存在且不是 nums[i] 自己
        if (index.containsKey(other) && index.get(other) != i)
            return new int[] {i, index.get(other)};
    }
    
    return new int[] {-1, -1};
}

這樣,因爲哈希表的查詢時間爲 O(1),算法的時間複雜度下降到 O(N),可是須要 O(N) 的空間複雜度來存儲哈希表。不過綜合來看,是要比暴力解法高效的。

我以爲 Two Sum 系列問題就是想教咱們如何使用哈希表處理問題。咱們接着日後看。

TwoSum II

這裏咱們稍微修改一下上面的問題。咱們設計一個類,擁有兩個 API:

class TwoSum {
    // 向數據結構中添加一個數 number
    public void add(int number);
    // 尋找當前數據結構中是否存在兩個數的和爲 value
    public boolean find(int value);
}

如何實現這兩個 API 呢,咱們能夠仿照上一道題目,使用一個哈希表輔助 find 方法:

class TwoSum {
    Map<Integer, Integer> freq = new HashMap<>();

    public void add(int number) {
        // 記錄 number 出現的次數
        freq.put(number, freq.getOrDefault(number, 0) + 1);
    }
    
    public boolean find(int value) {
        for (Integer key : freq.keySet()) {
            int other = value - key;
            // 狀況一
            if (other == key && freq.get(key) > 1)
                return true;
            // 狀況二
            if (other != key && freq.containsKey(other))
                return true;
        }
        return false;
    }
}

進行 find 的時候有兩種狀況,舉個例子:

狀況一:add[3,3,2,5] 以後,執行 find(6),因爲 3 出現了兩次,3 + 3 = 6,因此返回 true。

狀況二:add[3,3,2,5] 以後,執行 find(7),那麼 key 爲 2,other 爲 5 時算法能夠返回 true。

除了上述兩種狀況外,find 只能返回 false 了。

對於這個解法的時間複雜度呢,add 方法是 O(1),find 方法是 O(N),空間複雜度爲 O(N),和上一道題目比較相似。

可是對於 API 的設計,是須要考慮現實狀況的。好比說,咱們設計的這個類,使用 find 方法很是頻繁,那麼每次都要 O(N) 的時間,豈不是很浪費費時間嗎?對於這種狀況,咱們是否能夠作些優化呢?

是的,對於頻繁使用 find 方法的場景,咱們能夠進行優化。咱們能夠參考上一道題目的暴力解法,藉助哈希集合來針對性優化 find 方法:

class TwoSum {
    Set<Integer> sum = new HashSet<>();
    List<Integer> nums = new ArrayList<>();

    public void add(int number) {
        // 記錄全部可能組成的和
        for (int n : nums)
            sum.add(n + number);
        nums.add(number);
    }
    
    public boolean find(int value) {
        return sum.contains(value);
    }
}

這樣 sum 中就儲存了全部加入數字可能組成的和,每次 find 只要花費 O(1) 的時間在集合中判斷一下是否存在就好了,顯然很是適合頻繁使用 find 的場景。

3、總結

對於 TwoSum 問題,一個難點就是給的數組無序。對於一個無序的數組,咱們彷佛什麼技巧也沒有,只能暴力窮舉全部可能。

通常狀況下,咱們會首先把數組排序再考慮雙指針技巧。TwoSum 啓發咱們,HashMap 或者 HashSet 也能夠幫助咱們處理無序數組相關的簡單問題。

另外,設計的核心在於權衡,利用不一樣的數據結構,能夠獲得一些針對性的增強。

最後,若是 TwoSum I 中給的數組是有序的,應該如何編寫算法呢?答案很簡單,前文「雙指針技巧彙總」寫過:

int[] twoSum(int[] nums, int target) {
    int left = 0, right = nums.length - 1;
    while (left < right) {
        int sum = nums[left] + nums[right];
        if (sum == target) {
            return new int[]{left, right};
        } else if (sum < target) {
            left++; // 讓 sum 大一點
        } else if (sum > target) {
            right--; // 讓 sum 小一點
        }
    }
    // 不存在這樣兩個數
    return new int[]{-1, -1};
}
相關文章
相關標籤/搜索