1. 程式人生 > >LeetCode - Two Sum I - II - III - IV、3Sum、3Sum Closest、4Sum、4Sum II 系列學習總結

LeetCode - Two Sum I - II - III - IV、3Sum、3Sum Closest、4Sum、4Sum II 系列學習總結

目錄

1 - Two Sum

2 - Two Sum II - Input array is sorted

3 - Two Sum III - Data structure design

4 - Two Sum IV - Input is a BST

5 - 3Sum

6 - 3Sum Closest

7 - 3Sum Smaller

8 - 4Sum

9 - 4Sum II


本文將包括上述9個LeetCode中與數列中的元素求和相關的題目,都不是很難,沒有 Hard 的題,但是有的題的細節還是值得總結和學習的。

1 - Two Sum

    就是在一個 vector<int> 中找到兩個數相加等於題目給出的 target,不能重複用一個數,並且題目只有一個解。

    Given nums = [2, 7, 11, 15], target = 9, Because nums[0] + nums[1] = 2 + 7 = 9, return [0, 1].

vector<int> twoSum(vector<int>& nums, int target) {
    unordered_map<int, int> mymap;
    vector<int> res;
    for (int i = 0; i < nums.size(); i++)
    {
        // 由於是一邊找一邊向map中插入,所以 mymap[target - nums[i] 一定小於 i(雖然讓這題沒要求順序)
        // 由於題目保證只有一組解,所以不需要擔心 5,5,5 找 target = 10的情況下,map值被覆蓋
        // 由於是先找,再賦值,也不需要擔心 5,5,6 找 target = 10的情況下,第二個 5 修改 mymap[5] 的值
        if (mymap.find(target - nums[i]) != mymap.end())
        {
            res.push_back(mymap[target - nums[i]]);
            res.push_back(i);			
            return res;
        }
        mymap[nums[i]] = i;
    }
    return res;
}

2 - Two Sum II - Input array is sorted

    和第一題一樣,區別就是這裡的 vector<int> 已經排好序了(sorted in ascending order),找出兩個數相加等於target,兩個數的索引保證 index1 < index2,並且兩個數的索引不是 zero-based 的,也就是索引從1開始。

vector<int> twoSum(vector<int>& numbers, int target)
{
    for(int i = 0; i < numbers.size(); i++)
    {
        if(i > 0 && numbers[i] == numbers[i - 1]) continue;
        // 從 numbers.begin() + i + 1 開始找,加速查詢,並且避免找到這個數自己
        auto ite = find(numbers.begin() + i + 1, numbers.end(), target - numbers[i]);
        if(ite != numbers.end())
            return vector<int>({i + 1, ite - numbers.begin() + 1});
        }
    return vector<int>();
}

3 - Two Sum III - Data structure design

    Design and implement a TwoSum class. It should support the following operations:add and find.

    add - Add the number to an internal data structure.
    find - Find if there exists any pair of numbers which sum is equal to the value.

    For example,
    add(1); add(3); add(5);
    find(4) -> true
    find(7) -> false

    這題就是把第一題變成了資料結構的設計。

class TwoSum {
public:
    void add(int number) {
        ++m[number];
    }
    bool find(int value) {
        for (auto a : m) {
            int t = value - a.first;
            if ((t != a.first && m.count(t)) || (t == a.first && a.second > 1))
                return true;
        }
        return false;
    }
private:
    unordered_map<int, int> m;
};

4 - Two Sum IV - Input is a BST

    給出一個二叉樹,判斷是否有兩個節點的 val 相加等於 target,返回 true 或 false。二叉樹定義如下:

// Definition for a binary tree node.
struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

    dfs程式碼如下:

bool dfs(TreeNode* root, int k, unordered_set<int>& valset)
{
    if(root == NULL)
        return false;
    if(valset.find(k - root->val) != valset.end())
        return true;
    valset.insert(root->val);
    return(dfs(root->left, k, valset) || dfs(root->right, k, valset));       
}
    
