1. 程式人生 > >LintCode 解題記錄 17.5.15 (tag: 雜湊表)

LintCode 解題記錄 17.5.15 (tag: 雜湊表)

LintCode Flatten Nested List Iterator
把一個List壓平,首先想到了Spark裡的flattenMap函式- -。
兩種思路,遞迴與非遞迴。遞迴就不說了,非遞迴的就用棧來實現。為什麼要用棧呢?比如我當前遍歷到一個元素仍然是一個List,那麼我應該是再把這個List的元素全部壓到棧裡然後立即依次取出來處理,所以是後入先出,所以用堆疊來實現。
遞迴:

class NestedIterator {
public:
    vector<int> flattenList;
    int len;
    NestedIterator(vector
<NestedInteger>
&nestedList) { // Initialize your data structure here. len = 0; build(flattenList, nestedList); } void build(vector<int> &flattenList, const vector<NestedInteger> &nestedList) { int len = nestedList.size(); for
(int i = 0; i < len; i++) { if (nestedList[i].isInteger()) { flattenList.push_back(nestedList[i].getInteger()); } else { build(flattenList, nestedList[i].getList()); } } } // @return {int} the next element in the iteration
int next() { // Write your code here return flattenList[len++]; } // @return {boolean} true if the iteration has more element or false bool hasNext() { // Write your code here return len < flattenList.size(); } };

非遞迴: 利用 堆疊來維持順序

class NestedIterator {
public:
    stack<NestedInteger> S;
    vector<int> ret;
    int len = 0;
    NestedIterator(vector<NestedInteger> &nestedList) {
        // Initialize your data structure here.

        reverse(nestedList.begin(), nestedList.end());
        for (auto list : nestedList) {
            S.push(list);
        }
        while (!S.empty()) {
            NestedInteger top = S.top();
            S.pop();
            if (top.isInteger())
                ret.push_back(top.getInteger());
            else {
                vector<NestedInteger> tmp = top.getList();
                reverse(tmp.begin(), tmp.end());
                for (auto num : tmp) {
                    S.push(num);
                }
            }
        }
    }

    // @return {int} the next element in the iteration
    int next() {
        // Write your code here
        return ret[len++];

    }

    // @return {boolean} true if the iteration has more element or false
    bool hasNext() {
        // Write your code here
        return len < ret.size();
    }
};

LintCode Insert Delete GetRandom O(1)
設計一種資料結構使其 插入、刪除以及隨機取一個數的平均時間複雜度都是O(1)。
剛拿到這種題的時候,我就想 肯定是藉助現有的資料結構來實現的,所以有哪些資料結構支援O(1)的插入、刪除呢?而且可以支援隨機訪問?C++中常用的就是 順序容器與關聯容器。關聯容器可以支援快速的插入、刪除,但是不能支援隨機快速訪問。而順序容器可以快速插入與隨機訪問,但是不能快速刪除。綜上,應該同時使用這兩種資料結構。
這裡用map來實現Hash的功能(數->vector下標),同時用一個vector來儲存插入的數。那麼如何在刪除某數的時候更新vector呢(因為vector無法快速刪除任一元素)?這裡用一個指標n來表示當前已經插入且未被刪除的數的個數。那麼當要刪除一個數時,把這個數與vector最後一個數交換,然後n–,那麼就可以“形象”上表明刪除了這個數。
隨機的話是使用C++的rand()函式,即產生[A,B]的隨機數: int x = A + rand()%(B-A+1);
程式碼如下:

class RandomizedSet {
public:
    map<int, int> mp;
    vector<int> v;
    int n;
    RandomizedSet() {
        // do initialize if necessary
        srand((unsigned)time(NULL));
        n = 0;
    }

    // Inserts a value to the set
    // Returns true if the set did not already contain the specified element or false
    bool insert(int val) {
        // Write your code here
        if (mp.find(val) == mp.end()) {
            if (n < v.size()) {
                v[n] = val;
            } else {
                v.push_back(val);
            }
            mp[val] = n;
            n++;
            return true;
        }
        return false;
    }

    // Removes a value from the set
    // Return true if the set contained the specified element or false
    bool remove(int val) {
        // Write your code here
        if (mp.find(val) != mp.end()) {
            v[mp[val]] = v[n-1];
            mp[v[n-1]] = mp[val];
            mp.erase(val);
            n--;
        }
        return false;
    }

    // Get a random element from the set
    int getRandom() {
        // Write your code here
        //srand( (unsigned)time(NULL));
        return v[rand()%n];
    }
};

其實這個思想是從九章上抄的,只是有一個困惑,mp的find函式難道不是O(n)複雜度嗎?這個平均O(1)又是如何得來的呢?
計算複雜度這一塊的確是我的弱項,需要看書補強。

LintCode Happy Numbers
簡單題。根據題意可知 要麼最後是為1要麼是再一次迴圈。所以用set來儲存已經出現的數,當再次出現一個已經在set裡的數時就跳出迴圈。

LintCode Hash Function
個人覺得LintCode比較重要的一個問題就是資料有時候沒給範圍比較讓人confused。這一題本質上就是一個進位制轉換的問題,但是要注意中間產生的數有可能溢位,所以要都用long long來儲存,而且要利用求餘運算的加法性質,即(a+b)%c=a%c+b%c

    int hashCode(string key,int HASH_SIZE) {
        // write your code here
        if (key.size() == 0) return 0;
        long long c = 1, ret = 0;
        for (int i = key.size()-1; i >= 0; i--) {
            long long tmp = key[i]*c;
            ret += tmp;
            ret %= HASH_SIZE;
            c *= 33;
            c %= HASH_SIZE;//由於是33的次方,所以這個數增長很快,所以要特別處理一下。
        }
        return ret;
    }

九章裡面程式碼:

class Solution {
public:
    int hashCode(string key,int HASH_SIZE) {
        int ans = 0;
        for(int i = 0; i < key.size();i++) {
            ans = (1LL * ans * 33 + key[i]) % HASH_SIZE; 
        }
    return ans;
    }
};

這裡面*1LL的用法是 在計算時,把int型別的變數轉變成long long,然後賦值給long long。馬克一下。

LintCode Intersection of Two Arrays
Challenge:Can U implement it in three different ways?
求兩個陣列的交集,挑戰用三種不同的方法。
最粗暴的二重迴圈肯定是不行的,所以想到的第一個辦法就是Hash陣列統計出現的次數。那麼只需要依次遍歷兩個陣列依次,複雜度為O(lenA+lenB)。
嫌麻煩可以用map來代替Hash,用陣列的話要加上一個offset,測試資料集範圍為[-200000, 200000]。

    int hashTable[400002];
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        // Write your code here
        vector<int> ret;
        memset(hashTable, 0, sizeof(hashTable));
        const int offset = 200001;
        for (int i = 0; i < nums1.size(); i++) {
            hashTable[nums1[i]+offset] = 1;
        }
        for (int i = 0; i < nums2.size(); i++) {
            if (hashTable[nums2[i]+offset] == 1) {
                ret.push_back(nums2[i]);
                hashTable[nums2[i]+offset] = 2;
            }
        }
        return ret;
    }

第二種方法是看到Tag裡面有一個Sort想到的,也就是類似合併排序的思想。

    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        // Write your code here
        vector<int> ret;
        set<int> s;
        sort(nums1.begin(), nums1.end());
        sort(nums2.begin(), nums2.end());
        int p1, p2;
        p1 = p2 = 0;
        while (p1 < nums1.size() && p2 < nums2.size()) {
            if (nums1[p1] == nums2[p2]) {
                s.insert(nums1[p1]);
                p1++;
                p2++;
            } else if (nums1[p1] > nums2[p2]) {
                p2++;
            } else
                p1++;
        }
        for (auto num : s) {
            ret.push_back(num);
        }
        return ret;
    }

