1. 不同的二叉搜索树(中等)
LeetCode题号:96
给定一个整数 n,求以 1 … n 为节点组成的二叉搜索树有多少种?
示例
输入: 3
输出: 5
解释
给定 n = 3, 一共有 5 种不同结构的二叉搜索树:
1 3 3 2 1
\ / / / \ \
3 2 1 1 3 2
/ / \ \
2 1 2 3
题解
这题……感觉主要是用了二叉搜索树的定义,如果不用按序输出所有二叉搜索树的话,关于树的成分就更少了。
先说下二叉搜索树,基本原则是:对于一个节点来说,左子树任意节点的值都比它的值小,而右子树任意节点的值都比它的值大。建立时就是小的值向左分流,大的值向右分流,空节点可以存储任一流过来的数值。
纸上画了n=1,2,3,4的情形,看到4作为根节点时,子树的情况跟n=3时一样。想到这题可以分解问题直到最简情形求解,典型的递归 / 递推。
其实这也是有实现基础的。
1,2,…,n是有序的,如果我们选定一个数作为当前根,那么左侧的数自然进入左子树,右侧的数自然进入右子树。当前根的子节点又是子树的新的“当前”根。
(问题可分解性)
实际操作为:k从1到n遍历,分别计算以k为根的情形数,累加。
以k为根的情形数=(n=k-1)的情形数×(n=n-k)的情形数。
1,2,…,n是连续的,如果我们选定一段建构成树,那么情形等价于以1为起点的连续数的建构成树。
(解法可移植性)
实际操作为:(k+1)->n等价于1->(n-k)
思路是综合各方面得出来的,最后选用了递归去处理,另加了记忆化去优化时间。其实也可以用递推去做。
源代码
#include<stdio.h>
long long mem[1005],n;//其实不必开这么大
long long s(long long n)
{
if(mem[n]) return mem[n];//记忆化处理
if(n==0) return mem[0]=1;//初值
for(int i=1;i<=n;i++)
mem[n]+=s(i-1)*s(n-i);//原理式,递归并累加
return mem[n];
}
int main(void)
{
scanf("%lld",&n);
printf("%lld",s(n));
return 0;
}
如果我们再考虑一下树的输出……还没想好……orz
2. 对称二叉树(简单)
LeetCode题号: 101
给定一个二叉树,检查它是否是镜像对称的。
示例
例如,二叉树 [1,2,2,3,4,4,3] 是对称的。
1
/ \
2 2
/ \ / \
3 4 4 3
但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:
1
/ \
2 2
/ \ / \
3 3
题解
问题的核心大概在于处理不同父节点的子节点间的比较,要求指针同时分别指向两个对称位置的节点。
单纯观察判断较为容易,从根节点向“下”检查即可。
由此转向算法分析,树的路径只有向下的单向路,从根节点向下,左右指针做对称的移动,左指针向右则右指针向左。
考虑到从根节点开始,问题处理的相似性(比较左右指针所指的对称节点,再比较左->左,右->右,比较左->右,右->左),采用递归的方法遍历,判断是否镜像对称并返回BOOL值。
源代码
/*
Definition for a binary tree node.
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
};
//LEETCODE提供的树节点,单向指向子树
*/
bool isSymmetric(struct TreeNode* root){
//只能处理一个节点,即为根节点。新定义一个处理两个节点的函数,用来向下对称遍历。
if(root==NULL) return true;//空树对称
return check(root->left,root->right);//开始左右对称检查
}
//用了int是因为bool不知为啥编译没过
int check(struct TreeNode* left,struct TreeNode* right)
{
if(left==NULL&&right==NULL) return true;//皆空,对称
if(left==NULL&&right!=NULL||left!=NULL&&right==NULL) return false;//半空,不对称
if(left->val!=right->val) return false;//均不空时取值才有意义
return check(left->left,right->right)*check(left->right,right->left);//对称向下移动指针检查
//真假值1和0,×来代替&
}
3.恢复二叉搜索树(困难)
LeetCode题号: 99
二叉搜索树中的两个节点被错误地交换。
请在不改变其结构的情况下,恢复这棵树。
示例
1
输入: [1,3,null,null,2]
1
/
3
\
2
输出: [3,1,null,null,2]
3
/
1
\
2
2
输入: [3,1,4,null,null,2]
3
/ \
1 4
/
2
输出: [2,1,4,null,null,3]
2
/ \
1 4
/
3
题解
开始理解二叉搜索树不深,算是没什么思路。想过递归深搜,找到错误节点之后回头去找到另一个错误节点,交换数值。但是这棵树没有回头路,实现这个功能也不容易。
另一个想法是,仍然递归深搜,添加两个参数,一个是当前节点的父节点的值,一个是左/右节点(应该存储比父节点小/大的值)。搜到错误的节点之后存到一个容量为2的栈里面,再遍历一遍修复。但是有个问题(个人没有暂时解决):没法确认大小错序的两个数是谁错了。如示例1,2和3大小错序,但其实是3错了,应该由3和1交换。如果处理不好肯定是不行的。而如果回溯或者继续深搜去检查,最糟糕的情况是从一端找到另一端,一个错误节点的两个边构成的节点对都要判断。判断的准则也很乱。写不出来。(一方面是本人想不清楚这个的处理方法,可能这个处理方案本身就不够有效)
场面一度十分尴尬然后跑去评论区白嫖借鉴。看到“中序遍历”恍然大悟(虽然现在不是最优解):一棵正确的二叉搜索树进行中序遍历,访问的节点的值是有序递增的。
无聊的说教时间:我们要先打好基础然后好好做题,知识体系是互通的……orz。不仅是树,特殊的遍历方法本身的特性就可以在一定程度上简化算法的实现,或者影响解题的实际思路。
下面讲下本菜鸡写的代码:
第一次中序遍历,数值存到栈里面,转存到一个新的栈然后排序,第二次中序遍历中修正。
实际操作中,修正的方式有两种:
1.找出错误节点并交换值
这个思路非常直白,找错误节点的判断原则不太容易,但是一共只有两个错误节点,也就是在有序的数组中,这两个节点的值互换了位置,只要把排序前后的数组元素一一比较就可以得出错误交换的值。
再次遍历(其实无所谓哪种顺序了,遍历就完事了)
错误经历:
- 误以为存储的值就是1,2,…,n,直接比较tre[i]!=i,试图省掉排序,然后被数据打脸……
- 交换数值的代码直接两个if,没有打else,然后这个if满足了,改过去,结果下个if又满足了,又改回来,自爆行为……
2.直接全部按排序后数组全部赋值
之前考虑了有序性,只要这次遍历还是中序遍历,仍然会有序访问节点,此时直接把排序之后的值按遍历顺序赋给各个节点即可,相当于新建一个正确的二叉搜索树,错误的节点也就被更正了。
提交结果都是24ms,11.6MB左右。
数组开的大小没影响这个内存。
直接赋值,不再去遍历对照排序前后的数组找错误节点,结果也没影响运行时间。
迷惑…orz…
源代码
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };//又是单向的节点
*/
int top,tre[1005],soret[1005],temp;//,t[2],te;
//栈指针,栈,转存栈,冒泡的临时变量,错误节点栈,栈指针
void recoverTree(struct TreeNode* root){
// te=0;//指针初始化
top=1; //其实可以赋0的,后面再解释
// tre[0]=-1e9;//试图直接比较得出错误节点然后弃疗的痕迹
visit(root);//第一次中序遍历
// tre[top]=1e9;//试图直接比较得出错误节点然后弃疗的痕迹
// if(tre[i]!=i)//错误经历
// t[te++]=i;
for(int i=1;i<top;i++)
soret[i]=tre[i];//转存
for(int i=1;i<top;i++)
for(int j=i+1;j<top;j++)
if(soret[i]>soret[j])
{
temp=soret[i];
soret[i]=soret[j];
soret[j]=temp;
}//不知道LEETCODE提供了啥样的sort,渣渣的冒泡
// for(int i=1;i<top;i++)
// if(tre[i]!=soret[i])
// t[te++]=tre[i];//找错误节点的思路
top=1;//重置栈指针
c_visit(root);//修复,另写了一个中序遍历
return;
}
void visit(struct TreeNode* rot){
if(rot->left==NULL&&rot->right==NULL)//无子树
{
tre[top++]=rot->val;//进栈
return;
}
if(rot->left!=NULL)//存在则访问
visit(rot->left);
tre[top++]=rot->val;//左-中-右的中序遍历
if(rot->right!=NULL)//存在则访问
visit(rot->right);
return;
}
void c_visit(struct TreeNode* rot){
// if(rot->val==t[0]) rot->val=t[1];//修正错误节点
// else if(rot->val==t[1]) rot->val=t[0];//当初忘了else的地方
if(rot->left!=NULL) c_visit(rot->left);
rot->val=soret[top++];//直接赋值更正
if(rot->right!=NULL) c_visit(rot->right);
return;
}