题目描述与示例
题目描述
小美拿到了一个长度为n
的字符串,她希望将字符串从左到右平铺成一个矩阵(先平铺第一行,然后是第二行,以此类推,矩阵有x
行y
列,必须保证x*y=n
,即每y
个字符换行,共x
行)。
该矩阵的权值定义为这个矩阵的连通块数量。小美希望最终矩阵的权值尽可能小,你能帮小美求出这个最小权值吗?
注:我们定义,上下左右四个方向相邻的相同字符是连通的。
输入描述
第一行输入一个正整数n
,代表字符串的长度。
第二行输入一个长度为n
的、仅由小写字母组成的字符串。
1`` ``<=`` ``n`` ``<=`` ``10^4
输出描述
输出一个整数表示最小权值。
示例
输入
9
aababbabb
输出
2
说明
平铺为3*3
的矩阵: aab
abb
abb
共有2
个连通块,4
个a
和5
个b
。
解题思路
这个问题的需求其实是很明确的,根据题意我们需要做两件事
- 找到所有可以摆列的方式,需要满足
x*y=n
,假设其中x
为小的,那么可以枚举1
到k
(k*k=n
,不需要枚举到n
),找到所有的枚举方式 - 每一个具体的枚举,通过DFS或者BFS的方式,找到其中的联通快数量。具体实现上标记每个位置是否访问,然后从一个方块如果未访问,那么联通块+1然后向四周搜索拓展、标记。
代码
解法一:DFS
Python
# 题目:【DFS&BFS】美团2023秋招-小美的字符串变换
# 作者:闭着眼睛学数理化
# 算法:DFS
# 相关习题:LeetCode200. 岛屿数量
# 代码有看不懂的地方请直接在群上提问
from math import inf, sqrt
DIRECTIONS = [(0, 1), (1, 0), (0, -1), (-1, 0)]
def dfs(grid, checkList, i, j, N, M):
# 标记点(i, j)为已检查过
checkList[i][j] = 1
# 遍历四个近邻点
for di, dj in DIRECTIONS:
ni, nj = i+di, j+dj
# 注意此处需要判断(ni, nj)在grid中的值,是否和(i, j)相等
if (0 <= ni < N and 0 <= nj < M and checkList[ni][nj] == 0
and grid[i][j] == grid[ni][nj]):
dfs(grid, checkList, ni, nj, N, M)
def solve(s, N, M):
# 根据s和N、M的值,构建N行M列的字符串矩阵grid
grid = [s[i:i+M] for i in range(0, N*M, M)]
# 检查列表
checkList = [[0] * M for _ in range(N)]
# 连通块数量
block_num = 0
# 双重遍历
for i in range(N):
for j in range(M):
# 尚未检查过的点,进行DFS
if checkList[i][j] == 0:
dfs(grid, checkList, i, j, N, M)
# 做完DFS,连通块数量+1
block_num += 1
# 返回连通块数量
return block_num
# 输入字符串长度n和字符串s
n = int(input())
s = input()
# 初始化答案为inf
ans = inf
# 遍历从1到sqrt(N)的所有因数N
for N in range(1, int(sqrt(n))+1):
# 如果n能够整除N,即N是n的因数
# 则另一个因数M = n // N
if n % N == 0:
M = n // N
# 分别以N和M作为长宽、宽长,进行计算,并且更新答案
ans = min(ans, solve(s, N, M))
ans = min(ans, solve(s, M, N))
print(ans)
Java
import java.util.*;
public class Main {
public static int minWeight(int n, String s) {
int minW = Integer.MAX_VALUE;
// 遍历所有可能的矩阵大小,找到所有满足x*y=n的x和y
for (int i = 1; i*i <= n; i++) {
if (n % i == 0) {
int x = i;
int y = n / i;
// 构建矩阵
char[][] matrix = new char[x][y];
for (int j = 0; j < x; j++) {
for (int k = 0; k < y; k++) {
matrix[j][k] = s.charAt(j * y + k);
}
}
// 计算矩阵的权值并更新最小权值
minW = Math.min(minW, countConnected(matrix));
//这里面x*y=n 其中一个作为长 一个作为宽 两个情况都要考虑
x=n/i;
y=i;
// 构建矩阵
matrix = new char[x][y];
for (int j = 0; j < x; j++) {
for (int k = 0; k < y; k++) {
matrix[j][k] = s.charAt(j * y + k);
}
}
// 计算矩阵的权值并更新最小权值
minW = Math.min(minW, countConnected(matrix));
}
}
return minW;
}
// 计算矩阵的连通块数量
public static int countConnected(char[][] matrix) {
int count = 0;
boolean[][] visited = new boolean[matrix.length][matrix[0].length];
int[][] directions = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[0].length; j++) {
if (!visited[i][j]) {
count++;
dfs(matrix, visited, directions, i, j);
}
}
}
return count;
}
// 深度优先搜索连通块
public static void dfs(char[][] matrix, boolean[][] visited, int[][] directions, int x, int y) {
visited[x][y] = true;//标记为true 避免重复计算
for (int[] direction : directions) {
int nx = x + direction[0];
int ny = y + direction[1];
if (nx >= 0 && nx < matrix.length && ny >= 0 && ny < matrix[0].length && !visited[nx][ny] && matrix[nx][ny] == matrix[x][y]) {
dfs(matrix, visited, directions, nx, ny);
}
}
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
scanner.nextLine();
String s = scanner.nextLine();
System.out.println(minWeight(n, s));
}
}
C++
#include <iostream>
#include <vector>
#include <cmath>
#include <climits> // Include this header for INT_MAX
using namespace std;
const vector<pair<int, int>> DIRECTIONS = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
void dfs(vector<string>& grid, vector<vector<int>>& checkList, int i, int j, int N, int M) {
checkList[i][j] = 1;
for (const auto& dir : DIRECTIONS) {
int ni = i + dir.first;
int nj = j + dir.second;
if (ni >= 0 && ni < N && nj >= 0 && nj < M && checkList[ni][nj] == 0 && grid[i][j] == grid[ni][nj]) {
dfs(grid, checkList, ni, nj, N, M);
}
}
}
int solve(string s, int N, int M) {
vector<string> grid(N, string(M, ' '));
vector<vector<int>> checkList(N, vector<int>(M, 0));
int block_num = 0;
for (int i = 0; i < N; ++i) {
for (int j = 0; j < M; ++j) {
grid[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(grid, checkList, i, j, N, M);
block_num += 1;
}
}
}
return block_num;
}
int main() {
int n;
cin >> n;
string s;
cin >> s;
int ans = INT_MAX;
for (int N = 1; N * N <= n; ++N) {
if (n % N == 0) {
int M = n / N;
ans = min(ans, solve(s, N, M));
ans = min(ans, solve(s, M, N));
}
}
cout << ans << endl;
return 0;
}
解法二:BFS
Python
Java
C++
时空复杂度
时间复杂度:O(``Nk``)
。N
为网格大小,k
为字符串变换的方法数,即N
的因数个数。
空间复杂度:O(``N``)
。
华为OD算法/大厂面试高频题算法练习冲刺训练
-
华为OD算法/大厂面试高频题算法冲刺训练目前开始常态化报名!目前已服务100+同学成功上岸!
-
课程讲师为全网50w+粉丝编程博主@吴师兄学算法 以及小红书头部编程博主@闭着眼睛学数理化
-
每期人数维持在20人内,保证能够最大限度地满足到每一个同学的需求,达到和1v1同样的学习效果!
-
60+天陪伴式学习,40+直播课时,300+动画图解视频,300+LeetCode经典题,200+华为OD真题/大厂真题,还有简历修改、模拟面试、专属HR对接将为你解锁
-
可上全网独家的欧弟OJ系统练习华子OD、大厂真题
-
可查看链接 OD算法冲刺训练课程表 & OD真题汇总(持续更新)
-
绿色聊天软件戳
od1336
了解更多