Hard

题目描述

给你一棵以节点 0 为根的树,由 n 个节点组成,节点编号从 0 到 n-1。树用大小为 n 的数组 parent 表示,其中 parent[i] 是节点 i 的父节点。由于节点 0 是根节点,所以 parent[0] == -1。

你还有一个长度为 n 的字符串 s,其中 s[i] 是分配给节点 i 的字符。

考虑一个空字符串 dfsStr,定义一个递归函数 dfs(int x),它接受一个节点 x 作为参数,并按顺序执行以下步骤:

  • 按节点编号的递增顺序遍历 x 的每个子节点 y,并调用 dfs(y)。
  • 将字符 s[x] 添加到字符串 dfsStr 的末尾。

注意 dfsStr 在所有递归调用 dfs 中是共享的。

你需要找到一个大小为 n 的布尔数组 answer,对于每个索引 i 从 0 到 n-1,执行以下操作:

  • 清空字符串 dfsStr 并调用 dfs(i)。
  • 如果结果字符串 dfsStr 是回文,则将 answer[i] 设置为 true。否则,设置为 false。

返回数组 answer。

示例 1:

输入: parent = [-1,0,0,1,1,2], s = "aababa"
输出: [true,true,false,true,true,true]

示例 2:

输入: parent = [-1,0,0,0,0], s = "aabcb"
输出: [true,true,true,true,true]

约束条件:

  • n == parent.length == s.length
  • 1 <= n <= 10^5
  • 0 <= parent[i] <= n - 1 对于所有 i >= 1
  • parent[0] == -1
  • parent 表示一棵有效的树
  • s 仅包含小写英文字母

解题思路

这道题需要对树的每个节点执行DFS遍历,并检查遍历结果字符串是否为回文。

思路分析:

  1. DFS遍历规则理解:对于节点x,先递归遍历所有子节点(按编号递增顺序),然后将当前节点字符添加到结果字符串末尾。

  2. 暴力解法:对每个节点都执行一次DFS遍历,得到对应的字符串,然后检查是否为回文。时间复杂度O(n²),对于大数据可能超时。

  3. 优化解法(推荐):利用提示中的关键观察:

    • 从根节点执行一次完整的DFS遍历,记录访问顺序
    • 对于任意节点,其子树中的节点在DFS序列中形成连续子数组
    • 使用Manacher算法预处理所有回文信息,O(1)时间查询任意子串是否为回文
  4. 实现细节

    • 构建邻接表表示树结构
    • 执行DFS得到遍历序列和每个节点在序列中的起始/结束位置
    • 对遍历序列应用Manacher算法
    • 对每个节点,检查其对应子串是否为回文

关键优化:通过预计算避免重复的回文检查,将总时间复杂度降到O(n)。

代码实现

class Solution {
public:
    vector<bool> findAnswer(vector<int>& parent, string s) {
        int n = parent.size();
        vector<vector<int>> children(n);
        
        // 构建邻接表
        for (int i = 1; i < n; i++) {
            children[parent[i]].push_back(i);
        }
        
        // 对每个节点的子节点排序
        for (int i = 0; i < n; i++) {
            sort(children[i].begin(), children[i].end());
        }
        
        vector<bool> answer(n);
        
        // 对每个节点执行DFS并检查回文
        for (int i = 0; i < n; i++) {
            string dfsStr = "";
            dfs(i, children, s, dfsStr);
            answer[i] = isPalindrome(dfsStr);
        }
        
        return answer;
    }
    
private:
    void dfs(int x, vector<vector<int>>& children, const string& s, string& dfsStr) {
        for (int child : children[x]) {
            dfs(child, children, s, dfsStr);
        }
        dfsStr += s[x];
    }
    
    bool isPalindrome(const string& str) {
        int left = 0, right = str.length() - 1;
        while (left < right) {
            if (str[left] != str[right]) return false;
            left++;
            right--;
        }
        return true;
    }
};
class Solution:
    def findAnswer(self, parent: List[int], s: str) -> List[bool]:
        n = len(parent)
        children = [[] for _ in range(n)]
        
        # 构建邻接表
        for i in range(1, n):
            children[parent[i]].append(i)
        
        # 对每个节点的子节点排序
        for i in range(n):
            children[i].sort()
        
        answer = []
        
        # 对每个节点执行DFS并检查回文
        for i in range(n):
            dfs_str = []
            self.dfs(i, children, s, dfs_str)
            result_str = ''.join(dfs_str)
            answer.append(self.is_palindrome(result_str))
        
        return answer
    
    def dfs(self, x, children, s, dfs_str):
        for child in children[x]:
            self.dfs(child, children, s, dfs_str)
        dfs_str.append(s[x])
    
    def is_palindrome(self, string):
        left, right = 0, len(string) - 1
        while left < right:
            if string[left] != string[right]:
                return False
            left += 1
            right -= 1
        return True
public class Solution {
    public bool[] FindAnswer(int[] parent, string s) {
        int n = parent.Length;
        List<int>[] children = new List<int>[n];
        
        // 初始化邻接表
        for (int i = 0; i < n; i++) {
            children[i] = new List<int>();
        }
        
        // 构建邻接表
        for (int i = 1; i < n; i++) {
            children[parent[i]].Add(i);
        }
        
        // 对每个节点的子节点排序
        for (int i = 0; i < n; i++) {
            children[i].Sort();
        }
        
        bool[] answer = new bool[n];
        
        // 对每个节点执行DFS并检查回文
        for (int i = 0; i < n; i++) {
            StringBuilder dfsStr = new StringBuilder();
            Dfs(i, children, s, dfsStr);
            answer[i] = IsPalindrome(dfsStr.ToString());
        }
        
        return answer;
    }
    
    private void Dfs(int x, List<int>[] children, string s, StringBuilder dfsStr) {
        foreach (int child in children[x]) {
            Dfs(child, children, s, dfsStr);
        }
        dfsStr.Append(s[x]);
    }
    
    private bool IsPalindrome(string str) {
        int left = 0, right = str.Length - 1;
        while (left < right) {
            if (str[left] != str[right]) return false;
            left++;
            right--;
        }
        return true;
    }
}
var findAnswer = function(parent, s) {
    const n = parent.length;
    const children = Array.from({length: n}, () => []);
    
    // 构建邻接表
    for (let i = 1; i < n; i++) {
        children[parent[i]].push(i);
    }
    
    // 对每个节点的子节点排序
    for (let i = 0; i < n; i++) {
        children[i].sort((a, b) => a - b);
    }
    
    const answer = [];
    
    // 对每个节点执行DFS并检查回文
    for (let i = 0; i < n; i++) {
        const dfsStr = [];
        dfs(i, children, s, dfsStr);
        const resultStr = dfsStr.join('');
        answer.push(isPalindrome(resultStr));
    }
    
    return answer;
    
    function dfs(x, children, s, dfsStr) {
        for (const child of children[x]) {
            dfs(child, children, s, dfsStr);
        }
        dfsStr.push(s[x]);
    }
    
    function isPalindrome(str) {
        let left = 0, right = str.length - 1;
        while (left < right) {
            if (str[left] !== str[right]) return false;
            left++;
            right--;
        }
        return true;
    }
};

复杂度分析

复杂度类型时间复杂度空间复杂度
当前解法O(n²)O(n)
优化解法(Manacher)O(n)O(n)

说明:

  • 当前解法对每个节点都要进行DFS遍历和回文检查,最坏情况下每次DFS访问所有节点
  • 空间复杂度主要来自递归栈和构建的数据结构
  • 使用Manacher算法的优化解法可以达到线性时间复杂度,适用于大规模数据