leetcode 219. 存在重复元素 II 题解

leetcode 219 题解

重要参考文章:

c++几乎双百的set解法:
官方解法集锦

收获

本篇题解是针对 leetcode 219题, 进行时间复杂度进行优化的提升。分别应用了以下几点:

  1. 用 unordered_set 来进行 k 窗口的构造,可以有效提升时间,能减少 4 ms 运行时间
  2. 通过优化 if-else 的条件,优化代码的可读性。
  3. 通过更改循环条件, int n = nums.size(); i < n; 替换 i < nums.size() ,能够手动优化时间 4ms
  4. 通过更改更改特殊情况判断条件,利用 unorder_set 的 size() 和 k 判断,能减少 4 ms

祝您某天运用这些提升,来获得一些乐趣。

题目 219. 存在重复元素 II

题目来源:力扣(LeetCode)
219. 存在重复元素 II

给定一个整数数组和一个整数 k,
判断数组中是否存在两个不同的索引 i 和 j,使得 nums [i] = nums [j],
并且 i 和 j 的差的 绝对值 至多为 k。

例子:

输入: nums = [1,2,3,1], k = 3
输出: true

输入: nums = [1,0,1,1], k = 1
输出: true

输入: nums = [1,2,3,1,2,3], k = 2
输出: false

解题全过程

玩了40分钟的 219

题目总耗时

第一次解法

class Solution {
  unordered_map<int, int> memo;
public:
  bool containsNearbyDuplicate(vector<int>& nums, int k) {
    for (int i = 0; i < nums.size(); ++i) {
      if (memo.find(nums[i]) != memo.end() 
          && i - memo[nums[i]] <= k) return true; 
      memo[nums[i]] = i;
    }
    return false;
  }
};

然后得到的成绩是 32 ms, 图中的第一个成绩.
研究快100 % 的代码是怎么样的呢?
看了 4 ms 的代码,是这样的

class Solution {
public:
    bool containsNearbyDuplicate(vector<int>& nums, int k) {
    //思路2,筛选大小为k的子数组序列
     if (k == 35000) return false;
    int length=end(nums)-begin(nums);
    if(length==1)return false;
    if(length<k) {
     for(int i=0;i<length;i++)
          for(int j=i+1;j<length;j++){
              if (nums[i]==nums[j])
              return true;};
    }
    if(length==k){
    for(int i=0;i<=length-k;i++)
          for(int j=i+1;j<i+k;j++){
              if (nums[i]==nums[j])
              return true;}
    }
    if(length>k){
        for(int i=0;i<=length-k;i++)
          for(int j=i+1;j<=i+k;j++){
              if (nums[i]==nums[j])
              return true;}
            for(int i=length-k;i<length-1;i++)
              for(int j=i+1;j<length;j++){
                if(nums[i]==nums[j])
                return true;}
          }
          return false;
    }
};

研究他为什么那么快:
从大框架上讲:
这个实现是根据 数组长度 len 和 题目设置相差 k 的值 进行对比, 然后再在每个分支当中用双循环来进行判断是否符合.
如果在每个 if-else 分支当中使用双循环这种通用的判断符合方法, 在经过最长的那个测试用例的时候, 必定是会超时的.
于是乎代码作者加了一个 k == 35000 的判断, 返回一个 false, 就可以通过这个最长的用例.
代码作者也是绝了, 很有研究搞针对的研究精神,一定要用双循环过这个测试. 然后搞了一个特殊条件.
这个代码的成绩是 4 ms.我通过去掉这个判断, 来从代码的验证,分别是成绩倒数第2和第3个是去掉了 k == 35000 的判断。

第二次解法

然后我研究了 “c++几乎双百的set解法”的答案的代码.

class Solution {
public:
    bool containsNearbyDuplicate(vector<int>& nums, int k) {
        unordered_set<int> existed;
        int n = nums.size();
        int curr = 0;
        for (int i = 0; i < n; ++i)
        {
            curr = nums[i];
            if (existed.find(curr) == existed.end())
            {
                existed.insert(curr);
                if (existed.size() > k)
                {
                    existed.erase(nums[i-k]);
                }
            }
            else
            {
                return true;
            }
        }

        return false;
    }
};

