路径数组变为统计数组

路径数组变为统计数组

题目描述

给定一个路径统计数组paths,表示一张图。paths[i] == j代表城市i连向城市j,如果paths[i] == i,则表示i城市是首都,一张图里只会有一个首都且图中除首都指向自己之外不会有环。例如,paths=[9,1,4,9,0,4,8,9,0,1],代表的图如图9-14所示。

由数组表示的图可以知道,城市1是首都,所以距离为0,离首都距离为1的城市只有城市9,离首都距离为2的城市有城市0、3和7,离首都距离为3的城市有城市4和8,离首都距离为4的城市有城市2、5和6。所以距离为0的城市有1座,距离为1的城市有1座,距离为2的城市有3座,距离为3的城市有2座,距离为4的城市有3座。那么统计数组为nums=[1,1,3,2,3,0,0,0,0,0]

[要求]

时间复杂度为 O ( n ) O(n) O(n),额外空间复杂度为 O ( 1 ) O(1) O(1)

输入描述:

第一行有一个整数N,表示节点个数。
接下来一行有N个整数表示paths。其中第i个整数表示第i-1个节点的祖先

输出描述:

输出N个整数表示nums。

示例1
输入
10
9 1 4 9 0 4 8 9 0 1
输出
1 1 3 2 3 0 0 0 0 0
备注:

N ⩽ 2 ∗ 1 0 5 N \leqslant 2∗10^5 N2105
保证输入合法。


题解:
解法一:

若不考虑空间要求,可以重新建图,然后 BFS 遍历即可。。。

解法二:

题目已经给出了图的层次信息,我们可以按照给的信息迭代(老麻烦了):

  • 首先对 paths 数组进行改写,paths[i] == j 表示城市 i 距离首都的距离为 j 的绝对值,paths中的值设置为负数
  • 在改写 paths 数组后,在 paths 数组上迭代修改成个数。。。

paths 数组改写:

  1. 从左到右遍历paths,先遍历到位置0:
    paths[0] == 9,首先令paths[0] == -1(这是为了标记起跳的城市),因为城市0指向城市9,所以跳到城市9.
    跳到城市9之后,paths[9] == 1,说明下一个城市是1,因为城市9是由城市0跳过来的,所以先令paths[9] = 0,然后跳向城市1。
    跳到城市1之后,paths[1] == 1,说明城市1是首都。现在开始往回跳,城市1是由城市9跳过来的,所以跳回城市9。
    根据之前设置的paths[9] == 0,我们知道城市9是由城市0跳过来的,在回跳之前先设置paths[9] = -1,表示城市9到首都的距离为1,之后回跳到0。
    根据之前的设置paths[0] == -1,我们知道城市0是起跳位置,所以不再回跳,令paths[0] == -2,表示到首都的距离为2。
    以上在跳向首都的过程中,paths数组有一个路径反指的过程,这是为了保证找到首都之后,能够完全跳回来。在跳回来的过程中,设置好这一路所跳的城市 即可。
  2. 对于其他位置,跳跃的过程同上,但是跳跃终止的条件可以不是跳到首都,当我们跳到下一个位置发现它的值是负数,说明这个位置已经计算出了到首都的距离,我们只要在这个基础上来设置距离即可。
  3. 首都我们单独处理即可,找到首都的位置然后将它的值设为0,表示距离为0。

paths 数组改写后的迭代:

得到距离数组后,数组中的距离值都用负数表示。接下来我们根据距离数组来计算得到统计数组,该过程也是一个跳跃的过程。

从左到右遍历数组,假设遍历到为 i,paths[i] == -j,那么我们可以知道城市 i 距离首都的距离是 j ,先令paths[i] == 0,表示此位置不再代表距离,然后跳到位置 j 处,如果位置 j 处的值为负数 -k,我们令 paths[j] = 1,表示我们已经找到一个距离为 j 的城市(城市 i )。然后根据 k 继续往下跳。如果位置 j 处的值为正数,说明该位置已经是距离为 j 的城市的数量统计,直接加1即可。

解法二代码:
#include <cstdio>

using namespace std;

const int N = 200000;

int n;
int paths[N];

void pathsToDis() {
    int cap = -1;
    for ( int i = 0; i < n; ++i ) {
        if ( paths[i] == i ) cap = i;
        else if ( paths[i] > -1 ) {
            int cur = paths[i];
            int pre = i;
            paths[i] = -1;
            while ( paths[cur] != cur ) {
                if ( paths[cur] > -1 ) {
                    int nxt = paths[cur];
                    paths[cur] = pre;
                    pre = cur;
                    cur = nxt;
                } else break;
            }
            int val = paths[cur] == cur ? 0 : paths[cur];
            while ( paths[pre] != -1 ) {
                int ppre = paths[pre];
                paths[pre] = --val;
                pre = ppre;
            }
            paths[pre] = --val;
        }
    }
    paths[cap] = 0;
}

void disToNums() {
    for ( int i = 0; i < n; ++i ) {
        int idx = paths[i];
        if ( idx < 0 ) {
            paths[i] = 0;
            while ( true ) {
                idx = -idx;
                if ( paths[idx] > -1 ) {
                    ++paths[idx];
                    break;
                } else {
                    int nxt = paths[idx];
                    paths[idx] = 1;
                    idx = nxt;
                }
            }
        }
    }
    paths[0] = 1;
}

int main(void) {
    scanf("%d", &n);
    for ( int i = 0; i < n; ++i ) scanf("%d", paths + i);
    pathsToDis();
    disToNums();
    for ( int i = 0; i < n; ++i )
        printf("%d%c", paths[i], " \n"[i == n - 1]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值