【深度优先搜索】【树】【状态压缩】2791. 树中可以形成回文的路径数

简介: 【深度优先搜索】【树】【状态压缩】2791. 树中可以形成回文的路径数

作者推荐

【深度优先搜索】【树】【有向图】【推荐】685. 冗余连接 II

本文涉及知识点

深度优先搜索 树 图论 状态压缩

LeetCode:2791. 树中可以形成回文的路径数

给你一棵 树(即,一个连通、无向且无环的图),根 节点为 0 ,由编号从 0 到 n - 1 的 n 个节点组成。这棵树用一个长度为 n 、下标从 0 开始的数组 parent 表示,其中 parent[i] 为节点 i 的父节点,由于节点 0 为根节点,所以 parent[0] == -1 。

另给你一个长度为 n 的字符串 s ,其中 s[i] 是分配给 i 和 parent[i] 之间的边的字符。s[0] 可以忽略。

找出满足 u < v ,且从 u 到 v 的路径上分配的字符可以 重新排列 形成 回文 的所有节点对 (u, v) ,并返回节点对的数目。

如果一个字符串正着读和反着读都相同,那么这个字符串就是一个 回文 。

示例 1:

输入:parent = [-1,0,0,1,1,2], s = “acaabc”

输出:8

解释:符合题目要求的节点对分别是:

  • (0,1)、(0,2)、(1,3)、(1,4) 和 (2,5) ,路径上只有一个字符,满足回文定义。
  • (2,3),路径上字符形成的字符串是 “aca” ,满足回文定义。
  • (1,5),路径上字符形成的字符串是 “cac” ,满足回文定义。
  • (3,5),路径上字符形成的字符串是 “acac” ,可以重排形成回文 “acca” 。
    示例 2:
    输入:parent = [-1,0,0,0,0], s = “aaaaa”
    输出:10
    解释:任何满足 u < v 的节点对 (u,v) 都符合题目要求。

提示:

n == parent.length == s.length

1 <= n <= 105

对于所有 i >= 1 ,0 <= parent[i] <= n - 1 均成立

parent[0] == -1

parent 表示一棵有效的树

s 仅由小写英文字母组成

深度优先搜索

树的路径一定是 起点 → \rightarrow公共祖先→ \rightarrow终点,特例是起点(或终点)就是公共祖先。

且从 u 到 v 的路径上分配的字符可以 重新排列 形成 回文    ⟺    \iff 各字符的数量只有1个是奇数或全部是偶数

状态压缩: mask &(1 << i ) 表示 ‘a’+i 出现的次数是奇数。

深度优先搜索的状态

暴力的办法是枚举左支和右支的状态,空间复杂度(226) 已经在超内存的边缘了。

状态优化

如果node1到node2的路径能构成回文,那node1→ \rightarrowroot→ \rightarrownode2 也能构成回文。起点(或终点)就是公共祖先 也符合。

单节点要排除,因为不符合v<v。

深度优先搜索的转移方程

vCnt[mask] 记录 各节点到root(0)的mask。

node1的压缩状态为:mask

cnt += vCnt[mask]-1

cnt += ∑ j : 0 25 v C n t [ m a s k ( 1 < < j ) ] \sum_{j:0}^{25}vCnt[mask^(1<<j)]j:025vCnt[mask(1<<j)]

深度优先搜索的返回值

cnt/2。

代码

核心代码

class Solution {
public:
  long long countPalindromePaths(vector<int>& parent, string s) {
    m_s = s; 
    vector<vector<int>> vNeiBo(parent.size());
    for (int i = 1; i < parent.size(); i++)
    {
      vNeiBo[parent[i]].emplace_back(i);
    }
    DFS(vNeiBo, 0, -1, 0);
    long long llRet = 0;
    for (const auto& [mask, cnt] : m_mMaskCount)
    {
      llRet += cnt*(cnt - 1);
      for (int j = 0; j < 26; j++)
      {
        const int iNewMask = mask ^ (1 << j);
        if (m_mMaskCount.count(iNewMask))
        {
          llRet += cnt*m_mMaskCount[iNewMask];
        }
      }
    }
    return llRet / 2;
  }
  void DFS(vector<vector<int>>& neiBo, int cur, int par, int parMask)
  {
    parMask ^= (1 << (m_s[cur]-'a'));
    m_mMaskCount[parMask]++;
    for (const auto& next : neiBo[cur])
    {
      if (next == par)
      {
        continue;
      }
      DFS(neiBo, next, cur, parMask);
    }
  }
  unordered_map<int, long long> m_mMaskCount;
  string m_s;
};

测试用例

