ARTS-for-week9-20181214_g++

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

每周完成一个ARTS:


Review 阅读了 Medium 上的一篇关于学习的文章

Tip 总结自己在台式机安装中标麒麟 V5 操作系统以及配置 Qt 环境的一些注意事项

Share 分享一篇关于 ZIP 压缩算法详细分析及解压实例解释

ARTS-for-week9-20181214_g++_02

又到周末啦,大家该吃该喝该玩,同时也不要抽点时间忘了学习呀。

下面更新 ARTS 第 九 周的内容。

1.Algorithm

LeetCode 5.Longest Palindromic Substring 链接 难度:[Medium]

【题意】

Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.

Example 1:

1
2
3
Input: "babad"
Output: "bab"
Note: "aba" is also a valid answer.

Example 2:

1
2
Input: "cbbd"
Output: "bb"

【思路一】

首先解释下什么是回文串,就是正反读起来都一样的字符串,比如 “bob”, “lovevol”, “moom” 等等。我们知道传统的验证回文串的方法就是判断一下当前和其对称位置验证值是否相等,那么对于找回文字串的问题,我们可以以每一个字符为中心,像两边扩散来寻找回文串,这个算法的时间复杂度是O(n*n),这里注意奇偶情况,由于回文串的长度可奇可偶,比如”bob”是奇数形式的回文,”moom”就是偶数形式的回文,两种形式的回文都要搜索。

对于奇数情况,我们就从遍历到当前位置为中心,向两边进行扩散。

对于偶数情况,我们就把当前位置和下一个位置当作偶数行回文串的最中间两个字符,然后向两边进行扩散。

【思路二】

我们可以直接在一个函数中解决这个问题,首先定义两个变量 start 和 maxLen,分别表示最长回文子串的起点跟长度,在遍历 s 中的字符的时候,我们首先判断剩余的字符数是否小于等于maxLen 的一半,是的话说明 maxLen 无法再变长了,直接 break 掉,否则就要继续判断。为什么要这样做呢?因为这点巧妙之处在于既然是回文串,所以必须有对称的特点!一旦剩余的字符数长度小于等于maxLen 的一半,说明之后这部分字符无法再贡献长度了

接着,我们用两个变量 l 和 r 分别指向当前位置,为了后面可以同时处理奇数和偶数的回文串,我们先要做的是向右遍历跳过重复的字符,这个操作很必要,比如对于 moom,i 在第一个o的位置,如果我们以 o 为最中心往两边扩散,是无法得到长度为 4 的回文串的,只有先跳过重复,此时 l 指向第一个 o,r 指向第二个 o,然后再向两边扩散。而对于 bob,i 在第一个 o 的位置时,无法向右跳过重复,此时 l 和 r 同时指向 o,再向两边扩散也是正确的,所以,之后的操作就是更新 start 和 maxLen ,跟上面的操作一样,参见代码如下:

【参考代码】

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#include <bits/stdc++.h>
using namespace std;

/*最开始想到的常规做法,时间复杂度是 O(n^2),空间复杂度是O(n),Time:16ms*/
class Solution1 {
public:
string findPalindrome(string s,int l,int r) {
int len = s.size();
while (l>=0 && r<=len-1 && s[l] == s[r]) {
l--;
r++;
}
return s.substr(l+1, r-l-1);
}
string longestPalindrome(string s) {
int len = s.size();
if(len<=1) return s;

string longestStr,tmpStr;
for(int i=0; i<len-1; ++i)
{
tmpStr=findPalindrome(s,i,i);//奇数回文串的情况,保存最长回文串
if(tmpStr.size()>longestStr.size()) longestStr=tmpStr;
tmpStr=findPalindrome(s,i,i+1);//偶数回文串的情况,保存最长回文串
if(tmpStr.size()>longestStr.size()) longestStr=tmpStr;
}
return longestStr;
}
};

