【OD机试题解法笔记】连续出牌数量

题目描述

有这么一款单人卡牌游戏,牌面由颜色和数字组成,颜色为红、黄、蓝、绿中的一种,数字为0-9中的一个。游戏开始时玩家从手牌中选取一张卡牌打出,接下来如果玩家手中

有和他上一次打出的手牌颜色或者数字相同的手牌,他可以继续将该手牌打出,直至手牌打光或者没有符合条件可以继续打出的手牌。

现给定一副手牌,请找到最优的出牌策略,使打出的手牌最多。
输入描述

输入为两行,第一行是每张手牌的数字,数字由空格分隔,第二行为对应的每张手牌的颜色,用r y b g这4个字母分别代表4种颜色,字母也由空格分隔。手牌数量不超过10。
输出描述

输出一个数字,即最多能打出的手牌的数量。
用例

输入

json 复制代码
1 4 3 4 5
r y b b r

输出

json 复制代码
3

说明

json 复制代码
如果打(1, r)-> (5, r),那么能打两张。
如果打(4,y) -> (4, b) -> (3, b),那么能打三张。

思考

暴力DFS。枚举每张牌作为打出的第一张牌,然后根据这张牌的数字和颜色在其余的牌中找匹配的继续打出,这个是递归搜索过程,可用 DFS 解决。需要注意:1)维护一个集合记录打出的牌,不要重复选相同的牌;2)每次选择一张牌 dfs 后需要回溯保证下次选择其它牌的前置状态正确。

算法过程

  1. 输入处理 :读取输入的两行数据,第一行是卡牌的数字数组 nums,第二行是对应的卡牌颜色数组 alphas
  2. 初始化 :定义全局变量 ans 用于记录最多能打出的卡牌数量,初始值为 0。
  3. 深度优先搜索(DFS)
    • 定义一个 used 集合来记录已经使用过的卡牌索引。
    • dfs 函数接收当前卡牌的索引 index 和当前已经打出的卡牌数量 count
    • 将当前卡牌标记为已使用,并增加 count
    • 更新 ans 为当前 count 和之前 ans 的最大值。
    • 遍历所有未被使用的卡牌,如果某张卡牌的数字或颜色与当前卡牌相同,则递归调用 dfs 继续搜索。
    • 回溯时,将当前卡牌从 used 集合中移除,以便尝试其他路径。
  4. 遍历所有起始卡牌 :对于每一张卡牌,作为起始卡牌调用 dfs,确保找到所有可能的出牌路径中的最大值。
  5. 输出结果 :最终输出 ans,即最多能打出的卡牌数量。
  6. 时空复杂度分析
    • 时间复杂度 :由于手牌数量不超过 10,最坏情况下需要遍历所有可能的出牌顺序。对于每张卡牌作为起始点,DFS 的递归深度最多为手牌数量 n,每次递归需要遍历剩余未使用的卡牌,因此时间复杂度为 O(n!),其中 n 是手牌数量。由于 n ≤ 10,实际运行时间是可接受的。
    • 空间复杂度 :主要空间消耗来自递归调用栈和 used 集合。递归深度最多为 nused 集合最多存储 n 个元素,因此空间复杂度为 O(n)

参考代码

cpp 复制代码
function solution() {
  const nums = readline().split(' ').map(Number);
  const alphas = readline().split(' ').map(Number);
  
  let ans = 0;
  const used = new Set();
  const dfs = function(index, count) {
    used.add(index);
    count++;
    ans = Math.max(ans, count);

    for (let i = 0; i < nums.length; i++) {
      if ( used.has(i) || nums[i] !== nums[index] && alphas[i] !== alphas[index]) continue;
      dfs(i, count);
      used.delete(i);
    }
  };

  for (let i = 0; i < nums.length; i++) {
    used.clear();
    dfs(i, 1);
  }

  console.log(ans);
}


const cases = [
`1 4 3 4 5
 r y b b r`
];

let caseIndex = 0;
let lineIndex = 0;

const readline = (function () {
  let lines = [];
  return function () {
    if (lineIndex === 0) {
      lines = cases[caseIndex]
        .trim()
        .split("\n")
        .map((line) => line.trim());
    }
    return lines[lineIndex++];
  };
})();

cases.forEach((_, i) => {
  caseIndex = i;
  lineIndex = 0;
  solution();
});
相关推荐
生成论实验室5 分钟前
《事件关系阴阳博弈动力学:识势应势之道》第四篇:降U动力学——认知确定度的自驱演化
人工智能·科技·神经网络·算法·架构
AI科技星18 分钟前
全域数学·72分册:场计算机卷【乖乖数学】
算法·机器学习·数学建模·数据挖掘·量子计算
Alice-YUE35 分钟前
【js高频八股】防抖与节流
开发语言·前端·javascript·笔记·学习·ecmascript
科研前沿1 小时前
镜像孪生VS视频孪生核心技术产品核心优势
大数据·人工智能·算法·重构·空间计算
水蓝烟雨1 小时前
1931. 用三种不同颜色为网格涂色
算法·leetcode
晨曦夜月2 小时前
map与unordered_map区别
算法·哈希算法
小陈phd2 小时前
TensorRT 入门完全指南(一)——从核心定义到生态工具全解析
人工智能·笔记
是上好佳佳佳呀2 小时前
【前端(十一)】JavaScript 语法基础笔记(多语言对比)
前端·javascript·笔记
图码2 小时前
如何用多种方法判断字符串是否为回文?
开发语言·数据结构·c++·算法·阿里云·线性回归·数字雕刻
handler012 小时前
Linux 内核剖析:进程优先级、上下文切换与 O(1) 调度算法
linux·运维·c语言·开发语言·c++·笔记·算法