Leetao's Blog

Talk is cheap, show me the code

0%

前言

showDialog 是 flutter 中一个展示 Material 风格对话框函数,正常情况下使用对话框,都是有相应对交互逻辑的,当我们需要改变某个属性的状态时,通常情况下会发现 setState() 无法使用。

场景

一般情况下我们使用 showDialog 的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
showDialog(
context: context,
builder: (BuildContext context){
return Container(
child: Scaffold(
body: Column(
children: <Widget>[
... // 省略代码
],
),
),
);
}
);

在这种情况下如果使用 setState() 会发现通过 setState() 更新的属性不会生效

解决

如何解决这个问题呢?有两个解决办法。在 dialog 中声明一个 StatefulBuilder 或者 StatefulWidget。可以看一下具体的例子:

StatefulBuilder 样例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
await showDialog<void>(
context: context,
builder: (BuildContext context) {
int selectedRadio = 0;
return AlertDialog(
content: StatefulBuilder( // 重点
builder: (BuildContext context, StateSetter setState) { // 重点
return Column(
mainAxisSize: MainAxisSize.min,
children: List<Widget>.generate(4, (int index) {
return Radio<int>(
value: index,
groupValue: selectedRadio,
onChanged: (int value) {
setState(() => selectedRadio = value);
},
);
}),
);
},
),
);
},
);

重点在6,7行代码

StatefulWidget 样例

自定义一个 StatuefulWidget 的组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Bird extends StatefulWidget {
const Bird({
Key key,
this.color = const Color(0xFFFFE306),
this.child,
}) : super(key: key);

final Color color;
final Widget child;

_BirdState createState() => _BirdState();
}

class _BirdState extends State<Bird> {
double _size = 1.0;

void grow() {
setState(() { _size += 0.1; });
}

@override
Widget build(BuildContext context) {
return Container(
color: widget.color,
transform: Matrix4.diagonal3Values(_size, _size, 1.0),
child: widget.child,
);
}
}

参考

Flutter: setState on showDialog()

写作平台的迁移历史

我从14年就开始写博客了,也就是大二的时候,陆陆续续地也在很多平台写过文章。最初的第一个平台是博客园,起初写的是自己刷题的记录,后来把大部分的文章都删了。接着就是在 Github 上使用 Hexo 搭建自己的个人博客,中间还穿插着在简书写过一段时间,后来简书出现过一次关于歧视程序员的事情,一怒之下就把所有写的文章也都删掉了。然后就在知乎上开了专栏,也开通了个人的微信公众号。随着时间的推移和年龄的增长,也越来越懒的在博客上折腾了,但是在别的平台去写文章,总有一种受制于人的感觉,最后就索性自己写买了个服务器写了一个个人博客网站,也就是现在的 Leetao’s Blog。

写博客的初衷

为什么会写博客呢?

作为一个每天都在接受新知识的程序员,有时候难免会遭遇各种问题,为了避免重蹈覆辙,所以就需要一个地方去记录这些东西。

为什么不写到记事本这种私密的地方呢?

自己吃过的亏分享出来,让别的人引以为戒,也可以帮助遇到相同问题的人快速的解决问题,最好的是可以和别人的思维产生碰撞,激发出新的想法来。
看到文章被别人评论,点赞,评论会有一种难以言表地成就感,我想这可能也是比较大的驱动力之一了。
对自己近期进行阶段性的回顾,工作久了就怕自己停滞不前,通过写博客可以帮助自己复盘,梳理自己是否有遗漏的点,是否学习到新的知识了。

写一篇博客要多久

写一篇文章不容易,写一篇让大家觉得好的文章更不容易。我通常写一篇博客,文章内容可能只有几百字,但是写起来可能会花掉我好几个小时,我得去思考如何写起来能让文章看起来更流畅,有条理性,希望读者能够从在我梳理的框架下很快的理解我写的东西。为了避免文章的可能出现错误,导致误导别人,需要去反复查证。为了可以在最短的篇幅中输出尽可能的知识,我可能会浏览很多同类的文章,去对比发现自己有没有遗漏什么重要的知识点。这也可能是自己写博客不太多的原因,当然有时候不知道写什么也是一大原因。

希望从写博客中收获什么

