题解:学而思编程 分组

【题目来源】

学而思编程:分组

【题目描述】

\(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

【核心思想】

  1. 问题分析:给定长度为 \(n\) 的数组 \(a_1, a_2, \ldots, a_n\),其中 \(a_i = 0\) 的位置为固定点,非零位置需要找到距离最近的 \(0\) 的距离。这是一个多源扩散 + 双向扫描问题,关键在于以每个 \(0\) 为中心向两侧扩散更新距离,取最小值。

  2. 算法选择

    • 初始化距离数组b[i] 初始化为无穷大,表示尚未计算距离
    • 多源扩散:遍历每个 \(0\) 的位置,分别向左和向右扫描,更新沿途非 \(0\) 位置到该 \(0\) 的距离,取已有距离的最小值
    • \(0\) 停止:扫描过程中遇到另一个 \(0\) 时停止,因为更远的 \(0\) 不可能提供更短距离
  3. 关键步骤

    • 初始化
      • 读取 \(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] == 0break(遇到另一个 \(0\),停止)
          • \(b[j] = \min(b[j], i - j)\):更新到左侧 \(0\) 的距离
        • 向右扫描\(j\)\(i+1\)\(n\)):
          • a[j] == 0break(遇到另一个 \(0\),停止)
          • \(b[j] = \min(b[j], j - i)\):更新到右侧 \(0\) 的距离
    • 输出b[1..n]
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n \times d)\)\(d\) 为相邻 \(0\) 之间的平均距离。最坏情况 \(O(n^2)\)(只有一个 \(0\)),实际通常更优
    • 空间复杂度:\(O(n)\),距离数组 b[1..n]
  5. 多源扩散的核心思想

    • 最近 \(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
posted @ 2026-07-04 14:56  团爸讲算法  阅读(4)  评论(0)    收藏  举报