ARTS-第-14-期_字段

阅读本文大概需要 10 分钟。

每周完成一个 ARTS

Algorithm 来源 LeetCode 22. Generate Parentheses。

Review 分享一篇关于数据库表连接的文章。

Tip 分享以一个例子通俗易懂的理解 TCP 的一次连接过程。

Share 分享关于团队目标和个人发展的一些看法。

下面更新 ARTS 第 十四 周的内容。

1.Algorithm

22. Generate Parentheses 链接 难度:[Medium]

【题意】

Given n pairs of parentheses, write a function to generate all combinations of well-formed parentheses.

For example, given n = 3, a solution set is:

[
"((()))",
"(()())",
"(())()",
"()(())",
"()()()"
]

给定一个数字 n,要求写一个方法,生成所有n个括号的可能合法组合。

【思路】

基本的素养,见到求所有可能的情况,所有可能组合的个数等,一般想到这几个概念:DFS,递归,回溯,有了这些直觉以后,你发现你看待问题的思路就清晰了,并且省去了了多余的想法。

一个直观的思路见下图

ARTS-第-14-期_递归_02

把左右括号拆开,从左往右写,看看能写出多少可能性?由于要写出所有的合法括号组合,先写左括号,左括号全部用完,再写右括号,这将是第一个最容易想到的可能性。然后呢?肯定需要回溯了。回溯到第一个位置,我们不写”(“,写一个”)”然后再试着看。直到所有的可能就全部写出来。

虚线箭头代表程序每次走到最终情况往回回溯的过程,每返回一次,都要检查一遍是否还有其他合法情况,如果没有继续返回,直到找到合法情况,进入下一个合法的情况。

