程序员如何巧妙学习算法技巧?
作者 | 帅地
责编 | 胡巍巍
今天和大家讲讲,在做算法题时常用的一些技巧。对于平时没用过这些技巧的人,或许你可以考虑试着去看看在实践中能否用的上这些技巧来优化问题的解。
巧用数组下标
数组的下标是一个隐含的很有用的数组,特别是在统计一些数字,或者判断一些整型数是否出现过的时候。
例如,给你一串字母,让你判断这些字母出现的次数时,我们就可以把这些字母作为下标,在遍历的时候,如果字母a遍历到,则arr[a]就可以加1了,即arr[a]++。
通过这种巧用下标的方法,我们不需要逐个字母去判断。
我再举个例子:
问题:给你n个无序的int整型数组arr,并且这些整数的取值范围都在0-20之间,要你在 O(n) 的时间复杂度中把这n个数按照从小到大的顺序打印出来。
对于这道题,如果你是先把这n个数先排序,再打印,是不可能O(n)的时间打印出来的。
但是数值范围在0-20。我们就可以巧用数组下标了。把对应的数值作为数组下标,如果这个数出现过,则对应的数组加1。
代码如下:
public void f(int arr[]) {
int[] temp = new int[21];
for (int i = 0; i < arr.length; i++) {
temp[arr[i]]++;
}
//顺序打印
for (int i = 0; i < 21; i++) {
for (int j = 0; j < temp[i]; j++) {
System.out.println(i);
}
}
}
利用数组下标的应用还有很多,大家以后在遇到某些题的时候可以考虑是否可以巧用数组下标来优化。
巧用取余
有时候我们在遍历数组的时候,会进行越界判断,如果下标差不多要越界了,我们就把它置为0重新遍历。特别是在一些环形的数组中,例如用数组实现的队列。往往会写出这样的代码:
for (int i = 0; i < N; i++) {
if (pos < N) {
//没有越界
// 使用数组arr[pos]
else {
pos = 0;//置为0再使用数组
//使用arr[pos]
}
pos++;
}
实际上我们可以通过取余的方法来简化代码。
for (int i = 0; i < N; i++) {
//使用数组arr[pos] (我们假设刚开始的时候pos < N)
pos = (pos + 1) % N;
}
巧用双指针
对于双指针,在做关于单链表的题是特别有用,比如“判断单链表是否有环”、“如何一次遍历就找到链表中间位置节点”、“单链表中倒数第k个节点”等问题。
对于这种问题,我们就可以使用双指针了,会方便很多。我顺便说下这三个问题怎么用双指针解决吧。
例如对于第一个问题,我们就可以设置一个慢指针和一个快指针来遍历这个链表。
慢指针一次移动一个节点,而快指针一次移动两个节点,如果该链表没有环,则快指针会先遍历完这个表,如果有环,则快指针会在第二次遍历时和慢指针相遇。
对于第二个问题,一样是设置一个快指针和慢指针。慢的一次移动一个节点,而快的两个。
在遍历链表的时候,当快指针遍历完成时,慢指针刚好达到中点。
对于第三个问题,设置两个指针,其中一个指针先移动k个节点。之后两个指针以相同速度移动。
当那个先移动的指针遍历完成的时候,第二个指针正好处于倒数第k个节点。
你看,采用双指针方便多了吧。所以以后在处理与链表相关的一些问题的时候,可以考虑双指针哦。
巧用移位运算
有时候我们在进行除数或乘数运算的时候,例如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();
)
具体的一些运算技巧,还得需要你们多在实践中尝试着去使用,这样用久后就会比较熟练了。
设置哨兵位
在链表的相关问题中,我们经常会设置一个头指针,而且这个头指针是不存任何有效数据的,只是为了操作方便,这个头指针我们就可以称之为哨兵位了。
例如我们要删除头第一个节点是时候,如果没有设置一个哨兵位,那么在操作上,它会与删除第二个节点的操作有所不同。
但是我们设置了哨兵,那么删除第一个节点和删除第二个节点那么在操作上就一样了,不用做额外的判断。当然,插入节点的时候也一样。
有时候我们在操作数组的时候,也是可以设置一个哨兵的,把arr[0]作为哨兵。
例如,要判断两个相邻的元素是否相等时,设置了哨兵就不怕越界等问题了,可以直接arr[i] == arr[i-1]?了。不用怕i = 0时出现越界。
当然我这只是举一个例子,具体的应用还有很多,例如插入排序,环形链表等。
与递归有关的一些优化
对于可以递归的问题考虑状态保存
当我们使用递归来解决一个问题的时候,容易产生重复去算同一个子问题,这个时候我们要考虑状态保存以防止重复计算。例如我随便举一个之前举过的问题
问题:一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法?
这个问题用递归很好解决。假设f(n)表示n级台阶的总跳数法,则有f(n) = f(n-1)+f(n - 2)。
递归的结束条件是当0 <= n <= 2时, f(n) = n。因此我们可以很容易写出递归的代码:
public int f(int n) {
if (n <= 2) {
return n;
} else {
return f(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 = new int[1000];
public int f(int n) {
if (n <= 2) {
return n;
} else {
if (arr[n] != 0) {
return arr[n];//已经计算过,直接返回
} else {
arr[n] = f(n-1) + f(n-2);
return arr[n];
}
}
}
这样,可以极大提高算法的效率。也有人把这种状态保存称之为备忘录法。
考虑自底向上
对于递归的问题,我们一般都是从上往下递归的,直到递归到最底,再一层一层着把值返回。
不过,有时候当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)。
因此,我们可以考虑使用自底向上的方法来做。
代码如下:
public int f(int n) {
if(n <= 2)
return n;
int f1 = 1;
int f2 = 2;
int sum = 0;
for (int i = 3; i <= n; i++) {
sum = f1 + f2;
f1 = f2;
f2 = sum;
}
return sum;
}
我们也把这种自底向上的做法称之为递推。
总结一下
当你在使用递归解决问题的时候,要考虑以下两个问题。
(1)是否有状态重复计算的,可不可以使用备忘录法来优化。
(2)是否可以采取递推的方法来自底向上做,减少一味递归的开销。
今天就先讲到这里,之后有时间再来多谢一些其他的。如果觉得不错,不妨点个赞。
作者:帅地,一个热爱编程的在校生,我的世界不只有coding,还有writing。目前维护订阅号「苦逼的码农」,专注于写算法与数据结构、Java、计算机网络。
声明:本文为作者个人投稿,版权归其所有。
推荐阅读:
谷歌基情录:TensorFlow、Hadoop、MapReduce 都靠他们诞生!
金立卒于 16 岁
向 Android 4.0 彻底说再见!
数读|DApp现状揭底: 80%活不过一周; 大量游戏营收不到0.5 ETH; EOS已成"博彩链"
我地铁都在努力改 Bug,为什么还要裁掉我?
程序员加班很严重吗?看看国外程序员怎么怼老板!
程序员为啥365天都背电脑包?这答案我服!
“男医生,女护士?”消除偏见,Google有大招
程序员如何巧妙学习算法技巧?相关推荐
- 左程云:程序员该如何学习算法?
大家好,我是左程云.我本科就读于华中科技大学.硕士毕业于在芝加哥大学.先后在IBM.百度.GrowingIO和亚马逊工作,是一个刷题7年的算法爱好者. 我是<程序员代码面试指南--IT名企算法与 ...
- 程序员应该如何学习算法?
算法不是纯粹拼智商的,初学者不要上来直接撸<算法导论>!这是血泪 建议一:首先你得会一门程序设计语言 建议二:基础知识,数据结构,推荐大家看一下<大话数据结构>这本书,这本书看 ...
- 一般项目中哪里体现了数据结构_优秀程序员都应该学习的数据结构与算法项目(GitHub 开源清单)...
前言 算法为王. 想学好前端,先练好内功,内功不行,就算招式练的再花哨,终究成不了高手:只有内功深厚者,前端之路才会走得更远. 强烈推荐 GitHub 上值得前端学习的数据结构与算法项目,包含 gif ...
- 如何使用jquery_好程序员web前端学习路线分享jQuery学习技巧
好程序员web前端学习路线分享jQuery学习技巧,jQuery在web前端学习中是一个必不可少的内容,很多小伙伴都在学习这阶段的时候遇到问题,今天我们就来聊一下jQuery,让我们一起来看一看吧! ...
- 程序员为什么要学算法?
"程序员必须会算法 ?" 程序员对算法通常怀有复杂情感,算法很重要是共识,但是否每个程序员都必须学算法是主要的分歧点. 很多人觉得像人工智能.数据搜索与挖掘这样高薪的工作才用得上算 ...
- 分享程序员面试的7个技巧
金九银十又开始了,不过这几年因为疫情的影响,职场面试竞争力也变得格外的紧张,这个时候除了实打实的技能,面试的时候还需要更多的技巧,双管齐下才能赢得更大的胜算,技能方面就不多说了,今天来分享一下程序员面 ...
- 外包3年,吃透这三份Java程序员必刷的算法宝典后,已从13K涨到25K
懂点算法,很有必要 "不学数据结构和算法,一辈子都是码畜".不管你是 Java 程序员.算法工程师.数据分析师,还是技术管理者.架构师...... 我们都有一个共同的目标,就是在技 ...
- 程序员如何自我学习和成长?
关于成长,这是一个上至10年的大牛.下至3年的菜鸟 都能参与的话题,作为一名在坑里挣扎了六年的码农,我 也一直在探索.一直在思考.一直在总结,作为一名码农 到底该怎么成长? 这是一个值得持续讨论的话题 ...
- 程序员编程艺术(算法卷):第一章、左旋转字符串
第一章.左旋转字符串 作者:July,yansha. 时间:二零一一年四月十四日. 说明:(狂想曲,有三层意思:1.思绪纷飞,行文杂乱无章,想到什么,记下什么.2.简单问题深入化,复杂问题精细化,不惧 ...
最新文章
- Paddle Detection
- django手机访问_在手机上运行Python的神器
- LaTeX单栏和双栏设置
- redis使用watch完成秒杀抢购功能
- tensorflow随笔-条件循环控制(4)
- 【ArcGIS风暴】根据海拔范围分级统计GIMMS 3g NDVI平均值案例教程——以甘肃省为例
- 详解HashMap数据结构实现
- python开发工具管理系统_Python开发桌面软件文档及网址管理工具,强迫症的福音...
- 【ASP.NET Web API教程】5.2 发送HTML表单数据:URL编码的表单数据
- Selenium学习(11) 网页截图
- java-png图片压缩,解决png图片压缩后背景变黑问题
- 阵列matlab程序,阵列信号处理的理论和应用 原书matlab 程序.rar
- php解密抖音小程序用户手机号/字节跳动小程序thinkphp
- 中职计算机专业英语说课稿,中职英语基础模块说课
- rax调用微信小程序原生事件
- 盲文压纹机和AAC设备
- MICCAI2019论文分享 PART①
- linux(安装在虚拟机)读取U盘
- 常用的校验注解之 @NotNull、@NotBlank、@NotEmpty 的区别
- 基于平滑、差分的矩形波零漂(基线漂移)消除算法(MATLAB实现,代码和数据见CSDN同名资源)