首先是写作本身带来的乐趣了,写作本身就是一件很有意思的事情,能够帮助自己平静下来,很显然当一个人心浮气躁的时候是无法完成写作的,当你全身心地投入到写作当中,你就自然而然的安静下来了。

其次写博客最大的希望是收获读者的认可了。不过现在我发现一个现象,包括我自己,大家很少去对浏览的博客去写什么评论,点赞之类的。更多的可能是解决问题,关闭网页;新建文件夹,收藏一下,很少去和作者互动一下。

这是自己学习 Rust 的学习笔记,由于 Rust 语言处于初期,变动可能会比较大,文章不一定会及时同步,学习过程中需要仔细甄别

复合类型

复合类型(Compound types)可以将多个值组合成一个类型。Rust 有两个原生的复合类型:元组(tuple)和数组(array)

元组

元组是一个将多个其他类型的值组合进一个复合类型的主要方式。我们使用包含在圆括号中的逗号分隔的值列表来创建一个元组。元组中的每一个位置都有一个类型,而且这些不同值的类型也不必是相同的。这个例子中使用了可选的类型注解:

1
2
fn main() {
let tup: (i32, f64, u8) = (500, 6.4, 1);}

tup 变量绑定到整个元组上,因为元组是一个单独的复合元素。为了从元组中获取单个值,可以使用模式匹配(pattern matching)来解构(destructure)元组值,像这样:

1
2
3
4
5
6
7
fn main() {
let tup = (500, 6.4, 1);

let (x, y, z) = tup;

println!("The value of y is: {}", y);
}

程序首先创建了一个元组并绑定到 tup 变量上。接着使用了 let 和一个模式将 tup 分成了三个不 同的变量, x 、 y 和 z 。这叫做 解构 ( destructuring ),因为它将一个元组拆成了三个部分。最后,程序打印出了 y 的值,也就是 6.4。让我们看一下编译的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 Compiling learn_rust_by_example v0.1.0 (F:\workspace\rust\learn_rust_by_example)
warning: unused variable: `x`
--> src\main.rs:4:10
|
4 | let (x, y, z) = tup;
| ^ help: consider using `_x` instead
|
= note: #[warn(unused_variables)] on by default

warning: unused variable: `z`
--> src\main.rs:4:16
|
4 | let (x, y, z) = tup;
| ^ help: consider using `_z` instead

Finished dev [unoptimized + debuginfo] target(s) in 3.74s
Running `target\debug\learn_rust_by_example.exe`
The value of y is: 6.4

这里编译似乎出现了 warning,提示我们有两个没有使用的变量 x,z, 可以使用 _x, 和 _z 去替代,ok,让我们试着修改一下代码,

1
2
3
4
5
fn main() {
let tup = (500, 6.4, 1);
let (_x, y, _z) = tup;
println!("The value of y is: {}", y);
}

然后重新编译一下:

1
2
3
4
   Compiling learn_rust_by_example v0.1.0 (F:\workspace\rust\learn_rust_by_example)
Finished dev [unoptimized + debuginfo] target(s) in 0.90s
Running `target\debug\learn_rust_by_example.exe`
The value of y is: 6.4

warning 消失了,那么问题来了 _variablevariable 有什么区别呢?

首先需要明确的一点是两者都是正常的变量,两者最大的区别就在于,只是对于 _variable Rust 的编译器允许它不被使用。对于 Rust 编译器来说,希望正常定义的变量在接下来都可以被使用的,但是有时候会存在,我们部分变量可能只是一个预留值,为了应对将来的情况,或者函数的拓展,这个时候就可以使用 _variable 从而消除 Rust 的提示。

除了 _variable, Rust 中还存在 _ 的变量,这两者有很大差别,就像我们上面说的那样 _variable 是正常的变量,意味着 _variable 只有当离开作用域才会被销毁,但是 _ 不一样,它会被立即销毁,也就意味着 _ 不存在变量绑定的操作

让我们再次试着修改上面的例子

eg: _variable

1
2
3
4
5
6
7
fn main() {
let tup = (500, 6.4, 1);

let (_x, y, _) = tup;

println!("The value of y is: {}, x is {}", y,_x);
}

结果如下:

1
2
3
4
   Compiling learn_rust v0.1.0 (/Users/leetao/Workspace/rt/learn_rust)
