【四十五】【算法分析与设计】递归算法练习,递归实现组合型枚举,N皇后问题,中序序列,[NOIP2001]求先序排列,前中后序序列本质,维护和递归实现的不同位置,当前节点or上一节点,开始节点设置为虚拟

递归实现组合型枚举

链接:登录—专业IT笔试面试备考平台_牛客网 来源:牛客网

时间限制:C/C++ 1秒,其他语言2秒

空间限制:C/C++ 32768K,其他语言65536K

64bit IO Format: %lld

题目描述

从 1~n 这 n 个整数中随机选出 m 个,输出所有可能的选择方案。n>0n \gt 0n>0, 0≤m≤n0 \leq m \leq n0≤m≤n, n+(n−m)≤25n+(n-m)\leq 25n+(n−m)≤25。

输入描述:

两个整数n,m。

输出描述:

按照从小到大的顺序输出所有方案每行1个。

首先,同一行内的数升序排列,相邻两个数用一个空格隔开。其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面(例如1 3 9 12排在1 3 10 11前面)。

示例1

输入

复制5 3

5 3

输出

复制1 2 3 1 2 4 1 2 5 1 3 4 1 3 5 1 4 5 2 3 4 2 3 5 2 4 5 3 4 5

1 2 3

1 2 4

1 2 5

1 3 4

1 3 5

1 4 5

2 3 4

2 3 5

2 4 5

3 4 5

 
#include <bits/stdc++.h> // 引入常用的库,这是C++编程中一个非官方的但广泛使用的头文件,包含了大部分标准库。
using namespace std; // 使用 std 命名空间,这样可以不用频繁地写 std:: 前缀。

vector<vector<int>> ret; // 用来存储所有组合的二维向量。
vector<int> path; // 用来存储当前正在探索的组合路径。
int n, m; // n 是可选择的最大数字,m 是组合中数字的数量。

void dfs(int pos) { // 定义一个深度优先搜索函数,参数 pos 表示当前探索的起始位置。
    path.push_back(pos); // 将当前位置添加到路径中。
    if (path.size() == m) { // 如果路径长度等于 m,说明找到了一个有效的组合。
        ret.push_back(path); // 将当前路径(一个有效组合)添加到结果集中。
    }
    for (int i = pos + 1; i <= n; i++) { // 遍历所有可能的下一个位置。
        dfs(i); // 递归地进行深度优先搜索。
    }
    path.pop_back(); // 回溯,移除路径中的最后一个元素,以探索其他可能的路径。
}

int main() { // 主函数。
    int _n, _m; // 用于输入的 n 和 m 值。
    cin >> _n >> _m; // 从标准输入读取 n 和 m 的值。
    n = _n; // 将读入的值赋给全局变量 n。
    m = _m; // 将读入的值赋给全局变量 m。
    for (int i = 1; i <= n; i++) { // 从 1 到 n 遍历所有可能的起始位置。
        dfs(i); // 对每个起始位置调用深度优先搜索函数。
    }
    for (auto x : ret) { // 遍历所有找到的组合。
        for (auto y : x) { // 遍历组合中的每个数字。
            cout << y << " "; // 输出数字并在数字间加空格。
        }
        cout << endl; // 每个组合后输出换行符。
    }
}

N皇后问题

链接:登录—专业IT笔试面试备考平台_牛客网 来源:牛客网

时间限制:C/C++ 1秒,其他语言2秒

空间限制:C/C++ 262144K,其他语言524288K

64bit IO Format: %lld

题目描述

给出一个n×nn\times nn×n的国际象棋棋盘,你需要在棋盘中摆放nnn个皇后,使得任意两个皇后之间不能互相攻击。具体来说,不能存在两个皇后位于同一行、同一列,或者同一对角线。请问共有多少种摆放方式满足条件。

输入描述:

一行,一个整数n(1≤n≤12)n(1\le n \le 12)n(1≤n≤12),表示棋盘的大小。

输出描述:

输出一行一个整数,表示总共有多少种摆放皇后的方案,使得它们两两不能互相攻击。

示例1

输入

复制4

4

输出

复制2

2

 

维护和回溯都在上一个节点实现

 
#include <bits/stdc++.h>
using namespace std;
int ret;
int n;
vector<bool> check1;
vector<bool> check2;
vector<bool> checkcol;
void dfs(int i, int j) {
    int newrow = i + 1;
    if (newrow == n) {
        ret++;
        return;
    }

    for (int col = 0; col < n; col++) {
        if (!check1[newrow - col + n] && !check2[newrow + col] && !checkcol[col]) {
        //维护和递归都在上一个节点实现
            check1[newrow - col + n] = check2[newrow + col] = checkcol[col] = true;
            dfs(newrow, col);
            check1[newrow - col + n] = check2[newrow + col] = checkcol[col] = false;
        }
    }

}

