给定 1~N 的初始排列,求其第 M 个字典序后继排列(初始排列为第 1 个)。通过 DFS 回溯生成排列,首次强制使用初始排列,后续按字典序枚举,利用num计数找到目标排列,核心在于统一 0/1 下标逻辑避免边界错误,适用于 M 较小(≤100)的场景,本质是字典序排列的顺序生成与计数。
在这里插入图片描述

【算法思路】

  1. 递归终止条件
    ◦ 首先检查 return0 标志,如果为 true,则直接返回。这是为了在找到目标排列后停止搜索。
    ◦ 当 x == n 时,表示已经处理完了所有的位置(因为是 0 - based 索引,处理了 0 到 n - 1 共 n 个位置),此时排列构建完成。
    ◦ 对于构建完成的排列,num 加 1,表示生成了一个新的排列。
    ◦ 如果 num 等于 m + 1,说明找到了目标排列(因为初始排列算第 1 个,往后数 m 个),此时输出这个排列,并将 return0 设置为 true,以终止后续的搜索。

  2. 排列构建过程(深度优先搜索部分)
    ◦ 当 num 为 0(即首次生成排列)时,按照初始排列来构建当前排列。通过获取 mars[x] 的值(这是初始排列中第 x + 1 个位置的数字,因为是 0 - based 索引,这里的 x 对应初始排列中的第 x + 1 个位置),然后检查这个数字是否未被使用(通过 st 数组)。如果未被使用,则将其标记为已使用(st[target] = true,这里的 target 就是 mars[x]),并将其放入当前构建的排列 arr 中(arr[x]=target),然后递归调用 dfs(x + 1) 处理下一个位置。在递归返回后,需要进行回溯操作,即将该数字标记为未使用(st[target]=false),以便在其他分支中可以再次使用。
    ◦ 当 num 不为 0 时(即不是首次生成排列),按照字典序生成后续排列。通过循环遍历数字 1 到 n(for(int i = 1; i <= n; i++)),对于每个未被使用的数字(if(!st[i])),将其标记为已使用,放入当前排列 arr 中,然后递归调用 dfs(x + 1),在递归返回后同样进行回溯操作,将数字标记为未使用并清空当前位置在 arr 中的值。

【代码示例】

#include <iostream>
#include <cstring>
using namespace std;

const int N = 10010;
int n, m;
int arr[N];          // 存储当前生成的排列,记录每一位的数字
int mars[N];         // 存储火星人输入的初始排列(题目中给定的手指排列顺序)
int num = 0;         // 记录生成的排列序号,从输入排列开始计数
bool st[N];          // 标记数字是否已使用(true:已用;false:未用)
bool return0;        // 终止标志,找到目标排列后设为true,提前结束搜索

// DFS函数:生成排列,x表示当前处理排列的第x位
void dfs(int x) {
    if (return0) {  // 若已找到目标排列,直接返回,避免无效计算
        return;
    }
    if (x > n) {    // 生成完整排列
        num++;      // 排列序号+1
        if (num == m + 1) {  // 找到目标排列(输入排列是第1个,往后数m个)
            for (int i = 1; i <= n; i++) {
                cout << arr[i];
                if (i != n) cout << " ";  // 控制输出格式,数字间用空格分隔
            }
            return0 = true;  // 标记搜索结束
        }
        return;
    }
    for (int i = 1; i <= n; i++) {
        if (!num) {  // 首次生成排列(num=0),使用输入的初始排列
            i = mars[x];  // 强制取初始排列中对应位置的数字
        }
        if (!st[i]) {  // 数字i未使用
            st[i] = true;  // 标记为已用
            arr[x] = i;    // 将i放入当前排列的第x位
            dfs(x + 1);    // 递归处理下一位
            st[i] = false; // 回溯:撤销标记,恢复数字i的未使用状态
            arr[x] = 0;     // 回溯:清空当前位置,恢复递归前状态
        }
    }
}

int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        cin >> mars[i];  // 读取火星人输入的初始排列
    }
    dfs(1);  // 从排列第1位开始,启动DFS生成排列
    return 0;
}

注意:逻辑位置起始点的一致性:统一数组索引和排列数字,强制使用1-based下标

Logo

集算法之大成!助力oier实现梦想!

更多推荐