Finished dev [unoptimized + debuginfo] target(s) in 2.86s
Running `target/debug/learn_rust`
The value of y is: 6.4, x is 500

eg:_

1
2
3
4
5
fn main() {
let tup = (500, 6.4, 1);
let (_x, _y, _) = tup;
println!("The value of _ is {}", _);
}

结果如下:

1
2
3
4
5
6
7
8
   Compiling learn_rust v0.1.0 (/Users/leetao/Workspace/rt/learn_rust)
error: expected expression, found reserved identifier `_`
--> src/main.rs:4:39
|
4 | println!("The value of _ is {}", _);
| ^ expected expression

error: aborting due to previous error

说完了,这个我们回到最初的元组,元祖除了用模式匹配解构外,也可以使用点号( . )后跟值的索引来直接访问它们

1
2
3
4
5
6
7
fn main() {
let x: (i32, f64, u8) = (500, 6.4, 1);
let first = x.0; // 索引值也是从 0 开始
let second = x.1;
let third = x.2;
println!("first :{}, second:{}, third: {}",first,second,third);
}

看一下编辑结果:

1
2
3
4
   Compiling learn_rust v0.1.0 (/Users/leetao/Workspace/rt/learn_rust)
Finished dev [unoptimized + debuginfo] target(s) in 0.46s
Running `target/debug/learn_rust`
first :500, second:6.4, third: 1

数组

另一个包含多个值的方式是数组。与元组不同,数组中的每个元素的类型必须相同。Rust 中 的数组与一些其他语言中的数组不同,因为 Rust 中的数组是固定长度的:一旦声明,它们的长度不能增长或缩小. 数组是一整块分配在栈上的内存。 使用索引来访问数组的元素。

1
2
3
4
5
6
7
fn main() {
let a = [1, 2, 3, 4, 5]; // 写法一
// let a: [i32; 5] = [1, 2, 3, 4, 5]; 写法二
let first = a[0];
let second = a[1];
println!("first:{}, second:{}",first,second);
}

结果:

1
2
3
4
   Compiling learn_rust v0.1.0 (/Users/leetao/Workspace/rt/learn_rust)
Finished dev [unoptimized + debuginfo] target(s) in 0.56s
Running `target/debug/learn_rust`
first:1, second:2

结果很简单,现在让我们看一下数组初始化方法,官方文档定义的语法如下:

Syntax
ArrayType :
      [ Type ; Expression ]

正常情况下我们都可以省略掉 [Type; Expression], 因为 Rust 是一个静态类型的语言,这就意味着在编辑期间,编译器可以推断出,我们使用的类型以及数组的长度。

那么现在有一个问题,对于一个数组,我们想限制它的 type, 而不是由编译器在编译期间推断出来,该怎么办呢?

别着急,看我下面的例子,行不行?

1
2
3
4
5
6
fn main() {
let a:[i32;_] = [1, 2, 3, 4, 5];
let first = a[0];
let second = a[1];
println!("first:{}, second:{}",first,second);
}

这里我用上午提到的 _ 替代了原来属于Expression 的位置,是不是看起来没有问题?让我们编译一下试试

1
2
3
4
5
6
7
8
9
10
   Compiling learn_rust v0.1.0 (/Users/leetao/Workspace/rt/learn_rust)
error: expected expression, found reserved identifier `_`
--> src/main.rs:2:16
|
2 | let a:[i32;_] = [1, 2, 3, 4, 5];
| - ^ expected expression
| |
| while parsing the type for `a`

error: aborting due to previous error

好像编译失败了,这是为什么呢?

注意_ 只能在两种情况下使用

1. 在模式匹配中,用于匹配一个被忽略的值
2. 作为一个类型的占位符

在上面的例子,数组长度是一个 Expression,并不是一个 Type,所以在这里不能使用 _,那有没有解决办法呢?自然是有的,你只需要将 Type 添加到数组中任意一个元素上就可以了

1
2
3
4
5
6
fn main() {
let a = [1i32, 2, 3, 4, 5];
let first = a[0];
let second = a[1];
println!("first:{}, second:{}",first,second);
}

看一下结果

1
2
3
4
   Compiling learn_rust v0.1.0 (/Users/leetao/Workspace/rt/learn_rust)
