LintCode 解题记录 17.5.15 (tag: 哈希表)

来源:互联网 发布:打开stp的软件 编辑:程序博客网 时间:2024/05/18 01:43

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;    }

这里简单说一下map和unordered_map的区别:
前者有序,后者无序。操作的时间复杂度位:O(logN) vs O(1),因为前者是基于红黑树而后者是hash 。但是后者需要更大的空间。所以在一般用map来表示hash查找表时,如果对先后顺序不是那么在意的话,推荐用无序容器。这也是为啥上述算法的复杂度是O(N)。贴几个关于此的链接:
http://stackoverflow.com/questions/2196995/is-there-any-advantage-of-using-map-over-unordered-map-in-case-of-trivial-keys
http://www.cnblogs.com/ganganloveu/p/3728849.html

上面两道题几乎都没有很完美的解决,看来确实还是练的少了。。今天听闻杭电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;    }
原创粉丝点击