bool findTarget(TreeNode* root, int k)
{
    unordered_set<int> valset;
    return dfs(root, k, valset);
}

5 - 3Sum

    在一個 vector<int> 中找到所有的滿足 a + b + c = 0 的 unique 的三元組,比如

    Given array nums = [-1, 0, 1, 2, -1, -4], A solution set is: [ [-1, 0, 1], [-1, -1, 2] ]

    直接用一個數 i 從 0 走到 n - 2 的位置,另外兩個數從 i + 1 和 n - 1 想內夾逼即可。

vector<vector<int>> threeSum(vector<int>& nums)
{
    vector<vector<int> > res;
    int n = nums.size();
    if (n < 3) return res;
    sort(nums.begin(), nums.end());

    for (int i = 0; i < n - 2; i++)
    {
        if (nums[i] > 0) break;       // 因為是sorted,所以第一個數大於0肯定就過了
        if (i > 0 && nums[i - 1] == nums[i]) continue;    // 跳過相同的數

        int l = i + 1, r = n - 1;	
        while (l < r)
        {
            int a = nums[i], b = nums[l], c = nums[r];
            int sum = a + b + c;
            if (sum == 0)
            {
                res.push_back(vector<int>({ a, b, c }));
                while (b == nums[++l]);                   // 跳過相同的數
                while (c == nums[--r]);                   // 跳過相同的數
                // 相當於
                // while(nums[l + 1] == b) ++l;
                // while(nums[r - 1] == c) --r;
                // ++l; --r;
            }
            else if (sum > 0) --r;
            else ++l;
        }
    }
    return res;
}

6 - 3Sum Closest

    找出一組 a, b, c 三個數加起來與 target 最接近,也就是 abs(a + b + c - target) 最小,題目保證只有一組解。

    這道題和上邊的求和等於0一個意思,不過判斷條件變了,但是值得注意的是這題不能像 3 sum 一題中用 while (b == nums[++l]) 方式跳過一些相同的數,一個是因為求和 == target 的話就返回了,因為這肯定是最好的情況,另個一個原因是因為比如 -1, 0, 1, 1, 55,當 l 指向第一個1時,不能因為後邊也是1就直接向後走,因為可能也會由於 l < n 的限制導致r走不到 1 這個位置。

int threeSumClosest(vector<int>& nums, int target)
{
    int res = -1;
    int diff = 9999;
    int n = nums.size();
    sort(nums.begin(), nums.end());
    for (int i = 0; i < n - 2; i++)
    {
        if(i > 0 && nums[i] == nums[i - 1]) continue;   // 跳過相同的數
        int l = i + 1, r = n - 1;
        while (l < r)
        {
            int ln = nums[l], rn = nums[r];
            int tmp_sum = nums[i] + ln + rn;
            int tmp_diff = abs(tmp_sum - target);
            if (tmp_diff == 0) return target;           // 0肯定是最好的情況了
            if (tmp_diff < diff)
            {
                res = tmp_sum;
                diff = tmp_diff;
            }
            if (tmp_sum < target)      ++l;
            else if (tmp_sum > target) --r;
        }
    }
    return res;
}

7 - 3Sum Smaller

Given an array of n integers nums and a target, find the number of index triplets i, j, k with 0 <= i < j < k < n that satisfy the condition nums[i] + nums[j] + nums[k] < target.

For example, given nums = [-2, 0, 1, 3], and target = 2.

Return 2. Because there are two triplets which sums are less than 2:

[-2, 0, 1]
[-2, 0, 3]

    題目要求找出所有滿足 nums[i] + nums[j] + nums[k] < target 的 i, j, k 的三元組,返回三元組的個數。並且題目要求 O(n2)的時間。

    和上一題一樣,只不過判斷又變了,三道 3Sum 的題的中心思想都是一個數 i 從 0 走到 n - 2 的位置,另外兩個數每次從 i + 1 和 n - 1 想內夾逼,三道題不同的點就是這兩個數夾逼的方式變了。