Finished dev [unoptimized + debuginfo] target(s) in 5.09s
Running `target/debug/learn_rust`
first:1, second:2

最后,我们现在都知道数组是通过索引访问的,那么我们出现越界操作会出现什么呢?下面看一个例子:

1
2
3
4
5
6
fn main() {
let a = [1, 2, 3, 4, 5];
let index = 10;
let element = a[index];
println!("The value of element is: {}", element);
}

看一下结果:

1
2
3
4
5
   Compiling learn_rust v0.1.0 (/Users/leetao/Workspace/rt/learn_rust)
Finished dev [unoptimized + debuginfo] target(s) in 0.07s
Running `target/debug/learn_rust`
thread 'main' panicked at 'index out of bounds: the len is 5 but the index is 10', src/main.rs:4:19
note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

编译并没有产生任何错误,不过程序会出现一个 运行时(runtime)错误 并且不会成功退出。当尝试用 索引访问一个元素时,Rust 会检查指定的索引是否小于数组的长度。如果索引超出了数组长度,Rust 会 panic ,这是 Rust 术语,它用于程序因为错误而退出的情况。

前言

最近准备用 kindleGen 将一些 RSS 生产 mobi 然后推送到 kindle 上去,发现 kindleGen 没有办法在 macOS 下使用了,使用的时候会出现错误:

1
bad CPU type in executable

问题

找了一些资料,最后发现原因是macOS Catalina 10.15 不再支持 32 位应用,而亚马逊并没有升级 kindlegen

解决办法

亚马逊更新了 kindle-previewer,支持 macOS Catalina 10.15,而 kindle-previewer 的包里带有 kindlegen。

下载安装 kindle-previewer,安装完成之后查看应用程序的包内容,在 /Contents/MacOS/lib/fc/bin 中找到 kindlegen

前言

通常情况下,我们会使用Flask来开发网站,但是也有的时候我们只是想用 Flask 来简单的创建 RestFul 的 Api,这个时候可能会使用其他的 Flask 拓展来帮助完成这一切,我前段时间就使用 Flask-Restful 这个库去实现简单的接口了。既然是接口必然是提供给别人的使用的,这个时候就涉及到序列化的问题,Flask-Restful 这个库提供了一个 fields 模块帮助完成结果的格式化,比如常用的 fields.String,fields.Float,fields.Integer 等等,使用起来也很方便,直到我开始使用 fileds.DateTime

fields.DateTime

首先看一下 Flask-Restful 中对 fields.DateTime 的说明:

_class _fields.``DateTime(dt_format=’rfc822’, **kwargs)
Return a formatted datetime string in UTC. Supported formats are RFC 822 and ISO 8601.
See email.utils.formatdate() for more info on the RFC 822 format.
See datetime.datetime.isoformat() for more info on the ISO 8601 format.

通过文档可以得知使用 fields.DateTime 返回的字符串是 UTC 即时间标准时间,很明显和我们不是一个时区的。并且支持的格式只有 RFC 822** **和 **ISO 8601**,还很贴心的给了说明链接。那么这俩个格式时间是什么样子呢?看一下例子:

1
2
3
4
# RFC 2822,
Fri, 09 Nov 2001 01:08:47 -0000
# ISO 8601
2019-05-18T15:17:08.132263

很明显这个 fields.DateTime 不是我们要的时间格式化的 fields,一是时区不对,二是格式也不是我们正常想要的那样。那么怎么解决这个问题?这个时候就需要自定义fileds了。

自定义 fields

fileds.Raw

官方给的自定义格式是使用 fields.Raw,继承 fields.Raw 然后实现 format 函数,看下官方的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
class UrgentItem(fields.Raw):
def format(self, value):
return "Urgent" if value & 0x01 else "Normal"

class UnreadItem(fields.Raw):
def format(self, value):
return "Unread" if value & 0x02 else "Read"

fields = {
'name': fields.String,
'priority': UrgentItem(attribute='flags'),
'status': UnreadItem(attribute='flags'),
}

显然没有必要,我们已经有了一个现成的 fields.DateTime,那就在它的基础上去完善它。

CustomDate

代码很简单,新增一个参数 format,当在 dt_format 为 strftime 下使用,默认格式为 %Y-%m-%d %H:%M:%S,完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from flask_restful import fields


