笔试算法题(31):将有序数组转换成BST表示 & 线段树的应用

出题:要求将一个有序整数数组转换成最小深度的Binary Search Tree表示;

分析:由于需要是最小深度,所以BST应保持平衡,左右节点数大致相当,并且BST中当前根节点大于所有其左子树中的元素,小于所有其右子树中的元素。对于排序数组而言,中间元素必然作为根节点,然后递归对由中间元素分割的左右数组部分进行处理;

解题:


 1 struct Node {
2 int value;
3 Node *left;
4 Node *right;
5 };
6
7 Node* Array2BST(int *array, int i, int j) {
8 /**
9 * 如果上下范围相同说明只有一个节点
10 * 直接返回
11 * */
12 if(i==j) {
13 Node *root=new Node();
14 root->value=array[i];
15 root->left=NULL;
16 root->right=NULL;
17 return root;
18 }
19
20 int m=(i+j)/2;
21 Node *root=new Node();
22 root->value=array[m];
23 /**
24 * 由于(i+j)/2是向下取整,所以i+1=j时
25 * m=i,这个时候root的左子树为空
26 * */
27 if(m==i)
28 root->left=NULL;
29 else
30 root->left=Array2BST(array, i, m-1);
31 root->right=Array2BST(array, m+1, j);
32
33 return root;
34 }

出题:要求实现函数:
  size_t foo(unsigned int *a1, size_t a1l,
unsigned int *a2, size_t
a2l)
  其中a1和a2都是无符号数组,a1l和a2l是对应数组的长度,并且都为偶数。
  无符号数分成两个数字组成的多个数字区间,如下:
    a1为0,1,3,8,10,20
    a2为0,1,20,50,4,5
  则a1表示的区间为[0,1],[3,8],[10,20]
  则a2表示的区间为[0,1],[20,50],[4,5]
  所以a1和a2重叠的部分为[0,1],[4,5],长度为2
  foo函数用于求给定区间数组a1和a2的重叠长度
  注意:a1和a2长度不超过100万,并且同一个区间数组内部可能出现重叠

分析:

  • 线段树(Interval
    Tree)是BST的一种特例,它将一个区间划分成单元区间(最小元素),每个单元区间对应一个叶子节点;对于一个内部节点而言[a,b],它的左子树节
    点表示为[a,(a+b)/2],它的右子树节点表示为[(a+b)/2+1,b]。由于每次划分都是对等划分所以IT为平衡树,最终子节点数为N(对于
    整数区间而言就是所有数字分布的范围大小),深度为logN
    +1。快速确定某一个元素在哪些区间节点出现,时间复杂度为O(NlogN),但是空间复杂度为O(N);

  • 确定区间数组A和B中差值较小的的最大值m和最小值n(假设数组A的区间范围更小,O(A)),并构建区间[m,n]的线段树(O(AlogA)),这样
    可以节省空间,如果区间范围较大,可以使用离散的建树方法针对区间元素出现的范围构建多棵区间树,或者使用压缩的方法减少区间节点;

  • 在线段树中标记在A中出现的区间节点(O(AlogA));将B的区间节点插入线段树(实际上并没有新节点的插入,只是检测是否有A标记过的节点),如果有A区间节点标记的节点,则说明有重复(O(BlogA))。总时间复杂度O(AlogA);