/*考虑进一步优化 时间复杂度是 O(n^2),空间复杂度是O(n),Time:12ms*/
class Solution2 {
public:
void findPalindrome(string s,int l,int r,int &start,int &maxLen) {
int len = s.size();
while ( l>=0 && r<len && s[l] == s[r]) { // 向两边扩散寻找回文串
l--;
r++;
}
if( r-l-1 > maxLen){ //记录最大回文串长度开始和长度
maxLen = r-l-1;
start = l+1;
}
}
string longestPalindrome(string s) {
int len = s.size();
if(len<=1) return s;

int start=0,maxLen=0;
for(int i=0; i<len-1; ++i)
{
findPalindrome(s,i,i,start,maxLen);//奇数回文串的情况
findPalindrome(s,i,i+1,start,maxLen);//偶数回文串的情况
}
return s.substr(start,maxLen);
}
};
/*时间复杂度是 O(n^2),空间复杂度是O(1),Time:4ms*/
class Solution3 {
public:
string longestPalindrome(string s) {
if (s.size() < 2) return s;
int n = s.size(), maxLen = 0, start = 0;
for (int i = 0; i < n; ) {
if ( n - i <= maxLen / 2) break;//判断剩余的字符数是否小于等于maxLen 的一半
int l = i, r = i;
while (r < n - 1 && s[r + 1] == s[r]) ++r;//向右遍历跳过重复的字符
i = r + 1;
while (r < n - 1 && l > 0 && s[r + 1] == s[l - 1]) {
++r; --l;
}
if (maxLen < r - l + 1) {//更新 start maxLen
maxLen = r - l + 1;
start = l;
}
}
return s.substr(start, maxLen);
}
};

string stringToString(string input) {
assert(input.length() >= 2);
string result;
for (int i = 1; i < input.length() -1; i++) {
char currentChar = input[i];
if (input[i] == '\\') {
char nextChar = input[i+1];
switch (nextChar) {
case '\"': result.push_back('\"'); break;
case '/' : result.push_back('/'); break;
case '\\': result.push_back('\\'); break;
case 'b' : result.push_back('\b'); break;
case 'f' : result.push_back('\f'); break;
case 'r' : result.push_back('\r'); break;
case 'n' : result.push_back('\n'); break;
case 't' : result.push_back('\t'); break;
default: break;
}
i++;
} else {
result.push_back(currentChar);
}
}
return result;
}

int main() {
string line;
while (getline(cin, line)) {
string s = stringToString(line);

string ret = Solution().longestPalindrome(s);

string out = (ret);
cout << out << endl;
}
return 0;
}

2.Review

Why you learn the most when you feel like you’re struggling as a developer。(英文)

本次 Review 阅读了 Medium 上的一篇关于学习的文章。关于如何面对学习中碰到的困难和挫折,如何选择一种积极的心态去面对它们,如何更好的了解我们心中的想法并且跟随自己的想法去采取行动,去付出实践。

1.一个人通过努力和持续的努力才能成为更好的开发者。

如果有人对你说:有人天生就会某种技能,有人一生下来就会使用某种编程语言,熟练使用一些软件和技能。

你会怎么想?你赞同有些人刚出生就是伟大的开发者,还是认为人们通过一步步的努力成为伟大的开发者?有人说不否认有些天赋异禀的人,一出生就会某种技能。

在作者看来,这个问题的真正答案可能没人知道,所以作者宁愿选择相信一个人通过自己的努力才能成为一个好的开发者。

回到现实中来,这将是一个更有用的观点。这意味着,如果我努力学习某些东西,我就能学到它,如果我努力工作,我就能得到一份工作。这也意味着我必须接受的观点是,很多事情往往并不总是看起来那么简单或有趣。努力工作是必需的。

2. 当你第一次做某件事的时候,往往是最困难的时候

作为一名程序员,作者分享了自己的故事

”我有时会感到沮丧,因为我遇到了一些我不理解的东西,我觉得我应该理解它。曾经有一段时间我加入了一家使用 git 且被 git 专家包围的公司。有一段时间我终于不得不面对我的 SQL 技能不是很好的事实。在每一个实践机会中,我都觉得自己应该擅长这些技能。毕竟,我是一位拥有多年经验的高级全栈工程师,事实是,虽然我是一名经验丰富的工程师,但没有多少经验可以改变我的现实 ,我第一次学习这些技能的细节,起初我对他们中的任何一个都不是那么擅长。虽然有时新事物很容易,但有时却不容易。“