def _custom_format(dt, fmt):
if isinstance(dt, str):
return dt
return dt.strftime(fmt)


class CustomDate(fields.DateTime):
'''
自定义CustomDate,原有的fileds.DateTime序列化后
只支持 rfc822,ios8601 格式,新增 strftime 格式
strftime格式下支持 format 参数,默认为 '%Y-%m-%d %H:%M:%S'
'''

def __init__(self, dt_format='rfc822', format=None, **kwargs):
super(fields.DateTime, self).__init__(**kwargs)
self.dt_format = dt_format
self.fmt = format if format else '%Y-%m-%d %H:%M:%S'

def format(self, value):
if self.dt_format in ('rfc822', 'iso8601'):
return super(fields.DateTime.format(value))
elif self.dt_format == 'strftime':
return _custom_format(value, self.fmt)
else:
raise Exception('Unsupported date format %s' % self.dt_format)

使用方法很简单和上面使用 fields.Raw 自定义的fields 一样:

1
2
3
fields = {
'name': CustomDate(dt_format='strftime')
}

后记

本来准备给 Flask-Restful 提个 PR,把 DateTime 新增 strftime 方法的,后来发现老早之前就有人提了add a way for Fields.DateTime to custom datetime like strftime,不过一直没有合并,这个项目也不是很活跃了,并且不支持 swagger,小伙伴在想使用 flask 创建 api 的话可以考虑使用Flask-RESTX支持swagger功能。

前言

在 Flask 中 Flask-SQLAlchemy 应该是最常用的 ORM 了,通过它来构建 Model 来映射数据库中的表。通常情况下都是一个表对应一个模型,这种方案很简单。那么如果是多个表对应一个模型的情况下,该如何处理呢?接下来结合一个具体的案例来讲解这一内容。

案例

现在我有一个数据库 test.db,它的表如下:

1590195419952-4200c916-62c2-4f6e-9c1b-d79859502926.png

有很多表并且表名类似,而且表名类似的表的结构相同,以 UPS1_20190716 为例,看一下它的表结构:

1
2
3
4
5
Id
workTime
device
status
temp

那么如何定义Model呢?像下面这样?

1
2
3
4
5
6
7
8
9
class UPS120190719:
__tablename__ = 'UPS1_20190716'
Id = db.Column(db.Integer,primary_key=True)
workTime = db.Column(db.Time)
...

class UPS220190716:
__tablename__ = 'UPS2_20190716'
...

显然这样子代码不够优雅,有重复的代码,需要优化。那么该怎么解决呢?

解决方案

有俩种方案都可以解决上述问题,不过俩种方案应用场景略有不同,可以根据实际情况来决定。

类的继承

第一种方法就是利用类可以继承的特性去实现了,我们定义一个表的基类,然后其它 UPS 表继承 UpsBase即可:

1
2
3
4
5
6
7
8
9
10
class UpsBase:
Id = db.Column(db.Integer,primary_key=True)
workTime = db.Column(db.Time)
...

class UPS120190716(UpsBase):
__tablename__ = 'UPS1_20190716'

class UPS220190716(UpsBase):
__tablename__ = 'UPS2_20190716'

当表的数量固定这是一个很好的解决方案,但是一旦当数据库的表会发生变动,比如隔天增长几张表,显然我们不太可能天天定义新的Model,然后重新部署服务的。这个情况就需要用到第二种解决方案了。

动态定义表类

同样的这种方法也需要首先定义表的基类:

1
2
3
4
class UpsBase:
Id = db.Column(db.Integer,primary_key=True)
workTime = db.Column(db.Time)
...

但是这里就不需要定义其它 UPS 表的类了,这里是动态定义,那就意味着,我们只在需要的时候创建,那么如何创建呢?在 Python 中可以使用 type 函数动态创建类,需要注意的时,在创建时需要设置 **tablename **的值。

1
Ups1_20190716 = type('UPS120190716', (UpsBase, db.Model), {'__tablename__':'UPS1_20190716'})

之后就可以创建实例了:

1
Ups = Ups1_20190716()

例子

下面补充一个例子:如果根据表名动态创建表,如果表不存在则创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
from flask import Flask,jsonify
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import inspect


app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///test.sqlite3"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False

