题目
标题和出处
标题:使数组互补的最少操作次数
难度
7 级
题目描述
要求
给定一个长度为偶数 n \texttt{n} n 的整数数组 nums \texttt{nums} nums 和一个整数 limit \texttt{limit} limit。每一次操作,可以将 nums \texttt{nums} nums 中的任何整数替换为 1 \texttt{1} 1 到 limit \texttt{limit} limit 之间的另一个整数,包含 1 \texttt{1} 1 和 limit \texttt{limit} limit。
如果对于所有下标 i \texttt{i} i(下标从 0 \texttt{0} 0 开始), nums[i] + nums[n - 1 - i] \texttt{nums[i] + nums[n - 1 - i]} nums[i] + nums[n - 1 - i] 都等于同一个数,则数组 nums \texttt{nums} nums 是互补的。例如,数组 [1,2,3,4] \texttt{[1,2,3,4]} [1,2,3,4] 是互补的,因为对于所有下标 i \texttt{i} i, nums[i] + nums[n - 1 - i] = 5 \texttt{nums[i] + nums[n - 1 - i] = 5} nums[i] + nums[n - 1 - i] = 5。
返回使数组 nums \texttt{nums} nums 互补的最少操作次数。
示例
示例 1:
输入:
nums
=
[1,2,4,3],
limit
=
4
\texttt{nums = [1,2,4,3], limit = 4}
nums = [1,2,4,3], limit = 4
输出:
1
\texttt{1}
1
解释:经过
1
\texttt{1}
1 次操作,可以将数组 nums 变成
[1,2,2,3]
\texttt{[1,2,2,3]}
[1,2,2,3]。
nums[0]
+
nums[3]
=
1
+
3
=
4
\texttt{nums[0] + nums[3] = 1 + 3 = 4}
nums[0] + nums[3] = 1 + 3 = 4
nums[1]
+
nums[2]
=
2
+
2
=
4
\texttt{nums[1] + nums[2] = 2 + 2 = 4}
nums[1] + nums[2] = 2 + 2 = 4
nums[2]
+
nums[1]
=
2
+
2
=
4
\texttt{nums[2] + nums[1] = 2 + 2 = 4}
nums[2] + nums[1] = 2 + 2 = 4
nums[3]
+
nums[0]
=
3
+
1
=
4
\texttt{nums[3] + nums[0] = 3 + 1 = 4}
nums[3] + nums[0] = 3 + 1 = 4
对于每个
i
\texttt{i}
i,
nums[i]
+
nums[n-1-i]
=
4
\texttt{nums[i] + nums[n-1-i] = 4}
nums[i] + nums[n-1-i] = 4,所以
nums
\texttt{nums}
nums 是互补的。
示例 2:
输入:
nums
=
[1,2,2,1],
limit
=
2
\texttt{nums = [1,2,2,1], limit = 2}
nums = [1,2,2,1], limit = 2
输出:
2
\texttt{2}
2
解释:经过
2
\texttt{2}
2 次操作,可以将数组
nums
\texttt{nums}
nums 变成
[2,2,2,2]
\texttt{[2,2,2,2]}
[2,2,2,2]。不能将任何数字变更为
3
\texttt{3}
3,因为
3
>
limit
\texttt{3} > \texttt{limit}
3>limit。
示例 3:
输入:
nums
=
[1,2,1,2],
limit
=
2
\texttt{nums = [1,2,1,2], limit = 2}
nums = [1,2,1,2], limit = 2
输出:
0
\texttt{0}
0
解释:
nums
\texttt{nums}
nums 已经是互补的。
数据范围
- n = nums.length \texttt{n} = \texttt{nums.length} n=nums.length
- 2 ≤ n ≤ 10 5 \texttt{2} \le \texttt{n} \le \texttt{10}^\texttt{5} 2≤n≤105
- 1 ≤ nums[i] ≤ limit ≤ 10 5 \texttt{1} \le \texttt{nums[i]} \le \texttt{limit} \le \texttt{10}^\texttt{5} 1≤nums[i]≤limit≤105
- n \texttt{n} n 是偶数
解法
思路和算法
当数组 nums \textit{nums} nums 互补时,对于所有符合 0 ≤ i < j < n 0 \le i < j < n 0≤i<j<n 且 i + j = n − 1 i + j = n - 1 i+j=n−1 的下标 i i i 和 j j j, nums [ i ] + nums [ j ] \textit{nums}[i] + \textit{nums}[j] nums[i]+nums[j] 都是相同的值。
以下将符合 0 ≤ i < j < n 0 \le i < j < n 0≤i<j<n 且 i + j = n − 1 i + j = n - 1 i+j=n−1 的下标 i i i 和下标 j j j 处的两个元素称为一对元素。
由于数组 nums \textit{nums} nums 中的每个整数可以替换成 [ 1 , limit ] [1, \textit{limit}] [1,limit] 范围中的任意整数,因此每一对元素的和的取值范围是 [ 2 , limit × 2 ] [2, \textit{limit} \times 2] [2,limit×2]。令 sumLimit = limit × 2 \textit{sumLimit} = \textit{limit} \times 2 sumLimit=limit×2,则每一对元素的和的取值范围是 [ 2 , sumLimit ] [2, \textit{sumLimit}] [2,sumLimit]。
用 target \textit{target} target 表示数组 nums \textit{nums} nums 互补时的每一对元素的和,则 2 ≤ target ≤ sumLimit 2 \le \textit{target} \le \textit{sumLimit} 2≤target≤sumLimit。对于一对元素 nums [ i ] \textit{nums}[i] nums[i] 和 nums [ j ] \textit{nums}[j] nums[j],将初始时这一对元素的和记为 sum \textit{sum} sum,为了使这一对元素的和变成 target \textit{target} target,需要执行 0 0 0 次至 2 2 2 次操作。
-
如果执行 0 0 0 次操作,则这一对元素的和一定是 sum \textit{sum} sum。当 target = sum \textit{target} = \textit{sum} target=sum 时,执行 0 0 0 次操作即可使这一对元素的和等于 target \textit{target} target。
-
如果执行 1 1 1 次操作,则将其中的较大元素替换成 1 1 1 之后可以得到元素和最小值 min ( nums [ i ] , nums [ j ] ) + 1 \min(\textit{nums}[i], \textit{nums}[j]) + 1 min(nums[i],nums[j])+1,将其中的较小元素替换成 limit \textit{limit} limit 之后可以得到元素和最大值 max ( nums [ i ] , nums [ j ] ) + limit \max(\textit{nums}[i], \textit{nums}[j]) + \textit{limit} max(nums[i],nums[j])+limit。当 min ( nums [ i ] , nums [ j ] ) + 1 ≤ target ≤ max ( nums [ i ] , nums [ j ] ) + limit \min(\textit{nums}[i], \textit{nums}[j]) + 1 \le \textit{target} \le \max(\textit{nums}[i], \textit{nums}[j]) + \textit{limit} min(nums[i],nums[j])+1≤target≤max(nums[i],nums[j])+limit 时,执行 1 1 1 次操作即可使这一对元素的和等于 target \textit{target} target。
-
当 target < min ( nums [ i ] , nums [ j ] ) + 1 \textit{target} < \min(\textit{nums}[i], \textit{nums}[j]) + 1 target<min(nums[i],nums[j])+1 或 target > max ( nums [ i ] , nums [ j ] ) + limit \textit{target} > \max(\textit{nums}[i], \textit{nums}[j]) + \textit{limit} target>max(nums[i],nums[j])+limit 时,需要执行 2 2 2 次操作才能使这一对元素的和等于 target \textit{target} target。
令 minSumOne = min ( nums [ i ] , nums [ j ] ) + 1 \textit{minSumOne} = \min(\textit{nums}[i], \textit{nums}[j]) + 1 minSumOne=min(nums[i],nums[j])+1, maxSumOne = max ( nums [ i ] , nums [ j ] ) + limit \textit{maxSumOne} = \max(\textit{nums}[i], \textit{nums}[j]) + \textit{limit} maxSumOne=max(nums[i],nums[j])+limit。根据上述分析可知, target \textit{target} target 在不同区间对应的一对元素的操作次数如下。
-
当 target \textit{target} target 位于区间 [ 2 , minSumOne − 1 ] [2, \textit{minSumOne} - 1] [2,minSumOne−1] 时,操作次数是 2 2 2。
-
当 target \textit{target} target 位于区间 [ minSumOne , sum − 1 ] [\textit{minSumOne}, \textit{sum} - 1] [minSumOne,sum−1] 时,操作次数是 1 1 1。
-
当 target = sum \textit{target} = \textit{sum} target=sum 时,操作次数是 0 0 0。
-
当 target \textit{target} target 位于区间 [ sum + 1 , maxSumOne ] [\textit{sum} + 1, \textit{maxSumOne}] [sum+1,maxSumOne] 时,操作次数是 1 1 1。
-
当 target \textit{target} target 位于区间 [ maxSumOne + 1 , sumLimit ] [\textit{maxSumOne} + 1, \textit{sumLimit}] [maxSumOne+1,sumLimit] 时,操作次数是 2 2 2。
为了得到使数组互补的最少操作次数,需要对 2 ≤ target ≤ sumLimit 2 \le \textit{target} \le \textit{sumLimit} 2≤target≤sumLimit 的每个 target \textit{target} target 计算操作次数。由于每一对元素的和的范围是 [ 2 , sumLimit ] [2, \textit{sumLimit}] [2,sumLimit],因此直接计算每个 target \textit{target} target 的操作次数的时间复杂度过高。为了降低时间复杂度,需要使用差分数组。
创建长度为 sumLimit + 1 \textit{sumLimit} + 1 sumLimit+1 的差分数组 diffs \textit{diffs} diffs,对于 1 ≤ k ≤ sumLimit 1 \le k \le \textit{sumLimit} 1≤k≤sumLimit, diffs [ k ] \textit{diffs}[k] diffs[k] 表示 target = k \textit{target} = k target=k 和 target = k − 1 \textit{target} = k - 1 target=k−1 对应的操作次数之差。对于每一对元素 nums [ i ] \textit{nums}[i] nums[i] 和 nums [ j ] \textit{nums}[j] nums[j],更新差分数组的做法如下。
-
将 diffs [ minSumOne ] \textit{diffs}[\textit{minSumOne}] diffs[minSumOne] 的值减 1 1 1。
-
将 diffs [ sum ] \textit{diffs}[\textit{sum}] diffs[sum] 的值减 1 1 1。
-
如果 sum < sumLimit \textit{sum} < \textit{sumLimit} sum<sumLimit,将 diffs [ sum + 1 ] \textit{diffs}[\textit{sum} + 1] diffs[sum+1] 的值加 1 1 1。
-
如果 maxSumOne < sumLimit \textit{maxSumOne} < \textit{sumLimit} maxSumOne<sumLimit,将 diffs [ maxSumOne + 1 ] \textit{diffs}[\textit{maxSumOne} + 1] diffs[maxSumOne+1] 的值加 1 1 1。
遍历数组 nums \textit{nums} nums 之后,遍历差分数组,计算每个元素和对应的操作次数,得到最少操作次数。具体做法是,将操作次数初始化为 n n n,表示数组中的所有元素都需要替换,对于 1 ≤ i ≤ sumLimit 1 \le i \le \textit{sumLimit} 1≤i≤sumLimit,将 diffs [ i ] \textit{diffs}[i] diffs[i] 加到操作次数,即可得到元素和 i i i 对应的操作次数。遍历差分数组结束之后,即可得到最少操作次数。
证明
上述解法中,对于每一对元素分别考虑 5 5 5 个区间,按照区间从小到大的顺序,每个区间对应的操作次数依次是 2 2 2、 1 1 1、 0 0 0、 1 1 1、 2 2 2。
如果每个区间都非空,即区间的长度大于等于 1 1 1,则上述解法可以得到正确的结果。需要证明:如果存在空区间,即区间的长度等于 0 0 0,上述解法仍可以得到正确的结果。
操作次数是 0 0 0 的区间一定包含一个数,因此不为空。对于其余 4 4 4 个区间,为了方便分析,假设 nums [ i ] ≤ nums [ j ] \textit{nums}[i] \le \textit{nums}[j] nums[i]≤nums[j],则每个变量的值如下: sumLimit = limit × 2 \textit{sumLimit} = \textit{limit} \times 2 sumLimit=limit×2, sum = nums [ i ] + nums [ j ] \textit{sum} = \textit{nums}[i] + \textit{nums}[j] sum=nums[i]+nums[j], minSumOne = nums [ i ] + 1 \textit{minSumOne} = \textit{nums}[i] + 1 minSumOne=nums[i]+1, maxSumOne = nums [ j ] + limit \textit{maxSumOne} = \textit{nums}[j] + \textit{limit} maxSumOne=nums[j]+limit。则其余 4 4 4 个区间的等价表示如下。
-
[ 2 , minSumOne − 1 ] = [ 2 , nums [ i ] ] [2, \textit{minSumOne} - 1] = [2, \textit{nums}[i]] [2,minSumOne−1]=[2,nums[i]];
-
[ minSumOne , sum − 1 ] = [ nums [ i ] + 1 , nums [ i ] + nums [ j ] − 1 ] [\textit{minSumOne}, \textit{sum} - 1] = [\textit{nums}[i] + 1, \textit{nums}[i] + \textit{nums}[j] - 1] [minSumOne,sum−1]=[nums[i]+1,nums[i]+nums[j]−1];
-
[ sum + 1 , maxSumOne ] = [ nums [ i ] + nums [ j ] + 1 , nums [ j ] + limit ] [\textit{sum} + 1, \textit{maxSumOne}] = [\textit{nums}[i] + \textit{nums}[j] + 1, \textit{nums}[j] + \textit{limit}] [sum+1,maxSumOne]=[nums[i]+nums[j]+1,nums[j]+limit];
-
[ maxSumOne + 1 , sumLimit ] = [ nums [ j ] + limit + 1 , limit × 2 ] [\textit{maxSumOne} + 1, \textit{sumLimit}] = [\textit{nums}[j] + \textit{limit} + 1, \textit{limit} \times 2] [maxSumOne+1,sumLimit]=[nums[j]+limit+1,limit×2]。
用 [ start , end ] [\textit{start}, \textit{end}] [start,end] 表示每个区间,则区间的长度是 max ( 0 , end − start + 1 ) \max(0, \textit{end} - \textit{start} + 1) max(0,end−start+1)。由于 1 ≤ nums [ i ] ≤ nums [ j ] ≤ limit 1 \le \textit{nums}[i] \le \textit{nums}[j] \le \textit{limit} 1≤nums[i]≤nums[j]≤limit,因此这 4 4 4 个区间都满足 end ≥ start − 1 \textit{end} \ge \textit{start} - 1 end≥start−1,即当区间为空时一定有 end = start − 1 \textit{end} = \textit{start} - 1 end=start−1。
用区间 [ start , end ] [\textit{start}, \textit{end}] [start,end] 更新差分数组时,做法是将差分数组的下标 start \textit{start} start 处的元素加 1 1 1 并将下标 end + 1 \textit{end} + 1 end+1 处的元素减 1 1 1,或者将差分数组的下标 start \textit{start} start 处的元素减 1 1 1 并将下标 end + 1 \textit{end} + 1 end+1 处的元素加 1 1 1。当区间 [ start , end ] [\textit{start}, \textit{end}] [start,end] 为空时, end + 1 = start \textit{end} + 1 = \textit{start} end+1=start,因此更新差分数组的效果是两次更新抵消,不会更新差分数组的任何元素值。
因此,如果存在空区间,上述解法仍可以得到正确的结果。
代码
class Solution {
public int minMoves(int[] nums, int limit) {
int sumLimit = limit * 2;
int[] diffs = new int[sumLimit + 1];
int n = nums.length;
for (int i = 0, j = n - 1; i < j; i++, j--) {
int sum = nums[i] + nums[j];
int minSumOne = Math.min(nums[i], nums[j]) + 1;
int maxSumOne = Math.max(nums[i], nums[j]) + limit;
diffs[minSumOne]--;
diffs[sum]--;
if (sum < sumLimit) {
diffs[sum + 1]++;
}
if (maxSumOne < sumLimit) {
diffs[maxSumOne + 1]++;
}
}
int minimumMoves = n, moves = n;
for (int i = 1; i <= sumLimit; i++) {
moves += diffs[i];
minimumMoves = Math.min(minimumMoves, moves);
}
return minimumMoves;
}
}
复杂度分析
-
时间复杂度: O ( n + limit ) O(n + \textit{limit}) O(n+limit),其中 n n n 是数组 nums \textit{nums} nums 的长度, limit \textit{limit} limit 是允许替换成的最大整数。需要创建长度为 limit × 2 + 1 \textit{limit} \times 2 + 1 limit×2+1 的差分数组,需要遍历数组 nums \textit{nums} nums 一次更新差分数组的值,需要遍历差分数组一次计算最少操作次数,因此时间复杂度是 O ( n + limit ) O(n + \textit{limit}) O(n+limit)。
-
空间复杂度: O ( limit ) O(\textit{limit}) O(limit),其中 limit \textit{limit} limit 是允许替换成的最大整数。需要创建长度为 limit × 2 + 1 \textit{limit} \times 2 + 1 limit×2+1 的差分数组。