leetcode題解(查詢表問題)

吳軍旗發表於2019-02-28

查詢,是使用計算機處理問題時的一個最基本的任務,因此也是面試中非常常見的一類問題。很多演算法問題的本質,就是要能夠高效查詢。學會使用系統庫中的map和set,就已經成功了一半。

set的使用

兩類查詢問題

  • 查詢有無:元素’a’是否存在?set;集合
  • 查詢對應關係(鍵值對應):元素’a’出現了幾次?map;字典
  • 通常語言的標準庫中都內建set和map
  • 容器類
  • 遮蔽實現細節
  • 瞭解語言中標準庫裡常見容器類的使用

常見操作:

  • insert
  • find
  • erase:刪除
  • change (map)

leetcode349. 兩個陣列的交集

paste image
  • 結果中每個元素只能出現一次
  • 出現的順序可以是任意的

程式碼實現


    // 349. Intersection of Two Arrays
    // 時間複雜度:O(nlogn)
    // 空間複雜度:O(n)
    class Solution {
    public:
        vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
            //O(nlogn)
    
            set<int> record;
            for( int i = 0 ; i < nums1.size() ; i ++ )
                record.insert(nums1[i]);
            //O(nlogn)
            set<int> resultSet;
            for( int i = 0 ; i < nums2.size() ; i ++ )
                if( record.find( nums2[i] ) != record.end() )
                    resultSet.insert( nums2[i] );
            //o(n)
            vector<int> resultVector;
            for(set<int>::iterator iter = resultSet.begin() ; iter != resultSet.end() ; iter ++ )
                resultVector.push_back( *iter );
    
            return resultVector;
        }
    };
    

改寫程式:

    // 349. Intersection of Two Arrays
    class Solution {
    public:
        vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
    
            set<int> record(nums1.begin(), nums1.end());
    
            set<int> resultSet;
            for( int i = 0 ; i < nums2.size() ; i ++ )
                if( record.find( nums2[i] ) != record.end() )
                    resultSet.insert( nums2[i] );
    
            return vector<int>(resultSet.begin(), resultSet.end());
        }
    };

複製程式碼

map的使用

leetcode350. 兩個陣列的交集 II

paste image

程式碼實現


    // 350. Intersection of Two Arrays II
    // 時間複雜度:O(nlogn)
    // 空間複雜度:O(n)
    class Solution {
    public:
        vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
    
            map<int, int> record;
            //O(nlogn)
            for( int i = 0 ; i < nums1.size() ; i ++ )
                record[nums1[i]] += 1;
    
            //o(nlogn)
            vector<int> resultVector;
            for( int i = 0 ; i < nums2.size() ; i ++ )
                if( record[ nums2[i] ] > 0 ){
                    resultVector.push_back( nums2[i] );
                    record[nums2[i]] --;
                }
    
            return resultVector;
        }
    };
複製程式碼

思考

  • 陣列有序如何改寫?

set和map不同底層實現的區別

常見操作:

  • insert

  • find

  • erase

  • change (map)

  • set和map可以有不同的底層實現

底層實現
雜湊表

雜湊表不管查詢、插入、刪除都是O(1)的時間複雜度。

  • 雜湊表的缺點是失去了資料的順序性

資料的順序性

  • 資料集中的最大值和最小值

  • 某個元素的前驅和後繼

  • 某個元素的floor和ceil

  • 某個元素的排位rank

  • 選擇某個排位的元素select

  • map和set的底層實現為平衡二叉樹

  • unordered_map和unordered_set的底層實現為雜湊表

改用雜湊表實現的程式碼


    #include <unordered_set>
    using namespace std;
    
    // 349. Intersection of Two Arrays
    // 時間複雜度:O(n)
    // 空間複雜度:O(n)
    class Solution {
    public:
        vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
            // O(n)
            unordered_set<int> record(nums1.begin(), nums1.end());
            // O(n)
            unordered_set<int> resultSet;
            for( int i = 0 ; i < nums2.size() ; i ++ )
                if( record.find( nums2[i] ) != record.end() )
                    resultSet.insert( nums2[i] );
            // O(n)
            return vector<int>(resultSet.begin(), resultSet.end());
        }
    };
   


	//改寫過使用hash表實現底層的unorder_map

    #include <unordered_map>
    using namespace std;
    
    /// 350. Intersection of Two Arrays II
    // 時間複雜度:O(n)
    // 空間複雜度:O(n)
    class Solution {
    public:
        vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
            // O(n)
            unordered_map<int, int> record;
            for( int i = 0 ; i < nums1.size() ; i ++ )
                record[nums1[i]] += 1;
            // O(n)
            vector<int> resultVector;
            for( int i = 0 ; i < nums2.size() ; i ++ )
                if( record[ nums2[i] ] > 0 ){
                    resultVector.push_back( nums2[i] );
                    record[nums2[i]] --;
                }
            // O(n)
            return resultVector;
        }
    };
    
複製程式碼

