题目大意:
给定一个整数序列nums和两个整数t和k,要求检查能否在该序列中找到两个元素nums[i]与nums[j],满足:i与j的差的绝对值小于k且nums[i]与nums[j]的差的绝对值小于t。
解题思路:
给定一个整数序列nums和两个整数t和k,要求检查能否在该序列中找到两个元素nums[i]与nums[j],满足:i与j的差的绝对值小于k且nums[i]与nums[j]的差的绝对值小于t。
解题思路:
本题题面很简单,解题思路也很明了:使用滑动窗口的方式遍历数组。因为要求i与i的差的绝对值小于i,因此需要比较k+1个元素的值,窗口长度应当为k+1。窗口中的值需要按从小到大的顺序排列好,每遍历一个新的元素,将元素按从小到大的顺序插入到窗口中的合适位置,并返回它与左右元素的差值绝对值中的较小者。如果该值不大于t,则返回true,否则继续遍历下一个元素。每遍历一个元素,窗口整体向右平移一位。
由于窗口的滑动涉及到节点的插入与删除,用数组的话会比较繁琐,因此我想到用单链表来存储窗。具体实现的代码如下:
struct linknode{
int data; //数据域
linknode* next;
linknode(linknode *ptr=NULL){ next=ptr; } //构造函数
};
class linklist{
public:
linklist(){ first=NULL;}; //初始化,注意构造函数是无返回值的
void remove(int x); //移除值为指定数据的节点
long long insert(int x); //插入节点,注意是按顺序插的。返回值是插入值和左右元素的差值
long long nearest_dis(); //相邻节点距离的最小值
private:
linknode* first; //头结点
int maxsize; //链表的最大长度,为k
};
void linklist::remove(int x){
if(first==NULL) return; //空链表不可移除
linknode *p=first,*q;
while(p!=NULL&&p->data!=x)
{
q=p; //记录前一个节点
p=p->next;
}
if(p==NULL) return;
if(p==first) //如果摘的是第一个节点
first=first->next;
else
q->next=p->next;
delete[] p;
}
long long linklist::insert(int x){
if(first==NULL) { linknode* t=new linknode; t->data=x; t->next=NULL; first=t; return 2*(long long)INT_MAX+2; } //如果是空链表的话,注意返回值不影响结果
linknode *p=first,*q;
while(p!=NULL&&p->data<x)
{
q=p;
p=p->next;
}
linknode* r=new linknode;
r->data=x;
r->next=NULL;
if(p==NULL) //插入到最后一个位置
{
q->next=r;
return (long long)(r->data)-(long long)(q->data);
}
else if(p==first) //插入到头部
{
first=r;
r->next=p;
return (long long)(p->data)-(long long)(r->data);
}
else //插入到中间
{
q->next=r; r->next=p;
return min((long long)(p->data)-(long long)(r->data),(long long)(r->data)-(long long)(q->data));
}
}
long long linklist::nearest_dis(){
if(first==NULL) //如果是空链表
return 2*(long long)INT_MAX+2;
linknode* p=first;
long long mindis=2*(long long)INT_MAX+2;
while(p->next!=NULL)
{
mindis=min((long long)mindis,(long long)((p->next)->data)-(long long)(p->data));
p=p->next;
}
return mindis;
}
class Solution {
public:
bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t) {
int leng=nums.size();
long long u;
linklist lkl;
if(leng<k+1) k=leng-1;
int j=k+1;
for(int i=0;i<k+1;i++) //现将前k个节点插入到原始链表中
{
int c=nums[i];
lkl.insert(c); //按序插入
}
u=lkl.nearest_dis();
if(u<=(long long)t) return true;
while(j<leng) //每次删一个再插一个,比较与t的大小
{
int p=nums[j];
long long q;
lkl.remove(nums[j-k-1]);
q=lkl.insert(p);
if(q<=t) return true;
j++;
}
return false;
}
};
链表中的元素是从小到大排列好的,以方便比较差值,同时要兼顾两int型数据相减可能会超出整形数范围的情况。
本地测试无误后,将结果提交。遗憾的是,测试的40个用例只通过了39个,规模最大的用例不幸超时,其运行时间足足有1秒多。参考了一下discuss中的解法,我发现高票解答基本上也都是通过滑动窗口的方式解决。不一样的是,采用的数据结构以哈希表、set为主。以如下代码为例:
class Solution {
public:
bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t) { //使用哈希表解决
if(k<1||t<0) return false; //特殊情况处理
map<long long,long long> Hash;
int leng=nums.size();
for(int i=0;i<leng;i++)
{
long long jud=(long long)nums[i]+INT_MAX; //整体偏移,避免过零处的取余运算出错
long long comp;
comp=jud/((long long)t+1); //注意t有没有可能是INT_MAX
if(Hash.find(comp)!=Hash.end()||(Hash.find(comp+1)!=Hash.end()&&Hash[comp+1]-jud<=t)||(Hash.find(comp-1)!=Hash.end()&&jud-Hash[comp-1]<=t))
return true;
if(Hash.size()>=k) //此时要将最左侧的数据推出
{
map<long long,long long>::iterator it=Hash.find(((long long)nums[i-k]+INT_MAX)/((long long)t+1));
Hash.erase(it);
}
Hash[comp]=jud;
}
return false;
}
};
相比较我的解法,他使用了一个map来存储窗口,map的键(key)为元素的值除以t+1,值(value)为元素的值。这样做的目的在于简化比较的流程:每遍历到一个新的元素时,将其除以t+1。首先检视map中有无键等于该值的元素,若有,则说明它们之间的差值必然小于t,又由于它们同时在窗口内,下标差必然小于k,满足条件,返回true;若没有,则检视map中有无键等于该值 +1或-1的元素,若找到,再比较该元素的value与当前遍历值差值的绝对值,若小于t,则同样满足条件,返回true。
如此一来,则无需将元素从小到大排列。同时利用已经封装好的函数,也大大简化了代码的书写。事实证明,这种方法比我的方法高效得多,即使是最大规模用例,也仅仅耗时26ms。可见选择合适的数据结构与容器对于程序时间复杂度的降低是多么的重要!