每周完成一个 ARTS:
Algorithm 来源 LeetCode 438. Find All Anagrams in a String。
Review 分享关于 IO 多路复用之 select,poll,epoll 详解。
Tip 分享 50 行 Python 代码实战,教你用微信每天自动给女朋友说晚安。
Share 分享有关于批判性思维的思考。
一 Algorithm
438. Find All Anagrams in a String 链接 难度:[Eazy]
【题意】
Given a string s and a non-empty string p, find all the start indices of p‘s anagrams in s.
Strings consists of lowercase English letters only and the length of both strings s and p will not be larger than 20,100.
The order of output does not matter.
Example 1:
123456789 | Input:s: "cbaebabacd" p: "abc"Output:[0, 6]Explanation:The substring with start index = 0 is "cba", which is an anagram of "abc".The substring with start index = 6 is "bac", which is an anagram of "abc". |
Example 2:
12345678910 | Input:s: "abab" p: "ab"Output:[0, 1, 2]Explanation:The substring with start index = 0 is "ab", which is an anagram of "ab".The substring with start index = 1 is "ba", which is an anagram of "ab".The substring with start index = 2 is "ab", which is an anagram of "ab". |
【思路】
这道题是给出两个字符串,源字符串 s 和匹配字符串 p,让我们在 源字符串 s 中找出匹配字符串 p 的所有变位字符串的位置,所谓变位字符串就是字符种类个数相同但是顺序可以不同的两个字符串。
首先想到的一种暴力解法:先在匹配字符串 p 中统计每个字符出现的次数,然后从源字符串 s 开头开始查找,每次寻找 p 个长度的字符串,并且判断每次寻找的 p 个长度的字符串里字符种类是否和匹配字符串 p 相同,如果不相同则跳出循环,否则记录索引位置。
【解法一】
1234567891011121314151617181920212223242526272829 | Author: rongweiheTime: 2019-02-17*/// 时间复杂度 O(n*m)class {public: vector<int> findAnagrams(string s, string p) { if(s.empty()) return {}; int ls = s.size(); int lp = p.size(); vector<int> res,cnt(20100,0); for(char c:p) ++cnt[c]; int i=0,j; while( i<ls ){ bool b_match = true; vector<int> tmp = cnt; for(j=i; j<i+lp; ++j){ if(--tmp[s[j]] < 0){ b_match = false; break; } } if(b_match) res.push_back(i); ++i; } return res; }}; |
下面这种利用 滑动窗口 Sliding Window 的方法也比较巧妙。建议手动写写代码调试一遍,领会该算法的精髓。
这种方法的思想也是首先统计匹配字符串 p 的字符个数,然后利用两个变量 left 和 right 表示滑动窗口的左右边界,另外用 cnt 表示匹配字符串 p 中需要匹配的字符个数。
注意 C++ 中的 后增 ++ 和后减 – 操作执行的是先赋值后运算,所以如果右边界的字符已经在哈希表中了即哈希表出现次数大于等于 1,说明该字符在 p 中有出现,则匹配的字符数减一,cnt 自减 1。
- 如果此时 cnt 减为 0 了,说明 p 中的字符都匹配上了,那么将此时左边界加入结果 res 中。
- 如果此时 right 和 left 的差正好为 p 的长度,说明此时应该去掉最左边的一个字符,并且此时应该还原哈希表 m 中该字符出现次数,即如果该字符在哈希表中的个数大于等于 0,说明该字符是 p 中的字符。
【解法二】
1234567891011121314151617181920212223242526272829303132333435363738394041424344 | Author: rongweiheTime: 2019-02-17*/// 时间复杂度 O(n)class {public: vector<int> findAnagrams(string s, string p) { if (s.empty()) return {}; vector<int> res; int ls=s.size(); int lp=p.size(); int cnt=lp; unordered_map<char,int>m; int left=0,right=0,tmp=0; for(char c:p) m[c]++;//m[a]=m[b]=1; while(right < ls) { //cout<<"old"<<m['a']<<m['b']<<endl; if( tmp = (m[s[right++]]--) >=1 ) { //cout<<"right="<<right<<" "<<"tmp="<<tmp<<m[s[right]]<<endl; --cnt; } // cout<<"111"<<m['a']<<m['b']<<endl; if(cnt==0) { //cout<<"cnt==0"<<"right="<<right<<" "<<"left="<<left<<endl; res.push_back(left); } if( right-left == lp && m[s[left++]]++ >=0 ) { //cout<<"right-left == lp"<<" "<<"right="<<right<<" "<<"left="<<left<<endl; ++cnt; } //cout<<"222"<<m['a']<<m['b']<<endl; } return res; }};//resultRuntime: 48 ms, faster than 49.68% of C++ online submissions for Find All Anagrams in a String. |
二 Review
Linux IO模式及 select、poll、epoll详解
本次 Review 阅读了 一篇关于Linux IO模式及 select、poll、epoll 的知识。结合 《UNPVTN》学到了很多。
继续分享 IO 多路复用之 epoll 详解。
第 17 期我们讲到了Select poll epoll 都是多路复用的机制。
IO多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或写就绪)能够通知应用程序进行相应的读写操作,但select,poll,epoll本质上都是同步IO,因为它们需要在读写事件就绪后自己负责进行读写,也就是说这个读写操作是阻塞的,而异步IO则无需自己负责进行读写。
在 第 17 期 里,我们可以看到,select 和 poll 都 大专栏 ARTS需要在返回后,通过遍历文件描述符来获取已经就绪的 socket,事实上,同时连接的大量客户端在同一时刻可能只是很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降。
- epoll 函数
epoll 操作过程需要三个接口,分别如下
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
其中
int epoll_create(int size):创建一个 epoll 句柄,size 用来告诉内核这个监听的数目一共有多大。这个参数不同于 select 中的第一个参数,给出最大监听的 fd+1 的值,参数 size 不是限制了 epoll 所能监听的描述的最大数,只是对内核初始化分配数据结构的一个建议。当创建好一个 epoll 句柄之后,它就会占用一个 fd 值,在 Linux 环境下通过 /proc/进程ID/fd 查看这个 fd,所以在用完 epoll 之后,必须使用 close 函数关闭,否则会导致 fd 被耗尽。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event event):该函数是对指定描述符 fd 执行 OP 操作。
– epfd 是 epoll_create() 函数的返回值。
–op 表示 操作,用三个宏来表示:添加 EPOLL_CTL_ADD,删除 EPOLL_CTL_DEL,修改EPOLL_CTL_MOD。分别表示添加、删除和修改对 fd 的监听事件。
–fd 表示需要监听的 fd(文件描述符)。
–epoll_event:告知内核需要监听什么事。
struct epoll_event 结构如下:
12345678910111213
struct epoll_event { __uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */};//events可以是以下几个宏的集合:EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);EPOLLOUT:表示对应的文件描述符可以写;EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);EPOLLERR:表示对应的文件描述符发生错误;EPOLLHUP:表示对应的文件描述符被挂断;EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里d
int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout)
等待 epfd 上的 io 事件,最多返回 maxevents 个事件。参数 events 表示从内核得到事件的集合;maxevents 表示内核这个 events 有多大;这个 maxevents 的值不能大于创建 epoll_create() 的 size(); 参数 timeout 是超时时间(毫秒,0 会立即返回)该函数返回需要处理的事件数目,如返回 0 表示超时。
工作模式
epoll 对文件描述符的操作有两种模式:LT(level trigger )和ET(edge trigger),LT模式是默认模式,LT模式与ET模式区别如下:
LT模式:当 epoll_wait 检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用 epoll_wait时,会再次响应应用程序并通知此事件。
ET模式:当 epoll_wait 检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用 epoll_wait 时,不会再响应应用程序并通知此事件。
LT模式:LT(level trigger)是缺省模式,并且同时支持阻塞和非阻塞,在这种模式中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的 fd 进行 IO 操作。如果你不做任何操作,内核还是会继续通知。
ET模式:ET(edge trigger)是高速模式,只支持阻塞套接字,在这种模式下,当描述符从未就绪变为就绪时,内核通过 epoll 告诉应用进程,然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不在变为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个 EWOULDBLOCK 错误)。但是请注意,如果一直不对这个 fd 做 IO 操作(从而导致它再次变为未就绪),内核不会发送更多的通知。
ET 模式在很大程度上减少了 epoll 事件被触发的次数,因此效率要比 LT 高,epoll 工作在ET模式的时候,必须使用阻塞套接字,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务 dead 饿死。
关于 epoll 对文件描述符的两种工作模式,目前理解的还不是很深刻,后续有新的体会会持续分享。
三 Tip
50 行 Python 代码实战,教你用微信每天自动给女朋友说晚安。
文章灵感是来自作者 botoo,博客:http://www.cnblogs.com/botoo/p/8622379.html。最近自己也在学习 itchat 库的使用,所以我把里面的代码改了下。
其中 get_news() 函数是用来获取金山词霸每日一句,英文和翻译。
另一个函数 send_news() 是用来给你的好友每天定时发送消息。
代码
1234567891011121314151617181920212223242526272829303132333435363738 | from __future__ import unicode_literalsfrom threading import Timerfrom wxpy import *import requestsimport itchatdef get_news(): url = "http://open.iciba.com/dsapi"; r = requests.get(url); contents = r.json()['content']; translation = r.json()['translation']; return contents,translation;def send_news(): try: # 登录你的微信号,会弹出网页二维码,扫描即可 itchat.auto_login(hotReload=True); #获取对应的好友备注 #改成你最心爱的人的名字 my_friend = itchat.search_friends(name=u'彩虹宝宝'); #获取对应名称的一串数字 rainbowBB = my_friend[0]["UserName"]; #获取金三词典的内容 msg1 = str(get_news()[0]); content = str(get_news()[1][17:]); msg2 = str(content); msg3 = "来自你最爱的人"; #发送消息 itchat.send(msg1,toUserName=rainbowBB); itchat.send(msg2,toUserName=rainbowBB); itchat.send(msg3,toUserName=rainbowBB); # 每 86400 秒(1天)发送一次 t = time(86400,send_news()); t.start(); except: msg4 = u"今天最爱的人出现啦"; itchat.send(msg4,toUserName=rainbowBB); if __name__ == "__main__": send_news() |
最后运行程序,就会定时发送消息给你最心爱的人。运行结果如下。
四 Share
最近在得到 APP 上学习了一些课程,记录下一些有关于批判性思维精彩的分享。
资讯爆炸的时代,我们每天会接收到太多讯息,然而不是每则讯息都会让你增广见闻、获得新知。也因此,具有判断与批判性的思考在当下的时代是一种比较重要的能力。
当判断这个能力培养起来之后,在面对成千上万的新的消息和咨询时,才有办法真的判断哪些是值得的消息和讯息。
以下是四个阶段的思考阶段,循序渐进的进入到每一个阶段后,你会发现自己看事情的角度就跟以前大不相同了。
批判性思维分为四个阶段。
①相信别人告诉你的任何事
当你无法独立思的时候,你会相信别人告诉你的一切。在这个阶段的人会毫无疑问地接受一切。
改变的方法就是对自己有自信,对任何事都保有自己的看法,不要一味地认可和接受。
②脑中的想法相互对立
当你有了自己的想法之后,脑中同时可能有多种想法在脑中互相抗衡,最终剩下 2-3 个比较有合理的判断。如果你处于在这个阶段,说明了你对每件事都有了自己的想法和判断。已经开始自动过滤掉一些无谓的资讯。这时不用太慌张,合理的判断会带给你信心,一些天马行空的想法则会激荡出不同火花,在这个过程中学会用不同角度看事情。
③从不同角度看
在这个阶段的思考者,他可以同时从不同角度去看同一件事情。这里举一个简单的粒子,我们日常生活中买一个杯子来说,现在你可能同时想到的是材质、耐用程度、价钱和实用度等等。要成为这个等级的思考者,首先要知道,一件事情不可能有绝对性,要考虑到当时的状况与需求才能做出正确的判断,透过一次次判断与结果相互符合,证实自己的思考方向是正确的。
④点线面&立体维度
这个等级的思考者,通常大家会对于你强烈的直觉与反应感到敬佩,面对同样一件事,相比较其他人,有时候你表现得相当果断,有时候你可以迅速做出判断与推论。简单抽象来说就是可以从更广泛的角度来看待事物,就算是遇上不熟悉的情况与问题,也能迅速抽丝剥茧确定问题的根本,然后解决。
当你开始进行批判性思维发展阶段时,你会自然而然地将事物看成点线面,搜集线索,观察细节,最后连线成面,再从不同立体思维的角度去思考,并逐渐拥有洞察力和观察力。
02/17/2019
加贝木苇