LeetCode之501:二叉搜索树中的众数

前言

这道leetcode上标签为简单的题目,差点把我整哭了。

一、题目描述

给定一个有相同值的二叉搜索树(BST),找出 BST 中的所有众数(出现频率最高的元素)。

假定 BST 有如下定义:

结点左子树中所含结点的值小于等于当前结点的值
结点右子树中所含结点的值大于等于当前结点的值
左子树和右子树都是二叉搜索树
例如:

给定 BST [1,null,2,2], 
   1
    \
     2
    /
   2
返回[2].

提示:如果众数超过1个,不需考虑输出顺序

进阶:你可以不使用额外的空间吗?(假设由递归产生的隐式调用栈的开销不被计算在内)

二、最初思路

1.思路

这是一个二叉搜索树,直接递归进行中序遍历,相同的数字将会连续输出,然后在外层设置变量,维护最大值即可。

2.代码

	/**
	 * tempNum:上一个数字
	 */
	int tempNum;
	/**
	 * 目前数字的个数
	 */
	int tempCount;
	/**
	 * 数字出现最多个数
	 */
	int maxCount;
	List<Integer> list;
	
	
	public int[] findMode(TreeNode root) {
		if (root == null) {
			return new int[0];
		}
		list = new ArrayList<Integer>();
		tempCount = 0;
		maxCount = 0;
		dfs(root);
		int [] res = new int [list.size()];
		for (int i = 0; i < res.length; i++) {
			res[i] = list.get(i);
		}
		 return res;
	}
	 
	public void dfs(TreeNode node) {
		if (node == null) {
			return;
		}
		dfs(node.left);	
		/**
		 * 处理第一个数字
		 */
		if (maxCount == 0) {
			list.add(node.val);
			maxCount = tempCount = 1;
			tempNum = node.val;
		/**
		 * 非第一个数字
		 */
		}else {
			//当前数字和前一个数字是否相同
			if (node.val == tempNum) {
				//相同则tempCount++
				tempCount ++;
				//若maxCount == tempCount,那么此时的val也是众数
				if (maxCount == tempCount) {
					list.add(node.val);
					tempNum = node.val;
				}
				//如果tempCount > maxCount,那么新的众数出现,清除list
				if (maxCount < tempCount) {
					maxCount = tempCount;
					list.clear();
					list.add(node.val);
				}
			}else {
			//当前数字和前一个数字不相同
				if (maxCount == 1) {
					//如果不存在出现2次的数字,那么任一数字都是众数
					list.add(node.val);
					tempNum = node.val;
				}else {
					//与上一个数字相同,重新计数
					tempNum = node.val;
					tempCount = 1;
				}
			}
			
		}	
		dfs(node.right);
	}

3、注意点:

  • 以下这个两个if千万不要放反了。反了之后,当maxCount < tempCount 之后,maxCount会被赋值为tempCount,那么maxCount == tempCount,则也会执行另一个if,最终导致结果出错。
				//若maxCount == tempCount,那么此时的val也是众数
				if (maxCount == tempCount) {
					list.add(node.val);
					tempNum = node.val;
				}
				//如果tempCount > maxCount,那么新的众数出现,清除list
				if (maxCount < tempCount) {
					maxCount = tempCount;
					list.clear();
					list.add(node.val);
				}
  • “如果不存在出现2次的数字,那么任一数字都是众数。”所以还是不能直接将与上一个数字相同视为重新计数的条件。
//当前数字和前一个数字不相同
				if (maxCount == 1) {
					//如果不存在出现2次的数字,那么任一数字都是众数
					list.add(node.val);
					tempNum = node.val;
				}else {
					//与上一个数字相同,重新计数
					tempNum = node.val;
					tempCount = 1;
				}
			}
  • 如果特殊情况太多,不要懒~,动手画一下。这道题目思路并不复杂,算法也很简单,但是就是容易绕,所以,做题时还是要认真。

三、优化

