LintCode 解题记录 17.5.19 (tag: Hash表2)

Hash表相关的题目对我来说有点吃力了。很多题目远超规定时间仍然想不出最优解。所以看别人的最优解、然后自己消化吸收就会花不少时间。希望能提高吧。
LintCode Majority Number
给定一个数组,找出其中出现次数大于数组大小一半的数。(输入数据保证只有唯一一个满足要求的解)
直接用Hash统计出现次数,然后判断即可。O(n)的时间与空间复杂度。
问:能否仅以O(1)的空间复杂度实现?时间复杂度仍然为O(n)。
这就要求不用hash表,只用几个int变量遍历数组一次求解。这里用了Greedy的思想,即局部的majority也是整体的majority。那么什么情况下局部的majority是整体的majority呢?直接看代码:

    int majorityNumber(vector<int> nums) {
        // write your code here
        int candidate, count = 0;
        for (int i = 0; i < nums.size(); i++) {
            if (count == 0) {
                candidate = nums[i];
                count++;
            } else {
                if (candidate == nums[i]) {
                    count++;
                } else {
                    count--;
                }
            }
        }
        return candidate;
    }

从代码中可以看出,用一个count指针计算candidate的出现次数。遍历到一个数,如果此时这个数不是candidate,count–,否则count++。那么需要注意的就是考虑count==0。count==0代表了什么呢?就是在已经遍历过的数中,candidate占了一半,非candidate占了一半。那么count == 0代表剩下未遍历的数组中的majority_number就是最终的majority_number。所以直接从新开始寻找新的candidate即可。
着重理解这里majority_number > nums.size()/2这个条件的应用。然后可以尝试思考一下majority Number II 这道题。

LintCode Majority Number II
上一题其实可以这么理解:由于majority_number的出现次数>size/2,那么我可以两两丢弃一组不同的数,那么最后剩下的数一定就是答案。丢弃一组不同的数在上述代码的体现就是count–。那么,对于此题,我们可以三三丢弃,那么最后有可能剩下一个数或者两个不同的数,就需要在遍历一遍看看剩下的两个数哪个才是majority_number就好了。

    int majorityNumber(vector<int> nums) {
        // write your code here
        int count1 = 0, count2 = 0;
        int candidate1 = 0, candidate2 = 0;
        for (int i = 0; i < nums.size(); i++) {
            if (candidate1 == nums[i]) {
                count1++;
            } else if (candidate2 == nums[i]) {
                count2++;
            } else if (count1 == 0) {
                candidate1 = nums[i];
                count1 = 1;
            } else if (count2 == 0) {
                candidate2 = nums[i];
                count2 = 1;
            } else {
                //相当于丢弃三个数,即当前数、candidate1、candidate2
                count1--;
                count2--;
            }
        }
        count1 = count2 = 0;
        for (int i = 0; i < nums.size(); i++) {
            if (candidate1 == nums[i]) count1++;
            if (candidate2 == nums[i]) count2++;
        }
        return count1 > count2 ? candidate1 : candidate2;
    }

LintCode Majority Number III
这一题又是上一题的扩展。majority_number > size/k,那么我每发现k个不同的数,就将其丢弃,最后的majority_number一定就在剩下的数中。至多只可能剩下k-1个不同的数。所以这道题可以以Hash的思想在O(k)的空间复杂度下解决。这种hash思想仍然可以用于前面两题,只是k太小所以直接用两个指针来代替来这个hash。

    int majorityNumber(vector<int> nums, int k) {
        // write your code here
        unordered_map<int, int> hash;
        for (auto e : nums) {
            if (hash.size() < k-1 && hash.find(e) == hash.end()) {
                hash[e] = 1; //加入候选集合
                continue;
            }
            if (hash.find(e) == hash.end()) { //开始丢弃k个不同的数
                auto ite = hash.begin();
                while (ite != hash.end()) {
                    ite->second--; //候选数出现次数-1,代表丢弃了该数
                    if (ite->second == 0) {
                        ite = hash.erase(ite);//从候选数集合中剔除,注意这个erase函数,iterator erase(iter),即返回一个指向iter之后的元素的迭代器。如果iter指向最后一个元素,则返回.end()
                        continue;
                    }
                    ite++;
                }
            } else {
                hash[e]++;
            }
        }
        //在候选数集合中 找到出现次数最多的即为答案
        for (auto &tmp : hash) {
            tmp.second = 0;
        }
        int maxNum = 0, ret = 0;
        for (auto e : nums) {
            if (hash.find(e) != hash.end()) {
                hash[e]++;
                if (hash[e] > maxNum) {
                    maxNum = hash[e];
                    ret = e;
                }
            }
        }
        return ret;

    }

