【C++笔试强训】day05

news2024/9/23 9:25:46

游游的you

在这里插入图片描述

思路

贪心:优先组成 you,最少的字母决定了you的数量。需要注意:如果oo剩下n个,那么相邻oo的个数是n-1个,而不是n/2个。

例如

oooooo
oo oo oo
 oo oo 

6个o,两两组合有3对,掐头去尾有2对,总共5对。

代码

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

int main() 
{
    int q;
    cin >> q;
    
    while (q--) 
    {
        int a, b, c;
        cin >> a >> b >> c;
        int you_num = min({a, b, c});
        int oo_num = max(0, b - you_num - 1);
        cout << you_num * 2 + oo_num << endl;
    }
    
    return 0;
}

腐烂的苹果

BFS

将每个腐烂的苹果视为一个起点,同时扩展它们对周围苹果的影响。每个腐烂的苹果每分钟会将相邻的苹果腐烂,类似于层层扩散的波浪,因此 BFS 是非常适合的。

  1. 初始化队列:首先,我们需要找到所有腐烂的苹果,并将它们的坐标加入到 BFS 队列中。每个腐烂的苹果作为一个初始点,且时间为0。
  2. BFS 扩展:每次从队列中取出一个腐烂的苹果,检查它的四个方向(上下左右),如果相邻格子是一个完好的苹果,则将其腐烂,并将其加入到队列中,同时记录时间。
  3. 判断结果:BFS 结束后,检查是否还有完好的苹果。如果有,返回 -1;否则返回所需的最大时间。
代码
class Solution
{
public:
    // 定义方向数组,表示四个方向
    const int directions[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};

    int rotApple(vector<vector<int>> &grid) {
        int n = grid.size(), m = grid[0].size();
        queue<pair<int, int>> q;
        int freshApples = 0; // 记录新鲜苹果的数量

        // 初始化队列,并统计新鲜苹果的数量
        for (int i = 0; i < n; ++i)
            for (int j = 0; j < m; ++j)
                if (grid[i][j] == 2) q.push({i, j});
                else if (grid[i][j] == 1) freshApples++;

        // 如果没有新鲜苹果,直接返回0
        if (freshApples == 0) return 0;
        int minutes = 0;
        // BFS 扩展
        while (!q.empty())
        {
            int size = q.size();
            bool rottenThisMinute = false;
            for (int i = 0; i < size; ++i)
            {
                auto [x, y] = q.front();
                q.pop();
                for (int d = 0; d < 4; ++d)
                {
                    int nx = x + directions[d][0];
                    int ny = y + directions[d][1];
                    // 检查新位置是否合法,并且是否有新鲜苹果
                    if (nx >= 0 && nx < n && ny >= 0 && ny < m && grid[nx][ny] == 1)
                    {
                        grid[nx][ny] = 2; // 使其腐烂
                        q.push({nx, ny});
                        freshApples--;
                        rottenThisMinute = true;
                    }
                }
            }
            // 如果这分钟有苹果腐烂,增加时间
            if (rottenThisMinute) minutes++;
        }
        // 如果还有新鲜苹果,返回 -1
        return freshApples == 0 ? minutes : -1;
    }
};

DFS

使用 DFS 来解决这个问题会有一些不方便之处,特别是在处理需要“波浪”式传播的情况时(例如,腐烂的苹果会同时向四周扩散)。DFS 适合用于深度优先搜索,而 BFS 更适合于层层扩展、逐层处理的场景。

  1. BFS 的层次扩展特性:
    • BFS 一次处理一个层次上的所有节点(在这个问题中,相当于同一时间点所有腐烂的苹果),自然适合模拟每分钟同时传播腐烂的过程。
    • 使用 BFS,每分钟的腐烂传播相当于从一个层次扩展到下一个层次,时间计数器每次增加1分钟。
  2. DFS 的局限性:
    • DFS 适用于深度遍历,它会一直沿着一条路径深入,直到不能再走为止,然后回溯。这种遍历方式难以模拟同一时间点多个苹果同时腐烂的情况。
    • 如果用 DFS,你需要额外的逻辑来同步和控制腐烂的时间进度,这样实现起来会非常复杂且容易出错。

实际 DFS 实现的难点:

  1. 多源同时起点处理:DFS 通常处理单源问题较多,而这里每个腐烂的苹果都是一个起点,需要多源处理。
  2. 同步时间控制:需要对每个腐烂的苹果扩散时间进行精确控制,使得同时腐烂的效果能正确实现。

即便如此,我们仍然可以尝试用 DFS 来实现,只是实现会更加复杂。

代码
class Solution
{
public:
    const int directions[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};

