关于二叉树的路径

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;
    }
};

当然了以上的代码依然有个别用例超时,还需要改动储存的具体方案。

相关推荐
kali-Myon1 小时前
ctfshow-web入门-JWT(web345-web350)
前端·学习·算法·web安全·node.js·web·jwt
神洛华3 小时前
datawhale11月组队学习 模型压缩技术3:2:4结构稀疏化BERT模型
深度学习·算法·bert
南宫生4 小时前
力扣-Hot100-二叉树其二【算法学习day.33】
java·数据结构·学习·算法·leetcode·动态规划
trueEve4 小时前
SQL,力扣题目1126,查询活跃业务
算法·leetcode·职场和发展
别开生面的阿杰4 小时前
蓝桥杯-洛谷刷题-day3(C++)
c++·算法·蓝桥杯
Mr.W.T5 小时前
JVM垃圾回收详解(重点)
java·jvm·算法
___Dream5 小时前
算法闭关修炼百题计划(八)
算法
浮生如梦_6 小时前
Halcon 3D平面度
图像处理·算法·计算机视觉·平面·视觉检测
码农多耕地呗6 小时前
刷别的学校oj—河工大oj1073-1099
开发语言·c++·算法
南宫生7 小时前
力扣-Hot100-二叉树其一【算法学习day.32】
数据结构·学习·算法·leetcode