递归的过程中,也是要消耗内存的。我们在中序遍历的时候,一定先遍历左子树,然后遍历当前节点,最后遍历右子树。在常规方法中,我们用递归回溯或者是栈来保证遍历完左子树可以再回到当前节点,但这需要我们付出额外的空间代价。我们需要用一种巧妙地方法可以在 O(1)的空间下,遍历完左子树可以再回到当前节点。我们希望当前的节点在遍历完当前点的前驱之后被遍历,我们可以考虑修改它的前驱节点的 right 指针。当前节点的前驱节点的right指针可能本来就指向当前节点(前驱是当前节点的父节点),也可能是当前节点左子树最右下的节点。如果是后者,我们希望遍历完这个前驱节点之后再回到当前节点,可以将它的right 指针指向当前节点。
Morris 中序遍历的一个重要步骤就是寻找当前节点的前驱节点,并且 Morris 中序遍历寻找下一个点始终是通过转移到 \rm rightright 指针指向的位置来完成的。

	 int base, count, maxCount;
	    List<Integer> answer = new ArrayList<Integer>();

	    public int[] findMode(TreeNode root) {
	        TreeNode cur = root, pre = null;
	        while (cur != null) {
	            if (cur.left == null) {
	                update(cur.val);
	                cur = cur.right;
	                continue;
	            }
	            pre = cur.left;
	            while (pre.right != null && pre.right != cur) {
	                pre = pre.right;
	            }
	            if (pre.right == null) {
	                pre.right = cur;
	                cur = cur.left;
	            } else {
	                pre.right = null;
	                update(cur.val);
	                cur = cur.right;
	            }
	        }
	        int[] mode = new int[answer.size()];
	        for (int i = 0; i < answer.size(); ++i) {
	            mode[i] = answer.get(i);
	        }
	        return mode;
	    }

	    public void update(int x) {
	        if (x == base) {
	            ++count;
	        } else {
	            count = 1;
	            base = x;
	        }
	        if (count == maxCount) {
	            answer.add(base);
	        }
	        if (count > maxCount) {
	            maxCount = count;
	            answer.clear();
	            answer.add(base);
	        }
	    }

四、tools

在写leetcode的时候,写树的测试用例挺烦的,所以写了一个小工具类,用来将数组转化为树。泛型有点忘记了,这里先放上整型的,后面再改成泛型。



/**
 * 这个用来做测试用的,将数组转换为树;这里只实现了整型的,并且null用Integer.MAX_VAL表示
 * @author SanfordChern
 * @Date 2020-09-25 09:22
 * @TODO
 */
public class Array2Tree {
	
	public static TreeNode change(int[] array) {
		if (array.length == 0) {
			return null;
		}
		TreeNode root = new TreeNode(array[0]);
		dfs(root, array, 0, array.length);
		return root;
	}
	
	/**
	 * 
	 * @param node  
	 * @param i node结点的深度(从第0层开始计算)
	 * @param length 数组的长度
	 */
	public static void dfs(TreeNode node, int[] array, int i, int length) {
		if (2 * i + 1 > length) {
			return;
		}
		if (2 * i + 2 < length) {
			if (array[2*i+1] != Integer.MAX_VALUE) {
				node.left = new TreeNode(array[2*i+1]);
				dfs(node.left, array, 2*i+1, length);
			}
			if (array[2*i+2] != Integer.MAX_VALUE) {
				node.right = new TreeNode(array[2*i+2]);
				dfs(node.right, array, 2*i+2, length);
			}
		}
		if(2 * i + 1 == length - 1){
			if (array[2*i+1] != Integer.MAX_VALUE) {
				node.left = new TreeNode(array[2*i+1]);
				dfs(node.left, array, 2*i+1, length);
			}
		}
	}
	

}

测试:

	 public static void main(String[] args) {
			Test1_2 test1 = new Test1_2();
			int m = Integer.MAX_VALUE;
			int[] array = new int[] {1,m,2,m,m,2};
			TreeNode root = Array2Tree.change(array);
			int[] res = test1.findMode(root);
			for (int i : res) {
				System.out.println(i);
			}
	}

有那么点小坑,没注意看,leetcode竟然有[1,null,2,2]的树,可能是我看得太少了吧,着实坑,这写法~。自己写tools测的时候,就复制了它的用例,然后就gg了,最后才发现emmm。然后改成[1,m,2,m,m,2]测。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值