大厂笔试真题讲解—美团23年—小美的字符串变换

本题主要讲解小美的字符串变换的要点和细节,根据步骤一步步思考方便理解

提供c++的核心代码以及acm模式代码,末尾

题目描述

小美拿到了一个长度为 n 的字符串,她希望将字符串从左到右平铺成一个矩阵(先平铺第一行,然后是第二行,以此类推,矩阵有 x 行 y 列,必须保证 x * y=n,即每 y 个字符换行,共 x 行)。 

该矩阵的权值定义为这个矩阵的连通块数量。小美希望最终矩阵的权值尽可能小,你能帮小美求出这个最小权值吗? 

注:我们定义,上下左右四个方向相邻的相同字符是连通的。

输入描述

第一行输入一个正整数 n(1 <= n <= 10^4),代表字符串的长度。 

第二行输入一个长度为 n 的、仅由小写字母组成的字符串。

输出描述

输出一个整数表示最小权值。

输入示例
9
aababbabb
输出示例
2
提示信息

平铺为 3 * 3 的矩阵:

aab

abb

abb 

共有 2 个连通块,4 个 a 和 5 个 b。

具体要点(针对笔试的ACM模式进行讲解): 

1. 首先明确我们获取的输入是什么,分别是:

        正整数 n(1 <= n <= 10^4),代表字符串的长度。 

        长度为 n 的、仅由小写字母组成的字符串

    其次考虑输出是什么:即最小连通块的数量

所以我们先写好我们的输入和输出:

int main() {
    //获取输入
    int n;//正整数n
    cin >> n;
    string s;//字符串s
    cin >> s;

    //执行函数,得到输出
    Solution sol;
    cout << sol.fun(n, s) << endl; //返回最小连通块的数量

    return 0;
}

2. 然后题目要求把s平铺成一个矩阵,没有给定矩阵的长宽,但是给了矩阵的长×宽=n

所以我们就要考虑到枚举所有形状的矩阵,对其每种形状进行计算和处理

下面我们用N代表长,M代表宽N

如何遍历所有的形状呢:我们首先会想到遍历N(1-n),去用n/N获取M的对应值,但是我们需要保证N是n的因数,即除完后没有余数。

所以我们接下里这样子写:

    int fun(int n, string s) {//接受我们的输入
        int N = 0, M = 0;//N代表行,M代表列
        int ans = INT_MAX;
        for (N = 1;N <= n;N++) {//遍历N,枚举所有的形状
            if (n % N == 0) {//保证没有余数
                M = n / N; //计算对应的M
                ans = min(ans, solve(N, M, s));//暂时不用管,还没讲到
            }
        }
        return ans;//我们的输出,最小的连通块数
    }

3. 接下来我们去写solve函数中的内容,老样子,首先明确solve的输入输出

        输入是N,M,s,输出是该形状下的连通块数量

然后考虑我们的函数内部要做些什么:

  • 我们虽然知道了N,M,但是具体矩阵内部元素我们还没有填充,需要用s去挨个填满,后续才能计算连通块
  • 计算连通块,我们要去判断一个位置的上下左右,是不是相同字符(然后继续判断该位置下个位置)不断递归,我们就会考虑用搜索算法(这里使用dfs深度优先)
  • 接着,虽然我们判断出来这个位置是不是连通,但是我们没有记录,我们需要维护一个一样形状的矩阵记录我们的判断结果,checklist
  • 等我们搜索完成一次后(检索了起始位置的字符相同的连通区域),就把连通区 blocknum++
  • 最后返回blocknum
    int solve(int N, int M, string s) {
        int blocknum = 0;
        vector<string>arr(N, string(M, ' '));
        //定义检查表
        vector<vector<int>> checklist(N, vector<int>(M, 0));
        //填充arr
        for (int i = 0;i < N;i++) {
            for (int j = 0;j < M;j++) {
                arr[i][j] = s[i * M + j];
            }
        }
    //遍历每一个位置,每个位置都进行深度搜索,
    //虽然在dfs递归过程中,有的位置在dfs中被检索到了,
    //但是由于还有其他的不同字符的连通块,我们要对每个位置作为起始都搜索一遍
        for (int i = 0;i < N;i++) {
            for (int j = 0;j < M;j++) {
                if (checklist[i][j] == 0) {
                    //执行深度优先搜索,递归
                    dfs(arr, checklist, i, j, N, M);
                    //等深度优先搜索完成之后,对block进行++
                    blocknum++;
                }
            }
        }
        return blocknum;;
    }