int main() {
    cin >> n;
    check1.resize(2 * n);
    check2.resize(2 * n);
    checkcol.resize(n);
    
    for (int col = 0; col < n; col++) {
    维护和递归都在上一个节点实现
        check1[0 - col + n] = check2[0 + col] = checkcol[col] = true;
        dfs(0, col);
        check1[0 - col + n] = check2[0 + col] = checkcol[col] = false;
    }
    cout << ret;
}

维护和回溯都在当前节点实现

 
#include <bits/stdc++.h>
using namespace std;
int ret;
int n;
vector<bool> check1;
vector<bool> check2;
vector<bool> checkcol;
void dfs(int i, int j) {
    int newrow = i + 1;
    if (newrow == n) {
        ret++;
        return;
    }
    //维护和递归都在当前节点实现
    int col = j; int row = i;
    check1[row - col + n] = check2[row + col] = checkcol[col] = true;
    for (int newcol = 0; newcol < n; newcol++) {
        if (!check1[newrow - newcol + n] && !check2[newrow + newcol] && !checkcol[newcol]) {
            
            dfs(newrow, newcol);
        }
    }
    check1[row - col + n] = check2[row + col] = checkcol[col] = false;
}

int main() {
    cin >> n;
    check1.resize(2 * n);
    check2.resize(2 * n);
    checkcol.resize(n);

    for (int col = 0; col < n; col++) {
        //维护和递归都在当前节点实现

        dfs(0, col);

    }
    cout << ret;
}

维护在当前节点实现和回溯都在上一个节点实现

 
#include <bits/stdc++.h>
using namespace std;
int ret;
int n;
vector<bool> check1;
vector<bool> check2;
vector<bool> checkcol;
void dfs(int i, int j) {
    int newrow = i + 1;
    if (newrow == n) {
        ret++;
        return;
    }
    //维护在当前节点实现,回溯在上一节点实现
    int col = j; int row = i;
    check1[row - col + n] = check2[row + col] = checkcol[col] = true;
    for (int newcol = 0; newcol < n; newcol++) {
        if (!check1[newrow - newcol + n] && !check2[newrow + newcol] && !checkcol[newcol]) {

            dfs(newrow, newcol);
            check1[newrow - newcol + n] = check2[newrow + newcol] = checkcol[newcol] = false;
        }
    }

}

int main() {
    cin >> n;
    check1.resize(2 * n);
    check2.resize(2 * n);
    checkcol.resize(n);

    for (int col = 0; col < n; col++) {
        //维护在当前节点实现,回溯在上一节点实现

        dfs(0, col);
        check1[0 - col + n] = check2[0 + col] = checkcol[col] = false;
    }
    cout << ret;
}

维护在上一个节点实现和回溯都在当前节点实现

 
#include <bits/stdc++.h>
using namespace std;
int ret;
int n;
vector<bool> check1;
vector<bool> check2;
vector<bool> checkcol;
void dfs(int i, int j) {
    int newrow = i + 1;
    int col = j; int row = i;
    if (newrow == n) {
        ret++;
        check1[row - col + n] = check2[row + col] = checkcol[col] = false;
        return;
    }
    //维护在上一个节点实现,回溯在当前节点实现
    
    
    for (int newcol = 0; newcol < n; newcol++) {
        if (!check1[newrow - newcol + n] && !check2[newrow + newcol] && !checkcol[newcol]) {
            check1[newrow - newcol + n] = check2[newrow + newcol] = checkcol[newcol] = true;
            dfs(newrow, newcol);
            
        }
    }
    check1[row - col + n] = check2[row + col] = checkcol[col] = false;

}

int main() {
    cin >> n;
    check1.resize(2 * n);
    check2.resize(2 * n);
    checkcol.resize(n);

    for (int col = 0; col < n; col++) {
        //维护在上一个节点实现,回溯在当前节点实现
        check1[0 - col + n] = check2[0 + col] = checkcol[col] = true;
        dfs(0, col);
        
    }
    cout << ret;
}

开始节点是虚拟节点

 
#include <bits/stdc++.h> // 引入常用的库,包括大部分标准库的头文件。
using namespace std; // 使用命名空间 std,避免在调用标准库功能时需要加前缀 std::。

