给定一棵二叉树,树中的节点值是 1 到 9 之间的数字。如果二叉树中的节点值至少有一个排列组合是回文,则称该路径为伪回文路径。
返回从根节点到叶节点的伪 Palindromic 路径的数目。
Example 1:
Input: root = [2,3,1,3,1,null,1] Output: 2 Explanation: The figure above represents the given binary tree. There are three paths going from the root node to leaf nodes: the red path [2,3,3], the green path [2,1,1], and the path [2,3,1]. Among these paths only red path and green path are pseudo-palindromic paths since the red path [2,3,3] can be rearranged in [3,2,3] (palindrome) and the green path [2,1,1] can be rearranged in [1,2,1] (palindrome).
Example 2:
Input: root = [2,1,1,1,3,null,null,null,null,null,1] Output: 1 Explanation: The figure above represents the given binary tree. There are three paths going from the root node to leaf nodes: the green path [2,1,1], the path [2,1,3,1], and the path [2,1]. Among these paths only the green path is pseudo-palindromic since [2,1,1] can be rearranged in [1,2,1] (palindrome).
Example 3:
Input: root = [9] Output: 1
上手一看这个题目应该是一个树上的回溯搜索,然后需要一个结构对路径上的值进行存储。同时还需要一个函数能够判断这个结构是否能够重组成为回文。
判断回文我们只需要统计一个路径上字符出现的次数,要么全部为偶数,要么只有一个数字为奇数则是回文。
然后又考虑到路径只考虑从根节点到叶子节点,所以用回溯算法也不完全正确,应该采用深度优先搜索DFS,直到搜索到根节点才结束。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int pseudoPalindromicPaths (TreeNode* root) {
return dfs(root, 0);
}
int dfs(TreeNode* node, int path) {
if (!node) return 0;
path ^= 1 << node->val;
// cout << path << endl;
if (!node->left && !node->right) {
cout << (path & (path - 1)) << endl;
return (path & (path - 1)) == 0;
}
return dfs(node->left, path) + dfs(node->right, path);
}
};
这是我在gpt商店的Code Tutor中学习的方法,本来认为判断回文需要用一个哈希表来存储和遍历。但是这种方法实在是太出乎我预料了。
在这个上下文中,"当前节点值的位"指的是一个位掩码中的一个特定位,该位代表了一个从1到9的数字。位掩码是一个整数值,其每一位(binary digit)都可以独立表示一些信息。在我们的场景中,我们使用位掩码的每一位来表示数字1到9是否在当前路径上出现了奇数次。
当我们在路径中遇到一个数字时,我们通过执行异或操作^=
和位移操作<<
来切换相应的位。比如,如果节点的值是3
,那么1 << node->val
会得到一个只在第三位上是1
的数(假设最低位是第一位),其他位都是0
。执行异或操作后,这个位掩码中对应于数字3
的位会切换:如果它之前是0
(表示到目前为止,3
出现了偶数次),它会变成1
(现在出现了奇数次),反之亦然。
回文的一个关键属性是,除了最多一个字符外,其他所有字符都应该出现偶数次。在位掩码中,一个1
表示对应的数字出现了奇数次,而0
表示出现了偶数次。因此,如果路径可以形成回文,那么在位掩码中,至多只能有一位是1
。
我们通过path & (path - 1)
操作来检查这一条件。这个操作的效果是将位掩码中的最低位的1
变成0
。如果path
中只有一位是1
或者没有1
,那么path & (path - 1)
的结果会是0
。
path
中没有1
,那么所有数字都出现了偶数次,路径是回文。path
中只有一位是1
,那么只有一个数字出现了奇数次,其他都是偶数次,路径也是回文。path
中有多于一位是1
,那么path & (path - 1)
不会是0
,表示有多于一个数字出现了奇数次,路径不能形成回文。这种方法允许我们以非常高效的方式检查一个路径是否满足回文的条件,仅通过一次位操作和比较操作就可以完成。
按位与(&
):对于每一位,只有两个操作数的相应位都是1时,结果才为1。
5 & 3
(二进制101 & 011
)等于1
(二进制001
)。按位或(|
):对于每一位,如果两个操作数的相应位中至少有一个为1,则结果为1。
5 | 3
(二进制101 | 011
)等于7
(二进制111
)。按位异或(^
):对于每一位,如果两个操作数的相应位一个是1另一个是0,则结果为1。
5 ^ 3
(二进制101 ^ 011
)等于6
(二进制110
)。按位非(~
):对操作数的每一位取反。
~5
(二进制~101
)的结果为...1111111111111010
(假设是32位整数,则前面的位都被置为1)。左移(<<
):将操作数的二进制表示向左移动指定的位数,右边空出的位用0填充。
5 << 1
(二进制101
左移1位)等于10
(二进制1010
)。右移(>>
):将操作数的二进制表示向右移动指定的位数,对于无符号类型,左边空出的位用0填充;对于有符号类型,行为依赖于实现(通常左边空出的位用符号位填充,即算术右移)。
5 >> 1
(二进制101
右移1位)等于2
(二进制10
)。切换位:path ^= 1 << node->val;
使用按位异或和左移来切换path
中对应node->val
的位。如果该位原来是0(即数字出现次数为偶数),它会变为1(出现次数变为奇数),反之亦然。
检查是否只有一个位为1:(path & (path - 1)) == 0
利用按位与操作来检查path
中是否至多只有一位是1。这是判断一个数是否是2的幂或者是0的常用技巧,也被用于检查路径是否可以形成伪回文。
所以回顾一下这个题目我们就能明白为什么强调,节点的值是1-9。