4. 接着我们要把dfs写完整

首先考虑dfs的输入

        要对i,j的四周进行搜索,所以要保证不越界,需要N,M帮助判断

        要搜索,需要对象 arr

        要记录搜索结果,需要checklist

接着,考虑递归终止条件

        我们递归中维护checklist,所以只要操作checklist即可,不需要返回值

        当我们下个位置越界下个位置字符不等于当前字符checklist不等于0,我们就终止了递归

其中,我们要预先定义一个DIRECTIONS,用来计算上下左右的位置,

    void dfs(vector<string>& arr, vector<vector<int>>& checklist, int i, int j, int N, int M) {
        //搜索到该位置就置1,不需要return,直接对checklist操作
        checklist[i][j] = 1;
        //深度优先策略
        for (const auto& dir : DIRECTIONS) {
            //计算下一个位置的索引
            int i_next = i + dir.first;
            int j_next = j + dir.second;
            //对位置进行判断,是否在范围内,checklist是否==0,arr的值是否与上一个一致
            if (i_next >= 0 && i_next < N && j_next >= 0 && j_next < M && checklist[i_next][j_next] == 0 && arr[i][j] == arr[i_next][j_next])
                dfs(arr, checklist, i_next, j_next, N, M);
        }
    }
    //下,右,上,左
    const vector<pair<int, int>> DIRECTIONS = {
    {0, 1},
    {1, 0},
    {0, -1},
    {-1, 0}
    };

至此,我们就完成了这个代码的主要编写,对于这种比较复杂的代码,我们需要先一步步深入考虑

  • 先考虑输入输出,
  • 再考虑代码解决什么,
  • 解决主要问题之前我们需要构造什么,
  • 如果涉及递归,想清楚递归的终止条件,
  • 递归的参数可以再写递归过程中慢慢加入,
  • 考虑递归是否需要返回值(还是需要维护数组)

 希望我的讲解对你的学习有一点作用

完整代码如下:

#include <iostream>
#include <vector>
#include <cmath>
#include <climits> // Include this header for INT_MAX

using namespace std;
class Solution {
    //下,右,上,左
    const vector<pair<int, int>> DIRECTIONS = {
    {0, 1},
    {1, 0},
    {0, -1},
    {-1, 0}
    };
public:
    int solve(int N, int M, string s) {
        int blocknum = 0;
        vector<string>arr(N, string(M, ' '));
        //定义检查表
        vector<vector<int>> checklist(N, vector<int>(M, 0));
        //填充arr
        for (int i = 0;i < N;i++) {
            for (int j = 0;j < M;j++) {
                arr[i][j] = s[i * M + j];
            }
        }
        for (int i = 0;i < N;i++) {
            for (int j = 0;j < M;j++) {
                if (checklist[i][j] == 0) {
                    //执行深度优先搜索,递归
                    dfs(arr, checklist, i, j, N, M);
                    //等深度优先搜索完成之后,对block进行++
                    blocknum++;
                }
            }
        }
        return blocknum;;
    }

    //深度优先搜索
    void dfs(vector<string>& arr, vector<vector<int>>& checklist, int i, int j, int N, int M) {
        //搜索到该位置就置1,不需要return,直接对checklist操作
        checklist[i][j] = 1;
        //深度优先策略
        for (const auto& dir : DIRECTIONS) {
            //计算下一个位置的索引
            int i_next = i + dir.first;
            int j_next = j + dir.second;
            //对位置进行判断,是否在范围内,checklist是否==0,arr的值是否与上一个一致
            if (i_next >= 0 && i_next < N && j_next >= 0 && j_next < M && checklist[i_next][j_next] == 0 && arr[i][j] == arr[i_next][j_next])
                dfs(arr, checklist, i_next, j_next, N, M);
        }
    }

    
    int fun(int n, string s) {
        int N = 0, M = 0;//N代表行,M代表列
        int ans = INT_MAX;
        for (N = 1;N <= n;N++) {//求出N和M的值
            if (n % N == 0) {
                M = n / N;
                ans = min(ans, solve(N, M, s));
            }
        }
        return ans;
    }
};

int main() {
    int n;
    cin >> n;
    string s;
    cin >> s;
    Solution sol;
    cout << sol.fun(n, s) << endl;
    return 0;
}

  • 25
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值