相似題目

  • leetcode242 Valid Anagram

Anagram:一個字串中的字母調整順序後與原字串一致。

  • 空串

  • 字符集

  • leetcode202 Happy Number

判斷一個數是否為happy number。happy number是指,一個數,將其替換為其各位數字的平方和,重複這個過程,如果最終能得到1,這是happy number,如果這個過程陷入了一個不包含1的迴圈,則不是happy number

判斷一個數是否為happy number。以19為例:

1^2 + 9^2 = 82

8^2 + 2^2 = 68

6^2 + 8^2 = 100

1^2 + 0^2 + 0^2 = 1 Happy Number!

複製程式碼
  • leetcode290 Word Pattern

給出一個模式(pattern)以及一個字串,判斷這個字串是否符合模式?


如pattern=“abba”,str=“dog cat cat dog”,返回true
    
如pattern=“abba”,str=“dog cat cat fish”,返回false
    
字符集?
    
空串符合任意模式?還是不符合任意模式?
    
複製程式碼
  • leetcode205 Isomorphic Strings

判斷兩個字串是否同構?
如果我們能夠尋找到一個字符集到字符集的對映,使得通過這個字符集的對映,s可以轉變為t,則稱為s和t同構。


如 egg 和 add,返回true
如 foo 和 bar,返回false
如 paper 和 title,返回true

複製程式碼

注意

  • 字符集?

  • 空串

  • 是否可以一個字母對映到自己?

  • 451 Sort Characters By Frequency

給定一個字串,按照字母出現頻率的倒序重組整個字串

如“tree”,返回“eert”
如“cccaaa”,返回“cccaaa”
如“Aabb”,返回“bbAa”
對於相同頻次的字母,順序任意。大小寫敏感。
複製程式碼

一個使用查詢表的經典問題

leetcode1 兩數之和

paste image

注意

  • 索引從0開始計算還是從1開始計算?
  • 沒有解怎麼辦?
  • 有多個解怎麼辦? 保證有唯一解

暴力解法:O(n^2)

  • 遍歷所有資料對。判斷是否等於target。

雙索引對撞

  • 排序後,使用雙索引對撞:O(nlogn) + O(n) = O(nlogn)

使用查詢表

  • 查詢表。將所有元素放入查詢表,之後對於每一個元素a,查詢 target – a 是否存在。存在的話索引是誰。
  • 將所有元素放入查詢表 – 不行
  • 只把v前面的放入查詢表

    //時間複雜度:O(n)
    //空間複雜度:O(n)
    class Solution {
    public:
        vector<int> twoSum(vector<int>& nums, int target) {
    
            unordered_map<int,int> record;
            for( int i = 0 ; i < nums.size() ; i ++ ){
    
                int complement = target - nums[i];
                if( record.find(complement) != record.end() ){
                    int res[] = {i, record[complement]};
                    return vector<int>(res, res + 2);
                }
    
                record[nums[i]] = i;
            }
    
            throw invalid_argument("the input has no solution");
        }
    };
複製程式碼

相似問題

leetcode15 3Sum

給出一個整形陣列,尋找其中的所有不同的三元組(a,b,c),使得a+b+c=0
如 nums = [-1, 0, 1, 2, -1, -4]
結果為[ [-1, 0, 1], [-1,-1,2] ]

  • 不同的三元組?
  • 如果有多個解,解的順序?
  • 如果沒有解?

leetcode18 4Sum

給出一個整形陣列,尋找其中的所有不同的四元組(a,b,c,d),使得a+b+c+d 等於一個給定的數字target。
如 nums = [1, 0, -1, 0, -2, 2],target = 0
結果為[ [-1, 0, 0, 1], [-2, -1, 1, 2], [-2, 0, 0, 2] ]

leetcode16 3Sum Closest

給出一個整形陣列,尋找其中的三個元素a,b,c,使得a+b+c的值最接近另外一個給定的數字target

如 nums = [-1, 2, 1, -4],target = 1
結果為2 ( -1 + 2 + 1 = 2 )

複製程式碼

注意

  • 如果有多個解,其和target值的接近程度一樣怎麼辦?

  • 如果沒解?(可不可能沒解?)

靈活選擇鍵值

leetcode454 4Sum II

paste image

暴力解法:O(n^4)

500^4 = 625,0000,0000

將D中的元素放入查詢表:O(n^3)

500^3 = 1,2500,0000

將C+D的每一種可能放入查詢表:O(n^2)

  • 500^2 = 25,0000
  • 要記錄每一種和出現了多少次,所以使用map
程式碼實現:
    //時間複雜度O(n^2)
    //空間複雜度O(n^2)
    class Solution {
    public:
        int fourSumCount(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>& D) {
    
            assert( A.size() == B.size() && B.size() == C.size() && C.size() == D.size() );
            unordered_map<int,int> hashtable;
            for( int i = 0 ; i < C.size() ; i ++ )
                for( int j = 0 ; j < D.size() ; j ++ )
                    hashtable[C[i]+D[j]] += 1;
    
            int res = 0;
            for( int i = 0 ; i < A.size() ; i ++ )
                for( int j = 0 ; j < B.size() ; j ++ )
                    if( hashtable.find(-A[i]-B[j]) != hashtable.end() )
                        res += hashtable[-A[i]-B[j]];
    
            return res;
        }
    };
    
