数据结构与算法
1 前言
1.1 为什么学?
- 代码开发和优化过程会不断需要数据结构和算法思想的知识。数据结构与算法是入职大厂的必考内容。快手,今日头条,阿里等大厂面试。
- 互联网竞争激烈,“僧多粥少”的情况直接提高了面试门槛—体现在对数据结构、算法思维、代码效率优化(应届生还不要求,能用多种方法实现即可)等知识的储备上。
- 数据结构与算法的学习和落地训练。
- 计算机领域基本知识要基本功扎实。
1.2 预防针
- 从原理到应用,学的快,忘得也快
- 刷题是必须的,但是看着答案不理解,效率低下。
- 掌握理论知识,但一遇到实际问题仍然无从下手。
1.3 怎么学习?
从具体案列,数据结构基础知识,常用算法、高频真题演练和面试技巧五个方面;
- 数据结构:复杂度的降低,要求对数据有更好的组织方式,正是用数据结构来解决的问题。为了能合理选择数据结构,需要全面分析任务对数据处理和计算的操作,再根据不同数据结构在这些基本操作中的优劣特点去灵活选用合适的数据结构。
- 常用算法:递归,二分法,排序算法,动态规划。
- 高频真题详解和面试技巧
2 衡量代码的度量方法-复杂度
复杂度–衡量程序运行的效率的度量。
数据(组织)结构决定着算法和代码结构。对于常用的增删改查操作:Eg: 数组查改简便但增删却很麻烦,而链表相反。这就对应着时间复杂度不同。
2.1 复杂度和计算机实际任务处理效率关系
计算机通过一个个程序去执行计算任务,也就是对输入数据进行加工处理,并最终得到结果的过程。每个程序都是由代码构成的,编写代码的核心就是要完成计算。但对于同一个计算任务,不同计算方法得到结果的过程复杂程度不一样。
2.2 怎么衡量复杂度?
复杂度脑海里是一种意识,只需要知道是什么级别,而不需要具体。
- 执行代码消耗的是什么?计算时间和计算空间(计算过程中)。
Eg: 立交桥–当没立交桥存在时,所有车辆分批次通过,分别消耗大家的时间;立交桥后:所有车辆同时通过,等于是消耗空间资源来换取时间资源。 - 代码对于资源的消耗的量化衡量?时间复杂度和空间复杂度一定是与数据输入量相关的。
复杂度是一个关于数据量n的函数 O(f(n))。但是通常我们只关心时间复杂度就足够了,因为硬件足够多。
O(n)表示:复杂度与数据量个数 n线性相关。 O(log(n)): 复杂度与数据量个数 n 对数相关。
2.3 重要概念
- 复杂度与具体的常系数无关;O(n)和O(2n)表示的是同样的复杂度。
- 多项式级的复杂度相加的时候,选择最高者作为结果。
时间复杂度与代码的结构有关。
2.4 基本结论
- 一个顺序结构的代码,时间复杂度是O(1)
- 二分查找(分而治之的二分策略),时间复杂度是O(log n)----最短的时间复杂度了;
- 一个for循环是O(n);并列循环是O(2n)=O(n);嵌套循环是O(n^2)
使用这些基本的结论,再去分析代码的时间复杂度
#3 代码优化
数据结构对于降低复杂度的内容。代码效率优化就是要将可行解提高到更优解;
3.1 核心思路
3.1.1 牺牲空间换时间–数据结构连接时空
- 程序优化起点:暴力解法
这是一种不计较任何时间复杂度或空间复杂度的,但最直观的暴力解法。
优化思路一:梳理程序–利用程序框图等来达到可视化,看其流程中是否有无效的计算或者无效的存储。
优化思路二:常用降低时间复杂度方法:递归、二分法、排序算法和动态规划。
优化思路三:无价变有价–把时间复杂度转移到空间复杂度上。桥梁就是数据结构,对于一个开发任务,若能找到高效的数据组织方式,采用合理的数据结构的话,那就可以实现时间复杂度的再次降低,虽然这通常会增加空间复杂度。
核心思路:起点:暴力解法—>梳理程序,去除无效计算和操作—>常用降低时间算法应用----->时空转换,设计合理数据结构。
3.2 案列
假传万卷书,真传一案例。
Case1:假设有任意多张面额为2元,3元,7元的货币,现用他们凑出100元,总共有多少种可能性?
// 起点: 暴力法--穷举法
public void s2_1(){
int count =0;
for(int i=0;i<=100/7;i++){
for(int j = 0;j<=100/3;j++){
for(int k=0;l<=100/2);k++){
if(i*7+j*3+k*2=100){
count+=1;
}
}
}
}
system.out.println(count);
};
分析:时间复杂度O(n^3),代码中最内层的for循环是多余的,当确定用i张7元和j张3元的,只需判断能否使用2元来凑就可以了;
// 思路一:将代码中的无效计算去除
public void s2_1(){
int count =0;
for(int i=0;i<=100/7;i++){
for(int j = 0;j<=100/3;j++){
if((100-i*7-j*3>=0)&&((100-i*7-j*3)%2==0)){
count++;
}
}
}
system.out.println(count);
};
Case2: 查找一个数组中,出现次数最多那个元素的数值。例如数组a[7]=[1,2,3,4,5,5,6]中,5出现次数最多:2次;
// 采用两层循环:第一层循环:对数组每个元素遍历;第二层循环:对一层遍历的数字,去遍历计算其出现的次数
public void s2_1(){
int a[]=[1,2,3,4,5,5,6];
int val_max=-1;
int time_max=0;
int time_tmp=0;
for(int i=0;i<a.length;i++){
time_tmp+=0;
for(int j=0;j<a.length;j++){
if(a[i]==a[j]){
time_tmp+=1;
}
if(time_tmp>time_max){
time_max=time_tmp;
val_max=a[i];
}
}
}
system.out.println(val_max);
}
分析:采用两层for循环,时间复杂度O(n^2)。在代码中,没有冗余的无效计算。再去优化,考虑常用算法来替代;最后思路考虑:数据结构来时空转化;
// 通过一次循环确定:在一次遍历的过程中同步不记录下每个元素出现的次数,然后再通过查找次数最大的元素。
// 定义一个结构的K-V字典,用来存放 元素-出现次数的K-V关系字典。
public void s2_2(){
int a[]={1,2,3,4,5,5,6};
Map<Integer,Integer> d=new HashMap<>();
for(int i =0;i<a.length;I++){
if(d.containsKey(a[i])){
d.put(a[i],d.get(a[i])+1);
}
else{
d.put(a[i],1);
}
int val_max=-1;
int time_,ax=0;
for(Integer Key : d.keySet()){
if(d.get(Key)>time_max){
time_max=d.get(key);
val_max=key;
}
}
}
system.out.println(val_max);
}
分析:并列for循环,O(n);通过采用高效但一般复杂的数据结构,完成时空转移,降低复杂度。
3.3 小结
降低复杂度,代码优化的步骤;
- 起点:只需围绕问题本身出发能够实现就行;
- 无效操作(计算)处理:取出冗余的for循环和其他不必要的计算。
- 算法思维替换: 使用常用的 递归法、二分法、排序算法和动态规划来替换掉原来的遍历解法。
- 时空转换:数据结构+算法思维,构造高效的数据结构。
学习来源: 文字教程–拉钩教育