算法思想一:普通解法

解题思路:

除了群主牛牛,每个群友都有一个等级大于他的人作为朋友,那么最终是一个以牛牛为根的树。所以对于每个群友,一直往上找到第一个等级足够处理这个群友产生的问题上级,并统计答案。
在此基础上加一个字典,缓存每个人i遇到等级v时候最终对应的解决人是谁,下次遇到就不用递归去找了
流程:
1、初始化返回数组 res,字典 dic:缓存每个人i遇到等级v时候最终对应的解决人是谁
2、遍历产生的问题 k 数组:
    1、如果当前人的能力值大于问题等级,则将 其存入字典中记录
    2、否则,进行循环:当不是群主且当前人的能力值小于问题等级
        1、找到此题应该寻找的朋友 t = p[t-1] -1
        2、判断这个朋友做的题等级记录是否在字典中,若在,则此人做题数量加1
        3、如果当前人能力值大于问题等级,则继续加入字典
3、最后返回 res
图解:

代码展示:

#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
#
# 算法交流群
# @param n int整型 群员个数
# @param a int整型一维数组 群员的等级
# @param p int整型一维数组 群友寻求帮助的人
# @param k int整型一维数组 群友产生的问题等级
# @return int整型一维数组
#
class Solution:
    def solve(self , n , a , p , k ):
        # write code here
        # 返回数组
        res = [0]*n
        # 字典 缓存每个人i遇到等级v时候最终对应的解决人是谁
        dic ={}
        # 遍历
        for i,v in enumerate(k):
            # 能力值大于问题等级  存入字典
            if a[i]>=v:
                dic[(i,v)] = i
                res[i]+=1
            else:
                t = i
                # 不是群主 且 能力值小于问题等级
                while t!=0 and a[i]<v:
                    # 寻找朋友
                    t = p[t-1] -1
                    # 如果 t 对应的题在字典中,则此人的题数加1
                    if (t,v) in dic:
                        res[dic[(t,v)]]+=1
                        break
                    # 如果 t的能力值大于问题等级,继续加入字典
                    if a[t]>=v:
                        dic[(i,v)]=t
                        res[t]+=1
                        break
        return res

复杂度分析

时间复杂度O(N):N表示问题数量,依次遍历时间
空间复杂度O(N):字典占用空间

算法思想二:最优解法

解题思路:

因为牛牛的编号是1且它全场最高。整棵树是以1为根的有根树。一个结点的权值一定大于它的子孙结点权值。整棵树呈堆的形式。
因为从根到一个结点的路径上,点的权值是越远离根结点,权值越小,满足二分性质。
树上倍增求得每个点向上 2^i 层的祖先。求法为:用 f[u][i] 表示结点 u 向上跳 2^i  层可以到达的结点。
然后在上面用类似二分的方法找到第一个能解决当前问题的祖先。

代码展示:

C++版本
class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 算法交流群
     * @param n int整型 群员个数
     * @param a int整型vector 群员的等级
     * @param p int整型vector 群友寻求帮助的人
     * @param k int整型vector 群友产生的问题等级
     * @return int整型vector
     */
    int f[100005][20];
    vector<int> g[100005];
    void dfs(int u){
       // cout<<"u:"<<u<<endl;
        for(int i = 0; i < 19; ++i) f[u][i+1] = f[f[u][i]][i];
        for(int i = 0; i < g[u].size(); ++i) dfs(g[u][i]);
    }
    vector<int> solve(int n, vector<int>& a, vector<int>& p, vector<int>& k) {
        // write code here
        vector<int> ans(n, 0);
        for(int i = 0; i <= n; ++i) g[i].clear();
        memset(f, 0, sizeof f);
        for(int i = 0; i < n-1; ++i) f[i+2][0] = p[i],g[p[i]].push_back(i+2) ;
        dfs(1);
        if(k[0] <= a[0]) ans[0]++;
        for(int i = 1; i < n; ++i){
            if(k[i] <= a[i]){ans[i]++; continue;}
            int u = i+1;
            for(int j = 18; j >= 0; --j){
                if(f[u][j] == 0) continue;
                int t = f[u][j];
                if(k[i] > a[t-1]) u = t;
            }
            u = f[u][0];
            if(u == 0) continue;
            else ans[u-1]++;
        }
        return ans;
    }
};

复杂度分析

时间复杂度O(NlogN):N表示问题数量,一层遍历再加一层二分统计时间
空间复杂度O(NlogN):需要额外的空间存储倍增数组