複製程式碼

思考

  • 將A+B和C+D的每一種可能放入兩個查詢表:O(n^2)

相似問題

  • leetcode49 Group Anagrams

給出一個字串陣列,將其中所有可以通過顛倒字元順序產生相同結果的單詞進行分組。

如 [ “eat”, “tea”, “tan”, “ate”, “nat”, “bat”]
返回[ [“ate”, “eat”, “tea”], [“nat”, “tan”], [“bat”] ]

複製程式碼

注意

  • 字符集
  • 大小寫敏感

靈活選擇鍵值2

leetcode447 迴旋鏢的數量(Number of Boomerangs)

paste image

暴力解法:O(n^3)

  • 三重迴圈列舉所有的可能性。

使用查詢表

  • 觀察到 i 是一個“樞紐”,對於每個點i,遍歷其餘點到i的距離
  • 對於每個樞紐i,計算它到其它點j的距離,並將距離作為鍵存入map中,value為距離的個數。
  • O(n^2)

    //時間複雜度:O(n^2)
    //空間複雜度:O(n)
    class Solution {
    public:
        int numberOfBoomerangs(vector<pair<int, int>>& points) {
    
            int res = 0;
            for( int i = 0 ; i < points.size() ; i ++ ){
    
                // record中儲存 點i 到所有其他點的距離出現的頻次
                unordered_map<int, int> record;
                for( int j = 0 ; j < points.size() ; j ++ )
                    if( j != i )
                        record[dis(points[i], points[j])] += 1;
    
                for( unordered_map<int, int>::iterator iter = record.begin() ; iter != record.end() ; iter ++ )
                    res += (iter->second)*(iter->second-1);
            }
            return res;
        }
    
    private:
        int dis( const pair<int,int> &pa, const pair<int,int> &pb){
            return (pa.first - pb.first) * (pa.first - pb.first) +
                   (pa.second - pb.second) * (pa.second - pb.second);
        }
    };
    
    
複製程式碼

相似問題

leetcode149 Max Points on a Line

給出2D平面上的n個點,求出最多有多少個點在一條直線上?

  • 點座標的範圍
  • 點座標的表示(整數?浮點數?浮點誤差?)

查詢表和滑動視窗

paste image

暴力解法:O(n^2)

  • 時間效能不滿足

滑動視窗

  • 結合使用滑動視窗和查詢表,不斷查詢當前滑動視窗內有沒有重複值。

    // 時間複雜度: O(n)
    // 空間複雜度: O(k)
    class Solution {
    public:
        bool containsNearbyDuplicate(vector<int>& nums, int k) {
    
            if( nums.size() <= 1 )
                return false;
    
            if( k <= 0 )
                return false;
    
            unordered_set<int> record;
            for( int i = 0 ; i < nums.size() ; i ++ ){
    
                if( record.find( nums[i] ) != record.end() )
                    return true;
    
                record.insert( nums[i] );
    
                // 保持record中最多有k個元素
                // 因為在下一次迴圈中會新增一個新元素,使得總共考慮k+1個元素
                if( record.size() == k + 1 )
                    //刪除掉最左側元素
                    record.erase( nums[i-k] );
            }
    
            return false;
        }
    };
    
複製程式碼

相似問題

  • leetcode217 Contains Duplicate

給出一個整形陣列,若陣列中存在相同的元素,則返回true,否則返回false.

二分搜尋樹底層實現的順序性

leetcode220. 存在重複元素 III

paste image

思想

維持滑動窗的大小為k
遍歷每一個元素,在活動視窗中尋找|v-nums[i]| < t, 即視窗中的元素範圍為:[v-t…v+t]之間。採用ceil和floor可以實現

程式碼實現


    // 時間複雜度: O(nlogn)
    // 空間複雜度: O(k)
    class Solution {
    public:
        bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t) {
    
            set<long long> record;
            for( int i = 0 ; i < nums.size() ; i ++ ){
    
                if( record.lower_bound( (long long)nums[i] - (long long)t ) != record.end() &&
                    *record.lower_bound( (long long)nums[i] - (long long)t ) <= (long long)nums[i] + (long long)t )
                    return true;
    
                record.insert( nums[i] );
    
                if( record.size() == k + 1 )
                    record.erase( nums[i-k] );
            }
    
            return false;
        }
    };
    
複製程式碼

————————-華麗的分割線——————–

看完的朋友可以點個喜歡/關注,您的支援是對我最大的鼓勵。

個人部落格番茄技術小棧掘金主頁

想了解更多,歡迎關注我的微信公眾號:番茄技術小棧

番茄技術小棧

相關文章