1003 字
5 分钟
二叉树算法题
**二叉搜索树(BST)**又称二叉查找树或二叉排序树。若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉搜索
问题:已知一个搜索二叉树,后序遍历的数组posArr,请根据posArr,重建出整棵数,返回新建树的头节点
例如 数组posArr= [2,4,3,6,7,8,5],建出二叉树:
public class PosArrayToBST { public static class Node{ public int value; public Node left; public Node right; public Node(int v){ value = v; } }
public static Node startPosArrayToBST(int[] posArr){ return process(posArr, 0, posArr.length-1); } // 使用posArr[L....R]这些数字,来建树,返回建好的树的头节点 // 递归 public static Node process(int[] posArr, int L, int R){ if (L>R){ return null; } // 头节点的数为R的数,即posArr最后的数为头节点 Node head = new Node(posArr[R]); if (L == R){ // 说明该树只有一个节点 return head; } int M = L -1; int left = L; int right = R; while (left <= right){ // 等同于 mid = (L+R)/2,之所以用位移,是因为位移更快,除法底层更复杂 // int 范围 -21亿~21亿,L+R过大时有溢出风险, 所以用 L + (R - L) int mid = left + ((right - left) >> 1); if (posArr[mid] < posArr[R]){ M = mid; left = mid +1; }else { right = mid - 1; } } head.left = process(posArr, L, M); head.right = process(posArr, M+1, R-1); return head; }}M = L -1 原因:当二叉树既有左子树,又有右子树,M会改变,初始值无所谓,但当二叉树只有左边时([1,2,3,4,5]),M最后的结果就为R-1,后续的递归
head.right = process(posArr, M+1, R-1);// M = R -1 ;上面的结构就变为:head.right = process(posArr, R, R-1); // 无效head.right = null;右边同理。等同于
if(M == -1){ head.right = process(posArr, L, R-1);}else if(M == R-1){ head.left = process(posArr, L, R-1);}else{ head.left = process(posArr, L, M); head.right = process(posArr, M+1, R-1);}以上算法,最坏情况下时间复杂度 O(N * ㏒₂N)
问题二:给定长度为m的字符串aim,以及一个长度为n的字符串str。问能否在str中找到一个长度为m的连续子串,使得这个子串刚好由aim的m个字符组成,顺序无所谓,返回任意满足条件的一个子串的起始位置,未找到返回-1。
public class containExactly { // 时间复杂度 O(N^3 * logN) 不行 public static int wayOne(String s, String a){ if(s == null || a == null || s.length() < a.length()){ return -1; } char[] aim = a.toCharArray(); System.out.println("aim 的值为" + aim); Arrays.sort(aim); // aim 排完序 String aimSort = String.valueOf(aim); for(int L = 0; L< s.length(); L++){ for(int R = L; R < s.length(); R++){ // substring 获取的范围是 [ ) char[] cur = s.substring(L, R+1).toCharArray(); Arrays.sort(cur); System.out.println(cur); String curSort = String.valueOf(cur); if (curSort.equals(aimSort)){ return L; } } } return -1; } // 下面这个复杂度低 public static int wayTwo(String s, String a){ if(s == null || a == null || s.length() < a.length()){ return -1; } char[] aim = a.toCharArray(); int[] count = new int[256]; int M = aim.length; char[] str = s.toCharArray(); for (int i = 0; i < M; i++) { count[aim[i]] ++; } int inValidTimes = 0, R = 0; // 先让窗口拥有M个字符 for (; R< M; R++){ if(count[str[R]]-- <=0){ inValidTimes++; } } System.out.println(inValidTimes); // R==M [0...M-1] // 第一次形成长度为M 的窗口 for(; R < str.length; R++){ if (inValidTimes == 0){ return R-M; } if (count[str[R]]-- <= 0){ inValidTimes++; } if (count[str[R - M]]++ < 0){ inValidTimes--; } } return inValidTimes == 0 ? R - M : -1; } public static void main(String[] args) { String string = "abcdabcdsfcas"; String a = "abc"; int result = wayTwo(string, a); System.out.println(result); }}方法二解释:假设 aim = “acabb”, str = “caabcb”, M为aim的长度,值为5,count 记录aim每一个字符出现的次数,即第一个for循环。第二个for循环, str 前 M个字符在count列表出现的次数,刚好减掉aim在count列表里次数的值,则说明str前M个字符符合要求,如果不符合则进入第三个for循环,判断str中[1…M]的字符是否满足要求,否则再次进入第三个for循环,判断str中的[2….M+1]字符是否满足要求。