从而发现一个有用的观点是,当你第一次做某件事的时候,你是不擅长这件事本身的。就像这样……

“我以前从未用 Java 编程 - 我不应该擅长它。这就是为什么我要上这堂课“

“我以前从未将代码提交给 git repo 我不应该知道如何去做。这就是为什么我要求我的同事寻求帮助“

这种想法会逐渐消除了你脑海中的声音,比如之前你可能经常会想:说不准我会失败;我很可能会失败;我可能做不好这件事;我不应该做得好 。

这就是为什么作者建议在这种情况下,你更应该鼓起勇气,对自己说:我要试一试 !只有去试一试,你才能有机会把事情做得更好。

3 处理代码并不总是很有趣,即使任务不好玩,它仍然可以完成

作者分享到:

“有时我会遇到软件开发中没有乐趣的任务。我的 Spark 集群中的某些数据处理代码导致节点随机失败,或者无论我做什么,一些应该工作的库都无法工作。这些时刻并不好玩,有时我真的宁愿做任何事情,除了花时间去弄清楚发生了什么。问题是,我知道编码并不总是很有趣,有时我只需要卷起袖子并“做好工作”。虽然这样的时间很难,但我发现它们通常都有一线希望 ,事实证明,碰到困难的时候是我们学到最多的东西的时候。”

4.当你面对最大的挑战的时候,是你学得最多的时候 - 有时这样的失败是正常的

当你通过尝试做一些对你来说太难的事情挑战自己时,到最后你会发现那正是你真正学习的时刻。这些经历中的每一个痛苦的时刻,意味着你可能要走很多死胡同,比如说代码删了又写,写了又删。你尝试了很多种方法,而且失败了很多次。但是这种压力教会了你新的技能并使你成为一个更好的开发者。如果没有那种压力,你可能就不会吸取教训。当经历了这些过来之后,你选择相信,当你感到压力,挣扎,有点紧张时,这是一件好事。俗话说,没有压力就没有动力,你正在努力,所以你正在学习。而且慢慢学会接纳那些紧张的感觉,虽然它们令人不安,但是在另一种程度上,它们给了你向前进步的动力和鞭策,使得你能够持续的学习。

5.思维是一个强大的工具

你可以选择你所相信的东西,你的信念可以帮助你克服在成为更好的开发者的道路上不可避免的挑战。我希望这些心理框架(或你自己的一些)对于帮助你克服自己的挣扎是有用的。不要放弃。继续。

当你不理解某些事情时,继续努力并不断尝试去理解它。如果你试试,你会惊讶于你的能力。

个人 review

当你的领导,导师给你一个难题,让你去做一件你没做过的事情时,你应该珍惜这个机会,这绝对是提升你个人能力的一次机会,所以,不光要习惯被赶鸭子上架,还要去争取这样的机会。

在这个世界上,只有两样东西是别人抢不走的:一个是吃进肚子里的饭,一个是装进脑子里的经验。一个别人不想抢,不稀罕;一个谁都想抢,但是抢不走。

有句俗话这样说,温水煮青蛙,当自己觉得挺好的,每天过过日子,上班下班,也不学习,也不用提高自己,工作又能胜任,这时候,往往是最危险的时候。

还记得当年 20 岁生日的时候,我给自己写过这么一段话

“当你觉得自己很疲惫,觉得自己很无知,觉得自己怎么什么事都做不好的时候,你就要成长了;当你什么事都做得非常好,轻车熟路,把每件事都安排得井井有条的时候,证明你已经停止进步了,因为你的能力已经完全能够胜任现在所有的事了”。

共勉~

3.Tip

踩坑记~

台式机安装中标麒麟 V5 操作系统 以及 配置 Qt 环境记录

最近由于项目的需要,需要在实验室几台台式机安装中标麒麟的操作系统以及配置 Qt 环境,这里将自己的一些安装的过程记录下来,方便有需要的人可以提供一个参考, 结果在实际操作中发现还是会碰到一些问题。步骤如下:

Step 1 – 制作一个光盘镜像,安装系统

将 中标麒麟 V5 操作系统 下载地址 中标麒麟 刻录到光盘,完成之后将光驱插入到台式机,开机进入 Boot 设置光驱第一启动项,重启进入安装界面,开始安装系统。

