【算法21天:Day21】第六章二叉树 LeetCode 二叉搜索树中的众数(501)

35 阅读4分钟

题目二:

image.png

解法一:(递归 | 普通二叉树)

如果不是二叉搜索树,最直观的方法一定是把这个树都遍历了,用map统计频率,然后根据频率来进行筛选。

完整代码如下:

var findMode = function(root) {
    // 使用递归中序遍历
    let map = new Map();
    // 1. 确定递归函数以及函数参数
    const traverTree = function(root) {
        // 2. 确定递归终止条件
        if(root === null) {
            return ;
        }
        traverTree(root.left);
         // 3. 单层递归逻辑
        map.set(root.val,map.has(root.val)?map.get(root.val)+1:1);
        traverTree(root.right);
    }
    traverTree(root);
    //上面把数据都存储到map
    //下面开始寻找map里面的
    // 定义一个最大出现次数的初始值为root.val的出现次数
    let maxCount = map.get(root.val);
    // 定义一个存放结果的数组res
    let得res = [];
    for(let [key,value] of map) {
        // 如果当前值等于最大出现次数就直接在res增加该值
        if(value === maxCount) {
            res.push(key);
        }
        // 如果value的值大于原本的maxCount就清空res的所有值,因为找到了更大的
        if(value>maxCount) {
            res = [];
            maxCount = value;
            res.push(key);
        }
    }
    return res;
};

解法二:(递归 | 二叉搜索树)

既然是搜索树,它中序遍历就是有序的

var searchBST = function(cur) {
    if (cur == NULL) return ;
    searchBST(cur.left);       // 左
    (处理节点)                // 中
    searchBST(cur.right);      // 右
    return ;
}

遍历有序数组的元素出现频率,从头遍历,那么一定是相邻两个元素作比较,然后就把出现频率最高的元素输出就可以了。

关键是在有序数组上的话,好搞,在树上怎么搞呢?

二叉树:搜索树的最小绝对差 (opens new window)中我们就使用了pre指针和cur指针的技巧,这次又用上了。

用一个指针指向前一个节点,这样每次cur(当前节点)才能和pre(前一个节点)作比较。

而且初始化的时候pre = NULL,这样当pre为NULL时候,我们就知道这是比较的第一个元素。

if (pre == NULL) { // 第一个节点
    count = 1; // 频率为1
} else if (pre.val == cur.val) { // 与前一个节点数值相同
    count++;
} else { // 与前一个节点数值不同
    count = 1;
}
pre = cur; // 更新上一个节点

此时又有问题了,因为要求最大频率的元素集合(注意是集合,不是一个元素,可以有多个众数),如果是数组上大家一般怎么办?

应该是先遍历一遍数组,找出最大频率(maxCount),然后再重新遍历一遍数组把出现频率为maxCount的元素放进集合。(因为众数有多个)

这种方式遍历了两遍数组。

那么我们遍历两遍二叉搜索树,把众数集合算出来也是可以的。

但这里其实只需要遍历一次就可以找到所有的众数。

那么如何只遍历一遍呢?

如果 频率count 等于 maxCount(最大频率),当然要把这个元素加入到结果集中(以下代码为result数组),代码如下:

if (count == maxCount) { // 如果和最大值相同,放进result中
    result.push(cur.val);
}

是不是感觉这里有问题,result怎么能轻易就把元素放进去了呢,万一,这个maxCount此时还不是真正最大频率呢。

所以下面要做如下操作:

频率count 大于 maxCount的时候,不仅要更新maxCount,而且要清空结果集(以下代码为result数组),因为结果集之前的元素都失效了。

if (count > maxCount) { // 如果计数大于最大值
    maxCount = count;   // 更新最大频率
    result = [];     // 很关键的一步,不要忘记清空result,之前result里的元素都失效了
    result.push(cur.val);
}

完整代码:

var findMode = function(root) {
    let res = []
    let pre = null // 记录前一个结点
    let count = 0 // 统计频率
    let maxCount = 0 // 最大频率
    var inOrder = function(cur) {
        if (cur === null) {
            return
        }
        inOrder(cur.left) // 左
                          // 中
        if (pre === null) {
            count = 1
        }else if (pre.val === cur.val) { // 与前一个节点数值相同
            count++
        } else {
            count = 1 // 与前一个节点数值不同
        }
        pre = cur // 更新上一个节点

        if (count === maxCount) { // 如果和最大值相同,放进res中
            res.push(cur.val)
        } 
        if (count > maxCount) { // 如果计数大于最大值频率
            maxCount = count // 更新最大频率
            res = [] // 很关键的一步,不要忘记清空result,之前result里的元素都失效了
            res.push(cur.val)
        }

        inOrder(cur.right) // 右
    }
    inOrder(root)
    return res
};

解法三:(迭代法)

同递归解法,只是改为迭代法,中间处理逻辑一致。

var findMode = function(root) {
    let stack = []
    let pre = null
    let cur = root
    let res = []
    let count = 0
    let maxCount = 0
    while (cur || stack.length) {
        if (cur) {
            stack.push(cur)
            cur = cur.left
        } else {
            cur = stack.pop()
            if (!pre) {
                count = 1
            } else if (pre.val === cur.val) {
                count++
            } else {
                count = 1
            }
            pre = cur
            if (count === maxCount) {
                res.push(cur.val)
            }
            if (count > maxCount) {
                maxCount = count
                res = []
                res.push(cur.val)
            }
            
            cur = cur.right
        }
    }
    return res
};