词条信息

admin
admin
超级管理员
词条创建者 发短消息   

相关词条

热门词条

更多>>
什么是端口?到底是做什么的呢?
端口一般指两种,一种是硬件比如路由器或者交换机的插网线的端口,一种是软件的逻辑的概念,比如http的80端口!...
7种进阶方法让你快速测试端口连通性
Ping是Windows、Linux和Unix系统下的一个检查网络连通性的命令工具,对于大部分互联网用户来说很...
电脑开机,总需要按F1,是什么原因造成的?
一.主板掉电这个说法是行业内的叫法了,一般是主板的CMOS电池没电了导致的。也是最常见的一种提示你按F1的提示...
社保降费对个人有什么影响?
下调城镇职工基本养老保险单位缴费比例是政府给企业发的一个大红包,特别是对于企业来说是一个利好,但是对个人来说有...
车辆“出险”对下年保费的影响,到底有多大?
【出险对交强险的影响】【出险对商业险的影响】车辆“出险”对下年保费的影响,到底有多大?这里有必要先提下车险第三...

精选图集

更多>>
简易百科旧版 >>所属分类 >> 算法   

一些常用的算法技巧总结

标签: 算法 技巧 总结

顶[0] 发表评论(0) 编辑词条

今天和大家讲讲,在做算法题时常用的一些技巧。对于平时没用过这些技巧的人,或许你可以考虑试着去看看在实践中能否用的上这些技巧来优化问题的解。


目录

1. 巧用数组下标编辑本段回目录


数组的下标是一个隐含的很有用的数组,特别是在统计一些数字,或者判断一些整型数是否出现过的时候。例如,给你一串字母,让你判断这些字母出现的次数时,我们就可以把这些字母作为下标,在遍历的时候,如果字母a遍历到,则arr[a]就可以加1了,即 arr[a]++;


通过这种巧用下标的方法,我们不需要逐个字母去判断。


我再举个例子:


问题:给你n个无序的int整型数组arr,并且这些整数的取值范围都在0-20之间,要你在 O(n) 的时间复杂度中把这 n 个数按照从小到大的顺序打印出来。


对于这道题,如果你是先把这 n 个数先排序,再打印,是不可能O(n)的时间打印出来的。但是数值范围在 0-20。我们就可以巧用数组下标了。把对应的数值作为数组下标,如果这个数出现过,则对应的数组加1。


代码如下:


publicvoidf(intarr[]) {

int[] temp = newint[ 21];

for( inti = 0; i < arr.length; i++) {

temp[arr[i]]++;

}

//顺序打印

for( inti = 0; i < 21; i++) {

for( intj = 0; j < temp[i]; j++) {

System. out.println(i);

}

}

}


提醒:可以左右滑动


利用数组下标的应用还有很多,大家以后在遇到某些题的时候可以考虑是否可以巧用数组下标来优化。


2. 巧用取余编辑本段回目录


有时候我们在遍历数组的时候,会进行越界判断,如果下标差不多要越界了,我们就把它置为0重新遍历。特别是在一些环形的数组中,例如用数组实现的队列。往往会写出这样的代码:


