1、問題描述
給定一個整數數組nums,k,t,判斷數組中是否存在兩個不同的索引i,j,使得nums[i]和nums[j]之差的絕對值不超過t,i和j之差的絕對值不超過k。
例如:
輸入: nums = [1,2,3,1], k = 3, t = 0
輸出: true
2、解題思路
解決這道題需要找到一組滿足以下兩個條件的和:
有很多的方法可以實現這一目標:
方法1:在滑動窗口內線性搜索
思想:每一個元素與它之前的個元素進行比較,查看它們的數值之差的絕對值是否在以內。具體實現時,維持一個大小爲的滑動窗口,在這種情況下,第一個條件始終是滿足的,只需要通過線性搜索檢查第二個條件是否滿足就可以了。
複雜度分析:
時間複雜度:,其中每次搜索都需要花費的時間,需要注意的是,每次搜索最多需要比較次,哪怕比大。
空間複雜度:,額外開闢的空間爲常數個。
方法2:將滑動窗口元素組織成自平衡的二叉搜索樹
分析:方法1真正的瓶頸在於檢查第二個條件是否滿足時需要掃描滑動窗口內的所有元素,因此,我們需要考慮有沒有比全掃描更好的方法;
- 一種可能的想法是始終維持窗口內的元素有序,然後檢查左右邊界之差是否滿足第二個條件,這種方法我們雖然可以在的時間內找到元素插入位置, 但將該元素插入到正確位置時還是需要移動個元素,所以時間複雜度還是。
- 另一種想法是將滑動窗口內的元素組織成一個自平衡的二叉搜索樹(大小爲)。然後對於數組中的每個元素,在中找到小於等於的最大數和大於等於的最小數,如果或者成立,則說明滑動窗口中含有滿足第二個條件的兩個元素,否則不含有。
思想:
初始化一棵空的自平衡的二叉搜索樹set;
遍歷nums中的每個元素x,並進行如下操作:
在set上查找大於等於x的最小數s,如果s-x<=t , 則返回true;
在set上查找小於等於x的最大數g,如果x-g<=t, 則返回true;
在set中插入x;
如果set的大小超過了k,則從set中去除最早加入樹中的那個數;
返回false;
複雜度分析:
我們需要遍歷這個長度爲的數組,對於每次遍歷,搜索、插入和刪除操作都需要花費時間,故時間複雜度爲
空間複雜度:存儲二叉搜索樹所佔的額外空間,爲
需要注意的是:
-
當數組中的元素非常大的時候,進行數學運算可能造成溢出(比如2147483647 - (-1))。所以可以考慮使用支持大數的數據類型,例如 long。
-
C++ 中的 std::set,std::set::upper_bound 和 std::set::lower_bound 分別等價於 Java 中的 TreeSet,TreeSet::ceiling 和 TreeSet::floor。Python 標準庫不提供自平衡 BST。
方法3:利用桶的思想在線性時間內解決問題
分析:受桶排序思想的啓發,可以利用桶在線性時間內解決該問題。
桶排序的思想如下所示:
將元素劃分到不同桶中,接着把每個桶再獨立的使用不同的排序算法進行排序,最後按照桶的順序收集所有元素便得到了一個有序數組。
比如對大小在1到100之間的8個數進行排序,首先,我們創建5個桶(區間),這5個桶分別包含的區間是,這8個數中的任意一個都必然在一個桶中,對於值爲的元素來說,其桶標籤爲,這裏。接着我們對每個桶內元素進行排序,最後按照桶的順序收集所有元素。
接着我們通過一個與本問題及其相似的問題來引入算法的詳細步驟:
我們把每個元素看成是一個人的生日,假設你的生日是3月份的某一天,現在需要知道班級中是否有人生日和你的生日間隔在之內,在這裏,我們假設每個月爲30天,很明顯,我們只需要檢查所有生日在2月份、3月份、和4月份的同學就可以了。
之所以能這麼做的原因在於,每個人的生日都屬於一個桶,我們把這個桶稱之爲月份。每個桶包含的區間範圍都是t,這能極大對簡化我們的問題。很顯然,任何不在同一個桶或相鄰桶的兩個元素之間的距離一定是大於t的。
我們把上述提到的桶的思想運用到這個問題中來,我們設計一些桶,讓它們包含區間.。我們把桶當作窗口,於是每次只需要檢查所屬的桶和相鄰桶的元素,終於,我們可以在常量時間內解決在窗口內搜索的問題了。需要注意的是,本問題中每個桶都只包含一個元素,因爲如果超過一個元素,則說明有兩個元素的距離在t之內,可以直接得到問題的答案了。
思想:
初始化一個空桶map;
遍歷nums的每個元素x,並進行如下操作:
獲取元素x所屬的桶編號m=getid(x,w);
如果桶m中已經含有一個元素,則直接返回true;
如果桶m-1中含有一個元素y,並且y與x的距離小於t,則返回true;
如果桶m+1中含有一個元素z,並且z與x的距離小於t,則返回true;
把<m,x>插入map(在桶m爲空,並且相鄰桶沒有重複元素的情況下);
如果x的索引i大於k,從map中刪除<getid(nums[i-k],w),nums[i-k]>;
複雜度分析:
時間複雜度:對於這n個元素中的任意一個來說,最多只需要做3次搜索、1次插入、1次刪除,而這些操都是常量時間的,故時間複雜度爲;
空間複雜度:需要開闢的額外空間主要是散列表,其大小和所包含的元素個數有關,故空間複雜度爲.
3、代碼實現
#include<map>
#include<cmath>
class Solution {
public:
//獲取x所在的桶的id,由於-3/5=0,而我們需要-3/5 = -1
long getId(long x, long w){
return x < 0 ? (x + 1)/w - 1 : x/w;
}
long Abs(long a , long b){
return a < b ? b-a : a-b;
}
bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t) {
if(t < 0){
return false;
}
map<long, long> bucket;
long w = (long)t + 1;
// map<long,long>::iterator iter;
for(int i = 0; i < nums.size(); i++){
// cout<<"第"<<i<<"步:"<<endl;
// for(iter = bucket.begin(); iter != bucket.end(); iter++){
// cout<<"<"<<iter->first<<","<<iter->second<<">"<<endl;
// }
long id = getId(nums[i],w);
if(bucket.find(id) != bucket.end()){
return true;
}
if(bucket.find(id-1) != bucket.end() && Abs(bucket[id-1],nums[i]) <= t){
return true;
}
if(bucket.find(id+1) != bucket.end() && Abs(bucket[id+1],nums[i]) <= t){
return true;
}
bucket[id] = nums[i];
if(i >= k){
bucket.erase(bucket.find(getId(nums[i-k],w)));
}
}
return false;
}
};
【附加】leetcode同類型題目
217. 存在重複元素
219. 存在重複的元素 II