华为OD机试 - 魔法咒语组合 - 深度优先搜索DFS(Python/JS/C/C++ 新系统 200分)

在这里插入图片描述

华为OD机试 新系统 统一考试题库清单(持续收录中)以及考点说明(Python/JS/C/C++)

专栏导读

本专栏收录于《华为OD机试真题(Python/JS/C/C++)》

刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新。

一、题目描述

玩家收集了 N 个魔法咒语碎片,它们可以组合成强大的魔法阵。但并不是所有碎片都能使用——只含咒语中没有重复字母的碎片才是纯净的,可以激活。

请找出所有纯净碎片,输出由所有纯净碎片组成的所有魔法阵方案。

备注: 所有魔法咒语碎片互不重复## 二、输入描述

二、输入描述

魔法咒语碎片为一个字符串,仅由小写字母构成,没有分隔符,字符串长度不超过 26。

魔法咒语碎片数量 N 小于等于 6 个。

三、输出描述

魔法阵方案,由所有纯净碎片组成的长字符串,每个纯净碎片的字符串之间以空格隔开(" ")。

所有魔法阵方案按字母序排列输出。

四、测试用例

测试用例1:

1、输入

fire water earth

2、输出

earth fire water
earth water fire
fire earth water
fire water earth
water earth fire
water fire earth

3、说明

fire、water、earth 内部都没有重复字母,因此三个碎片全部保留。

3 个字符串共有:

3! = 6

种排列。

测试用例2:

1、输入

noon sun sky

2、输出

sky sun
sun sky

3、说明

noon 中字母 n 和 o 都重复出现,因此被过滤。

五、解题思路

1、核心分成两步:

  1. 筛选纯净碎片
  2. 每个字符串只包含 a~z,长度不超过 26。
  3. 使用一个 int 的低 26 位表示 26 个字母是否出现。
  4. 如果某个字母对应的二进制位已经是 1,说明该字母重复,该字符串直接淘汰。
  5. 例如 noon 中第二次遇到 o 时即可判断不纯净。

2、生成所有字典序排列

  1. 先把所有纯净碎片按字典序排序。
  2. 使用 DFS + 回溯生成全排列。
  3. 每层都按照排序后的顺序选择未使用字符串,因此生成结果天然就是字典序,不需要最后再次排序。

六、Python算法源码

import sys


def is_pure(s):
    """
    判断字符串内部是否存在重复字母。
    """
    seen = set()

    for ch in s:
        # 当前字母已经出现过,说明该碎片不是纯净碎片
        if ch in seen:
            return False

        seen.add(ch)

    return True


def main():
    # 读取所有以空白字符分隔的字符串
    fragments = sys.stdin.read().split()

    # 过滤纯净碎片,并提前按字典序排序
    pure = sorted([s for s in fragments if is_pure(s)])

    used = [False] * len(pure)
    path = []
    ans = []

    def dfs():
        """
        使用 DFS + 回溯生成所有全排列。
        """

        # 已经使用全部纯净碎片,得到一个完整方案
        if len(path) == len(pure):
            if path:
                ans.append(" ".join(path))
            return

        /*
        Python 中不能使用这种注释形式
        */

    # 实际 DFS 写在下面

七、JavaScript算法源码

const fs = require('fs');

const input = fs.readFileSync(0, 'utf8').trim();

// 输入为空时特殊处理,否则按照任意空白字符切分
const fragments = input.length === 0
    ? []
    : input.split(/\s+/);


/**
 * 判断字符串内部是否存在重复字母
 */
function isPure(s) {
    const seen = new Set();

    for (const ch of s) {

        // 已经出现过当前字母,则该碎片不是纯净碎片
        if (seen.has(ch)) {
            return false;
        }

        seen.add(ch);
    }

    return true;
}


// 过滤纯净碎片,然后按照字典序排序
const pure = fragments.filter(isPure).sort();

const used = new Array(pure.length).fill(false);
const path = [];
const ans = [];


/**
 * DFS + 回溯生成全部排列
 */
function dfs() {

    // 已使用所有纯净碎片,得到完整方案
    if (path.length === pure.length) {

        if (path.length > 0) {
            ans.push(path.join(' '));
        }

        return;
    }

    /*
     * pure 已经按照字典序排序。
     *
     * 每一层都按照下标从小到大选择尚未使用的碎片,
     * 因此最终 DFS 生成的所有方案天然符合字典序。
     */
    for (let i = 0; i < pure.length; i++) {

        if (used[i]) {
            continue;
        }

        // 做选择
        used[i] = true;
        path.push(pure[i]);

        dfs();

        // 回溯,恢复递归前的状态
        path.pop();
        used[i] = false;
    }
}


dfs();

process.stdout.write(ans.join('\n'));

八、C算法源码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define MAX_N 6
#define MAX_LEN 26

