1. 二叉树的伪回文路径
二叉树的伪回文路径。本道题需要我们解决两个问题,如何保存每一条从根节点到叶子节点的路径?如何判断这条路径上的节点是否是一个伪回文路径?针对以上两个问题,我们一一解决。
1.1 给定一个序列如何判断它是一个伪回文路径?
首先,伪回文路径就是当给定序列的所有排列中有一个是回文序列,则该序列就是伪回文路径。对于一个回文序列,从中间剖开,它是对称的。一个回文序列至多出现一个元素个数为奇数的元素。 所以我们只需要遍历序列,计算每个元素的出现频率,当出现两个奇数个元素的时候,该序列必定不是伪回文序列。代码如下:
arduino
bool isValid(vector<int>& nums) {
/*使用map记录元素个数*/
unordered_map<int, int> cnt;
for(auto i : nums) {
cnt[i]++;
}
int flag = 0;
/*遍历map,统计奇数个元素的个数*/
for(auto ite : cnt) {
if(ite.second % 2 == 1) flag++;
}
return flag > 1 ? false : true;
}
1.2 如何保存树中的一条路径?
路径的定义是:从根节点到叶子节点上节点的序列。我们需要一边遍历树,一边保存遍历过的节点。我们使用一个全局变量记录满足条件的路径。在递归遍历的时候,vector<int>
来记录元素,需要注意的是,这里使用的是值传递。
scss
int count = 0;
void backtrack(TreeNode* root, vector<int> path) {
/*必须是叶子节点,而不是遇到空节点*/
if(root == nullptr) return;
path.push_back(root->val);
/*遇到叶子节点,则判断路径是否是伪回文路径*/
if(!root->left && !root->right) {
if(isValid(path)) count++;
return;
}
backtrack(root->left, path);
backtrack(root->right, path);
}
int pseudoPalindromicPaths (TreeNode* root) {
vector<int> v;
backtrack(root, v);
return count;
}
这种做法从正确性上来说,已经可以解决这道题了。但是由于使用的是值传递(可以避免pop
操作),对于比较长的测试数据会超时,没有办法全部通过测试用例。
2. 二叉树的所有路径
本题目是leetcode257题。从题目的思路上来说,与上一题是类似的,甚至是更简单。因为我们只需要做好元素的保存就可以。我先直接放出代码。
scss
class Solution {
public:
/*全局变量,用来保存结果*/
vector<string> result;
void backtrack(TreeNode* root, string str) {
/*遇到空节点就返回,遇到叶子节点再做处理*/
if(root == nullptr) return;
if(!root->left && !root->right) {
str += to_string(root->val);
result.push_back(str);
}
str += to_string(root->val) + "->";
backtrack(root->left, str);
backtrack(root->right, str);
}
vector<string> binaryTreePaths(TreeNode* root) {
string str;
backtrack(root, str);
return result;
}
};
需要强调的是,代码中每次都是处理当前层节点的元素,递归过程中也没有对子节点为空的判断,而是到了下一层再做处理。如果是空节点直接返回,是叶子节点则保存结果。传递的过程中同样使用的是值传递。 再来对比一下我原先的做法:
scss
class Solution {
public:
void searchTrace(TreeNode* curr, vector<int>& path, vector<string>& result) {
/*把当前层节点保存到path中*/
path.push_back(curr->val);
/*当前节点是叶子节点,则开始处理路径上的节点,并保存*/
if(curr->left == nullptr && curr->right == nullptr) {
string tmp;
for(int i = 0; i < path.size() - 1; i++) {
tmp += to_string(path[i]);
tmp += "->";
}
tmp += to_string(path[path.size()-1]);
result.push_back(tmp);
return;
}
/* 递归访问之前,做了非空判断
* 且path传递的是引用,所以每次递归返回都需要对元素进行pop
*/
if(curr->left) {
searchTrace(curr->left, path, result);
path.pop_back();
}
if(curr->right) {
searchTrace(curr->right, path, result);
path.pop_back();
}
}
vector<string> binaryTreePaths(TreeNode* root) {
vector<int> path;
vector<string> result;
searchTrace(root, path, result);
return result;
}
};
3. 上面两种不同的保存元素的做法中我困惑的点
对于递归中采用值传递来保存元素的做法非常易于理解,且形式和普通的树的递归遍历方式相像。但有些题目无法使用。当值传递的类型是vector<int>
,值传递就很容易超时或者超出了内存限制。比如对于第一题,我做出如下修改:
/**
* 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:
bool isValid(vector<int>& nums) {
unordered_map<int, int> cnt;
for(auto i : nums) {
cnt[i]++;
}
int flag = 0;
for(auto ite : cnt) {
if(ite.second % 2 == 1) flag++;
}
return flag > 1 ? false : true;
}
int count = 0;
void backtrack(TreeNode* root, vector<int>& path) {
/*必须是叶子节点,而不是遇到空节点*/
if(root == nullptr) return;
path.push_back(root->val);
if(!root->left && !root->right) {
if(isValid(path)) count++;
return;
}
/*因为是引用传递,每次递归返回,都需要pop元素*/
backtrack(root->left, path);
path.pop_back();
backtrack(root->right, path);
path.pop_back();
}
int pseudoPalindromicPaths (TreeNode* root) {
vector<int> v;
backtrack(root, v);
return count;
}
};
针对原来的值传递超时的问题,我把函数修改为引用传递,且每次递归子树返回都添加了pop
操作来弹出元素,但结果还是错误的。比如针对以下这颗树:
以上代码给出的结果是3,而不是1。这是因为我的代码并没有对子节点做非空判断,而是在下一层做空判断并直接返回。对于叶子节点没有任何问题,但是对于有单个子节点为空的节点就会造成误判。比如在上面这颗树中,当遍历到这里时
此时遍历3的左子树为空并返回,注意此时并没有在path
中加入节点。返回后误弹出了3这个元素。path
中的元素变为[2, 1],再遍历右子树1这个节点的时候,就判定为伪回文序列 。简单来说,由于返回的情况有两种,其中一种情况会在没有加入任何子节点的情况下返回,返回后又弹出元素,造成误判。我们需要做的修改就是,只在子节点非空的情况下再做递归,同时删除开头的判断为空if
语句。代码修改为以下:
/**
* 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:
bool isValid(vector<int>& nums) {
unordered_map<int, int> cnt;
for(auto i : nums) {
cnt[i]++;
}
int flag = 0;
for(auto ite : cnt) {
if(ite.second % 2 == 1) flag++;
}
return flag > 1 ? false : true;
}
int count = 0;
void backtrack(TreeNode* root, vector<int>& path) {
/*必须是叶子节点,而不是遇到空节点*/
path.push_back(root->val);
if(!root->left && !root->right) {
if(isValid(path)) count++;
return;
}
if(root->left) {
backtrack(root->left, path);
path.pop_back();
}
if(root->right) {
backtrack(root->right, path);
path.pop_back();
}
}
int pseudoPalindromicPaths (TreeNode* root) {
vector<int> v;
backtrack(root, v);
return count;
}
};
当然了以上的代码依然有个别用例超时,还需要改动储存的具体方案。