我发现比我改进的地方,在于, 答案的作者能够利用 unordered_set 组成 一个由 大小为 k 的窗口的通用想法,来进行排除是否存在这个窗口之内是否相同数字的问题。
然后我觉得本质上和 unordered_map<int, int> 应该是一致的
所以我自己实现了下面代码

class Solution {
  unordered_map<int, int> memo;
public:
  bool containsNearbyDuplicate(vector<int>& nums, int k) {
    for (int i = 0; i < nums.size(); ++i) {
      if (memo.find(nums[i]) != memo.end() 
          && i - memo[nums[i]] <= k) return true; 
      memo[nums[i]] = i;
      if (i-k>=0) memo.erase(nums[i-k]);
    }
    return false;
  }
};

得到的成绩是 36 ms.
36ms成绩

还是很好奇,为什么只有 36 ms, 没有达到别人的 28 ms.
都是基于同一套想法(一次遍历的框架, 每次插入元素到map), 但是没有同一套数据结构 (unordered_set).
首先要确定上面的代码,是否在我测试的时候, 真的还是 28ms 的成绩.
我曾经遇到过拿100%的代码,实际上运行不再是100%了。
于是我得到了下面成绩:
实际28ms
问题是:
那么同一套想法, 我的实现差在哪里,还需要改进哪里,才能有相同的时间?
首先是利用unordered_set,来改进,我就得到以下代码

利用 unordered_set 改进

基于我的判断: 利用 unordered_set 作为数据结构, 会有能够减少保存一个输入, 并且通过大小 k 来判断以及 set 的属性来进行判断是否已经有重复.
对比 unordered_map 会有减少一个判断, 以及一条语句的优势.

unordered_map 中的实现, 会包括 if (i-k>=0) memo.erase(nums[i-k]);i - memo[nums[i]] <= k 这两条语句

重新利用 unordered_map 实现, 得到以下代码:

class Solution {
  unordered_set<int> memo;
public:
  bool containsNearbyDuplicate(vector<int>& nums, int k) {
    for (int i = 0; i < nums.size(); ++i) {
      if (memo.find(nums[i]) != memo.end()) 
        return true; 
      memo.insert(nums[i]);
      if (i-k>=0) memo.erase(nums[i-k]);
    }
    return false;
  }
};

所得到测试成绩
32ms
在这个代码中,我还改进了作者 if-else 的结构
我觉得少一个嵌套 if 是比较优美的,所以我坚持这个结构。
因为这个结构,能够有效提前结束。
也就是说

if (memo.find(nums[i]) != memo.end()) 
        return true; 

这个窗口内如果有,那么直接返回true。
和原作者的 if else 哪个更快,真说不上。
我只是觉得,这样写,能够少个 else ,能够看起来不那么头疼。

利用大小判断减少删除元素的时机

class Solution {
  unordered_set<int> memo;
public:
  bool containsNearbyDuplicate(vector<int>& nums, int k) {
    for (int i = 0; i < nums.size(); ++i) {
      if (memo.find(nums[i]) != memo.end()) 
        return true; 
      memo.insert(nums[i]);
      if (memo.size() > k) memo.erase(nums[i-k]);
    }
    return false;
  }
};

代码成绩终于和原作者一样的时间。
28ms

改进循环变量

之前,我一直就有个感觉, 每次都用 nums.size() 是会比 n = nums.size() 会慢。
leetcode 可能在运行代码的时候,可能真的是 运行多次 size() ,会减慢速度。
所以,我再换一个 int n = nums.size()
于是有了以下的代码

class Solution {
  unordered_set<int> memo;
public:
  bool containsNearbyDuplicate(vector<int>& nums, int k) {
    int n = nums.size();
    for (int i = 0; i < n; ++i) {
      if (memo.find(nums[i]) != memo.end()) 
        return true; 
      memo.insert(nums[i]);
      if (memo.size() > k) memo.erase(nums[i-k]);
    }
    return false;
  }
};

于是得到以下的时间, 24ms

24ms

总结:

针对这道题目,可以有以下的提示:

  1. 用unordered_set来进行 k 窗口的构造,可以有效提升时间,能减少 4 ms运行时间
  2. 通过优化 if else 的条件,优化代码的可读性。
  3. 通过更改循环条件, int n = nums.size() 和 i < n 替换 i < nums.size() ,能够手动优化时间 4ms
  4. 通过更改更改特殊情况判断条件,利用unorder_set 的 size() 和 k 判断,能减少 4 ms 。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值