// 保存所有纯净碎片
char pure[MAX_N][MAX_LEN + 1];

int pureCount = 0;

// used[i] 表示第 i 个纯净碎片是否已经使用
int used[MAX_N];

// path 保存当前排列中每个位置对应的碎片下标
int path[MAX_N];


/**
 * 判断一个字符串是否为纯净碎片
 */
int isPure(const char *s)
{
    int seen[26] = {0};

    for (int i = 0; s[i] != '\0'; i++)
    {
        int index = s[i] - 'a';

        // 当前字母已经出现过,说明存在重复字母
        if (seen[index])
        {
            return 0;
        }

        seen[index] = 1;
    }

    return 1;
}


/**
 * qsort 使用的字符串比较函数
 */
int cmpString(const void *a, const void *b)
{
    return strcmp((const char *)a, (const char *)b);
}


/**
 * 使用 DFS + 回溯生成所有排列
 */
void dfs(int depth)
{
    /*
     * 已经选择了全部纯净碎片,
     * 当前 path 就是一种完整的魔法阵方案。
     */
    if (depth == pureCount)
    {
        // 没有纯净碎片时不输出空方案
        if (pureCount == 0)
        {
            return;
        }

        for (int i = 0; i < pureCount; i++)
        {
            if (i > 0)
            {
                printf(" ");
            }

            printf("%s", pure[path[i]]);
        }

        printf("\n");
        return;
    }

    /*
     * pure 已经提前按照字典序排序。
     *
     * 当前层按照下标从小到大选择碎片,
     * 因此 DFS 的最终输出天然按照字典序排列。
     */
    for (int i = 0; i < pureCount; i++)
    {
        if (used[i])
        {
            continue;
        }

        // 做选择
        used[i] = 1;
        path[depth] = i;

        dfs(depth + 1);

        // 回溯:释放当前碎片
        used[i] = 0;
    }
}


int main(void)
{
    char fragment[MAX_LEN + 1];

    /*
     * 每次读取一个碎片,直到输入结束。
     *
     * %26s 可以避免字符串超过数组空间。
     */
    while (scanf("%26s", fragment) == 1)
    {
        if (isPure(fragment))
        {
            strcpy(pure[pureCount], fragment);
            pureCount++;
        }
    }

    // 对所有纯净碎片按照字典序排序
    qsort(
        pure,
        pureCount,
        sizeof(pure[0]),
        cmpString
    );

    dfs(0);

    return 0;
}

九、C++算法源码

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

using namespace std;


/**
 * 判断当前碎片内部是否存在重复字母
 */
bool isPure(const string& s)
{
    bool seen[26] = {false};

    for (char c : s)
    {
        int index = c - 'a';

        // 当前字母已经出现过,说明该碎片不纯净
        if (seen[index])
        {
            return false;
        }

        seen[index] = true;
    }

    return true;
}


/**
 * DFS + 回溯生成所有纯净碎片的全排列
 */
void dfs(
    const vector<string>& pure,
    vector<bool>& used,
    vector<string>& path,
    vector<string>& ans)
{
    /*
     * 已经选择全部纯净碎片,
     * 当前 path 就是一种完整魔法阵方案。
     */
    if (path.size() == pure.size())
    {
        if (!path.empty())
        {
            string line;

            for (size_t i = 0; i < path.size(); ++i)
            {
                if (i > 0)
                {
                    line += ' ';
                }

                line += path[i];
            }

            ans.push_back(line);
        }

        return;
    }

    /*
     * pure 已经按照字典序排序,
     * 按下标从小到大进行 DFS,
     * 最终结果天然也是按照字典序排列。
     */
    for (size_t i = 0; i < pure.size(); ++i)
    {
        if (used[i])
        {
            continue;
        }

        // 做选择
        used[i] = true;
        path.push_back(pure[i]);

        dfs(pure, used, path, ans);

        // 回溯,恢复递归之前的状态
        path.pop_back();
        used[i] = false;
    }
}


int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    vector<string> pure;

    string fragment;

    // 不断读取碎片直到输入结束
    while (cin >> fragment)
    {
        if (isPure(fragment))
        {
            pure.push_back(fragment);
        }
    }

    // 提前排序,使 DFS 能直接按照字典序产生结果
    sort(pure.begin(), pure.end());

    vector<bool> used(pure.size(), false);
    vector<string> path;
    vector<string> ans;

    dfs(pure, used, path, ans);

    // 输出所有排列
    for (size_t i = 0; i < ans.size(); ++i)
    {
        if (i > 0)
        {
            cout << '\n';
        }

        cout << ans[i];
    }

    return 0;
}


🏆下一篇:华为OD机试真题 - 简易内存池(Python/JS/C/C++ 新系统 200分)

🏆本文收录于,华为OD机试真题(Python/JS/C/C++)

刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

哪 吒

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

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

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

打赏作者

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

抵扣说明:

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

余额充值