关闭

[LeetCode]problem 80. Remove Duplicates from Sorted Array II

137人阅读 评论(0) 收藏 举报
分类:

link

TAG

双指针

思维方式

容器的end()调用耗时显著性

方法

算是很容易想到的题,然而写起来却很纠结…还得多练啊..

我的思路是,记录重复的次数,初始为0,如果相等一次,则首先+1,然后判断是否小于2,如果是,则继续移动双指针并赋值;如果否,则只需移动遍历指针即可。如果不等,则清零重复次数,移动双指针并赋值。

纠结的问题有:

  1. 遍历指针从哪里开始?

    uniq指针不得不从第一个开始,那么遍历指针如果从第一个开始,那么上述过程就不对了。

    我们需要明确的就是,uniq指针在上述逻辑表示指向uniq结果的最后一个位置。所以,根据此定义,其实遍历指针必须从第2个位置开始(表示第一个位置以及是uniq的)!自己的纠结是逻辑不清导致的。

  2. 输入限制

    上述逻辑不能处理输入为空的情况!

    这是因为此逻辑成立的条件就是数组非空!因为uniq表示uniq部分的最后一个位置,如果数组未空,那么根本不存在uniq部分。

以上的方法很多条件判断。

完成上述代码后,看了DISCUSS,第一个HOT的答案真是让人难以置信!原来可以如此简洁与简单!

我把代码贴过来吧:

int removeDuplicates(vector<int>& nums) {
    int i = 0;
    for (int n : nums)
        if (i < 2 || n > nums[i-2])
            nums[i++] = n;
    return i;
}

我将其改写为等价的如下代码:

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        size_t sz = nums.size() ;
        if(sz < 3) { return sz ;}
        size_t rear_pos = 1 ; 
        for(size_t pos = 2; pos < sz ; ++pos)
        {
            if(nums[pos] != nums[rear_pos-1])
            {
                nums[++rear_pos] = nums[pos];
            }
        }
        return rear_pos + 1 ;
    }
};

核心就是——不需要要记录有多少重复的数字,只需判断已有结果的状态!如果当前遍历指针的值与当前uniq指针(uniq部分的尾位置)的前一个位置的值相等(代码中是大于关系,其实可以改为不等于),那么就必然有:结果中已经有两个数相同了。

这其实是两种思维方式

我的想法,很常规,就是考虑复制指针的状态,看目前已经有多少个重复的了。

题解的想法,是考虑已有结果的状态!看结果中是否已经有两个相等了(因为有序,只需一个位置判断)。

代码

这里贴上自己想的方法,相对繁琐。

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        if(nums.size() < 3) { return nums.size() ; }
        const int DUPLICATE_LIMIT = 2 ;
        vector<int>::iterator dup_uniq_iter = nums.begin() ;
        int duplicates_cnt = 0 ;
        for(auto iter = nums.begin() + 1; iter < nums.end() ; ++iter)
        {
            if(*iter == *dup_uniq_iter)
            {
                ++duplicates_cnt ;
                if(duplicates_cnt < DUPLICATE_LIMIT)
                {
                    *++dup_uniq_iter = *iter ;
                }
            }
            else
            {
                *++dup_uniq_iter = *iter ;
                duplicates_cnt = 0 ;
            }
        }
        size_t newSz = dup_uniq_iter - nums.begin() + 1 ;
        nums.resize(newSz);
        return newSz;
    }
};

后记

关于迭代次与下标的速度比较:

对第二种方法,我写的迭代器版本:

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        size_t sz = nums.size() ;
        if(sz < 3){ return sz ; } // always satisfied
        vector<int>::iterator rear_iter = nums.begin() + 1 ;
        for(auto iter = nums.begin()+2 ; iter != nums.end(); ++iter)
        {
            if(*iter != *(rear_iter-1))
            {
                *++rear_iter = *iter ;
            }
        }
        return rear_iter - nums.begin() + 1 ;
    }
};

以上与下标版逻辑完全一致,然而迭代器版本跑了20ms,而下标版只需要16ms

这之间的差距时如何形成的呢?

注意for循环中的nums.end()调用!当我把它在循环前保存下来作为一个变量来代替时,即:

//for(auto iter = nums.begin()+2 ; iter != nums.end(); ++iter)
auto end_iter = nums.end() ;
for(auto iter = nums.begin()+2 ; iter != end_iter; ++iter)

迭代器的版本时间消耗也成为了16ms!

由此可见容器的end()调用其实也是一定程度耗时的(在此种情况下有些显著啊!!)

以后如果有超大量的循环,且容器是只读,那么存储尾后迭代器或许是有必要的。

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    有什么值得去写?

    一位师兄以前说,现在互联网上的各种教程都是相互拷贝,一堆垃圾,根本没有写的必要。我想想觉得也很有道理。

    如果你只是记录下一个软件的安装过程,那还不如去看官方文档。但是如果你加上遇到的坑以及解决办法,那么这或许是一篇有意义的文章。如果你只是记录下一个函数简单用法,那怎么不去看官方文档(确实我现在都是去官方文档看)?但是如果你加上一些这个函数常用场景,用起来的漂亮代码,内部实现原理,那么这篇文章也是有意义的。

    最近因为找工作,在刷LeetCode。以前我还从网上找题解,后来我直接在DISCUSS上看HOT就好了。那我记录有何理由?我可以记录思考过程,可以考虑代码的优化,可以展示清晰易读标准C++11的代码

    个人资料
    • 访问:65420次
    • 积分:1688
    • 等级:
    • 排名:千里之外
    • 原创:104篇
    • 转载:13篇
    • 译文:0篇
    • 评论:13条