Medium
题目描述
给定一个正整数 n 和一个整数 target。
返回长度为 n 的整数数组,使得:
- 数组元素的和等于
target - 数组元素绝对值组成大小为
n的排列
如果不存在这样的数组,返回空数组。
大小为 n 的排列是整数 1, 2, ..., n 的重新排列。
示例 1:
输入:n = 3, target = 0
输出:[-3,1,2]
解释:
和为 0 且绝对值组成大小为 3 的排列的数组有:
[-3, 1, 2]、[-3, 2, 1]、[-2, -1, 3]、[-2, 3, -1]、[-1, -2, 3]、[-1, 3, -2]、[1, -3, 2]、[1, 2, -3]、[2, -3, 1]、[2, 1, -3]、[3, -2, -1]、[3, -1, -2]
其中字典序最小的是 [-3, 1, 2]。
示例 2:
输入:n = 1, target = 10000000000
输出:[]
解释:
不存在和为 10000000000 且绝对值组成大小为 1 的排列的数组。因此答案是 []。
约束条件:
1 <= n <= 10^5-10^10 <= target <= 10^10
解题思路
这道题的核心思路是贪心算法,我们需要构造一个字典序最小的数组,其元素绝对值为 1 到 n 的排列,且和为目标值。
分析过程:
可行性判断:首先考虑所有数为正数的情况,此时和为
S = n*(n+1)/2。通过将某些正数变为负数来调整总和。如果将数字x从正变负,总和减少2x。边界条件:
- 如果
target > S,无解(即使全为正数也达不到) - 如果
target < -S,无解(即使全为负数也达不到) - 如果
(S - target)为奇数,无解(因为每次翻转改变偶数)
- 如果
贪心策略:设
D = S - target,我们需要通过翻转一些数字使总和减少D。为了得到字典序最小的结果,应该从大到小考虑翻转数字。这样可以确保较小的正数尽可能保持正号,出现在数组前面。构造结果:确定每个数字的符号后,按升序排列即可得到字典序最小的结果。
算法步骤:
- 计算所有正数的和 S
- 检查可行性
- 从 n 到 1 贪心地选择要翻转的数字
- 构造最终数组并排序
代码实现
class Solution {
public:
vector<int> lexSmallestNegatedPerm(int n, long long target) {
long long S = (long long)n * (n + 1) / 2;
// Check feasibility
if (target > S || target < -S || (S - target) % 2 == 1) {
return {};
}
long long D = S - target;
vector<bool> isNegative(n + 1, false);
// Greedily choose numbers to flip
for (int i = n; i >= 1 && D > 0; i--) {
if (2LL * i <= D) {
isNegative[i] = true;
D -= 2LL * i;
}
}
// Build result array
vector<int> result;
for (int i = 1; i <= n; i++) {
result.push_back(isNegative[i] ? -i : i);
}
sort(result.begin(), result.end());
return result;
}
};
class Solution:
def lexSmallestNegatedPerm(self, n: int, target: int) -> List[int]:
S = n * (n + 1) // 2
# Check feasibility
if target > S or target < -S or (S - target) % 2 == 1:
return []
D = S - target
is_negative = [False] * (n + 1)
# Greedily choose numbers to flip
for i in range(n, 0, -1):
if D > 0 and 2 * i <= D:
is_negative[i] = True
D -= 2 * i
# Build result array
result = []
for i in range(1, n + 1):
result.append(-i if is_negative[i] else i)
result.sort()
return result
public class Solution {
public int[] LexSmallestNegatedPerm(int n, long target) {
long S = (long)n * (n + 1) / 2;
// Check feasibility
if (target > S || target < -S || (S - target) % 2 == 1) {
return new int[0];
}
long D = S - target;
bool[] isNegative = new bool[n + 1];
// Greedily choose numbers to flip
for (int i = n; i >= 1 && D > 0; i--) {
if (2L * i <= D) {
isNegative[i] = true;
D -= 2L * i;
}
}
// Build result array
int[] result = new int[n];
for (int i = 1; i <= n; i++) {
result[i - 1] = isNegative[i] ? -i : i;
}
Array.Sort(result);
return result;
}
}
var lexSmallestNegatedPerm = function(n, target) {
const sum = n * (n + 1) / 2;
if (Math.abs(target) > sum || (sum - target) % 2 !== 0) {
return [];
}
const result = [];
const used = new Set();
let remaining = target;
for (let pos = 0; pos < n; pos++) {
for (let val = -n; val <= n; val++) {
if (val === 0 || used.has(Math.abs(val))) continue;
const newRemaining = remaining - val;
const remainingPositions = n - pos - 1;
if (remainingPositions === 0) {
if (newRemaining === 0) {
result.push(val);
return result;
}
continue;
}
const availableNums = [];
for (let i = 1; i <= n; i++) {
if (!used.has(i) && i !== Math.abs(val)) {
availableNums.push(i);
}
}
if (availableNums.length !== remainingPositions) continue;
let minSum = 0, maxSum = 0;
for (let num of availableNums) {
minSum -= num;
maxSum += num;
}
if (newRemaining >= minSum && newRemaining <= maxSum &&
(maxSum - newRemaining) % 2 === 0) {
result.push(val);
used.add(Math.abs(val));
remaining = newRemaining;
break;
}
}
}
return [];
};
复杂度分析
| 复杂度类型 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(n log n) | 主要开销在最后的排序步骤 |
| 空间复杂度 | O(n) | 需要额外的数组存储结果和标记 |