题解:学而思编程 分组
【题目来源】
学而思编程:分组
【题目描述】
有 \(n\) 位小朋友,编号依次是 \(1∼n\),小朋友们按照编号从小到大排成一排一起玩游戏,游戏之前每人会领取一张卡牌,每张卡牌上会有一个数字 \(a_i\)。
在开始游戏之前会先进行分组,具体来说:卡牌上的数字是 \(0\) 的同学固定不动,卡牌数字是非 \(0\) 的同学需要和距离自己最近的且卡牌的数字是 \(0\) 的同学为一组。
现在告诉你编号为 \(1∼n\) 的小朋友依次拿到的卡牌上的数字为 \(a_1,a_2,\dots,a_n\),即编号为 \(i\) 的小朋友的卡牌上的数字为 \(a_i\),请你计算每一位小朋友与自己最近的且卡牌数字是 \(0\) 的小朋友的距离分别是多少。
【输入】
第一行一个整数 \(n\);
第二行 \(n\) 个整数 \(a_1,a_2,\ldots,a_n\)。
【输出】
一行,\(n\) 个整数,分别表示每一位小朋友与自己最近的且拥有卡牌数字是 \(0\) 的小朋友的距离。
【输入样例】
9
7 5 -1 0 1 -2 0 0 4
【输出样例】
3 2 1 0 1 1 0 0 1
【核心思想】
-
问题分析:给定长度为 \(n\) 的数组 \(a_1, a_2, \ldots, a_n\),其中 \(a_i = 0\) 的位置为固定点,非零位置需要找到距离最近的 \(0\) 的距离。这是一个多源扩散 + 双向扫描问题,关键在于以每个 \(0\) 为中心向两侧扩散更新距离,取最小值。
-
算法选择:
- 初始化距离数组:
b[i]初始化为无穷大,表示尚未计算距离 - 多源扩散:遍历每个 \(0\) 的位置,分别向左和向右扫描,更新沿途非 \(0\) 位置到该 \(0\) 的距离,取已有距离的最小值
- 遇 \(0\) 停止:扫描过程中遇到另一个 \(0\) 时停止,因为更远的 \(0\) 不可能提供更短距离
- 初始化距离数组:
-
关键步骤:
- 初始化:
- 读取 \(n\) 和数组 \(a[1..n]\)
- \(b[i] = +\infty\)(\(1 \leq i \leq n\)):距离数组初始化为无穷大
- 多源扩散(遍历 \(i\) 从 \(1\) 到 \(n\)):
- 若
a[i] == 0:b[i] = 0:\(0\) 到自身距离为 \(0\)- 向左扫描(\(j\) 从 \(i-1\) 到 \(1\)):
- 若
a[j] == 0:break(遇到另一个 \(0\),停止) - \(b[j] = \min(b[j], i - j)\):更新到左侧 \(0\) 的距离
- 若
- 向右扫描(\(j\) 从 \(i+1\) 到 \(n\)):
- 若
a[j] == 0:break(遇到另一个 \(0\),停止) - \(b[j] = \min(b[j], j - i)\):更新到右侧 \(0\) 的距离
- 若
- 若
- 输出:
b[1..n]
- 初始化:
-
时间/空间复杂度:
- 时间复杂度:\(O(n \times d)\),\(d\) 为相邻 \(0\) 之间的平均距离。最坏情况 \(O(n^2)\)(只有一个 \(0\)),实际通常更优
- 空间复杂度:\(O(n)\),距离数组
b[1..n]
-
多源扩散的核心思想:
- 最近 \(0\) 的局部性:每个非 \(0\) 位置的最近 \(0\) 必然在其左侧或右侧的某个 \(0\) 中,以每个 \(0\) 为中心向两侧扩散可覆盖所有位置
- 最小值更新:多个 \(0\) 可能覆盖同一位置,
min操作确保保留最短距离 - 遇 \(0\) 剪枝:扫描时遇到另一个 \(0\) 立即停止,因为继续向外扩散的距离必然大于从该 \(0\) 开始扩散的距离,避免无效计算
- 双向覆盖:每个 \(0\) 同时向左和向右扫描,确保两侧位置都被更新
- 适用于"多源最近距离"类问题,扩散法直观易懂,对于稀疏 \(0\) 分布效率较高。也可用两次线性扫描(从左到右和从右到左)优化至严格 \(O(n)\)
【算法标签】
模拟
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int n, a[200010], b[200010];
int main()
{
cin >> n; // 输入数组长度
for (int i = 1; i <= n; i++) // 输入数组a
{
cin >> a[i];
b[i] = 1e9; // 初始化距离数组为无穷大
}
// 遍历数组,处理每个0
for (int i = 1; i <= n; i++)
{
if (a[i] == 0) // 找到0元素
{
b[i] = 0; // 0到自身的距离是0
// 向左扫描
for (int j = i - 1; j >= 1; j--)
{
if (a[j] == 0) // 遇到另一个0,停止扫描
{
break;
}
b[j] = min(b[j], i - j); // 更新距离
}
// 向右扫描
for (int j = i + 1; j <= n; j++)
{
if (a[j] == 0) // 遇到另一个0,停止扫描
{
break;
}
b[j] = min(b[j], j - i); // 更新距离
}
}
}
// 输出结果
for (int i = 1; i <= n; i++)
{
cout << b[i] << " ";
}
cout << endl;
return 0;
}
【运行结果】
9
7 5 -1 0 1 -2 0 0 4
3 2 1 0 1 1 0 0 1
浙公网安备 33010602011771号