LintCode Anagrams
给定一个字符串数组,返回其中所有的Anagrams的字符串。两个字符串是Anagrams,如果他们打乱字母顺序后可以相等。
马上就想到的思路,建立一个hash:string -> vector(string),其中key是排序后的字符串,value是原本的字符串容器。那么遍历一遍之后,最后在遍历一遍,去掉那些size == 1的vector,把所有字符串圧到最终的vector中。
代码:

    vector<string> anagrams(vector<string> &strs) {
        // write your code here
        vector<string> ret;
        unordered_map<string, vector<string>> hash;
        for (int i = 0; i < strs.size(); i++) {
            string tmp = strs[i];
            sort(tmp.begin(), tmp.end());
            hash[tmp].push_back(strs[i]);
        }
        for (auto ite=hash.begin(); ite != hash.end(); ite++) {
            if (ite->second.size() == 1) continue;
            for (auto s : ite->second) {
                ret.push_back(s);
            }
        }
        return ret;
    }

尝试着来分析复杂度: 假设有n个字符串,每个字符串平均长度为k,那么时间复杂度O(nklogk) (第一个for循环),第二个for循环的复杂度最多为O(n)。(即为push每一个字符串)。综合起来应该是O(nklogk)。
有一种O(n)的来判断两个字符串是不是Anagrams的方法:(由于本题hash的key是排序后的所以这种方法并不适合本题)
用一个hash统计str1中的字符的出现次数,然后遍历一遍str2,如果当前遍历到的字符的hash值>=1,就代表其出现在str1中,cnt++并且相应的hash值–,否则就肯定不是Anagrams。

LintCode copy List with random pointer
虽然AC了但总感觉怪怪的。先贴我的AC代码:用的是递归。

    RandomListNode *copyRandomList(RandomListNode *head) {
        // write your code here
        if (head == NULL) return NULL;
        RandomListNode *ret = new RandomListNode(head->label);
        ret->random = head->random;
        ret->next = copyRandomList(head->next);
        return ret;
    }
    //后来思考了一下,修改了代码,用一个hash来存储oldNode->newNode。
    unordered_map<RandomListNode*, RandomListNode*> hash;
    RandomListNode *copyRandomList(RandomListNode *head) {
        // write your code here
        if (head == NULL) return NULL;
        RandomListNode *ret = new RandomListNode(head->label);
        hash[head] = ret;
        ret->next = copyRandomList(head->next);
        if (head->random != NULL) {
            ret->random = hash[head->random];
        } else {
            ret->random = NULL;
        }
        return ret;
    }
    //上述空间复杂度为O(n)。要想达到O(1)的空间复杂度,就不能用hash表来存储。经百度,发现可以把新节点插入到老节点的后面。这样的话hash就可以用原有的next来表示。然后就可以很方便的给新节点的random域赋值。最后在把链表拆成一个新链表与一个老链表。
        RandomListNode *copyRandomList(RandomListNode *head) {
        // write your code here
        //第一步,遍历老链表,对每一个节点都新建一个节点,并把其插在老节点的后面
        RandomListNode *pold, *pnew, *poldNext, *result;
        pold = head, pnew = poldNext = result = NULL;
        while (pold) {
            RandomListNode *tmp = new RandomListNode(pold->label);
            tmp->next = pold->next;
            pold->next = tmp;
            pold = tmp->next;
            if (result == NULL) {
                result = tmp;
            }
        }
        //第二步,遍历这个新链表,更新random
        pold = head;
        while (pold) {
            if (pold->random)
                pold->next->random = pold->random->next;
            pold = pold->next->next;
        }
        //第三步,遍历这个链表,把新链表提取出来
        pold = head, pnew = result;
        while (pnew->next) {
            pnew->next = pnew->next->next;
            pnew = pnew->next;
        }
        //pnew->next = NULL;
        return result;
    }

LintCode Longest Substring Without Repeating Characters
给定一个字符串,求这个字符串没有重复字符的最长子串。
要求O(n)的时间复杂度。
字符串的子串问题,又是要求只能遍历字符串一遍,自然就会想到用两个指针法。
思路:声明一个left指针,与一个right指针,同时声明一个hash表用来判断是否出现过某字符hash:char->position。开始移动right指针,直到遍历到一个已经出现的字符,那么[left, right-1]就是某没有重复字符的子串。然后left就跳到hash[right]+1的地方,然后用新的right去更新hash[right]。注意如果遍历到某hash[right] < left,代表其也未出现在窗口内。直接拿新的right更新hash[right]即可。

    int lengthOfLongestSubstring(string s) {
        // write your code here
        unordered_map<char, int> hash;
        int mlen = 0, left = 0, right = 0, tmplen = 0;
        while (right < s.size()) {
            if (hash.find(s[right]) == hash.end() || hash[s[right]] < left) {
                hash[s[right]] = right;
                tmplen++;
                right++;
                //mlen = max(mlen, tmplen);
            } else {
                //如果发现一个已经出现的元素
                left = hash[s[right]]+1;
                tmplen = right - left + 1;
                hash[s[right]] = right;
                right++;
            }
            mlen = max(mlen, tmplen);
        }

        return mlen;
    }