    void dfs(vector<vector<int>> &grid, vector<vector<int>> &timeGrid, int x, int y,
             int currentTime)
    {
        int n = grid.size();
        int m = grid[0].size();

        for (int d = 0; d < 4; ++d)
        {
            int nx = x + directions[d][0];
            int ny = y + directions[d][1];
            if (nx >= 0 && nx < n && ny >= 0 && ny < m && grid[nx][ny] == 1)
            {
                if (timeGrid[nx][ny] == -1 || timeGrid[nx][ny] > currentTime + 1)
                {
                    timeGrid[nx][ny] = currentTime + 1;
                    dfs(grid, timeGrid, nx, ny, currentTime + 1);
                }
            }
        }
    }

    int rotApple(vector<vector<int>> &grid)
    {
        int n = grid.size();
        int m = grid[0].size();
        vector<vector<int>> timeGrid(n, vector<int>(m, -1));
        int freshApples = 0;

        // 初始化腐烂的苹果时间为0
        for (int i = 0; i < n; ++i)
        {
            for (int j = 0; j < m; ++j)
            {
                if (grid[i][j] == 2)
                {
                    timeGrid[i][j] = 0;
                    dfs(grid, timeGrid, i, j, 0);
                }
                else if (grid[i][j] == 1)
                {
                    freshApples++;
                }
            }
        }

        // 找到最大时间
        int maxTime = 0;
        for (int i = 0; i < n; ++i)
        {
            for (int j = 0; j < m; ++j)
            {
                if (grid[i][j] == 1)
                {
                    if (timeGrid[i][j] == -1)
                        return -1;
                    maxTime = max(maxTime, timeGrid[i][j]);
                }
            }
        }

        return maxTime;
    }
};

孩子们的游戏(圆圈中最后剩下的数)

image-20240713183721150

思路

这个问题可以用著名的约瑟夫环(Josephus Problem)的思路来解决。我们要找出最后剩下的那个小朋友的编号。经典的约瑟夫问题可以通过递归或迭代的方式来解决,并且时间复杂度为 O(n),空间复杂度为 O(1)。

约瑟夫问题的递推公式如下:

  • 令 f(n, m) 表示在 n 个人报数,每次报到 m 出列的情况下,最后一个人的位置(0-based)。
  • 初始状态下,当只有一个人时,唯一的那个人的位置为0,即 f(1, m) = 0。
  • 当有 n 个人时,最后剩下的人的位置可以通过 f(n-1, m) 的结果来递推得到,即 f(n, m) = (f(n-1, m) + m) % n。
代码

递归(朴素动态规划——大问题化简为子问题):

class Solution {
public:
    int LastRemaining_Solution(int n, int m) {
        // 返回当前i个人,报数j的第k个人的编号
        function <int(int, int, int)> dfs = [&](int i, int j, int k) -> int
        {
            // 第1个人,说明是从0开始报数
            if (k == 1) 
                return (j - 1 + i) % i;
            // 返回i-1个人,报数j的第k-1个人,后面j个人的编号
            return (dfs(i - 1, j, k - 1) + j) % i;
        };
        return dfs(n, m, n);
    }
};

循环链表(最符合手动操作):

在约瑟夫环问题中,每次删除一个元素后,我们需要确保剩余元素仍然形成一个有效的循环链表。这就需要我们在删除某个元素时,正确更新它的指针。

初始化链表:通过数组nxt和pre来表示链表中每个节点的下一个和上一个节点。

遍历链表:每次移动m步,找到要删除的元素。

删除元素:更新前驱和后继指针,将当前节点从链表中移除。

class Solution
{
public:
    int LastRemaining_Solution(int n, int m)
    {
        // 初始化双向循环链表
        vector<int> nxt(n);
        vector<int> pre(n);
        for (int i = 0; i < n - 1; i++)
        {
            nxt[i] = i + 1;
            pre[i + 1] = i;
        }
        nxt[n - 1] = 0;  // 形成一个循环
        pre[0] = n - 1;  // 完成循环

        int x = 0;  // 从第一个元素开始

        // 删除元素直到只剩下一个
        while (n > 1)
        {
            // 找到第 m 个节点
            for (int i = 1; i < m; i++)
            {
                x = nxt[x];
            }

            // 删除第 m 个节点(关键操作)
            nxt[pre[x]] = nxt[x];
            pre[nxt[x]] = pre[x];

            x = nxt[x];  // 移动到下一个节点
            n--;  // 减少链表的大小
        }

        return x;  // 最后剩下的节点
    }
};

动态规划:

