1630. 等差子数组
难度中等
如果一个数列由至少两个元素组成,且每两个连续元素之间的差值都相同,那么这个序列就是 等差数列 。更正式地,数列 s
是等差数列,只需要满足:对于每个有效的 i
, s[i+1] - s[i] == s[1] - s[0]
都成立。
例如,下面这些都是 等差数列 :
1, 3, 5, 7, 9 7, 7, 7, 7 3, -1, -5, -9
下面的数列 不是等差数列 :
1, 1, 2, 5, 7
给你一个由 n
个整数组成的数组 nums
,和两个由 m
个整数组成的数组 l
和 r
,后两个数组表示 m
组范围查询,其中第 i
个查询对应范围 [l[i], r[i]]
。所有数组的下标都是 从 0 开始 的。
返回 boolean
元素构成的答案列表 answer
。如果子数组 nums[l[i]], nums[l[i]+1], ... , nums[r[i]]
可以 重新排列 形成 等差数列 ,answer[i]
的值就是 true
;否则answer[i]
的值就是 false
。
示例 1:
输入:nums =[4,6,5,9,3,7]
, l =[0,0,2]
, r =[2,3,5]
输出:[true,false,true]
解释: 第 0 个查询,对应子数组 [4,6,5] 。可以重新排列为等差数列 [6,5,4] 。 第 1 个查询,对应子数组 [4,6,5,9] 。无法重新排列形成等差数列。 第 2 个查询,对应子数组[5,9,3,7] 。
可以重新排列为等差数列[3,5,7,9] 。
示例 2:
输入:nums = [-12,-9,-3,-12,-6,15,20,-25,-20,-15,-10], l = [0,1,6,4,8,7], r = [4,4,9,7,9,10] 输出:[false,true,false,false,true,true]
提示:
n == nums.length
m == l.length
m == r.length
2 <= n <= 500
1 <= m <= 500
0 <= l[i] < r[i] < n
-10^5 <= nums[i] <= 10^5
思路:这道题很容易想到的思路就是我们对于每一个查询,对[L,R]区间内的数进行排序,然后判断每一对相邻的数的差值是不是都是一样的。这样总的复杂度为O(m*nlgn)。然而每次拷贝数据的代价是很高昂的,一个容易想到的优化思路是,如果存在两个查询之间存在包含关系或重叠部分,是可以为下一次的排序进行加速:
包含关系:如两个查询[1,10],[3,7],我们先查询[3,7],并对[3,7]之间的数进行排序,判断差分。到下一次查询[1,10]时,我们可以通过二分查找的方式将[1,2],[8,10]这两个区间上的数以有序的方式插进[3,7]。
重叠部分:如两个查询[3,7],[5,9],我们可以拆成[3,5],[5,9],可知这两部分都满足有序,此时可以用归并。
然而这样做编码复杂度会非常高,同时也无法加速无重叠 、无包含的查询。
我们从等差数列本身的性质入手,等差数列满足每一个相邻数对的查都是公差d。设有一个长度为n的等差数列,最大值为max_num,最小值为min_num,那么公差可以按照如下方式求解:
当我们有了公差之后,我们可以反推出这个数列内所有的数
因此本题的思路可以转换为,对每一个查询[L,R],算出公差d,并判断区间内每一个数是否满足下式且只出现一次(值得注意的是,如果公差为d即最大值等于最小值则一定是等差数列)。
class Solution {
public:
vector checkArithmeticSubarrays(vector& nums, vector& l, vector& r) {
int n = nums.size(), min_num[n + 5][n + 5], max_num[n + 5][n + 5], L, R, t, idx, len, d, tempIdx, diff, min_value, max_value;
const int maxn = 2e5 +7;
bool existItemIdx[maxn];
vector isArithmetic;
for(len = 1; len <= n; ++ len){
for(L = 0; L + len <= n; ++ L){
if(len == 1){
min_num[L][L] = max_num[L][L] = nums[L];
}else{
R = L + len - 1;
t = L + (len >> 1) -1;
min_num[L][R] = min(min_num[L][t], min_num[t + 1][R]);
max_num[L][R] = max(max_num[L][t], max_num[t + 1][R]);
}
}
}
for(idx = 0; idx < l.size(); ++ idx){
L = l[idx];
R = r[idx];
min_value = min_num[L][R];
max_value = max_num[L][R];
if(R - L <= 1 || max_value == min_value){//长度小于等于2或者最大最小值相等即公差为0
isArithmetic.push_back(true);
}else{
d = (max_value - min_value) / (R - L);
if(d * (R - L) == max_value - min_value){
memset(existItemIdx, false, sizeof(existItemIdx));
for(tempIdx = L; tempIdx <= R; ++ tempIdx){\
diff = nums[tempIdx] - min_value;
if(diff % d != 0 || existItemIdx[diff / d]){
isArithmetic.push_back(false);
break;
}else{
existItemIdx[diff / d] = true;
}
if(tempIdx == R){
isArithmetic.push_back(true);
}
}
}else{
isArithmetic.push_back(false);
}
}
}
return isArithmetic;
}
};
上述使用区间dp来求取区间最大、最小值有点大材小用了,也可以用对每一个查询遍历的方式。
class Solution {
public:
vector checkArithmeticSubarrays(vector& nums, vector& l, vector& r) {
int n = nums.size(), L, R, t, idx, len, d, tempIdx, diff, min_value, max_value;
const int maxn = 2e5 +7;
bool existItemIdx[maxn];
vector isArithmetic;
for(idx = 0; idx < l.size(); ++ idx){
L = l[idx];
R = r[idx];
min_value = max_value = nums[L];
for(tempIdx = L + 1; tempIdx <= R; ++ tempIdx){
min_value = min(min_value, nums[tempIdx]);
max_value = max(max_value, nums[tempIdx]);
}
if(R - L <= 1 || max_value == min_value){//长度小于等于2或者最大最小值相等即公差为0
isArithmetic.push_back(true);
}else{
d = (max_value - min_value) / (R - L);
if(d * (R - L) == max_value - min_value){
memset(existItemIdx, false, sizeof(existItemIdx));
for(tempIdx = L; tempIdx <= R; ++ tempIdx){\
diff = nums[tempIdx] - min_value;
if(diff % d != 0 || existItemIdx[diff / d]){
isArithmetic.push_back(false);
break;
}else{
existItemIdx[diff / d] = true;
}
if(tempIdx == R){
isArithmetic.push_back(true);
}
}
}else{
isArithmetic.push_back(false);
}
}
}
return isArithmetic;
}
};