洛谷P1706 全排列问题
全排列问题
题目描述
按照字典序输出自然数
输入格式
一个整数
输出格式
由
每个数字保留
样例 #1
样例输入 #1
3
样例输出 #1
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
提示
解析
前言
不要使用printf
格式化输出,性能十分的低!!!
DFS回溯法求全排列
回溯法是一种经常被用在
其本质是:走不通就回头。
伪码
int ans = 最坏情况;
void dfs(传入数值) {
if (到达目的地){
ans = 从当前解与已有解中选最优;
return...
}
for (遍历所有可能性)
if (可行) {
进行操作;
dfs(缩小规模);
撤回操作;
}
}
实现
import java.util.Scanner;
public class Main {
static Scanner sc = new Scanner(System.in);
//book[i]代表i已经被选择过了
static boolean[] book;
//存储当前的排列
static int[] num;
static int N;
static void dfs(int n) {
//已经搜索到最后一个数了
if (n > N) {
for (int i = 1; i <= N; ++i) {
System.out.print(" " + num[i]);
}
System.out.println();
}
//遍历所有数
for (int i = 1; i <= N; ++i) {
//如果i已经被选择,则跳过
if (book[i]) continue;
//给i这个数打上标记,代表选上i
book[i] = true;
//将i存入当前排列答案中
num[n] = i;
//继续搜索下一个数
dfs(n + 1);
//撤回标记
book[i] = false;
}
}
public static void main(String[] args) {
N = sc.nextInt();
num = new int[N + 1];
book = new boolean[N + 1];
dfs(1);
}
下一个排列
下一个排列的定义是:
给定数字序列的字典序中下一个更大的排列。如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
对于非严格递减的序列没有下一个排列
简单来说就是,下一个数比当前数大且尽可能的小
推导
举个例子,求7562541
的下一个排列
对于一个序列
越靠前的数对整个序列的影响幅度越大,越靠后的数对整个序列的影响幅度越小
对于非严格递减的数没有下一个排列
所以要找到一个靠后且有下一个排列的最小单元序列,也就是最后一个相邻的升序元素对
我们发现(2,5)
是最后一个相邻的升序的元素对,因此,2541
是满足条件的最小单元序列(这个子序列从第二个元素开始,一定是非严格递减的)
我们需要让这个子序列2541
大一点点,于是在2541
的后缀541
中从后向前找第一个大于首元素的数字,也就是4
,让它与第一个元素2
进行交换
这样得到的就是以4
为首的最大排列4521
,对其后缀521
进行逆序操作,就能得到以4
为首的最小排列4125
因此得到了7562541
的下一个排列7564125
描述
下一个排列的描述:
- 从后向前查找第一个相邻的 升序 的元素对(代表这个后缀有下一个排列),即找到最后一个
arr[i]<arr[i+1]
。(此时[i+1,end)
是非严格递减的) - 在后缀
[i+1,end)
中找到最小的大于arr[i]
的数,记为arr[k]
。由于后缀[i+1,end)
是非严格递减的,所以从后向前找到第一个大于arr[i]
的数即可 - 交换
arr[i]
和arr[k]
。这时得到的后缀是以arr[k]
为首的最大的排列 - 对后缀
[i+1,end)
进行逆序操作,来得到以arr[k]
为首的最小的排列。 - 若在步骤1中无符合条件的相邻的元素对,则说明此时数组是非严格降序的,没有下一个排列了
实现
import java.util.Scanner;
public class Main {
public static void swap(int[] arr, int x, int y) {
arr[x] ^= arr[y] ^ (arr[y] = arr[x]);
}
//作用:生成下一个排列 (数组范围[l,r])
public static boolean nextPermutation(int[] arr, int l, int r) {
if (arr.length <= 1) return false;
int i = r - 1;
while (i >= l && !(arr[i] < arr[i + 1])) --i;
if (i == l - 1) return false;
int k = r;
while (arr[i] >= arr[k]) --k;
swap(arr, i, k);
for (int left = i + 1, right = r; left < right; ++left, --right) swap(arr, left, right);
return true;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[] arr = new int[n + 1];
for (int i = 1; i <= n; ++i) arr[i] = i;
do {
for (int i = 1; i <= n; ++i) System.out.print(" " + arr[i]);
System.out.println();
} while (nextPermutation(arr, 1, n));
}
}
对比
DFS回溯 | 下一个排列 | |
---|---|---|
时间复杂度 | ||
空间复杂度 | 在不考虑栈消耗下,需要标记数组 |
数组内原地操作 |
是否支持序列包含重复元素 | 不支持(除非进行特判) | 完全支持 |
适用程度 | DFS回溯算法适用程度更广,应当优先掌握 | 仅适用于排列问题 |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人