int ret; // 用来存储皇后问题的解决方案数量。
int n; // 棋盘的大小,也是皇后的数量。
vector<bool> check1; // 用来标记哪些主对角线上已被占用,防止皇后相互攻击。
vector<bool> check2; // 用来标记哪些副对角线上已被占用,防止皇后相互攻击。
vector<bool> checkcol; // 用来标记哪些列已被占用,防止皇后相互攻击。

void dfs(int row){ // 深度优先搜索函数,参数 row 表示当前正在处理的行。
    if(row == n) { // 如果已经处理完所有行,意味着找到了一个解决方案。
        ret++; // 解决方案数加一。
        return; // 返回上一层。
    }
    
    for(int col = 0; col < n; col++){ // 遍历当前行的每一列。
        if(!check1[row - col + n] && !check2[row + col] && !checkcol[col]){ // 检查当前位置的列、主对角线和副对角线是否未被占用。
            check1[row - col + n] = check2[row + col] = checkcol[col] = true; // 标记当前列和两个对角线为占用状态。
            dfs(row + 1); // 处理下一行。
            check1[row - col + n] = check2[row + col] = checkcol[col] = false; // 回溯,取消当前位置的标记,尝试下一个可能的列。
        }
    }
}

int main(){ // 主函数。
    cin >> n; // 从标准输入读取棋盘大小。
    check1.resize(2 * n); // 调整数组大小,保证主对角线的索引不会出现负数。
    check2.resize(2 * n); // 调整数组大小,适应所有可能的副对角线。
    checkcol.resize(n); // 调整列检查数组的大小。
    
    dfs(0); // 从第0行开始深度优先搜索。
    cout << ret; // 输出找到的解决方案总数。
}

中序序列

链接:登录—专业IT笔试面试备考平台_牛客网 来源:牛客网

时间限制:C/C++ 2秒,其他语言4秒

空间限制:C/C++ 262144K,其他语言524288K

64bit IO Format: %lld

题目描述

给定一棵有n个结点的二叉树的先序遍历与后序遍历序列,求其中序遍历序列。

若某节点只有一个子结点,则此处将其看作左儿子结点

示例1

输入

复制5,[3,2,1,4,5],[1,5,4,2,3]

5,[3,2,1,4,5],[1,5,4,2,3]

返回值

复制[1,2,5,4,3]

[1,2,5,4,3]

说明

备注:

1≤n≤100,0001 \leq n \leq 100,0001≤n≤100,000

 
#include<bits/stdc++.h> // 引入常用的库,包括大部分标准库的头文件。
using namespace std; // 使用命名空间 std,避免在调用标准库功能时需要加前缀 std::。

class Solution { // 定义一个名为 Solution 的类。
public:
    struct TreeNode { // 定义一个结构体TreeNode,用来表示二叉树的节点。
        int val; // 节点存储的值。
        TreeNode* left; // 指向左子树的指针。
        TreeNode* right; // 指向右子树的指针。
    };
    vector<int> pre, suf; // 分别用来存储前序遍历和后序遍历的序列。
    unordered_map<int, int> hash1; // 用来存储后序遍历中每个值对应的索引。

    // 递归函数用于根据前序和后序遍历重建二叉树。
    TreeNode* dfs(int pre_start, int pre_end, int suf_start, int suf_end) {
        if (pre_start == pre_end) { // 如果当前只有一个节点。
            TreeNode* root = new TreeNode; // 创建新节点。
            root->val = pre[pre_start]; // 设置节点值。
            root->left = root->right = nullptr; // 设置左右子节点为空。
            return root;
        }
        if (pre_start > pre_end) return nullptr; // 如果序列无效,则返回空。

        TreeNode* root = new TreeNode; // 创建根节点。
        root->val = pre[pre_start]; // 设置根节点的值。
        int leftroot = pre[pre_start + 1]; // 获取左子树的根节点值。
        int index = hash1[leftroot]; // 找到左子树根节点值在后序遍历中的位置。

        int left_length = index - suf_start + 1; // 计算左子树的长度。

        // 递归构建左右子树。
        root->left = dfs(pre_start + 1, pre_start + left_length, suf_start, index);
        root->right = dfs(pre_start + left_length + 1, pre_end, index + 1, suf_end);

        return root;
    }
    vector<int> ret; // 用来存储中序遍历结果。
    // 主函数,接受节点数量、前序和后序序列,返回中序遍历序列。
    vector<int> solve(int n, vector<int>& _pre, vector<int>& _suf) {
        pre = _pre, suf = _suf; // 赋值给成员变量。

        for (int i = 0; i < suf.size(); i++) { // 构建后序遍历的索引哈希表。
            hash1[suf[i]] = i;
        }

        TreeNode* root = dfs(0, pre.size() - 1, 0, suf.size() - 1); // 从序列中重建二叉树。

        dfs1(root); // 进行中序遍历。

        return ret; // 返回中序遍历结果。
    }
    // 辅助递归函数,用于中序遍历二叉树并存储结果。
    void dfs1(TreeNode* root) {
        if (root == nullptr) return; // 如果节点为空,则返回。

        dfs1(root->left); // 遍历左子树。
        ret.push_back(root->val); // 访问当前节点。
        dfs1(root->right); // 遍历右子树。
    }
};

