2019-01-26 第二天(#189, #41, #299)

#189 Rotate Array

初见 (O(n^2)复杂度)

我觉得这个应该算是brute force。先用back()获取最后一个元素,用erase()擦除最后一个元素,再在数组头用insert()插入该元素。

class Solution {
public:
    void rotate(vector& nums, int k) {
        int size = nums.size();
        k %= size;
        for(int it = 0; it < k; it++){
            int temp = nums.back();
            nums.erase(nums.end()-1);
            nums.insert(nums.begin(), temp);
        }
    }
};

问题主要在insert()这个函数在数组头插入的话时间复杂度是O(n),那这么下来整体的时间复杂度就是O(n^2)。
我以为这个会超过时间限制,但是居然也打败了31.91%的人,可喜可贺可喜可贺。

辅助数组 (O(n)空间复杂度)

这个算是比较直观的解法了,具体而言就是把需要rotate的元素移出来存到另一个数组ans里,然后再把剩余元素push到ans中。这种方法出来的rotate元素会和所需要的刚好相反,可以翻转一下。

class Solution {
public:
    void rotate(vector& nums, int k) {
        int size = nums.size();
        k %= size;
        vector ans;
        for(int it = 0; it < k; it++){
            int temp = nums.back();
            ans.push_back(temp);
            nums.erase(nums.end()-1);
        }
        
        reverse(ans.begin(), ans.end());
        
        for(int ind = 0; ind < nums.size(); ind++){
            ans.push_back(nums.at(ind));
        }
        
        nums = ans;
    }
};

这方法把时间复杂度降到了O(n),但也有个缺点是它有O(n)的空间复杂度,还不如初见的那个brute force版本。

三次翻转 (O(1)空间复杂度)

这个方法说实话我自己肯定想不出来,但是在网站的solution里面有,去YouTube看了看也是主流的方法之一,权当背下来吧。
这个方法说白了就是把整个数组分成两部分:需要rotate“走”的部分,和剩余的部分。
举个例子:
数组[1 2 3 4 5 6 7],k = 3
那这个地方需要rotate走的就是[5 6 7]这一块,剩下的就是[1 2 3 4];
先把所有的元素翻转过来:[7 6 5 4 3 2 1]
然后翻转rotate走的部分:[5 6 7 4 3 2 1]
最后翻转剩下部分:[5 6 7 1 2 3 4]
三次翻转就可以完成这个rotate的过程。
代码实现:

class Solution {
public:
    void rotate(vector& nums, int k) {
        int size = nums.size();
        k %= size;
      
        reverse(nums.begin(), nums.end());
        reverse(nums.begin(), nums.begin()+k);
        reverse(nums.begin()+k, nums.end());
        
        
    }
};

要注意的是,reverse()这个函数接受的第二个参数应该指向需要翻转元素的后一个位置
这个算法的时间复杂度为O(n),空间复杂度为O(1)(不需要占用额外空间)。

#41 First Missing Positive

题目地址:https://leetcode.com/problems/first-missing-positive/
这个题显然可以用sorting解决,但是题目要求O(n)的复杂度,就不放上来了。

辅助数组(O(n)空间复杂度)

解法的核心思想是"Put its number in its right place"。
对于这道题来说,不需要考虑负数,也不需要考虑值大于数组长度的数(因为要找的是“最小缺失正数”,如果数组内出现了不连续,那么最小缺失正数的值必然小于数组长度)那么我只需要建立一个新的数组,然后把元素放到对应值作为下标的位置就好。
例如说nums[3] = 5,那我就让新数组ans[4]=5。
这样之后排出来的新数组算是部分排好序的,那再对这个新数组遍历一遍就能知道答案。

class Solution {
public:
    int firstMissingPositive(vector& nums) {
        int size = nums.size();
        vector ans(size, 0);
        
        for(auto it = nums.begin(); it != nums.end(); it++){
            if(*it <= size && *it > 0)
                ans.at(*it-1) = *it;
        }
        
        int smallest = 1;
        for(auto it = ans.begin(); it != ans.end(); it++){
            if(*it == smallest)
                smallest++;
        }
        
        return smallest;
    }
};

这个解法的时间复杂度为O(n),空间复杂度为O(n)。

交换(O(1)空间复杂度)

这题的要求其实是空间复杂度为常数。那么如果不能建立辅助数组,要重新对这个数组部分排序就只能用交换元素的方法了。
交换一次并不能达到效果,只要当前下标对应的值不是所需要的值,就必须一直交换。举个例子:
数组[5 3 1 2 4]。
第一次交换:
[4 3 1 2 5]
此时nums(0)处的数字依然不是1(换言之,这个地方的元素并不“对”),我们要一直交换到它为1为止:
[2 3 1 4 5]
继续:
[3 2 1 4 5]
[1 2 3 4 5]
这就完成了nums(0)处的交换,之后对nums(1)到nums(4)上都进行同样的判断。但由于此时整个数组已经是完全排序好了,之后的下标处就不需要再交换了。
代码如下:

class Solution {
public:
    int firstMissingPositive(vector& nums) {
        int size = nums.size();
        
        for(auto it = nums.begin(); it != nums.end();){
            if(*it <= size && *it > 0 && *it == nums.at(*it-1))
                it++;
            else if(*it <= size && *it > 0)
                iter_swap(it, nums.begin()+*it-1);
            else{
                *it = 0;
                it++;
            }
        }
        
        int smallest = 1;
        for(auto it = nums.begin(); it != nums.end(); it++){
            if(*it == smallest)
                smallest++;
        }
        
        return smallest;
    }
};

理论上来说这个方法应该是比用辅助数组的方法慢的(时间复杂度严格说应该是O(3n),辅助数组是O(2n)),实际上leetcode的运行结果也是如此。但是O(3n)=O(2n)=O(n),再加上O(1)的空间复杂度,这个算法也可以说是最优解之一。

#299 Bulls and Cows

题目地址:https://leetcode.com/problems/bulls-and-cows/

初见(Hash Table)

既然输入只有0到9一共10个数字,那不用Hash Table做简直是天理难容。把除了bull之外的情况都统计下来,只要双方数字都不为0的情况下就代表出现了cow的情况,这时候取其中较小的计数作为cow。

class Solution {
public:
    string getHint(string secret, string guess) {
        vector nums(10, 0);
        vector numbulls(10, 0);
        int size = secret.size();
        int bulls = 0;
        int cows = 0;
        for(int ind = 0; ind < size; ind++){
            if(secret.at(ind) == guess.at(ind))
                bulls++;
            else{
                int digit = guess.at(ind) - '0';
                int digitbull = secret.at(ind) - '0';
                nums.at(digit)++;
                numbulls.at(digitbull)++;
            }
            
        }
        for(int ind = 0; ind < nums.size(); ind++){
            if(nums.at(ind) > 0 && numbulls.at(ind) > 0){
                cows += min(nums.at(ind), numbulls.at(ind));
            }
        }
        
        string answer;
        answer = to_string(bulls) + "A" + to_string(cows) + "B";
        return answer;
    }
};

实际上只需要遍历数组一次(第二个for循环遍历的是长度为10的定长数组),时间复杂度为O(n)。

你可能感兴趣的:(2019-01-26 第二天(#189, #41, #299))