如何检查是否还有合法情况?很简单,合法括号的含义就是有几个左括号,就必然要对应几个右括号。那么每一次返回之后,看看是否还有 “(“ 可以用,如果有,就先用左括号,然后进入该情况递归。如果没有,就继续返回。所以,需要设置一个左括号计数器,一个右括号计数器来判断是否还有可以用的括号。那么如何判断走到最终结果了呢?当右括号和左括号计数器同时用完的时候,我们就认为走到最终结果了,直接 return。

检查不合法情况?如果在某次递归时,左括号的个数大于右括号的个数,说明先写了右括号,此时生成的字符串中右括号的个数大于左括号的个数,这种情况下明显是不合法的,即会出现 ‘)(‘ 这样的非法串,直接返回,不继续处理。

【参考代码】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
Author: rongweihe
Time: 2019-01-11
*/
class Solution {
public:
vector<string> generateParenthesis(int n) {
if(n<1) return vector<string>();
vector<string> res;
generateParenthesisDFS(n, n, "", res);
return res;
}
void generateParenthesisDFS(int left, int right, string tmp, vector<string> &res) {
if (left > right) return;//说明先构造了右括号
if (left == 0 && right == 0) res.push_back(tmp);
else {
if (left > 0) generateParenthesisDFS(left - 1, right, tmp + '(', res);
if (right > 0) generateParenthesisDFS(left, right - 1, tmp + ')', res);
}
}
};

2.Review

say-no-to-venn-diagrams (英文)

本次 Review 阅读了 一篇关于数据库表连接的文章,在这篇文章里,作者认为还有比维恩图更好的解释方式,结合文章说一些自己的想法,也许换一个角度解释,更容易懂。

在关系型数据库里面,每个实体有自己的一张表(table),所有属性都是这张表的字段(field),表与表之间根据关联字段“连接”(join)在一起。所以,表的连接 是关系型数据库的核心问题。

表的连接分好几种类型。

内连接(inner join)

外连接(outer join)

左连接(left join)

右连接(right join)

全连接(full join)

以前,很多教程和文章采用 维恩图 (两个圆的集合运算),来解释不同连接的差异。

ARTS-第-14-期_字段_03

ARTS-第-14-期_递归_04

ARTS-第-14-期_递归_05

ARTS-第-14-期_字段_06

所谓连接,就是 两张表根据关联字段,组合成一个数据集。问题是,两张表的关联字段的值往往是不一致的,如果关联字段不匹配,如何处置?比如,表 A 包含小猫和小狗,表 B 包含小狗和小猪,匹配的只有小狗这一条记录。

很容易看出,一共有四种处理方法。

只返回两张表匹配的记录,这叫内连接(inner join)。

返回匹配的记录,以及表 A 多余的记录,这就叫左连接(left join)。

返回匹配的记录,以及表 B 多余的记录,这就叫右连接(right join)。

返回匹配的记录,以及表 A 和表 B 各自多余的记录,这就叫全连接(left join)。

下图就是四种连接的图示,有没有觉得,这张图比上面的维恩图更易懂,也更准确。

ARTS-第-14-期_递归_07

上图中,表 A 的记录是 123,表 B 的记录是 ABC ,颜色代表匹配关系。返回结果中,如果另一张表没有匹配记录,则用 null 填充。

这四种连接,其实又可以分为两大类:内连接(inner join)表示只包含匹配的记录,外连接(outer join )表示还包含不匹配的连接。所以,左连接,右连接,全连接都属于外连接

这四种连接的 SQL 语句如下:

SELECT * FROM A
INNER JOIN B ON A.book_id = B.book_id;

SELECT * FROM A
LEFT JOIN B ON A.book_id = B.book_id;

SELECT * FROM A
RIGHT JOIN B ON A.book_id = B.book_id;

SELECT * FROM A
FULL JOIN B ON A.book_id = B.book_id;

上面的 SQL 语句还可以加上 ​​where​​ 条件从句,对记录进行筛选,比如只返回表 A 里面不匹配表 B 的记录。

SELECT * FROM A
LEFT JOIN B ON A.book_id = B.book_id;
WHERE B.id IS null;

另一个例子,返回表 A 或表 B 所有不匹配的记录

SELECT * FROM A
FULL JOIN B ON A.book_id = B.book_id;
WHERE A.id IS null OR B.id IS null;

注:因为 MySQL 不支持 FULL JOIN。

一种解决方法:

下面是替代方法

left join + union(可去除重复数据)+ right join

两张表时:

select * from A left join B on A.id = B.id (where 条件)

union

select * from A right join B on A.id = B.id (where条件);

此外,还存在一种特殊的连接,叫做 “交叉连接”(cross join) ,指的是表 A 和表 B 不存在关联字段,这时,表 A(共有 n 条记录)与表 B (共有 m 条记录)连接后,会产生一张包含 n x m 条记录的新表(见下图)。

ARTS-第-14-期_递归_08

3 Tip

继续从项目中学习 Unix 网络编程,分享一下以一个例子通俗易懂的理解 TCP 的一次连接过程。

建立TCP连接就好比一个电话系统。

socket 函数等同于有电话可用;

bind 函数是在告诉别人你的电话号码,这样他们可以呼叫你;

listen 函数是打开电话振铃,这样当有一个外来呼叫到达时,你就可以听到;

connect 函数要求我们知道对方的电话号码并拨打它;

accept 函数发生在被呼叫的人应答电话之时,由 accept 返回客户的标识(即客户的 IP 地址和端口号)类似于让电话机的呼叫者 ID 功能部件显示呼叫者的电话号码。

然而两者的不同之处在于 accept 只在连接建立之后返回客户的标识,而呼叫者 ID 功能部件却在我们选择应答或不应答电话之前显示呼叫者的电话号码。如果使用域名系统 DNS ,它就提供了一种类似于电话薄的服务, getaddrinfo 类似于在电话簿中查找某个人的电话号码,getnameinfo 则类似于有一本按照电话号码而不是按照用户名排序的电话薄。

下面是 TCP 建立一次连接的过程图

ARTS-第-14-期_递归_09

可以看到

客户端调用了一次 write() 函数,一次 read() 函数,一次 close()函数。

服务端调用了一次 write() 函数,两次 read() 函数,一次 close() 函数。

4 Share

前几天和一个百度师兄聊天,聊到一个话题:

当在一个团队中待了很久,有一天突然发现团队的整体目标跟自己个人发展不相符合的时候,你会做出怎样的选择呢?是继续留在原来团队,个人利益服从整体利益,毕竟待了这么久已经对团队有很深的感情;还是选择内部转岗,加入新的团队。但是加入新的团队意味着你必须面临一些风险:新的团队业务流程,涉及的技术栈你肯定一开始就不熟悉的,你能短时间内尽快上手吗?新的团队里的涉及的技术是不是你能 cover 住的呢?未来的应用场景大不大呢?未来这块是不是个新的发展趋势呢?选择新的团队,新的团队的价值观和整体目标就一定是符合你个人发展吗?

上面这些问题,我想,在一个人一生的职业生涯当中,或多或少会碰到这种情况,当面临这样的问题的时候,你会如何考虑呢?

总之,很佩服师兄的魄力,勇气和决心,毕竟,在之前的团队中,师兄也是一个管理者的存在,在我个人看来,我觉得任何一次选择,眼光要放远一点,视角要放大一点,长期看下去的话,优先选择个人感兴趣的方向,深度调研,选择的团队技术/产品方向是不是未来主流的发展的方向,当然了,新的选择,意味着新的机会,想起了之前一位大佬发的一条朋友圈:大部分人都缺乏的一种特质,而具备这种特质,你与大多数人就与众不同了!这种特质就是冒险精神!如何更好地理解?坚定的按照自己当时的判断,做好了去承担后果的准备,想要有更大的成就,想从一个当下不满意的阶级向更好的阶级跨越,光是努力是远远不够的,一定需要具备冒险精神,然后在时代的变革中,抓住机会,放手一搏,才能有机会翻身,跨越阶级,享受更好的资源,获得更大的成就!

读到这里,上面的文字对你有没有启发呢?欢迎和我交流沟通~


ARTS-第-14-期_递归_10


ARTS-第-14-期_字段_11