第三種方法是sort+binary_search。

    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        // Write your code here
        sort(nums1.begin(), nums1.end());
        set<int> s;
        for (auto num : nums2) {
            if (s.find(num) != s.end()) continue;
            auto it = lower_bound(nums1.begin(), nums1.end(), num);//找到第一個>= num的數
            if (it == nums1.end()) continue;//沒找到
            if (*it == num)
                s.insert(num);
        }
        vector<int> ret;
        for (auto num : s) {
            ret.push_back(num);
        }
        return ret;
    }

對於python的解法 我:黑人問號???

    def intersection(self, nums1, nums2):
        # Write your code here
        return list(set(nums1) & set(nums2))

LintCode Intersection of Two Arrays II
和上一題的區別是 重複的可以算進來,只要成對出現。
Challenge:
1. What if the given array is already sorted? How would you optimize your algorithm?
2. What if nums1’s size is small compared to num2’s size? Which algorithm is better?
3. What if elements of nums2 are stored on disk, and the memory is limited such that you cannot load all elements into the memory at once?

1.如果給定陣列是有序的,那麼只需要利用合併思想遍歷一遍即可完成,最壞複雜度為O(len1+len2))。
2.如果陣列1比陣列2要小,那麼用合併排序較好。
3.沒看懂。無法立即載入到記憶體上,那我一個一個讀取並計算不就好了嗎?
貼個自己寫的:

    int hashTable[400002];
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        // Write your code here
        vector<int> ret;
        //vector<int> hashTable(0x7fffffff, 0);
        memset(hashTable, 0, sizeof(hashTable));
        const int offset = 200001;
        for (int i = 0; i < nums1.size(); i++) {
            hashTable[nums1[i]+offset]++;
        }
        for (int i = 0; i < nums2.size(); i++) {
            if (hashTable[nums2[i]+offset] > 0) {
                ret.push_back(nums2[i]);
                hashTable[nums2[i]+offset]--;
            }
        }
        return ret;
    }