template<class T,class T2>
void Assert(const T& t1, const T2& t2)
{
  assert(t1 == t2);
}
template<class T>
void Assert(const vector<T>& v1, const vector<T>& v2)
{
  if (v1.size() != v2.size())
  {
    assert(false);
    return;
  }
  for (int i = 0; i < v1.size(); i++)
  {
    Assert(v1[i], v2[i]);
  }
}
int main()
{ 
  vector<int> parent;
  string s;
  {
    Solution sln;
    parent = { -1, 0, 0, 1, 1, 2 }, s = "acaabc";
    auto res = sln.countPalindromePaths(parent, s);
    Assert(res,8);
  }
  {
    Solution sln;
    parent = { -1, 0, 0, 0, 0 }, s = "aaaaa";
    auto res = sln.countPalindromePaths(parent, s);
    Assert(res, 10);
  }
  
}

2023年11月版

class Solution{

public:

long long countPalindromePaths(vector&parent, string s) {

m_c = parent.size();

m_str = s;

m_vNeiBo.assign(m_c, vector());

for (int i = 0; i < 26; i++)

{

m_iVilidMask[i] = 1 << i;

}

m_llRet = 0;

m_mMaskNums.clear();

int iRoot = -1;

for (int i = 0; i < m_c; i++)

{

if (-1 == parent[i])

{

iRoot = i;

}

else

{

m_vNeiBo[parent[i]].emplace_back(i);

}

}

dfs(iRoot,0);

return m_llRet;

}

void dfs(int cur,int iMask)

{

const int curMask = iMask ^ ( 1 << (m_str[cur] - ‘a’));

for (int i = 0; i < 27; i++)

{

const int iNeedMask = m_iVilidMask[i] ^ curMask;

if (m_mMaskNums.count(iNeedMask))

{

m_llRet += m_mMaskNums[iNeedMask];

}

}

m_mMaskNums[curMask]++;

for (const auto& child : m_vNeiBo[cur])

{

dfs(child, curMask);

}

}

int m_iVilidMask[27] = { 0 };//记录所有字符都是偶数和只有一个字符是奇数

vector<vector> m_vNeiBo;

std::unordered_map<int,int> m_mMaskNums;

int m_c;

long long m_llRet = 0;//不包括单节点的合法路径数

string m_str;

};


相关文章
|
16天前
|
算法
【递归搜索回溯专栏】专题二:二叉树中的深搜----求根节点到叶节点数字之和
【递归搜索回溯专栏】专题二:二叉树中的深搜----求根节点到叶节点数字之和
20 0
【剑指offer】-二叉树中和为某一值的路径-24/67
【剑指offer】-二叉树中和为某一值的路径-24/67
|
6月前
代码随想录Day15 二叉树 LeetCodeT513 找树左下角的值 T112路径总和 T106 从中序和后序遍历构造二叉树
代码随想录Day15 二叉树 LeetCodeT513 找树左下角的值 T112路径总和 T106 从中序和后序遍历构造二叉树
25 0
|
2月前
|
人工智能 算法 BI
【深度优先搜索】【树】【图论】2973. 树中每个节点放置的金币数目
【深度优先搜索】【树】【图论】2973. 树中每个节点放置的金币数目
|
4月前
|
算法
二叉树的结点个数、叶子结点个数的代码实现<分治算法>
二叉树的结点个数、叶子结点个数的代码实现<分治算法>
|
4月前
|
算法 测试技术 C#
C++深度优先搜索(DFS)算法的应用:树中可以形成回文的路径数
C++深度优先搜索(DFS)算法的应用:树中可以形成回文的路径数
|
5月前
|
算法
代码随想录算法训练营第十八天 | 力扣 513. 找树左下角的值、112. 路径总和、113. 路径总和 II、106. 从中序与后序遍历序列构造二叉树、105. 从前序与中序遍历序列构造二叉树
代码随想录算法训练营第十八天 | 力扣 513. 找树左下角的值、112. 路径总和、113. 路径总和 II、106. 从中序与后序遍历序列构造二叉树、105. 从前序与中序遍历序列构造二叉树
33 0
|
8月前
|
存储 算法
算法训练Day18|● 513.找树左下角的值● 112. 路径总和 113.路径总和ii● 106.从中序与后序遍历序列构造二叉树 105.从前序与中序遍历序列构造二叉树
算法训练Day18|● 513.找树左下角的值● 112. 路径总和 113.路径总和ii● 106.从中序与后序遍历序列构造二叉树 105.从前序与中序遍历序列构造二叉树
|
10月前
剑指offer 35. 二叉树中和为某一值的路径
剑指offer 35. 二叉树中和为某一值的路径
36 0
代码随想录刷题|LeetCode 513. 找树左下角的值 112. 路径总和 113.路径总和|| 106. 从中序与后序遍历序列构造二叉树 105.从前序与中序遍历序列构造二叉树
代码随想录刷题|LeetCode 513. 找树左下角的值 112. 路径总和 113.路径总和|| 106. 从中序与后序遍历序列构造二叉树 105.从前序与中序遍历序列构造二叉树
代码随想录刷题|LeetCode 513. 找树左下角的值 112. 路径总和 113.路径总和|| 106. 从中序与后序遍历序列构造二叉树 105.从前序与中序遍历序列构造二叉树