目录
-----------------------------------
-----------------------------------
-----------------------------------
-----------------------------------
-----------------------------------
-----------------------------------
-----------------------------------
2. 给一个等概率返回1-5的函数实现等概率返回1-7的函数
4. 随机盒子等概率返回from~to范围上任何一个数且要求要求from<=to
-----------------------------------
讲解一:绪论
一、变量
计算机变量(computer variables)是计算机语言中能储存计算结果或能表示值抽象概念,变量可以通过变量
名访问。
二、数据类型
1. 系统定义的数据类型(基本数据类型)
Java 语言而言,基本类型共有八种,可以分为三类:
- 字符类型:char
- 布尔类型:boolean
- 数值类型:byte、short、int、long、float、double。
2. 用户定义的数据类型(引用类型)
如果系统自定义的数据类型不够,可以自定义数据类型。
例如:Person、Animal、. . .
三、数据结构
一旦变量中有数据,就需要一种操纵这些数据的机制来求解问题。
数据结构(data structure)就是计算机中存储和组织数据的一种特定方式,它将使得数据处理更加有效。
根据元素的组织方式,数据结构可以分为两种类型:
- 线性结构:可以按线性次序访问元素,但它并不强制将所有元素连续地存储在一起,例如,链表、栈、队列
- 非线性结构:这种数据结构的元素是以非线性次序来存储和访问的。例如:树和图。
四、抽象数据类型
抽象数据类型( ADT,Abstract Data Type)是指一个数学模型以及定义在此数学模型上的一组操作。
它通常是对数据的某种抽象,定义了数据的取值范围及其结构形式,以及对数据操作的集合。
抽象数据类型的特征是将使用与实现分离,从而实行封装和隐藏信息。
抽象数据类型通过一种特定的数据结构在程序的某个部分得以实现,只关心在这个数据类型上的操作,而
不关心数据结构具体实现。
五、什么是算法
算法(Algorithm)是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令。
算法代表着用系统的方法描述解决问题的策略机制。
六、算法五特征
一个算法应该具有以下五个重要的特征:
(1)有穷性(Finiteness)
算法的有穷性是指算法必须能在执行有限个步骤之后终止;
(2)确切性(Definiteness)
算法的每一步骤必须有确切的定义;
(3)输入项(Input)
一个算法有0个或多个输入,以刻画运算对象的初始情况,所谓0个输入是指算法本身定出了初始条件;
(4)输出项(Output)
一个算法有一个或多个输出,以反映对输入数据加工后的结果。没有输出的算法是毫无意义的;
(5)可行性(Effectiveness)
算法中执行的任何计算步骤都是可以被分解为基本的可执行的操作步骤,即每个计算步骤都可以在有限时
间内完成(也称之为有效性)。
七、为什么需要算法分析
首先,算法分析与设计直接影响计算机软件的效率和性能,同时也影响到计算机软件的成功与失败。在
计算机软件中,如果使用的算法更加先进和高效,那么它的运行就更加可靠,更加高效。因此,对于计算
机软件的开发者来说,在进行算法分析与设计上花费一定的精力是非常有必要的,这样可以更好地保证计算机软件的可行性和可靠性。
此外,算法分析与设计还影响着计算机软件的灵活性和安全性。在算法分析与设计中,需要考虑计算机
软件本身的能力,并利用计算机硬件的特性来构建有效的计算机算法,以保证计算机软件的灵活性和安全
性。比如,当计算机软件遇到复杂的数据输入、硬件环境变化或非常庞大的数据处理量时,可以采用特定
的算法解决方案来优化计算机系统的性能,保证计算机系统的可靠性与安全性。
最后,算法分析与设计还有助于提高计算机软件的可维护性和可重复使用性。现代软件设计师在设计算法
时,注重算法的可重复性,即将算法分解为可重复使用的基础模块,这样无论是开发者还是用户都可以以
较低的成本进行软件的维护。算法可重复使用,一个算法模块可以在多个软件中被复用,而不需要进行改
变,从而节省了开发者的创新精力,同时也提高了软件的可维护性和可重复使用性。
综上所述,算法分析与设计有其极其重要的作用,对于计算机软件的可靠性、安全性、灵活性以及可维
护性和可重复使用性都有着不可或缺的重要意义。因此,在开发计算机软件的过程中,开发者应该充分重
视算法分析与设计的重要性,确保计算机软件的可行性和可靠性。
七、算法分析目的
算法分析的目标是根据运行时间及其他的一些因素(如内存、开发者的工作流等)来比较算法(或解决方
案)的优劣。
八、如何评判一个算法的好坏
1. 复杂度
(1)时间复杂度
算法的时间复杂度是指执行算法所需要的计算工作量。一般来说,计算机算法是问题规模 n 的函数f(n),算
法的时间复杂度也因此记做。
(2)空间复杂度
算法的空间复杂度是指算法需要消耗的内存空间。其计算和表示方法与时间复杂度类似,一般都用复杂度
的渐近性来表示。同时间复杂度相比,空间复杂度的分析要简单得多。
2. 正确性
算法的正确性是评价一个算法优劣的最重要的标准.
3. 可读性
算法的可读性是指一个算法可供人们阅读的容易程度
4. 健壮性
健壮性是指一个算法对不合理数据输入的反应能力和处理能力,也称为容错性。
九、复杂度分析
十、时间工具类
TimesUtil
public class TimesUtil {
private static final SimpleDateFormat fmt = new SimpleDateFormat("HH:mm:ss.SSS");
public interface Task {
void execute();
}
public static void test(String title, Task task) {
if (task == null) return;
title = (title == null) ? "" : ("【" + title + "】");
System.out.println(title);
System.out.println("开始:" + fmt.format(new Date()));
long begin = System.currentTimeMillis();
task.execute();
long end = System.currentTimeMillis();
System.out.println("结束:" + fmt.format(new Date()));
double delta = (end - begin) / 1000.0;
System.out.println("耗时:" + delta + "秒");
System.out.println("-------------------------------------");
}
}
-----------------------------------
讲解二:递归
一、什么是递归
任何调用自身的函数称为递归。每次函数都会用比原问题规模更小的问题来调用自身。
问题随着规模不断变小必须能最终收敛到基本情形。
二、为什么要用递归
递归代码通常比迭代代码更加简洁易懂,利用递归有时能够更好的解决实际问题。
一般来说,在编译或解释时,循环会转化为递归函数。
三、递归条件
- 在每一次调用自己时,必须是(在某种意义上)更接近于解
- 必须有一个终止处理或计算的准则
四、递归函数的格式
五、递归和内存
每次递归调用都会在内存中生成一个新的函数副本
例如:
调用过程如下:
六、递归与迭代对比
1. 递归
- 当达到基本情形的时候,递归终止
- 每次递归调用都需要额外的空间用于栈帧(内存)开销
- 如果出现无穷递归,程序可能会耗尽内存,并出现栈溢出
- 某些问题采用递归方法更容易解决
2. 迭代
- 当循环条件为假时,迭代终止
- 每次迭代不需要任何额外的空间开销
- 由于没有额外的空间开销,所以若出现死循环,则程序会一直循环执行
- 采用迭代求解问题可能没有递归解决方案那样显而易见
3. 两者区别
迭代和递归的区别有:
1、含义;2、结构不同;3、时间复杂度不同;4、用法不同;5、时间开销不同;6、无限重复后果不同。
迭代是利用已知的变量,不断用变量旧值递推新值直到结束;
递归是函数直接或间接调用函数自身,直到满足终止条件再逐层回归。
3.1. 含义
迭代:利用已知的变量值,不断用变量的旧值递推新值,直到到达结束状态。
递归:函数直接或间接调用函数自身,直到满足终止条件,再逐层回归。
3.2. 结构不同
迭代:迭代是环结构,从初始状态开始,每次迭代都遍历这个环,并更新状态,多次迭代直到到达结束状
态。
递归:递归是树结构,从字面可以理解为重复“递推”和“回归”的过程,当“递推”到达底部时就会开
始“回归”,其过程相当于树的深度优先遍历。
3.3. 时间复杂度不同
迭代:迭代的时间复杂度可以通过查找循环内重复的周期数来发现。
递归:递归的时间复杂度可以通过根据前面的调用查找第 n 个递归调用的值来查找。
因此,根据基情况找到目标情况,并根据基本情况求解,可以让我们了解递归方程的时间复杂度。
3.4. 用法不同
迭代:迭代是代码块的重复。这涉及更大的代码大小,但时间复杂度通常小于递归的时间复杂度。
递归:递归涉及再次调用相同的函数,因此代码长度非常小。
但是,正如我们在分析中看到的那样,当有相当数量的递归调用时,递归的时间复杂度可能会呈指数级增
长。因此,在较短的代码中使用递归是有利的,但时间复杂度较高。
3.5. 时间开销不同
迭代:迭代不涉及任何此类开销。
递归:与迭代相比,递归具有大量的开销。递归具有重复函数调用的开销,即由于重复调用同一函数,代
码的时间复杂度增加了许多倍。
3.6. 无限重复后果不同
迭代:由于迭代器赋值或增量错误,或在终止条件中,无限迭代将导致无限循环,这可能会导致也可能不
会导致系统错误,但肯定会进一步停止程序执行。
递归:在递归中,由于指定基本条件时出现一些错误,可能会发生无限递归调用,该基本条件永远不会变
为false,不断调用函数,这可能导致系统CPU崩溃。
七、(递归/迭代)与普通循环区别差异
- 递归与普通循环的区别:
循环是有去无回,而递归则是有去有回(因为存在终止条件)。
- 迭代与普通循环的区别:
迭代时,循环代码中参与运算的变量同时是保存结果的变量,当前保存的结果作为下一次循环计算的
初始值;
-----------------------------------
讲解三:分治
一、基本介绍
分治即“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把
子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合
并。这个技巧是很多高效算法的基础,如排序算法(快速排序,归并排序),傅立叶变换(快速傅立叶
变换)……
任何一个可以用计算机求解的问题所需的计算时间都与其规模有关。问题的规模越小,越容
易直接求解,解题所需的计算时间也越少。
例如,对于n个元素的排序问题,当n=1时,不需任何计算。n=2时,只要作一次比较即可排
好序。n=3时只要作3次比较即可,…。
而当n较大时,问题就不那么容易处理了。要想直接解决一个规模较大的问题,有时是相当困
难的。
二、算法思想
分治法的设计思想是:将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以
便各个击破,分而治之。
分治策略是:对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直
接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归
地解这些子问题,然后将各子问题的解合并得到原问题的解。这种算法设计策略叫
做分治法。
如果原问题可分割成k个子问题,1<k≤n,且这些子问题都可解并可利用这些子问题的解求出
原问题的解,那么这种分治法就是可行的。
由分治法产生的子问题往往是原问题的较小模式,这就为使用递归技术提供了方便。在这种情
况下,反复应用分治手段,可以使子问题与原问题类型一致而其规模却不断缩小,最终使子问题缩
小到很容易直接求出其解。这自然导致递归过程的产生。分治与递归像一对孪生兄弟,经常同时应
用在算法设计之中,并由此产生许多高效算法。
三、适用场景
分治法所能解决的问题一般具有以下几个特征:
- 该问题的规模缩小到一定的程度就可以容易地解决
- 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质。
- 利用该问题分解出的子问题的解可以合并为该问题的解;
- 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题。
第一条特征是绝大多数问题都可以满足的,因为问题的计算复杂性一般是随着问题规模的增加而增加;
第二条特征是应用分治法的前提它也是大多数问题可以满足的,此特征反映了递归思想的应用;
第三条特征是关键,能否利用分治法完全取决于问题是否具有第三条特征,如果具备了第一条和第二条特
征,而不具备第三条特征,则可以考虑用贪心法或动态规划法。
第四条特征涉及到分治法的效率,如果各子问题是不独立的则分治法要做许多不必要的工作,重复地解公
共的子问题,此时虽然可用分治法,但一般用动态规划法较好。
四、经典问题(分治求解)
(1)二分搜索
(2)大整数乘法
(3)Strassen矩阵乘法
(4)棋盘覆盖
(5)合并排序
(6)快速排序
(7)线性时间选择
(8)最接近点对问题
(9)循环赛日程表
(10)汉诺塔
五、分治步骤
step1 分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;
step2 解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题
step3 合并:将各个子问题的解合并为原问题的解。
六、分治法的复杂性分析
一个分治法将规模为n的问题分成k个规模为n/m的子问题去解。设分解阀值n0=1,且adhoc解规模
为1的问题耗费1个单位时间。
再设将原问题分解为k个子问题以及用merge将k个子问题的解合并为原问题的解需用f(n)个单位时间。
用T(n)表示该分治法解规模为|P|=n的问题所需的计算时间,则有:T(n)= k T(n/m)+f(n)
通过迭代法求得方程的解
七、思维过程(分治设计程序)
实际上就是类似于数学归纳法,找到解决本问题的求解方程公式,然后根据方程公式设计递归程序。
1、一定是先找到最小问题规模时的求解方法
2、然后考虑随着问题规模增大时的求解方法
3、找到求解的递归函数式后(各种规模或因子),设计递归程序即可。
-----------------------------------
讲解四:搜索
学习前言
本篇章所讲的搜索等同于查找算法,不过按照一定范围进行了一个整体梳理,进行了一个二次讲解。
一、基本介绍
搜索算法是一种根据初始条件和扩展规则构造一棵“解答树”并寻找符合目标状态的节点的过程1。
搜索算法的主要分类包括:
- 深度优先搜索(DFS):深度优先搜索遵循的搜索策略是尽可能“深”地搜索树。它的基本思想是:为了求得问题的解,先选择某一种可能情况向前(子结点)探索,在探索过程中,一旦发现原来的选择不符合要求,就回溯至父亲结点重新选择另一结点,继续向前探索,如此反复进行,直至求得最优解。
- 广度优先搜索*(BFS)。广度优先搜索算法是一种盲目搜寻法,目的是系统地展开并检查图中的所有节点,以找寻结果。换句话说,它并不考虑结果的可能位置,彻底地搜索整张图,直到找到结果为止2。
- 回溯法。回溯法是一种搜索算法,其采用了一种“走不通就掉头”思想作为其控制结构3。
以上就是搜索算法的主要分类及其基本思想,希望对你有所帮助。
二、定义
定义:需要在“树”中或者“图”中搜索到我们需要的序列或者位置。
三、特点
特点:通常是给定了一个“树”或“图”,然后要求里面满足要求的部分
四、常用搜索算法
常用的有三种搜索算法:深度优先搜索、广度优先搜索、回溯算法。
一般深度优先搜索能做的,广度优先搜索也能做。回溯算法是用来处理需要穷举出所有情况的问题,典型
的为排列组合问题,通常这也称为“暴力搜索”,且回溯算法相比于深度优先搜索和广度优先搜索有一个
显著的特点——“恢复现场”。
五、搜索算法的解题思路
1. 广度优先搜索
广度优先搜索的核心在于队列。
在广度优先遍历中,需要首先给队列填充第一个节点,然后循环进行如下队列操作的三步,直到队列中不
再有节点:
- 队列首部弹出一个节点,此节点是否访问过,如果访问过,跳过下面两步,重新弹出新的节点;否则访问此节点(访问这一步可以做很多操作)
- 查找此节点的所有可行节点(大多数情况下为不越界的邻接节点,但是有时候不仅要求邻接,对邻接节点的值的大小还有要求)
- 将第2步的可行节点们插入到队列尾部
队列中不再有节点时,就完成了一次广度优先搜索,
因此需要在广度优先搜索外再套一层for循环,让每个节点都有机会被广度优先搜索遍历。
注意上面的模板并不是唯一的,我们可以在查找可行节点的时候,就要求节点是未访问的,这样我们就无
需再在第一步判断。这种做法只是少了有限几步,两种模板在时间复杂度上并不会有差异。
695. 岛屿的最大面积 的广度优先搜索参考代码:
2. 深度优先搜索
深度优先搜索的核心在于递归。
写递归的时候一定要想明白:递归终止条件是什么?递归函数的返回值是什么?
深度优先搜索子函数三步:
- 首先判断此节点是否访问过,如果访问过,直接退出子函数;否则访问此节点(访问这一步可以做很多操作)
- 查找此节点的所有可行节点(大多数情况下为不越界的邻接节点,但是有时候不仅要求邻接,对邻接节点的值的大小还有要求)
- 递归调用此子函数去搜索第2步的可行节点们
在深度优先搜索中,主函数就是遍历所有的节点,让每个节点都有机会被深度搜索。
注意上面的模板并不是唯一的,我们可以在查找可行节点的时候,就要求节点是未访问的,这样我们就无
需再在第一步判断。这种做法只是少了有限几步,两种模板在时间复杂度上并不会有差异。
695. 岛屿的最大面积的深度优先搜索参考代码:
3. 回溯法
回溯法有点像深度优先遍历,不过回溯法每次往回走的时候都需要恢复现场,而深度优先遍历不需要。
即深度优先遍历的过程为:修改当前节点状态(如:标记此节点已访问)-> 递归遍历节点的邻接节点
回溯法的过程为:修改当前节点状态(如:标记此节点已访问)-> 递归遍历节点的邻接节点 -> 恢复当前
节点状态(如:标记此节点未访问)
回溯法需要“恢复现场”是因为在“修改当前节点状态”时,有很多“当前节点”需要修改,且它们都需
要被当做第一次修改来看待。
因为回溯法本质也是递归,所以需要特别地注意递归终止条件。
使用回溯法的步骤:
- 首先判断回溯是否深入到底了,如果是,则进行相关操作后(如复制某一数组),直接退出子函数(注意返回值);
- 判断此节点是否访问过,如果访问过,直接退出子函数(注意返回值);否则开始访问此节点(访问这一步可以做很多操作)
- 查找此节点的所有可行节点(大多数情况下为不越界的邻接节点,但是有时候不仅要求邻接,对邻接节点的值的大小还有要求)
- 递归调用此子函数去搜索第2步的可行节点们
- 恢复现场,也就是对第2步的访问操作进行逆操作。
还有另一种步骤如下所示,和上面的步骤相比就是在查找可行节点的时候,还要求可行节点是未访问的,
这样在回溯法中就可以及时剪枝,减少时间复杂度,所以更加推荐使用这种方法:
- 首先判断回溯是否深入到底了,如果是,则进行相关操作后(如复制某一数组),直接退出子函数(注意返回值);否则开始访问此节点(访问这一步可以做很多操作,访问这一步有时候还会移到第二步:“查找所有未访问的可行节点”之后,具体情况具体分析)
- 查找此节点的所有 未访问的 可行节点
(大多数情况下为不越界的邻接节点,但是有时候不仅要求邻接,对邻接节点的值的大小还有要求) - 递归调用此子函数去搜索第2步的 未访问的 可行节点们
- 恢复现场,也就是对第1步的访问操作进行逆操作。
回溯法适用的题型有:
17. 电话号码的字母组合 的回溯法参考代码:
六、搜索算法题(力扣)
简评:使用深度优先遍历或广度优先遍历。此题属于经典题,对于使用深度优先遍历而言,首先肯定需要
给矩阵中每个元素进行初始的深度遍历机会,因此有两个for循环;其次在写深度优先遍历的时候,需要确
定递归终止条件为元素访问过(此题的特殊性,可以直接用grid直接设置是否访问),因为要求面积,所
以