- T-Tips,记录至少一个技术或者技巧。主要为了总结和归纳你在日常工作和生活中遇到的知识点。
- R-Receive,只要有新知识的输入(技术、理财、哲学、文化等),都可以写下来。
- A-Algorithm,每周至少做一道算法题(如刷leetCode),把自己的理解和实现细节写下来,主要为了编程训练和学习。通过每周坚持训练提升自己的算法能力。
Tips
最近很多身边的人都在谈论“电动汽车”,大有当年苹果手机横空出世时引起众人热议时的氛围。也人有说现在电动汽车 vs. 燃油汽车,就好像当年智能手机 vs. 功能手机。虽然我没有开过电动汽车,但是最近打顺风车真遇到几次特斯拉 Model-3。从乘坐感觉上来说:
- 动力更强劲,加速更快、更平滑,没有燃油车的顿挫感。
- 驾驶更简单,车控操作非常简洁,尤其相对于手动挡燃油车,简直是天壤之别
- 驾驶更安全。辅助驾驶功能能够及时提醒司机前后左右的车况信息,可以大大降低驾驶技术门槛,尤其对于女司机来说是一个福音(此处无任何歧视女司机的看法)。自动刹车功能可以在司机来不及踩刹车的情况,自动紧急制动,避免追尾风险。
中国政府已经提出明确的电动车发展规划:
- 2030年一线城市全面推进燃油车退出计划
- 2040年二线城市全面推进燃油车退出计划
- 2050年全国推进燃油车退出计划
最近国家又出台了一系列促进汽车消费的利好政策:
- 一是要对现有限购政策进行调整,增加号牌指标
- 二是要开展汽车下乡,鼓励农村地区居民购买3.5吨及以下货车和1.6升及以下排量乘用车,补贴以旧换新
- 三是加强停车场充电桩等建设
总体而言,电动汽车已经是大势所趋,未来与电动汽车相关的产业将会蓬勃发展。目前全球电动汽车产量和销量增速最快的就是中国。在这场新能源汽车革命浪潮中,中国很有可能实现弯道超车,成为全球最大的汽车生产国和消费国。这也是国家为什么大力补贴新能源汽车产业,引导企业创新、规范市场秩序、促进个人汽车消费、并且引入外资(特斯拉)加强竞争。未来新能源汽车肯定会成为中国除出口贸易、基建投资、房地产之外的新的经济增长引擎。
Receive
如何根据人口曲线来判断投资方向?人一辈子,具体某一个人,他哪年结婚,哪年买房子,哪年换大房子,哪年生孩子,这些行为它都具有随机性。但是在统计意义上来讲,它其实是个定数,因为它符合大数定理,无非就是个正态分布,背后是几千万人的共同行为所塑造的一个峰值,它其实是非常稳定的。
那么我们根据这个曲线,就可以推测出一个人的消费时间点:
- 到了42岁左右,当人事业有成,收入较高,会换一个大房子
- 在48岁左右来到个人消费的高峰;例如,据统计成功男士买豪车一般在52岁左右
- 60岁左右是医药支出的高峰
- 65岁左右是购买养老和度假房的高峰
那么根据这些人房产消费的敏感时间点,再结合1962-1972年,中国出现的“婴儿潮”以及他们的孩子也就是1980-1990年之间出现“回声潮”,就可以推算出来:
- 2005-2012年,全中国会有一个房地产的牛市。注意,我说的是推算,是提前好几十年就可以提前推算出2005-2012年,全中国不管大大小小的城市都会有牛市,而且这个牛市会非常猛,即便发生金融危机也无法打断牛市的趋势。
- 2015-2020年应该是一个小牛市,而且偏重的是小户型房产,大城市远郊区县的房产也会涨的比较好,这是因为1985-1990年出生的人先后来到30岁左右婚房的消费高峰期(80后相对于他们的父辈普遍晚婚,80后高峰婚育年龄在30岁左右)。
- 2027-2030年左右还有一次温和的牛市,那可能是中国房地产最后的疯狂了,也就是说回声潮的人口来到他们人生买房的巅峰时刻40~42岁,以及他们的父母来到他们养老房的消费敏感期65岁,房子应该是集中在大的户型。
我刚才讲的些房产的牛市它在不同的区域表现出的力量是不一样的,年轻人口持续聚集的区域,房产价格即便是在其他地方熊市的时候,也在倔强地上涨;那么人口流出的区域,即便是在全国的牛市中间,它也会温和地下跌。现在我讲的基本上已经属于过去式,因为未来中国人口曲线的波动性很小了,基本上就是横着走、平着走、往下走。
所以,未来房价到底会不会暴涨,房子有没有投资价值,根据这个人口曲线的分析应该能够回答我们的问题:2027 还会有一波房价的集体狂欢,但主要集中在大户型,尤其是生活配套比较齐全、环境优雅的“城市商业次核心圈”。
Algorithm
KMP算法是一种改进后的字符串匹配算法,由D.E.Knuth与V.R.Pratt和J.H.Morris同时发现,因此人们称它为克努特-莫里斯-普拉特操作(简称KMP算法)。
KMP算法又称“看毛片”算法,是一个效率非常高的字符串匹配算法。相信很多人初识KMP算法的时候始终是丈二和尚摸不着头脑,要么完全不知所云,下面将结合本人的理解,用最浅显易懂的方法解释一下KMP算法。
首先为什么要使用KMP,比如abc这个串找它在abababbcabcac这个串中第一次出现的位置,那么如果直接暴力双重循环的话,abc的c不匹配母串中的第三个字母a后,abc将开始从母串中的头位置的下一个位置开始寻找,就是abc开始匹配母串中第二个字母bab...这样,而KMP的优势就在于可以让模式串向右滑动尽可能多的距离就是abc直接从模式串的第三个字母aba...开始匹配,为了实现这一目标,KMP需要预处理出模式串的移位跳转next数组。
1. KMP算法完成的任务是:
给定两个字符串S和M,长度分别为n和m,判断M是否在S中出现,如果出现则返回出现的位置。常规方法是遍历S的每一个位置,然后从该位置开始和M进行匹配,但是这种方法的复杂度是O(nm)。KMP算法通过计算移位跳转表next[...],使匹配的复杂度降为O(n+m)。
我们首先用一个图来描述kmp算法的思想。在字符串S中寻找M,假设匹配到位置i时两个字符才出现不相等(i位置前的字符都相等),这时我们需要将字符串M向右移动。常规方法是每次向右移动一位,但是它没有考虑前i-1位已经比较过这个事实,所以效率不高。事实上,如果我们提前计算某些信息,就有可能一次右移多位。假设我们根据已经获得的信息知道可以右移x位,我们分析移位前后的M的特点,可以得到如下的结论:
- B段字符串是M的一个前缀
- A段字符串是M的一个后缀
- A段字符串和B段字符串相等
所以右移x位之后,使M[k] 与 S[i] 对齐,继续从i位置进行比较的前提是:M的前缀B和后缀A相同。
2. KMP算法的核心思想:
所以KMP算法的核心即是计算字符串M每一个位置之前的字符串的前缀和后缀公共部分的最大长度。获得M每一个位置的最大公共长度之后,就可以利用该最大公共长度快速和字符串S比较。当每次比较到两个字符串的字符不同时,我们就可以根据最大公共长度将字符串M向右移动,接着继续比较下一个位置。
3. KMP算法关键难点就是弄清楚如何计算next数组 :为方便起见,next数组下标都从1开始,M数组下标依然从0开始
假设我们现在已经求得next[1]、next[2]、……next[i],分别表示不同长度子串的前缀和后缀最大公共长度,下面介绍如何用归纳法计算next[i+1] ?
假设k=next[i],它表示M位置i之前的字符串的前缀和后缀最大公共长度,即M[0...k-1] = M[i-k...i-1];
(1)若M[k] = M[i]相等, 则必定有next[i+1] = next[i] +1,即M位置i+1之前的字符串的前缀和后缀最大公共长度为k+1
为什么呢?也许你会半信半疑,下面用假设排除法来验证。
如上有两幅图,第一幅表示当M[k] = M[i]相等时,next[i+1] = next[i] +1;
第二幅假设next'[i+1] > next[i] +1成立,则两条虚线之间的阴影重叠长度肯定比第一幅图中大,从图中可以看出next'(i) = A + A‘ 的长度,而实际上next(i) = A的长度,且是最大长度,next'(i) = A + A‘ 是不可能的。因此假设不成立,则next[i+1] 的最大长度只能是 next[i] +1;
(2)若M[k] != M[i] 不相等,为求next[i+1] ,如下图我们需要将M继续往右移动(不能往左移,往左移上面已经用假设排除法验证不成立),获得其最大公共长度next[k], 即为第一条虚线与第二条虚线之间的阴影重叠长度。不难发现,next[k] 其实就是阴影B这段字符串的前缀和后缀最大公共长度,即 next[k] = next[ next[i] ] 。
令 next[k] = j, 继续判断M[i] 与 M[j] 是否相等,如果相等则 next[i+1] = next[k] + 1 = next[ next[i] ] + 1;如果不相等重复步骤(2),继续分割长度为next[ next[k] ]的字符串,直到字符串长度为0为止。
通过上述分析,我门可以总结出next[i]的递推公式:
若 M位置i之前的字符串的前缀和后缀最大公共长度为next[i] = k,即M[0...k-1] = M[i-k...i-1];则对于M位置i+1之前的字符串,则有如下可能
- M[k] == M[i] 此时 next(i+1)=k+1=next(i)+1
- M[k] ≠ M[i] 此时只能在M位置k之前的字符串中匹配,假设j=next(k),如果此时M[j]==M[i],则next(i+1)=j+1,否则重复此步骤,直到字符串长度为0为止
4. 下面给出一段计算next[]的代码:
#include<iostream.h>
#include<string.h>
void compute_next(const string& pattern)
{
const int pattern_length = pattern.size();
int *next_function = new int[pattern_length];
int index;
next_function[0] = -1;
for(int i=1;i<pattern_length;++i)
{
index = next_function[i-1];
//store previous fail position k to index;
while(index>=0 && pattern[i]!=pattern[index+1])
{
index = next_function[index];
}
if(pattern[i]==pattern[index+1])
{
next_function[i] = index + 1;
}
else
{
next_function[i] = -1;
}
}
for(i=0;i<pattern_length;++i)
{
cout<<next_function[i]<<endl;
}
delete[] next_function;
}
int main()
{
string pattern = "abaabcaba";
compute_next(pattern);
return 0;
}
这样求出来的next数组其实是从下标1开始的,因为下标0之前是个空串,下标1则对应着M串的第0个字符。我们设next[0]=-1,仅仅是个标志而已,没有什么特殊的含义。
运行结果为:
-1
-1
0
0
1
-1
0
1
2
Press any key to continue
有了next跳转表,那么实现kmp算法就是很简单的了,我们的原则还是从左向右匹配。当发生在i长度失配时,只要把pattern向右移动i-next(i)长度就可以了。如果失配时pattern_index==0,相当于pattern第一个字符就不匹配,这时就应该把target_index加1,向右移动1位就可以了。下图就是KMP算法的过程(红色即是采用KMP算法的执行过程):
5. 改进型KMP算法:
若引入f(j)作为媒介,对 f(j) 和 next(j) 重新定义如下:
- f(j)是满足pattern[1...k - 1] = pattern[(j - (k - 1))...j -1](k < j)的k中,k的最大值
- next[j]是所有满足pattern[1...k - 1] = pattern[(j - (k - 1))...j -1](k < j),且pattern[k] != pattern[j]的k中,k的最大值
根据定义,f(j)与next[j]的有如下递推公式:
- 如果 pattern[j] != pattern[f(j)],next[j] = f(j);
- 如果 pattern[j] = pattern[f(j)],next[j] = next[f(j)];
可以看出,本篇介绍的next(i) 其实就是f(j), 并不是最优跳转表。通过f(j)可以进一步计算最优的跳转表,最优跳转表对有多个重复字符的pattern[],会表现出非常高的性能!
有关最优跳转表的具体介绍,可以参见文章:blog.csdn.net/joylnwang/a…