int threeSumSmaller(vector<int>& nums, int target) {
    if (nums.size() < 3) return 0;
    int res = 0, n = nums.size();
    sort(nums.begin(), nums.end());
    for (int i = 0; i < n - 2; ++i) {
        int left = i + 1, right = n - 1;
        while (left < right) {
            if (nums[i] + nums[left] + nums[right] < target) {
                res += right - left; // 如果求和小於target了,那麼right從當前位置一直減到left,求和一定都小於target
                ++left;
            }
            else
                --right;
        }
    }
    return res;
}

8 - 4Sum

    和3Sum一樣,找出所有相加等於target的不重複的四元組。方法和3Sum也很類似,不過是兩個數從左往右,然後兩個數在後邊夾逼。

vector<vector<int> > fourSum(vector<int> &nums, int target)
{
    set<vector<int> > nset;  // 用set來保證沒有重複,其實用vector也沒有問題
    int n = nums.size();
    sort(nums.begin(),nums.end());
    for(int i = 0; i < n - 3; i++)
    {
        if(nums[i] > target / 4) break;                // 加上這句會快很多!!
        if(i > 0 && nums[i] == nums[i - 1]) continue;
        for(int j = i + 1; j < n - 2; j++)
        {
            if(j > i + 1 && nums[j] == nums[j - 1]) continue;
            
            int l = j + 1, r = n - 1;
            while(l < r)
            {
                int ln = nums[l], rn = nums[r];
                int tmp_sum = nums[i] + nums[j] + ln + rn;
                if(tmp_sum == target)
                {
                    nset.insert(vector<int>({nums[i], nums[j], ln, rn}));
                    //while(nums[l + 1] == ln) ++l;        // 可以用while跳過重複數字,但是這道題中會變慢
                    //while(nums[r - 1] == rn) --r;
                    ++l; --r;
                }
                else if(tmp_sum > target)
                {
                    --r;
                    //while(nums[r - 1] == rn) --r;       // 可以用while跳過重複數字,但是這道題中會變慢
                }
                else
                {
                    ++l;
                    //while(nums[l + 1] == ln) ++l;       // 可以用while跳過重複數字,但是這道題中會變慢
                }
            }
        }
    }
    return vector<vector<int> >(nset.begin(), nset.end());
}

 9 - 4Sum II

    題目給出4個相同大小的 vector<int>,然後找出四元組 i, j, k, l 滿足 A[i] + B[j] + C[k] + D[l] = 0,返回這樣玩的四元組的個數。值得注意的是,四元組是不能重複的,不過四個數加起來等於0的組合可以使重複的,這樣我們只需要記錄這4個數組各有哪些數,然後計算這些數組合加起來等於0的情況,比如1 + 2 - 1 - 2 =0,A中有兩個1,那麼就是兩種四元組。

    程式碼中,在前兩個陣列中,對數進行組合,記錄組合求和之後的數,比如 1 + 2 和 2 + 1,那麼就代表,陣列A+B中有兩個3,再在A+B這個陣列中,遍歷查詢有沒有後邊兩個陣列所有數的組合的相反數(這樣四個陣列組合加起來就是0了),就可以得到滿足條件的組合,並且這樣計算 1 + 2 - 2 - 1 和 2 + 1 - 2 - 1,不需要計算兩次,只需要知道 3 - 2 - 1 = 0,然後A+B中有兩個3就知道有兩種組合了。

int fourSumCount(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>& D)
{
    unordered_map<int, int>  abSum;
    for(auto a : A)
        for(auto b : B)
            ++abSum[a+b];
    int count = 0;
    for(auto c : C)
    {
        for(auto d : D)
        {
            auto it = abSum.find(0 - c - d);
            if(it != abSum.end())
                count += it->second;
        }
    }
    return count;
}