LintCode Max Points on a Line
不会题。最后看了网上的思路才AC。
需要考虑几个问题:1.存在重复的点 2.存在正无穷的直线
然后二次循环遍历points数组,一般的情况是就是算得两点的斜率为k,那么hash[k]++即可。

    int maxPoints(vector<Point>& points) {
        // Write your code here
        int maxNum = 0;
        unordered_map<double, int> mp; //斜率->这条线上有几个点
        for (int i = 0; i < points.size(); i++) {
            mp.clear();
            mp[INT_MIN] = 0;//这个是为了考虑全是重复的点的情况
            int duplicate = 1;//重复的点的个数
            for (int j = 0; j < points.size(); j++) {
                if (j == i) continue;
                if (points[j].x == points[i].x && points[j].y == points[i].y) {
                    duplicate++;
                    continue;
                }
                //斜率正无穷这里设置为INT_MAX
                double k = points[i].x == points[j].x ? INT_MAX: 
                    (double)(points[j].y-points[i].y) / (points[j].x-points[i].x);
                mp[k]++;
            }
            unordered_map<double, int>::iterator ite = mp.begin();
            //这里没想到用这种方式来处理重复的点
            for (; ite != mp.end(); ite++) {
                if (maxNum < ite->second + duplicate)
                    maxNum = ite->second + duplicate;
            }
        }
        return maxNum;
    }

这题目估计二刷的时候还是做不出来- -。

LintCode Minimum Window Substring
找出source中包含target的最短子串
滑窗法。用cnt记录窗内的在target内的字符的个数,起初cnt = target.size(),每发现一个cnt–
当cnt == 0时,得到一个包含target的子串。然后更新minLen。
然后应该固定right不动,left左移,直到cnt != 0时right才能继续向又移动。

    string minWindow(string &source, string &target) {
        // write your code here
        unordered_map<char, int> hash;
        string ret;
        int left = 0, right = 0, cnt = target.size();
        int minlen = INT_MAX, fleft = 0, fright = 0;
        for (auto c : target) {
            hash[c]++;
        }
        while (right < source.size()) {
            hash[source[right]]--;
            if (hash[source[right]] >= 0) {
                cnt--;
            }
            right++;
            while (cnt == 0) {
                //该字串包含了target中的所有字符
                int currlen = right - left;
                if (currlen < minlen) {
                    minlen = currlen;
                    fleft = left;
                    fright = right;
                }
                hash[source[left]]++;
                if (hash[source[left]] >= 1) {
                    cnt++;
                }
                left++;
            }
        }
        ret = source.substr(fleft, fright-fleft);
        return ret;
    }

实际上left和right都只从左到右扫描了一遍source,所以时间复杂度为O(2*n) = O(n);

LintCode Rehasing
题目不难,但感觉自己对于链表的操作不是很熟,总会陷入卡壳的地方。比如该题中的往链表中插入一个数并返回其头节点方法,不太熟练。

    ListNode* insert(ListNode *root, int val) {
        ListNode *pre = new ListNode(0);
        pre->next = root;
        ListNode *ret = pre;
        while (pre->next != NULL) {
            pre = pre->next;
        }
        pre->next = new ListNode(val);
        return ret->next;

    }
    vector<ListNode*> rehashing(vector<ListNode*> hashTable) {
        // write your code here
        int oldCapacity = hashTable.size();
        vector<int> nums;
        for (int i = 0; i < hashTable.size(); i++) {
            ListNode *p = hashTable[i];
            while (NULL != p) {
                nums.push_back(p->val);
                p = p->next;
            }
        }
        int newCapacity = 2*oldCapacity;
        vector<ListNode*> ret(newCapacity, NULL);
        for (int i = 0; i < nums.size(); i++) {
            int hashcode = (nums[i]%newCapacity + newCapacity) % newCapacity;
            ret[hashcode] = insert(ret[hashcode], nums[i]);
        }
        return ret;
    }

LintCode Two Sum - Input array is sorted
这个简单,头尾指针遍历就好了。

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

总结:
1.终于把hash的题目做完了,最大的感受就是自己真菜,一半的题目都不能顺利AC。学习别人的解法,然后自己理解敲出来,然后写在博客里,花了比做题还要长的时间。希望有所收获吧。
2.累。明儿好好休息。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值