解题:


  1 struct Interval {
2 int left;
3 int right;
4 int count;
5 };
6 /**
7 * 此函数用于创建线段树的基本结构
8 * */
9 void ConstructIT(int min, int max, Interval** InterTree, int index) {
10 /**
11 * 构建当前节点
12 * index用于索引InterTree中当前元素的位置
13 * 遵循父节点为i,则做有子节点分别为2*i, 2*i+1
14 * */
15 Interval *temp=new Interval();
16 temp->count=0;
17 temp->left=min;
18 temp->right=max;
19 InterTree[index]=temp;
20 /**
21 * 如果min和max相等,则说明已经到叶子节点
22 * */
23 if(min==max)
24 return;
25 /**
26 * 分别递归创建左右子节点
27 * */
28 int middle=(min+max)/2;
29 ConstructIT(min,middle,InterTree,index*2);
30 ConstructIT(middle+1,max,InterTree,index*2+1);
31 }
32 /**
33 * 此函数用于注入线段树的线段标记信息
34 * */
35 void InsertInfor(int min, int max, Interval** InterTree, int index) {
36 int middle=(InterTree[index]->left + InterTree[index]->right)/2;
37 if(max<middle) {
38 InsertInfor(min, max, InterTree, 2*index);
39 } else if(min>middle) {
40 InsertInfor(min, max, InterTree, 2*index+1);
41 } else if(min==InterTree[index]->left &&
42 max==InterTree[index]->right) {
43 InterTree[index]->count+=1;
44 return;
45 } else {
46 InsertInfor(min, middle, InterTree, 2*index);
47 InsertInfor(middle+1, max, InterTree, 2*index+1);
48 }
49 }
50 /**
51 * 此函数用于检查覆盖区间,注意此时的min和max可能超出
52 * InterTree的范围
53 * */
54 int CheckInterval(int min, int max, Interval** InterTree, int index) {
55 int middle=(InterTree[index]->left + InterTree[index]->right)/2;
56 if(max<middle) {
57 return CheckInterval(min, max, InterTree, 2*index);
58 } else if(min>middle) {
59 return CheckInterval(min, max, InterTree, 2*index+1);
60 } else if(min==InterTree[index]->left &&
61 max==InterTree[index]->right) {
62 if(InterTree[index]->count>0)
63 return InterTree[index]->count;
64 } else {
65 CheckInterval(min, middle, InterTree, 2*index);
66 CheckInterval(middle+1, max, InterTree, 2*index+1);
67 }
68 }
69 int GetOverlapSize(int *first, int lfirst, int *second, int lsecond) {
70 /**
71 * 获取first和second数组中各自的最大值与最小值的差值
72 * 选取差值较小的一个范围作为线段树的构建区间
73 * 节省空间消耗
74 * */
75 int minf=0, maxf=0,mins=0,maxs=0;
76 for(int i=1;i<lfirst;i+=2) {
77 if(maxf<first[i])
78 maxf=first[i];
79 if(minf>first[i])
80 minf=first[i];
81 }
82 for(int i=1;i<lsecond;i+=2) {
83 if(maxs<second[i])
84 maxs=second[i];
85 if(mins>second[i])
86 mins=second[i];
87 }
88 int min=minf,max=maxf;
89 int isFirst=true;
90 if((max-min)>(maxs-mins)) {
91 min=mins;
92 max=maxs;
93 isFirst=false;
94 }
95 /**
96 * 构建一个数组,数组元素为Interval*类型,
97 * 数组大小为线段树节点个数,由于线段树是
98 * 完全二叉树,所以节点数肯定为2N-1,N为区间
99 * 大小,也为叶节点数
100 * */
101 Interval *InterTree[2*(max-min+1)-1];
102 /**
103 * 首先构建线段树的基本结构
104 * */
105 ConstructIT(min, max, InterTree, 0);
106 /**
107 * 然后将线段树范围大小对应的区间数组元素
108 * 插入到线段树中,如果区间在线段树之外则
109 * 需要特殊处理
110 * */
111 int *temp=NULL, length=0;
112 int overlapSize=0;
113 if(isFirst) {
114 for(int i=1;i<lfirst;i+=2) {
115 if(first[i]<InterTree[0]->left ||
116 first[i-1]>InterTree[0]->right)
117 continue;
118 else if(first[i]==InterTree[0]->left ||
119 first[i-1]==InterTree[0]->right)
120 overlapSize++;
121 else
122 InsertInfor(first[i-1],first[i], InterTree, 0);
123 }
124 temp=second;
125 length=lsecond;
126 } else {
127 for(int i=1;i<lsecond;i+=2) {
128 if(second[i]<InterTree[0]->left ||
129 second[i-1]>InterTree[0]->right)
130 continue;
131 else if(second[i]==InterTree[0]->left ||
132 second[i-1]==InterTree[0]->right)
133 overlapSize++;
134 else
135 InsertInfor(second[i-1],second[i], InterTree, 0);
136 }
137 temp=first;
138 length=lfirst;
139 }
140 /**
141 * 顺序将temp中的区间元素插入到InterTree中
142 * */
143
144 for(int i=1;i<length;i+=2) {
145 length+=CheckInterval(temp[i-1],temp[i],InterTree,0);
146 }
147
148 }

时间: 2024-07-28 13:13:42

笔试算法题(31):将有序数组转换成BST表示 & 线段树的应用的相关文章

第108题:将有序数组转换成二叉搜索树

一. 问题描述 将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树. 本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1. 示例: 给定有序数组: [-10,-3,0,5,9], 一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树: 0 / \ -3   9 /   / -10  5 二. 解题思路 本题思路:采用二叉搜索树的特性+递归来进行求解,二叉树的根节点必定是有序数组的中间那个树,而数组左边则是左

笔试算法题(05):转换BST为双向链表 &amp; 查找栈中的最小元素

出题:把二元查找树转变成排序的双向链表.输入一棵二元查找树,要求将该二元查找树按照中序转换成一个排序的双向链表,要求不能创建任何新的节点,只能调整指针的指向: 分析: 递归的思路,当前节点需要进行的处理,并使用递归调用和返回值将子问题链接起来: 首先明白二元查找树的特性,变成有序双向链表后当前根节点的左节点为其原来左子树的最右节点,右节点为其原来右子树的最左节点:因此策略就是针对当前根节点索引的子树,首先判断其为上层节点的右子树还是左子树,从而决定返回最右还是最右节点:然后再递归处理当前根节点的

