由于相邻元素奇偶性不同,确定第一个元素填什么,后面元素的奇偶性就确定了。比如第一个数填的是偶数,那么后面元素一定是按照奇偶奇偶的顺序填。
- 这些偶数有
个不同的排列。 - 这些奇数有
个不同的排列。
如果
如果
如果
代码实现时,可以预处理
为方便计算,先把
看示例 1,按照第一个数分组,每一组的大小都是
- 当
时,第一个数在第一组中,一定是 。 - 当
时,第一个数在第二组中,一定是 。 - 当
时,第一个数在第三组中,一定是 。 - 当
时,第一个数在第四组中,一定是 。
所以根据
设
注意
具体请看 视频讲解,欢迎点赞关注~
# 预处理交替排列的方案数
f = [1]
i = 1
while f[-1] < 10 ** 15:
f.append(f[-1] * i)
f.append(f[-1] * i)
i += 1
class Solution:
def permute(self, n: int, k: int) -> List[int]:
# k 改成从 0 开始,方便计算
k -= 1
if n < len(f) and k >= f[n] * (2 - n % 2): # n 是偶数的时候,方案数乘以 2
return []
# cand 表示剩余未填入 ans 的数字
# cand[0] 保存偶数,cand[1] 保存奇数
cand = [list(range(2, n + 1, 2)), list(range(1, n + 1, 2))]
ans = [0] * n
parity = 1 # 当前要填入 ans 的数字的奇偶性
for i in range(n):
if n - 1 - i < len(f):
# 比如示例 1,按照第一个数分组,每一组的大小都是 size=f[n-1-i]=f[3]=2
# 知道 k 和 size 就知道我们要去哪一组(第 j 组)
j, k = divmod(k, f[n - 1 - i])
# n 是偶数的情况,第一个数既可以填奇数又可以填偶数,要特殊处理
if n % 2 == 0 and i == 0:
parity = 1 - j % 2
j //= 2
else:
j = 0 # n 很大的情况下,只能按照 1,2,3,... 的顺序填
ans[i] = cand[parity].pop(j)
parity ^= 1 # 下一个数的奇偶性
return ans
class Solution {
// 预处理交替排列的方案数
private static final List<Long> f = new ArrayList<>();
static {
f.add(1L);
for (int i = 1; f.getLast() < 1e15; i++) {
f.add(f.getLast() * i);
f.add(f.getLast() * i);
}
}
public int[] permute(int n, long k) {
// k 改成从 0 开始,方便计算
k--;
if (n < f.size() && k >= f.get(n) * (2 - n % 2)) { // n 是偶数的时候,方案数乘以 2
return new int[0];
}
// cand 表示剩余未填入 ans 的数字
// cand[0] 保存偶数,cand[1] 保存奇数
List<Integer>[] cand = new ArrayList[2];
cand[0] = new ArrayList<>();
for (int i = 2; i <= n; i += 2) {
cand[0].add(i);
}
cand[1] = new ArrayList<>();
for (int i = 1; i <= n; i += 2) {
cand[1].add(i);
}
int[] ans = new int[n];
int parity = 1; // 当前要填入 ans[i] 的数的奇偶性
for (int i = 0; i < n; i++) {
int j = 0;
if (n - 1 - i < f.size()) {
// 比如示例 1,按照第一个数分组,每一组的大小都是 size=2
// 知道 k 和 size 就知道我们要去哪一组
long size = f.get(n - 1 - i);
j = (int) (k / size); // 去第 j 组
k %= size;
// n 是偶数的情况,第一个数既可以填奇数又可以填偶数,要特殊处理
if (n % 2 == 0 && i == 0) {
parity = 1 - j % 2;
j /= 2;
}
} // else j=0,在 n 很大的情况下,只能按照 1,2,3,... 的顺序填
ans[i] = cand[parity].remove(j);
parity ^= 1; // 下一个数的奇偶性
}
return ans;
}
}
// 预处理交替排列的方案数
vector<long long> f = {1};
int init = []() {
for (int i = 1; f.back() < 1e15; i++) {
f.push_back(f.back() * i);
f.push_back(f.back() * i);
}
return 0;
}();
class Solution {
public:
vector<int> permute(int n, long long k) {
// k 改成从 0 开始,方便计算
k--;
if (n < f.size() && k >= f[n] * (2 - n % 2)) { // n 是偶数的时候,方案数乘以 2
return {};
}
// cand 表示剩余未填入 ans 的数字
// cand[0] 保存偶数,cand[1] 保存奇数
vector<int> cand[2];
for (int i = 2; i <= n; i += 2) {
cand[0].push_back(i);
}
for (int i = 1; i <= n; i += 2) {
cand[1].push_back(i);
}
vector<int> ans(n);
int parity = 1; // 当前要填入 ans 的数字的奇偶性
for (int i = 0; i < n; i++) {
int j = 0;
if (n - 1 - i < f.size()) {
// 比如示例 1,按照第一个数分组,每一组的大小都是 size=2
// 知道 k 和 size 就知道我们要去哪一组
long long size = f[n - 1 - i];
j = k / size; // 去第 j 组
k %= size;
// n 是偶数的情况,第一个数既可以填奇数又可以填偶数,要特殊处理
if (n % 2 == 0 && i == 0) {
parity = 1 - j % 2;
j /= 2;
}
} // else j=0,在 n 很大的情况下,只能按照 1,2,3,... 的顺序填
ans[i] = cand[parity][j];
cand[parity].erase(cand[parity].begin() + j);
parity ^= 1; // 下一个数的奇偶性
}
return ans;
}
};
// 预处理交替排列的方案数
var f = []int{1}
func init() {
for i := 1; f[len(f)-1] < 1e15; i++ {
f = append(f, f[len(f)-1]*i)
f = append(f, f[len(f)-1]*i)
}
}
func permute(n int, K int64) []int {
// k 改成从 0 开始,方便计算
k := int(K - 1)
if n < len(f) && k >= f[n]*(2-n%2) { // n 是偶数的时候,方案数乘以 2
return nil
}
// cand 表示剩余未填入 ans 的数字
// cand[0] 保存偶数,cand[1] 保存奇数
cand := [2][]int{}
for i := 2; i <= n; i += 2 {
cand[0] = append(cand[0], i)
}
for i := 1; i <= n; i += 2 {
cand[1] = append(cand[1], i)
}
ans := make([]int, n)
parity := 1 // 当前要填入 ans[i] 的数的奇偶性
for i := range n {
j := 0
if n-1-i < len(f) {
// 比如示例 1,按照第一个数分组,每一组的大小都是 size=2
// 知道 k 和 size 就知道我们要去哪一组
size := f[n-1-i]
j = k / size // 去第 j 组
k %= size
// n 是偶数的情况,第一个数既可以填奇数又可以填偶数,要特殊处理
if n%2 == 0 && i == 0 {
parity = 1 - j%2
j /= 2
}
} // else j=0,在 n 很大的情况下,只能按照 1,2,3,... 的顺序填
ans[i] = cand[parity][j]
cand[parity] = slices.Delete(cand[parity], j, j+1)
parity ^= 1 // 下一个数的奇偶性
}
return ans
}
预处理的时间和空间忽略不计。
- 时间复杂度:$\mathcal{O}(n^2)$。注:如果用有序集合或者树状数组维护剩余元素,可以做到
。考虑到本题 很小,直接删除元素是最快的(常数小)。 - 空间复杂度:$\mathcal{O}(n)$。
- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
- 单调栈(基础/矩形面积/贡献法/最小字典序)
- 网格图(DFS/BFS/综合应用)
- 位运算(基础/性质/拆位/试填/恒等式/思维)
- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)