由递归进行的子问题分析可以知道:忽略环形链表的下标,可以认为它的任意一个节点都是起点和终点的交界处。对于第i个人,认为它上一个人的编号是链表的起点,那么上一个人的编号+报数,就是第i个人的编号。由于是环形链表,对其长度取余数,就是真实的编号了。

转移:

  • 只有一个数,结果为0。

  • 否则: d p [ i ] = ( d p [ i − 1 ] + 报数 ) % 数组长度 dp[i] = (dp[i-1]+报数) \% 数组长度 dp[i]=(dp[i1]+报数)%数组长度

class Solution 
{
  public:
    int LastRemaining_Solution(int n, int m) 
    {
        int dp[n + 1];
        dp[1] = 0;
        for (int i = 2; i <= n; i++) 
            dp[i] = (dp[i - 1] + m) % i;
        return dp[n];
    }
};

迭代:

由于填充DP数组是一个根据上一个结果来推出现在的结果的过程,而旧状态只被使用一次,所以我们不需要用数组保留原先的旧状态。

class Solution {
public:
    int LastRemaining_Solution(int n, int m) {
        if (n == 0) return -1; // 边界条件处理
        int last = 0; // 初始状态下,当只有一个人时,唯一的那个人的位置为0
        for (int i = 2; i <= n; ++i)
            last = (last + m) % i;
        return last;
    }
};

初始状态处理:

  • 当 n 为 0 时,返回 -1,这是一种边界条件处理,虽然根据题目描述 n 不会为 0,但防止无效输入。

迭代过程:

  • 从 2 人开始,一直迭代到 n 人。
  • 每次迭代计算出当前人数下最后剩下的人的位置。

结果返回:

  • 最后得到的 last 即为 n 个人情况下最后剩下的人的编号。

这个算法利用了迭代而非递归,避免了栈溢出问题,同时空间复杂度也为 O(1),非常高效。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1950546.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

为什么样本方差(sample variance)的分母是 n-1?

样本均值与样本方差的定义 首先来看一下均值&#xff0c;方差&#xff0c;样本均值与样本方差的定义 总体均值的定义&#xff1a; μ 1 n ∑ i 1 n X i \mu\frac{1}{n}\sum_{i1}^{n} X_i μn1​i1∑n​Xi​ 也就是将总体中所有的样本值加总除以个数&#xff0c;也可以叫做总…

让你的程序有记忆功能。

目录 环境 代码 环境 大语言模型&#xff1a; gpt-40-mini Mem0: Empower your AI applications with long-term memory and personalization OpenAPI-Key: Mem0-Key&#xff1a; 代码 import osfrom dotenv import load_dotenv from openai import OpenAI from m…

UE5.4内容示例(1)- 学习笔记

https://www.unrealengine.com/marketplace/zh-CN/product/content-examples 《内容示例》是学习UE5的基础示例&#xff0c;可以用此示例熟悉一遍UE5的功能 模型与材质部分 StaticMeshes FBX_Import_Options Material_Advanced Material_Decals Material_Instances Material_N…

免费好用!还在用Stable Diffusion生艺术字?阿里AI艺术字生成工具,推荐试试!(附详细教程)

大家好&#xff0c;我是程序员X小鹿&#xff0c;前互联网大厂程序员&#xff0c;自由职业2年&#xff0c;也一名 AIGC 爱好者&#xff0c;持续分享更多前沿的「AI 工具」和「AI副业玩法」&#xff0c;欢迎一起交流~ 曾经那个口口声声对X小鹿说&#xff0c;「自己不太适合写文章…

【VUE】个人记录:父子页面数据传递

1. 父传子 在父页面中&#xff0c;调用子页面的组件位置处&#xff0c;通过“&#xff1a;”进行参数传递 <child-component :childData"parentData"></child-component>parentData对象&#xff0c;需要在父页面的data中进行初始化声明 在子页面中&am…

深入探索PHP框架:Symfony框架全面解析

1. 引言 在现代Web开发领域&#xff0c;PHP作为一种广泛使用的服务器端脚本语言&#xff0c;其框架的选择对于项目的成功至关重要。PHP框架不仅能够提高开发效率&#xff0c;还能确保代码的质量和可维护性。本文将深入探讨Symfony框架&#xff0c;这是一个功能强大且灵活的PHP…

Java 序列流:Java 对象的序列化和反序列化详解

1.概念 序列化是指将一个对象转换为一个字节序列&#xff08;包含对象的数据、对象的类型和对象中存储的属性等信息&#xff09;&#xff0c;它可以将多个InputStream对象串联起来&#xff0c;使得它们可以被当作一个单一的输入流来处理&#xff0c;以便在网络上传输或保存到文…

【反证法】932. 漂亮数组