Step 2 – rpm 包安装 G++

默认的中标麒麟 V5 操作系统自带 GCC 但是没有 G++ 环境 ,需要额外安装 ,一般在安装盘里面找到 对应的 SDK ,也就是工具包,复制到台式机某个目录下,这里需要注意的是,不同的环境有不同的 SDK,SDK 里面的 rpm 包也有不同的版本,首先查看安装好的中标麒麟操作系统的 GCC 版本,在桌面打开终端,输入

1 2 > $ gcc -v >

显示对应的版本,我的版本是 gcc-4.9.2 ,然后选择相应的 SDK,我的 SDK 版本是 V5-x86-NeoKylin-sdk,从光盘拷贝到台式机之后,我们可以看到有几个文件夹,包括 packages 等,进入 packages 目录的 C-developments 目录 我们可以看到有一个 g++-gcc-4.9.2.rpm 包,说明这个 g++ 安装包跟我们的 gcc 版本一致,接着在对应的目录打开终端,我们执行以下命令执行安装脚本

1 2 > $ sh install.sh >

终端显示你要选择的数字,不同的数字对应不同的安装功能,我们选择安装 C-developments 前对应的数字,安装过程等待十几秒之后,我们再在终端输入

1 2 > $ g++ -v >

显示 g++(4.9.2),OK,表示 G++ 环境已经配置完毕。

Step 3 – 安装 Qtcreator

根据需要,选择不同的 SDK 安装不同版本的 Qt,我这里安装 Qt4.8.5版本,打开 V5-x86-NeoKylin-sdk 工具包,在当前目录下打开终端,同样执行以下命令执行安装脚本

1 2 > $ sh install.sh >

终端显示你要选择的数字,我们选择安装 Qt 前对应的数字,安装过程等待十几秒之后,我们再在终端输入

1 2 > $ qtcreator >

系统打开 qtcreator IDE ,OK,说明 IDE安装成功。

Step 4 – 安装 Qt

源码编译 Qt-4.8.5

从 Qt 官网下载 Qt-everywhere-4.8.5.tar.gz 到台式机,进入目录,解压缩进入解压缩的目录,终端依次输入

1 2 3 4 5 6 > $ ./configure -prefix /usr/local/Qt-4.8.5 > $ o > $ yes > $ gmake > $ gmake install >

等待源码编译依次不报错之后。

终端输入

1 2 > $ vim /etc/profile >

在打开的文件的最后,在 vim 的 Normal 模式下按小写字母 o 添加几行如下

1 2 3 4 5 > QTDIR=/usr/local/Qt-4.8.5 > PATH=$QTDIR/bin:$PATH > LD_LIBRARY_PATH=$QTDIR/lib:$LD_LIBRARY_PATH > export QTDIR PATH LD_LIBRARY_PATH >

输入 :wq 保存之后

1 2 > $ source /etc/profile >

使得配置文件生效

最后,在终端输入

1 2 > $ qmake -v >

显示出对应版本说明 Qt 安装成功。

4.Share

ZIP压缩算法详细分析及解压实例解释(中文)

我们在日常生活中,需要传输文件,资料的时候,相对较小的文件往往很快的就传输过去了,比较大的文件传输的过程可能就没有那么快了,万一再碰上网络延迟,网络不好的情况,传输时间花个几分钟也是经常有可能的,在万一遇到比较紧急的情况,文件太大传输很慢,那么文件压缩在这种场合就迫切需要了。

其中一个原因是压缩后的数据容量减小后,磁盘访问 IO 的时间也缩短,尽管压缩和解压缩过程会消耗 CPU 资源,但是 CPU 计算资源增长得很快,但是磁盘 IO 资源却变化得很慢,比如目前主流的SATA 硬盘仍然是 7200 转,如果把磁盘的 IO 压力转化到 CPU 上,总体上能够提升系统运行速度。

压缩作为一种非常典型的技术,会应用到很多很多场合下,比如文件系统、数据库、消息传输、网页传输等等各类场合。

这篇文章详细的介绍了 ZIP 压缩算法,压缩格式的原理和实现。


爱学习,爱技术

ARTS-for-week9-20181214_回文串_03