LintCode Longest Palindrome
最長迴文串。只要統計各個單詞出現的次數即可。出現n次,那麼就可以讓迴文串長度增加n/2 * 2。同時還要宣告一個變數判斷是不是有單個字元剩餘,因為有可能出現AcA,和AA兩種情況。

    int longestPalindrome(string& s) {
        // Write your code here
        vector<int> Hash(130, 0);
        for (int i = 0; i < s.size(); i++) {
            Hash[s[i]]++;
        }
        int maxLen = 0;
        bool tag = false;
        for (int i = 0; i < 130; i++) {
            if (Hash[i] % 2 == 1) tag = true;
            maxLen += Hash[i] >> 1;
        }
        if (tag) return (maxLen<<1)+1;
        return maxLen << 1;
    }

LintCode Subarray Sum
即計算給定序列中子序列和為0的起點與終點。看到這題立馬想到了最大子序列和,然後又突然想到了線段是處理區間和問題,可以後來發現 區間不好找,所以轉而把目光就 放在了利用 字首和來求取 區間和的辦法。

    vector<int> subarraySum(vector<int> nums){
        // write your code here
        vector<int> ret;
        int len = nums.size();
        vector<int> preSum(len, 0);
        int sum = 0;
        for (int i = 0; i < len; i++) {
            sum += nums[i];
            preSum[i] = sum;
        }
        for (int i = 0; i < len; i++) {
            for (int j = i; j < len; j++) {
                if (preSum[j] - preSum[i] + nums[i] == 0) {
                    ret.push_back(i);
                    ret.push_back(j);
                    break;
                }
            }
            if (ret.size() != 0) break;
        }
        return ret;
    } 

那個,這道題和Hash有什麼聯絡呢?用一個變數sum統計字首和,同時用一個hash來標住sum出現與否,若sum第二次出現,那麼就說明第一次出現到第二次出現這個區間內的和為0。思路有了,那麼動手寫一下吧!

    vector<int> subarraySum(vector<int> nums){
        // write your code here
        map<int, int> hash;
        int sum = 0;
        hash[0] = -1;
        for (int i = 0; i < nums.size(); i++) {
            sum += nums[i];
            if (hash.find(sum) != hash.end()) {
                //not the first appear.
                vector<int> ret;
                ret.push_back(hash[sum]+1);
                ret.push_back(i);
                return ret;
            }
            hash[sum] = i;
        }
        vector<int> ret;
        return ret;
    }

LintCode Substring Anagrams
沒做出來,尷尬。
給定一個字串,尋找其子串,使其子串滿足和另一給定字串為Anagrams(字母亂序的字串)
思路:滑動視窗法。
滑動視窗法總結:http://blog.csdn.net/yy254117440/article/details/53025142
程式碼:

vector<int> findAnagrams(string& s, string& p) {
    // Write your code here
        vector<int> ret;
        map<char, int> hash;
        for (auto c: p) {
            hash[c]++;
        }
        int left = 0, right = 0, cnt = p.size();
        for (; right < s.size(); right++) {
            if (--hash[s[right]] >= 0) cnt--; //當前遍歷的字元為s[right],首先讓其出現次數--,然後若其hash仍然>=0,則說明該字元在p中,將其加入視窗中並使cnt--。cnt可以理解為即在窗口裡[left, right]也在p裡的字元的個數
            if (right - left == p.size()-1) { //如果視窗的大小等於p
                if (cnt == 0) //說明此時視窗內字串與p成Anagrams
                    ret.push_back(left);
                if (++hash[s[left++]] >= 1) cnt++; //維持視窗最大為p.size(),所以當其達到p.size()時左指標就要開始前進,把原本的元素“踢”出視窗,同時判斷更新hash值與cnt值。
            }
        }
        return ret;
}