[NOIP2001]求先序排列

链接:登录—专业IT笔试面试备考平台_牛客网 来源:牛客网

时间限制:C/C++ 1秒,其他语言2秒

空间限制:C/C++ 262144K,其他语言524288K

64bit IO Format: %lld

题目描述

给出一棵二叉树的中序与后序排列。求出它的先序排列。(约定树结点用不同的大写字母表示,长度 ≤ 8)。

输入描述:

2行,均为大写字母组成的字符串,表示一棵二叉树的中序与后序排列。

输出描述:

1行,表示一棵二叉树的先序。

示例1

输入

复制BADC BDCA

BADC

BDCA

输出

复制ABCD

ABCD

 
#include <bits/stdc++.h> // 引入常用的库,包括大部分标准库的头文件。
using namespace std; // 使用命名空间 std,避免在调用标准库功能时需要加前缀 std::。

class TreeNode { // 定义一个名为 TreeNode 的类,用于表示二叉树的节点。
public:
    char val; // 节点存储的值,此处为字符类型。
    TreeNode* left; // 指向左子节点的指针。
    TreeNode* right; // 指向右子节点的指针。
};

string midorder; // 存储中序遍历的字符串。
string backorder; // 存储后序遍历的字符串。
unordered_map<char, int> hash1; // 用于存储中序遍历中字符与其索引的映射,便于快速查找。
int n, m; // n 是中序遍历字符串的长度,m 是后序遍历字符串的长度。

// dfs 函数用于根据中序和后序遍历重建二叉树的一部分,并返回这部分的根节点。
TreeNode* dfs(int left, int right) { // 参数left和right定义了当前处理的中序遍历的区间。
    TreeNode* root; // 声明一个指向TreeNode的指针,用于存储当前子树的根节点。
    if (left == right) { // 如果当前区间只有一个元素。
        root = new TreeNode; // 创建新节点。
        root->val = midorder[left]; // 设置节点值。
        root->left = root->right = nullptr; // 设置左右子节点为空。
        return root; // 返回当前节点。
    }
    if (left > right) { // 如果区间无效,返回空指针。
        return nullptr;
    }

    for (int i = m - 1; i >= 0; i--) { // 从后序遍历的最后一个元素向前遍历。
        int pos = hash1[backorder[i]]; // 获取后序遍历当前元素在中序遍历中的索引。
        if (pos >= left && pos <= right) { // 检查当前元素是否在[left, right]区间内。
            root = new TreeNode; // 创建新的根节点。
            root->val = midorder[pos]; // 设置根节点的值。
            root->left = dfs(left, pos - 1); // 递归构建左子树。
            root->right = dfs(pos + 1, right); // 递归构建右子树。
            break; // 找到根节点后跳出循环。
        }
    }
    return root; // 返回构建的树的根节点。
}

// dfs1 函数用于前序遍历二叉树并输出。
void dfs1(TreeNode* root) {
    if (root == nullptr) return; // 如果节点为空,返回。
    cout << root->val; // 输出当前节点的值。
    dfs1(root->left); // 递归前序遍历左子树。
    dfs1(root->right); // 递归前序遍历右子树。
}

int main() {
    cin >> midorder >> backorder; // 读取中序和后序遍历的字符串。
    n = midorder.size(); // 获取中序遍历字符串的长度。
    m = backorder.size(); // 获取后序遍历字符串的长度。
    for (int i = 0; i < n; i++) { // 遍历中序遍历字符串。
        hash1[midorder[i]] = i; // 存储每个字符及其索引到hash表中。
    }

    TreeNode* root = dfs(0, n - 1); // 从整个区间重建二叉树。

    dfs1(root); // 前序遍历重建的二叉树并输出。
}

结尾

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。

同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。

谢谢您的支持,期待与您在下一篇文章中再次相遇!

  • 35
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

妖精七七_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值