for(inti = 0; i < N; i++) {

if(pos < N) {

//没有越界

// 使用数组arr[pos]

else{

pos = 0;//置为0再使用数组

//使用arr[pos]

}

pos++;

}


实际上我们可以通过取余的方法来简化代码


for(inti = 0; i < N; i++) {

//使用数组arr[pos] (我们假设刚开始的时候pos < N)

pos = (pos + 1) % N;

}


3. 巧用双指针编辑本段回目录


对于双指针,在做关于单链表的题是特别有用,比如“判断单链表是否有环”、“如何一次遍历就找到链表中间位置节点”、“单链表中倒数第 k 个节点”等问题。对于这种问题,我们就可以使用双指针了,会方便很多。我顺便说下这三个问题怎么用双指针解决吧。


例如对于第一个问题

我们就可以设置一个慢指针和一个快指针来遍历这个链表。慢指针一次移动一个节点,而快指针一次移动两个节点,如果该链表没有环,则快指针会先遍历完这个表,如果有环,则快指针会在第二次遍历时和慢指针相遇。


对于第二个问题

一样是设置一个快指针和慢指针。慢的一次移动一个节点,而快的两个。在遍历链表的时候,当快指针遍历完成时,慢指针刚好达到中点。


对于第三个问题

设置两个指针,其中一个指针先移动k个节点。之后两个指针以相同速度移动。当那个先移动的指针遍历完成的时候,第二个指针正好处于倒数第k个节点。

你看,采用双指针方便多了吧。所以以后在处理与链表相关的一些问题的时候,可以考虑双指针哦。


4. 巧用移位运算。编辑本段回目录


有时候我们在进行除数或乘数运算的时候,例如n / 2,n / 4, n / 8这些运算的时候,我们就可以用移位的方法来运算了,这样会快很多。


例如:

n / 2 等价于 n >> 1

n / 4 等价于 n >> 2

n / 8 等价于 n >> 3。

这样通过移位的运算在执行速度上是会比较快的,也可以显的你很厉害的样子,哈哈。

还有一些 &(与)、|(或)的运算,也可以加快运算的速度。例如判断一个数是否是奇数,你可能会这样做

if(n % 2== 1){

dosomething();

}

不过我们用与或运算的话会快很多。例如判断是否是奇数,我们就可以把n和1相与了,如果结果为1,则是奇数,否则就不会。即

if(n & 1== 1){

dosomething();

)

具体的一些运算技巧,还得需要你们多在实践中尝试着去使用,这样用久后就会比较熟练了。


5. 设置哨兵位编辑本段回目录


在链表的相关问题中,我们经常会设置一个头指针,而且这个头指针是不存任何有效数据的,只是为了操作方便,这个头指针我们就可以称之为哨兵位了。


例如我们要删除头第一个节点是时候,如果没有设置一个哨兵位,那么在操作上,它会与删除第二个节点的操作有所不同。但是我们设置了哨兵,那么删除第一个节点和删除第二个节点那么在操作上就一样了,不用做额外的判断。当然,插入节点的时候也一样。


有时候我们在操作数组的时候,也是可以设置一个哨兵的,把arr[0]作为哨兵。例如,要判断两个相邻的元素是否相等时,设置了哨兵就不怕越界等问题了,可以直接arr[i] == arr[i-1]?了。不用怕i = 0时出现越界。


当然我这只是举一个例子,具体的应用还有很多,例如插入排序,环形链表等。


6. 与递归有关的一些优化编辑本段回目录


(1).对于可以递归的问题考虑状态保存


当我们使用递归来解决一个问题的时候,容易产生重复去算同一个子问题,这个时候我们要考虑状态保存以防止重复计算。例如我随便举一个之前举过的问题

问题:一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法?

这个问题用递归很好解决。假设 f(n) 表示n级台阶的总跳数法,则有


f(n) = f(n-1) + f(n - 2)。


递归的结束条件是当0 <= n <= 2时, f(n) = n。因此我们可以很容易写出递归的代码


publicintf(intn){

if(n <= 2) {

returnn;

} else{

returnf(n - 1) + f(n - 2);

}

}


不过对于可以使用递归解决的问题,我们一定要考虑是否有很多重复计算。显然对于 f(n) = f(n-1) + f(n-2) 的递归,是有很多重复计算的。如


就有很多重复计算了。这个时候我们要考虑状态保存。例如用hashMap来进行保存,当然用一个数组也是可以的,这个时候就像我们上面说的巧用数组下标了。可以当arr[n] = 0时,表示n还没计算过,当arr[n] != 0时,表示f(n)已经计算过,这时就可以把计算过的值直接返回回去了。因此我们考虑用状态保存的做法代码如下:


//数组的大小根据具体情况来,由于int数组元素的的默认值是0

//因此我们不用初始化

int[] arr = newint[1000];

publicintf(intn){

if(n <= 2) {

returnn;

} else{

if(arr[n] != 0) {

returnarr[n];//已经计算过,直接返回

} else{

arr[n] = f(n-1) + f(n-2);

returnarr[n];

}

}

}


这样,可以极大着提高算法的效率。也有人把这种状态保存称之为备忘录法。


(2).考虑自底向上


对于递归的问题,我们一般都是从上往下递归的,直到递归到最底,再一层一层着把值返回。


不过,有时候当n比较大的时候,例如当 n = 10000时,那么必须要往下递归10000层直到 n <=2 才将结果慢慢返回,如果n太大的话,可能栈空间会不够用。


对于这种情况,其实我们是可以考虑自底向上的做法的。例如我知道


f(1) = 1;

f(2) = 2;


那么我们就可以推出 f(3) = f(2) + f(1) = 3。从而可以推出f(4),f(5)等直到f(n)。因此,我们可以考虑使用自底向上的方法来做。

代码如下:

publicintf(intn){

if(n <= 2)

returnn;

intf1 = 1;

intf2 = 2;

intsum = 0;

for(inti = 3; i <= n; i++) {

sum = f1 + f2;

f1 = f2;

f2 = sum;

}

returnsum;

}


我们也把这种自底向上的做法称之为递推。


总结一下


当你在使用递归解决问题的时候,要考虑以下两个问题

(1). 是否有状态重复计算的,可不可以使用备忘录法来优化。

(2). 是否可以采取递推的方法来自底向上做,减少一味递归的开销。

今天就先讲到这里,之后有时间再来多谢一些其他的。如果觉得不错,不妨点个赞。


【本文作者】

帅地:一个热爱编程的在校生,我的世界不只有coding,还有writing。目前维护订阅号「苦逼的码农」,专注于写计算机网络,数据结构等相关文章。

 

 

附件列表


按字母顺序浏览:A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

→我们致力于为广大网民解决所遇到的各种电脑技术问题
 如果您认为本词条还有待完善,请 编辑词条

上一篇数据结构
下一篇浅谈BHD的挖矿算法

0
1. 本站部分内容来自互联网,如有任何版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
2. 本站内容仅供参考,如果您需要解决具体问题,建议您咨询相关领域专业人士。
3. 如果您没有找到需要的百科词条,您可以到百科问答提问或创建词条,等待高手解答。

关于本词条的提问

查看全部/我要提问>>