2026-04-06:字典序最小和为目标值且绝对值是排列的数组。用go语言,给你一个正整数 n 和一个整数 target。
你需要构造一个长度为 n 的整数数组,要求同时满足:
1.数组中所有元素的总和必须等于 target。
2.把数组里每个元素取绝对值以后,得到的这 n 个数必须是 1,2,...,n 的某种排列(也就是每个数 1 到 n 都恰好出现一次,顺序可以不同)。
3.若不存在满足上述条件的数组,则返回一个空数组 []。
另外,在所有满足条件的数组里,你要返回字典序最小的那个。
字典序比较规则:对任意两个数组 a 和 b,从左到右找到它们的第一个不同位置 i,如果 a[i] < b[i],则认为 a 的字典序小于 b。
因此,总波动值为 1 + 1 + 1 = 3。
1 <= n <= 100000。
-10000000000 <= target <= 10000000000。
输入: 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]。
题目来自力扣3752。
分步详细过程
我们以n=3,target=0为例,完整拆解解题的每一步逻辑,全程不写代码,只讲原理和操作。
第一步:判断是否存在合法数组
首先要确定有没有满足条件的数组,这是解题的前提,核心分3个小步骤:
-
计算1~n的总和最大值 1到n所有数相加的总和是固定值,公式:
总和 = n*(n+1)/2n=3时:1+2+3=6,这个值是所有元素全为正数时的总和,记为mx。 -
判断target的数值范围是否合法 数组元素是1~n加正负号,所以总和的最大值是mx(全正),最小值是-mx(全负)。 如果target > mx 或者 target < -mx,直接判定无合法数组。 n=3,target=0:0在-6和6之间,范围合法。
-
判断奇偶性是否匹配 我们把一个正数
x改成负数-x,总和的变化是:减少了2x(总和 = 原总和 - 2x)。 这意味着:原总和 - 目标值 必须是偶数(因为是2的倍数),否则无法通过改符号得到target。 公式:(mx - target) % 2 == 0 才合法。 n=3,mx=6,target=0:6-0=6,6是偶数,奇偶性合法。✅ 三个条件都满足,存在合法数组;任意一个不满足,直接返回空数组。
第二步:计算需要取负号的数字总和
这是核心计算步骤,目的是找到哪些数字需要加负号:
- 设:需要取负号的数字的绝对值之和为
negS; - 推导公式:
negS = (mx - target) / 2; 原理:原总和mx,每把一个数x变负,总和减2x,总共需要减mx-target,所以总负号数的和就是这个值的一半。 - 代入计算:n=3,mx=6,target=0 → negS=(6-0)/2=3。 结论:我们需要从1、2、3中选若干个数,让它们的和等于3,这些数最终要变成负数。
第三步:确定哪些数字取负号(保证字典序最小)
字典序最小的规则:左边的数字尽可能小(优先用大负数),右边的数字尽可能大。 因为负数 < 正数,所以要让数组字典序最小,必须左边优先放绝对值大的负数。
操作规则:从最大的数字开始,依次尝试取负,直到选中的数字总和等于negS:
- 从n=3开始检查:3 ≤ 3(negS),选中3取负,剩余需要的和:3-3=0;
- 剩余需要的和为0,剩下的数字(2、1)都不取负,保持正数;
- 最终确定:只有数字3需要取负,1和2保持正数。
第四步:构造字典序最小的数组
按照「左边放负数,右边放正数,负数从大到小排,正数从小到大排」的规则填充数组:
- 数组长度为3,左指针从开头开始,右指针从末尾开始;
- 先把确定的负数(-3)放在最左侧(满足字典序最小);
- 剩下的正数(1、2)按从小到大的顺序,依次放在负数后面;
- 最终数组:[-3, 1, 2],和题目要求的输出完全一致。
时间复杂度与额外空间复杂度分析
1. 总时间复杂度
整个过程只做了一次从n到1的循环遍历,没有嵌套循环,也没有额外的递归/复杂操作。 循环次数等于n的值,因此时间复杂度为:O(n)。 (n最大为100000,O(n)的效率完全满足题目要求)
2. 总额外空间复杂度
我们只创建了一个结果数组(存储最终答案),没有使用其他额外的数据结构(如栈、队列、哈希表等),也没有递归调用栈。 结果数组的长度等于n,因此额外空间复杂度为:O(n)。 (如果不算结果数组的必要空间,额外辅助空间为O(1))
总结
- 解题分四大核心步骤:合法性判断 → 计算负号数字总和 → 选定负号数字 → 构造最小字典序数组;
- 时间复杂度:O(n)(线性遍历一次);
- 额外空间复杂度:O(n)(仅存储结果数组)。
Go完整代码如下:
package main
import (
"fmt"
)
func lexSmallestNegatedPerm(n int, target int64) []int {
t := int(target)
mx := n * (n + 1) / 2
if t > mx || -t > mx || (mx-t)%2 != 0 {
return nil
}
negS := (mx - t) / 2 // 取负号的元素(的绝对值)之和
ans := make([]int, n)
l, r := 0, n-1
// 从 1,2,...,n 中选一些数,元素和等于 negS
// 为了让负数部分的字典序尽量小,从大往小选
for x := n; x > 0; x-- {
if negS >= x {
negS -= x
ans[l] = -x
l++
} else {
// 大的正数填在末尾
ans[r] = x
r--
}
}
return ans
}
func main() {
n := 3
target := int64(0)
result := lexSmallestNegatedPerm(n, target)
fmt.Println(result)
}
Python完整代码如下:
# -*-coding:utf-8-*-
from typing import List, Optional
def lexSmallestNegatedPerm(n: int, target: int) -> Optional[List[int]]:
t = target
mx = n * (n + 1) // 2
if t > mx or -t > mx or (mx - t) % 2 != 0:
return None
negS = (mx - t) // 2 # 取负号的元素(的绝对值)之和
ans = [0] * n
l, r = 0, n - 1
# 从 1,2,...,n 中选一些数,元素和等于 negS
# 为了让负数部分的字典序尽量小,从大往小选
for x in range(n, 0, -1):
if negS >= x:
negS -= x
ans[l] = -x
l += 1
else:
# 大的正数填在末尾
ans[r] = x
r -= 1
return ans
def main():
n = 3
target = 0
result = lexSmallestNegatedPerm(n, target)
print(result)
if __name__ == "__main__":
main()
C++完整代码如下:
#include <iostream>
#include <vector>
std::vector<int>* lexSmallestNegatedPerm(int n, long long target) {
int t = static_cast<int>(target);
int mx = n * (n + 1) / 2;
if (t > mx || -t > mx || (mx - t) % 2 != 0) {
return nullptr;
}
int negS = (mx - t) / 2; // 取负号的元素(的绝对值)之和
std::vector<int>* ans = new std::vector<int>(n);
int l = 0, r = n - 1;
// 从 1,2,...,n 中选一些数,元素和等于 negS
// 为了让负数部分的字典序尽量小,从大往小选
for (int x = n; x > 0; x--) {
if (negS >= x) {
negS -= x;
(*ans)[l] = -x;
l++;
} else {
// 大的正数填在末尾
(*ans)[r] = x;
r--;
}
}
return ans;
}
int main() {
int n = 3;
long long target = 0;
std::vector<int>* result = lexSmallestNegatedPerm(n, target);
if (result != nullptr) {
std::cout << "[";
for (size_t i = 0; i < result->size(); i++) {
std::cout << (*result)[i];
if (i < result->size() - 1) std::cout << " ";
}
std::cout << "]" << std::endl;
delete result; // 记得释放内存
} else {
std::cout << "nil" << std::endl;
}
return 0;
}