db = SQLAlchemy(app=app)

class UpsBase:
Id = db.Column(db.Integer,primary_key=True)
workTime = db.Column(db.Time)

def table_exists(name):
engine = db.get_engine()
ret = inspect(engine).has_table(name)
print('Table "{}" exists: {}'.format(name, ret))
return ret


@app.route("/<table_name>")
def index(table_name):
if not table_exists(table_name):
up_test = dynamic_table(UpsBase, table_name)
up_test.__table__.create(db.engine)
return jsonify({"code":200})




def dynamic_table(table_base, table_name: str, bind_key='db') -> db.Model:
"""[summary]
Arguments:
table_base {Object} -- [表的属性类,包含表的所有列]
table_name {str} -- [表名]
bind_key {str} -- [绑定的数据库]
Returns:
[Model] -- [对应的 db Model]
"""
return type(table_name.capitalize(), (table_base, db.Model),
{'__tablename__': table_name, '__bind_key__': bind_key, '__table_args__': {'extend_existing': True}})()

前言

最近在使用 Anaconda 自带的 Python 环境,在交互模式下 import sqlite3 出现异常,异常信息如下:

1
2
3
4
5
6
7
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "c:\programdata\anaconda3\lib\sqlite3\__init__.py", line 23, in <module>
from sqlite3.dbapi2 import *
File "c:\programdata\anaconda3\lib\sqlite3\dbapi2.py", line 27, in <module>
from _sqlite3 import *
ImportError: DLL load failed: The specified module could not be found.

错误原因

从报错信息来看,错误原因很明显找不到对应的 dll,我也查阅了相关资料,得出了下面的俩种解决方案

解决方案一

下载对应的 dll,其实只需要下载一个 sqlite3.dll,下载地址 sqlite3.dll,根据系统位数下载对应的dll,然后将下载好的文件放在 Anaconda 默认的 Python.exe 所在的目录。

解决方案二

后来我检索了一下 Anaconda 的安装目录发现所需要的 dll 已经存在了,但是因为没有添加到 PATH 中所以没有检索出来,因此第二个解决办法就是将 path\to\Anaconda3\Library\bin 添加到 PATH 中,然后重新打开终端就可以解决问题了。

参考链接

Unable to import sqlite3 using Anaconda Python

给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树。s 的一个子树包括 s 的一个节点和这个节点的所有子孙。s 也可以看做它自身的一棵子树。

示例 1:
给定的树 s:

1
2
3
4
5
6
    3
/ \
4 5
/ \
1 2

给定的树 t:

1
2
3
  4 
/ \
1 2

返回 true,因为 t 与 s 的一个子树拥有相同的结构和节点值。

示例 2:
给定的树 s:

1
2
3
4
5
6
7
    3
/ \
4 5
/ \
1 2
/
0

给定的树 t:

1
2
3
  4
/ \
1 2

返回 false。

思路

这题是让我们判断一个树是否是另外一个树的子树,从题目的一开始s 的一个子树包括 s 的一个节点和这个节点的所有子孙结合第二个例子不难发现,中间的某个部分不能算是子树。那么就相当于需要判断s的所有子树中是否和t有一样的,最后就演变成判断两个树是否相等了。判断俩个树是否相等,可以用递归的写法。

判断两个树是否相等

1
2
3
4
5
6
7
8
def isSameTree(s: TreeNode, t: TreeNode) -> bool:
if not s and not t:
return True
if not s or not t:
return False
if s.val != t.val:
return False
return isSameTree(s.left,t.left) and isSameTree(s.right,t.right)

解法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def isSubtree(self, s: TreeNode, t: TreeNode) -> bool:
def isSameTree(s: TreeNode, t: TreeNode) -> bool:
if not s and not t:
return True
if not s or not t:
return False
if s.val != t.val:
return False
return isSameTree(s.left,t.left) and isSameTree(s.right,t.right)
if not s:
return False
if isSameTree(s,t):
return True
return self.isSubtree(s.left,t) or self.isSubtree(s.right,t)

开放思路

如果我们把两个树的所有节点值都保存到两个字符串 s’ 和 t’ 中,然后判断是否 t’ 在 s’ 中就能得出 t 是否是 s 的子树了?

答案是肯定的,有兴趣的可以按照这个思路去实践一下。