leetcode 219 题解
重要参考文章:
收获
本篇题解是针对 leetcode 219题, 进行时间复杂度进行优化的提升。分别应用了以下几点:
- 用 unordered_set 来进行 k 窗口的构造,可以有效提升时间,能减少 4 ms 运行时间
- 通过优化 if-else 的条件,优化代码的可读性。
- 通过更改循环条件,
int n = nums.size(); i < n;
替换i < nums.size()
,能够手动优化时间 4ms - 通过更改更改特殊情况判断条件,利用 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.
还是很好奇,为什么只有 36 ms, 没有达到别人的 28 ms.
都是基于同一套想法(一次遍历的框架, 每次插入元素到map), 但是没有同一套数据结构 (unordered_set).
首先要确定上面的代码,是否在我测试的时候, 真的还是 28ms 的成绩.
我曾经遇到过拿100%的代码,实际上运行不再是100%了。
于是我得到了下面成绩:
问题是:
那么同一套想法, 我的实现差在哪里,还需要改进哪里,才能有相同的时间?
首先是利用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;
}
};
所得到测试成绩
在这个代码中,我还改进了作者 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;
}
};
代码成绩终于和原作者一样的时间。
改进循环变量
之前,我一直就有个感觉, 每次都用 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
总结:
针对这道题目,可以有以下的提示:
- 用unordered_set来进行 k 窗口的构造,可以有效提升时间,能减少 4 ms运行时间
- 通过优化 if else 的条件,优化代码的可读性。
- 通过更改循环条件, int n = nums.size() 和 i < n 替换 i < nums.size() ,能够手动优化时间 4ms
- 通过更改更改特殊情况判断条件,利用unorder_set 的 size() 和 k 判断,能减少 4 ms 。