笔试算法题(07):还原后序遍历数组 &amp; 半翻转英文句段

出题:输入一个整数数组,判断该数组是否符合一个二元查找树的后序遍历(给定整数数组,判定其是否满足某二元查找树的后序遍历): 分析:利用后序遍历对应到二元查找树的性质(序列最后一个元素必定是根节点,从左向右第一个比根节点大的元素开始直到根节点之前的所有元素必定在右子树,之前的所有元素必定在左子树): 解题: 1 bool PostOrderCheck(int *array, int i, int j) { 2 /** 3 * 如快速排序一样,解决小子文件 4 * */ 5 if(j-i+1 ==

笔试算法题(22):二分法求旋转数组最小值 &amp; 骰子值概率

出题:将一个数组最开始的k个(K小于数组大小N)元素照搬到数组末尾,我们称之为数组的旋转:现在有一个已经排序的数组的一个旋转,要求输出旋转数组中的最小元素,且时间复杂度小于O(N): 分析: 时间复杂度小于O(N)也就是不能用常规的遍历思路:可以将数组看成两个都是递增序列(假设为升序)的子数组,并且前半段的元素均大于等于后半段的元素,分界点的位于后半段数组的第一个元素就是最小元素: 具体算法:两个指针left和right指向数组第一个和最后一个元素,使用Binary Search确定中间元素mi

笔试算法题(26):顺时针打印矩阵 &amp; 求数组中数对差的最大值

出题: 输入一个数字矩阵,要求从外向里顺时针打印每一个数字: 分析: 从外向里打印矩阵有多重方法实现,但最重要的是构建合适的状态机,这样才能控制多重不同的操作: 注意有四种打印模式(左右,上下,右左,下上),所以需要一个index变量控制每次循环时执行的打印模式: 注意水平打印和垂直打印分别需要两个变量控制打印元素,并且两组变量中的两个端点都是相互靠近的(hs和he,vs和he),每执行一种打印模式之前,需要更新当前打印模式中打印方向的其实坐标,因为它已经在上一种打印模式中打印过: 每一种打印模

笔试算法题(20):寻找丑数 &amp; 打印1到N位的所有的数

出题:将只包含2,3,5的因子的数称为丑数(Ugly Number),要求找到前面1500个丑数: 分析: 解法1:依次判断从1开始的每一个整数,2,3,5是因子则整数必须可以被他们其中的一个整除,如果不包含任何其他因子则最终的结果为1: 解法2:小丑数必然是某个大丑数的因子,也就是乘以2,3,或者5之后的值,所以可以利用已经找到的丑数来寻找下一个丑数,使用数组有序保存已经找到的丑 数,并且当前最大丑数值为M:用大于M/2的丑数乘以2得到M1,用大于M/3的丑数乘以3得到M2,用大于M/5的丑数

笔试算法题(53):四种基本排序方法的性能特征(Selection,Insertion,Bubble,Shell)

四种基本算法概述: 基本排序:选择,插入,冒泡,希尔.上述算法适用于小规模文件和特殊文件的排序,并不适合大规模随机排序的文件.前三种算法的执行时间与N2成正比,希尔算法的执行时间与N3/2(或更快)成正比: 前三种算法在平均,最坏情况下都是N2,而且都不需要额外的内存:所以尽管他们的运行时间只相差常数倍,但运行方式不同: 对于已经就序的序列而言,插入排序和冒泡排序的运行时间都是O(N),但是选择排序的时间仍旧是O(N^2): 因为Insertion和Bubble都是相邻项间的比较交换,所以不会出

笔试算法题(09):查找指定和值的两个数 &amp; 构造BST镜像树

出题:输入一个已经升序排序的数组和一个数字:要求在数组中查找两个数,这两个数的和正好等于输入的那个数字,输出任意一对数字就可以,要求时间复杂度是O(n): 分析:对于升序排序的数组{-i-j-k-m--},只有可能是i+m=j+k(j和k可能是同一个数),所以可以从两边往中间收缩而忽视其他交叉相加的情况: 解题: 1 void FindSumFactor(int *array, int length, int sum) { 2 int left=0, right=length-1; 3 whil

笔试算法题(24):找出出现次数超过一半的元素 &amp; 二叉树最近公共父节点

出题:数组中有一个数字出现的次数超过了数组长度的一半,请找出这个数字: 分析: 解法1:首先对数组进行排序,时间复杂度为O(NlogN),由于有一个数字出现次数超过了数组的一半,所以如果二分数组的话,划分元素肯定就是这个数字: 解法2:首先创建1/2数组大小的Hash Table(哈希表可以替代排序时间,由于一个数字出现超过了数组的一半,所以不同元素个数肯定不大于数组的一半),空间复杂度O(N),顺序扫描映射数 组元素到Hash Table中并计数,最后顺序扫描Hash Table,计数超过数组