LintCode Two Sum
給定一個數組,找出其中兩個元素值,使其和等於給定的數target。
Challenge:
Either of the following solutions are acceptable:
O(n)的空間複雜度,O(nlogn)或者O(n)的時間複雜度。
暴力搜尋法複雜度O(n^2),顯然是TLE了。於是考慮O(nlogn)的做法。用一個hash儲存原有陣列的位置資訊,然後排序,利用二分法來找符合題目要求的資訊。顯然複雜度位O(nlogn)排序+O(n*logn)。需要注意hash衝突的問題,所以hash宣告成vector。
蠢,真蠢。
後來發現O(nlogn)的另一種解法,即排序後用一頭一尾指標進行搜尋。仍然要注意重複元素的問題。程式碼:

    vector<int> twoSum(vector<int> &nums, int target) {
        // write your code here
        vector<int> ret;
        map<int, vector<int>> posi;
        for (int i = 0; i < nums.size(); i++) {
            posi[nums[i]].push_back(i+1); 
        }
        sort(nums.begin(), nums.end());
        int left = 0, right = nums.size()-1;
        while (left < right) {
            int temp = nums[left] + nums[right];
            if (temp == target) {
                if (nums[left] == nums[right]) {
                    ret.push_back(posi[nums[left]][0]);
                    ret.push_back(posi[nums[left]][1]);
                } else {
                    ret.push_back(posi[nums[left]][0]);
                    ret.push_back(posi[nums[right]][0]);
                }
                break;
            }else if (temp < target) 
                    left++;
            else right--;
        }
        sort(ret.begin(), ret.end());
        return ret;
    }

其實還可以先找到這兩個元素,然後在遍歷原陣列找其下標值。程式碼就不貼了。

下面說一下O(n)的做法。O(n)的做法也就是一次遍歷。看到這個做法我真是驚呆了,如此簡單為啥我想不到。。。
思路就是:遍歷陣列,把所有陣列值加進去。然後在遍歷一遍,當遍歷到一數i時,看一下map裡是否有數target-i,如果有,那麼迴圈就結束了。直接輸出就好,注意一下重複元素的問題。

    vector<int> twoSum(vector<int> &nums, int target) {
        // write your code here
        unordered_map<int, int> hash;
        vector<int> ret;
        for (int i = 0; i < nums.size(); i++) {
                hash[nums[i]] = i+1;
        }
        for (int i = 0; i < nums.size(); i++) {
            int t = target - nums[i];
            if (hash.find(t) != hash.end() && hash[t] != i) {
                ret.push_back(i+1);
                ret.push_back(hash[t]);
                break;
            }
        }
        return ret;
    }

上面兩道題幾乎都沒有很完美的解決,看來確實還是練的少了。。今天聽聞杭電oj不錯,就把這個oj作為以後刷題練演算法的地方吧。

LintCode 3Sum, 4Sum
是昨天做的2Sum的變種,直接暴力迴圈是不行的,可以用排序+two pointer的方法來做,即首先對陣列排序,然後Ksum當固定一個位置為i元素時,那麼問題就變成來i+1位置開始的k-1Sum問題。當是2Sum問題時就可以用 two pointer解決。
可以寫成遞迴,可是我寫出來的一點都不具有遞迴的簡潔行,反而臭的要死。4Sum還可以用hash來做成O(n2)的,但是寫了一下發現不對,就懶得debug了。
這一系列題都馬克一下,二刷的時候來研究。(原來我懶吧 - -)
ps:要注意重複的問題。即 要判斷當前元素是不是與前一個元素相同,如果相同,就直接跳過當前元素了。
3Sum:

    vector<vector<int> > threeSum(vector<int> &nums) {
        // write your code here
        vector<vector<int>> ret;
        sort(nums.begin(), nums.end());

        for (int i = 0; i < nums.size(); i++) {
            if (i > 0 && nums[i] == nums[i-1]) continue;
            int left = i+1, right = nums.size()-1;
            while (left < right) {
                if (left > i+1 && nums[left] == nums[left-1]) {
                    left++;
                    continue;
                }
                int sum = nums[left] + nums[right] + nums[i];
                if (sum == 0) {
                    vector<int> tmp;
                    tmp.push_back(nums[i]);
                    tmp.push_back(nums[left]);
                    tmp.push_back(nums[right]);
                    ret.push_back(tmp);
                    left++;

                } else if (sum < 0) left++;
                else right--;
            }
        }
        return ret;
    }

4Sum:

    vector<vector<int> > fourSum(vector<int> nums, int target) {
        // write your code here
        vector<vector<int> > ret;
        sort(nums.begin(), nums.end());
        for (int i = 0; i < nums.size(); i++) {
            if (i > 0 && nums[i] == nums[i-1]) continue;
            int left = i+1, right = nums.size()-1;
            //3 sum
            for (int j = left; j <= right; j++) {
                if (j > left && nums[j] == nums[j-1]) continue;
                int start = j+1, end = right;
                while (start < end) {
                    if (start > j+1 && nums[start] == nums[start-1]) {
                        start++;
                        continue;
                    }
                    int temp = nums[start] + nums[end] + nums[i] + nums[j];
                    if (temp == target) {
                        vector<int> temp;
                        temp.push_back(nums[i]);
                        temp.push_back(nums[j]);
                        temp.push_back(nums[start]);
                        temp.push_back(nums[end]);
                        ret.push_back(temp);
                        start++;
                    } else if (temp < target) start++;
                    else end--;
                }
            }
        }
        return ret;
    }