蓝桥杯备赛:Day5-P1706 全排列问题

📚 算法笔记:P1706 全排列问题 (DFS 基础)

1. 题目描述

P1706 全排列问题 - 洛谷

输出 1 ∼ N 1 \sim N 1∼N 的所有全排列,要求每个数字占 5 个场宽,排列按字典序从小到大输出。

2. 核心代码 (C++ 版本)

C++ 复制代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

ll N;
ll ans[15];      // 记录当前位置存了哪个数字
bool used[15];    // 标记数字 i 是否已被使用

void dfs(int position)
{
    // 1. 递归出口:当位置超过 N 时,说明 N 个坑位已填满
    if(position > N)
    {
        for(int i = 1; i <= N; i++)
        {
            cout << setw(5) << ans[i]; // 核心格式控制
        }	
        cout << "\n";
        return; // 功成身退,回溯到上一层
    }
    
    // 2. 尝试在当前位置填入数字 i
    for(int i = 1; i <= N; i++)
    {
        if(!used[i]) // 只有没用过的数字才能填入
        {
            ans[position] = i;  // 填入数字
            used[i] = true;     // 标记为已占用
            dfs(position + 1);  // 递归进入下一个位置
            used[i] = false;    // 【核心回溯】:撤销标记,释放数字
        }
    }
}

void solve()
{
    if(!(cin >> N)) return;
    dfs(1);	
}

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    
    int _ = 1;
    while(_--)
    {
        solve();
    }
    return 0;
}

3. 核心考点与注意事项

  • DFS 递归模型:本题体现了 DFS 最经典的思想------"不撞南墙不回头"。通过递归不断深入,直到触发出口条件。
  • 回溯机制 (Backtracking)used[i] = false; 是整段代码的灵魂。它保证了在完成一种排列并返回后,之前使用的数字能被释放,从而参与到其他分支的排列中。
  • 状态维护
    • position:记录递归的深度(即当前的坑位)。
    • i:记录横向的选择范围(即手里的数字)。
  • 格式要求setw(5)iomanip 库中的函数(万能头已包含),用于满足题目严格的场宽要求。

🕵️ 深度拆解:第二层工人的"工作日志"

假设 N = 3 N=3 N=3,第二层工人的任务是:填好第二个坑位(ans[2]

第一阶段:尝试数字 2
  1. 开始循环 :工人看手里有哪些牌。数字 1 1 1 被第一层拿走了,数字 2 2 2 还没人用。
  2. 填坑 :他在第二个坑里填入 2ans[2] = 2),并标记 used[2] = true
  3. 派发任务 :他大喊一声:"第三层,剩下的交给你了!",然后调用 dfs(3)
  4. 原地待命 :此时,第二层工人的程序暂停 在了 dfs(3) 这一行,他进入了漫长的等待。
第二阶段:回火(Backtracking)
  1. 任务返回 :过了一会儿,第三层跑完回来了(也就是 1 2 3 已经打印完了)。
  2. 苏醒 :第二层工人"苏醒"过来,接着执行 dfs(3) 下面的代码。
  3. 撤销操作 :他执行 used[2] = false。这意味着他把数字 2 从坑里拿了出来,重新放回手里。这一步极其关键,因为它让数字 2 重新变回了"可用状态"
第三阶段:开启新分支(i 变成 3)
  1. 继续循环 :因为他还在 for 循环里,执行完刚才那两行后,i++ 发生了。
  2. 寻找下一张牌 :现在 i 变成了 3
  3. 检查可用性 :他发现数字 3 3 3 也没被用过(used[3]false)。
  4. 新的尝试
  • 他在第二个坑里填入新的数字:ans[2] = 3
  • 标记 used[3] = true
  • 再次大喊:"第三层,我又来了!",调用 dfs(3)

4. 易错点回顾 (My Mistakes)

1. return 位置导致的"截断"错误

  • 错误经历 :曾将 return 放在 if 块之外,导致函数刚进入就直接结束,无法进入下方的 for 循环。
  • 教训 :在 DFS 中,return 通常只出现在递归出口(Base Case)中,代表当前路径搜索完毕。

2. 回溯的必要性理解

  • 反思 :如果不写 used[i] = false,数字被用过一次后就永远失效,最终只能输出 1 2 3 ... N 这一种结果,无法生成其他排列。
相关推荐
胖咕噜的稞达鸭2 小时前
C++技术岗面试经验总结
开发语言·网络·c++·网络协议·tcp/ip·面试
Wild_Pointer.2 小时前
高效工具实战指南:从零开始编写CMakeLists
c++
kpl_203 小时前
智能指针(C++)
c++·c++11·智能指针
Darkwanderor4 小时前
高精度计算——基础模板整理
c++·算法·高精度计算
Tanecious.4 小时前
蓝桥杯备赛:Day5-P1036 选数
c++·蓝桥杯
mmz12075 小时前
深度优先搜索DFS(c++)
c++·算法·深度优先
憧憬从前6 小时前
算法学习记录DAY1
c++·学习
A.A呐6 小时前
【C++第二十四章】异常
开发语言·c++
xiaoye-duck6 小时前
《算法题讲解指南:动态规划算法--子序列问题》--29.最长递增子序列的个数,30.最长数对链,31.最长定差子序列
c++·算法·动态规划