本文涉及知识点 分治 数学 反证法 LeetCode932. 漂亮数组 如果长度为 n 的数组 nums 满足下述条件&#xff0c;则认为该数组是一个 漂亮数组 &#xff1a; nums 是由范围 [1, n] 的整数组成的一个排列。 对于每个 0 < i < j < n &#xff0c;均不存在下标 k&#x…

ChatGPT的原理和成本

ChatGPT就是人机交互的一个底层系统&#xff0c;某种程度上可以类比于操作系统。在这个操作系统上&#xff0c;人与AI之间的交互用的是人的语言&#xff0c;不再是冷冰冰的机器语言&#xff0c;或者高级机器语言&#xff0c;当然&#xff0c;在未来的十来年内&#xff0c;机器语…

CSRF(Cross-site request forgery)

一、概述 1、CSRF定义 CSRF是一个web安全漏洞&#xff0c;该漏洞通过引诱用户来执行非预期的操作。该漏洞使得攻击者能够绕过同源策略&#xff0c;同源策略是一种用来阻止不同网站相互干扰的一种技术。 CSR跨站请求伪造&#xff0c;攻击者利用服务器对用户的信任&#xff0c…

MySQL练手 --- 1174. 即时食物配送 II

题目链接&#xff1a;1174. 即时食物配送 II 思路&#xff1a; 题目要求&#xff1a;即时订单在所有用户的首次订单中的比例。保留两位小数 其实也就是 即时订单 / 首次订单 所以&#xff0c;先求出首次订单&#xff0c;在首次订单的基础上寻找即时订单即可 解题过程&#x…

Mysql explain 优化解析

explain 解释 select_type 效率对比 MySQL 中 EXPLAIN 语句的 select_type 列描述了查询的类型,不同的 select_type 类型在效率上会有所差异。下面我们来比较一下各种 select_type 的效率: SIMPLE: 这是最简单的查询类型,表示查询不包含子查询或 UNION 操作。 这种查询通常是…

SpringCloud注册中心

SpringCloud注册中心 文章目录 SpringCloud注册中心1、注册中心原理2、Nacos注册中心2.1、部署nacos 3、服务注册4、服务发现 1、注册中心原理 在大型微服务项目中&#xff0c;服务提供者的数量会非常多&#xff0c;为了管理这些服务就引入了注册中心的概念。注册中心、服务提…

Redis实战篇(黑马点评)笔记总结

一、配置前后端项目的初始环境 前端&#xff1a; 对前端项目在cmd中进行start nginx.exe&#xff0c;端口号为8080 后端&#xff1a; 配置mysql数据库的url 和 redis 的url 和 导入数据库数据 二、登录校验 基于Session的实现登录&#xff08;不推荐&#xff09; &#xf…

微信小程序之接口测试

接口测试使用的方法及技术 测试接口文档 测试用例 用例执行&#xff1a;

算法日记day 20(二叉搜索树)

一、验证二叉搜索树 题目&#xff1a; 给你一个二叉树的根节点 root &#xff0c;判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下&#xff1a; 节点的左 子树 只包含 小于 当前节点的数。节点的右子树只包含 大于 当前节点的数。所有左子树和右子树自身必须也…

学习记录——day20 IO

IO基础 1、IO&#xff1a;&#xff08;inout output&#xff09; 程序与外部设备进行信息交换的过程 2、IO的分类&#xff1a;标准IO和文件IO 1&#xff09;标准IO&#xff1a;调用封装好的相关库函数&#xff0c;来实现数据的输入输出 2&#xff09;文件IO&#xff1a; 3、…

vue3响应式用法(高阶性能优化)

文章目录 前言&#xff1a;一、 shallowRef()二、 triggerRef()三、 customRef()四、 shallowReactive()五、 toRaw()六、 markRaw()七、 shallowReadonly()小结&#xff1a; 前言&#xff1a; 翻别人代码时&#xff0c;总结发现极大部分使用vue3的人只会用ref和reactive处理响…

废品回收小程序制作,数字化带来的商业机会

随着社会环保意识的增强&#xff0c;废品回收成为了一个热门行业&#xff0c;它不仅能够减少资源浪费&#xff0c;还能够带来新的商业机会&#xff01; 当下&#xff0c;废品回收小程序已经成为了回收市场的重要方式&#xff0c;为回收行业的发展注入新鲜活力&#xff0c;推动…

如何使用C#快速创建定时任务

原文链接&#xff1a;https://www.cnblogs.com/zhaotianff/p/17511040.html 使用Windows的计划任务功能可以创建定时任务。 使用schtasks.exe可以对计划任务进行管理&#xff0c;而不需要编写额外代码 这里掌握schtasks /CREATE 的几个核心参数就可以快速创建计划任务 /SC …