Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >一些常用的算法技巧总结

一些常用的算法技巧总结

作者头像
帅地
发布于 2018-11-29 10:15:09
发布于 2018-11-29 10:15:09
92600
代码可运行
举报
文章被收录于专栏:苦逼的码农苦逼的码农
运行总次数:0
代码可运行
今天和大家讲讲,在做算法题时常用的一些技巧。对于平时没用过这些技巧的人,或许你可以考虑试着去看看在实践中能否用的上这些技巧来优化问题的解。

1. 巧用数组下标

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

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

我再举个例子:

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

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

代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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);
            }
        }
    }

提醒:可以左右滑动

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

2. 巧用取余

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
for (int i = 0; i < N; i++) {
        if (pos < N) {
         //没有越界
         // 使用数组arr[pos]
        else {
          pos = 0;//置为0再使用数组
           //使用arr[pos]
         }
        pos++;
   }

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
for (int i = 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。

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
if(n % 2 == 1){
  dosomething();
}

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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。因此我们可以很容易写出递归的代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    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)已经计算过,这时就可以把计算过的值直接返回回去了。因此我们考虑用状态保存的做法代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//数组的大小根据具体情况来,由于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];
            }
        }
    }

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

(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)。因此,我们可以考虑使用自底向上的方法来做。

代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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). 是否可以采取递推的方法来自底向上做,减少一味递归的开销。

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

- End -

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-10-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 帅地玩编程 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
帅地给你总结了这份高频地算法解题技巧,助你更快速着解题!
对于算法技巧,之前的文章也写过一些算法技巧,不过相对零散一些,今天我把之前的很多文章总结了下,并且通过增删查改,给大家总结一些常用的算法解题技巧,当然,这些也不是多牛逼的技巧,不过可以让你的代码看起来更加短小精悍,如果你能够充分掌握这些技巧,能够混合运用起来,那么写出来的代码,必然可以让别人拍案叫绝。
帅地
2020/02/27
5190
为什么你学不会递归?告别递归,谈谈我的一些经验
可能很多人在大一的时候,就已经接触了递归了,不过,我敢保证很多人初学者刚开始接触递归的时候,是一脸懵逼的,我当初也是,给我的感觉就是,递归太神奇了!
帅地
2019/03/19
1K0
为什么你学不会递归?告别递归,谈谈我的一些经验
算法数据结构中有哪些奇技淫巧?
版权声明:本文为苦逼的码农原创。未经同意禁止任何形式转载,特别是那些复制粘贴到别的平台的,否则,必定追究。欢迎大家多多转发,谢谢。
帅地
2019/06/19
5690
什么是递归--What does resursion mean?
在Google.com.hk或者在Google.com上搜索 递归或者recursion 发现Google“抽了”,明明搜索正确,为啥还显示一个查询错误的提示?如下两图:
Fisherman渔夫
2019/07/31
6090
什么是递归--What does resursion mean?
Leetcode链表题目总结
  最近刷了一些链表相关的题目,总结了下比较经典的题目。在leetcode中,官方给出的说明是malloc的内存不需要释放,所以代码中都没有free。但是在实际的编程中,我们要将申请的内存释放掉,同时把指针指向NULL,这是一种良好的习惯。
嵌入式与Linux那些事
2021/05/20
5950
Leetcode链表题目总结
【初阶数据结构与算法】链表刷题之移除链表元素、反转链表、找中间节点、合并有序链表、链表的回文结构
题目链接:https://leetcode.cn/problems/remove-linked-list-elements/
TANGLONG
2024/11/19
690
【初阶数据结构与算法】链表刷题之移除链表元素、反转链表、找中间节点、合并有序链表、链表的回文结构
搞定大厂算法面试之leetcode精讲7.双指针
方法2.c=-(a+b): 确定了a和b,那就可以想两数之和一样,在map中寻找-(a+b),减少一层循环,时间复杂度O(n^2),空间复杂度O(n)。
全栈潇晨
2021/11/26
3370
【算法】213-每周一练 之 数据结构与算法(LinkedList)
这些都是数据结构与算法,一部分方法是团队其他成员实现的,一部分我自己做的,有什么其他实现方法或错误,欢迎各位大佬指点,感谢。
pingan8787
2019/07/25
6610
【算法】213-每周一练 之 数据结构与算法(LinkedList)
JS算法_知识点精讲
该系列的文章,大部分都是前面文章的知识点汇总,如果想具体了解相关内容,请移步相关系列,进行探讨。
前端柒八九
2022/12/19
2.3K0
JS算法_知识点精讲
JS算法探险之链表
说起,「链表」在前端领域,可能有些许的陌生。但是,它作为一个传统的数据结构,在一些高阶领域还是有用武之地的。
前端柒八九
2022/08/25
5600
JS算法探险之链表
精读《算法 - 滑动窗口》
滑动窗口算法是较为入门题目的算法,一般是一些有规律数组问题的最优解,也就是说,如果一个数组问题可以用动态规划解,但又可以使用滑动窗口解决,那么往往滑动窗口的效率更高。
黄子毅
2022/03/15
6550
精读《算法 - 滑动窗口》
看到后台这么多人私信我说:算法越学越扎心,有没啥破解之法?帅地熬夜撸出了这篇学习心得!
对于算法的学习,我也是从一个小白一步步走来,当然,现在仍然很菜,,,不过,鉴于我觉得还有一些人比我更菜了,我决定谈谈我算法学习过程走过的坑,以及自己总结的一些经验,之前也有写过一篇类似的,那时粉丝才几千,这篇算是修正版。
帅地
2020/03/16
5090
看到后台这么多人私信我说:算法越学越扎心,有没啥破解之法?帅地熬夜撸出了这篇学习心得!
Js算法与数据结构拾萃(3):链表
对于一个数组,想要做篡改是复杂度是非常高的,设想你接到一个需求,让你记录存储一个人的简历,你用数组储存,可能是这样的:
一粒小麦
2020/02/25
6550
双指针技巧直接秒杀五道算法题
本文是一两年前发过的 一篇文章,当时没多少人看,现在由于账号迁移的原因公众号里都搜索不到了,我就重新加工了一下,并且添加了新内容,直接套双指针技巧秒杀 5 道算法题。
labuladong
2021/09/23
3190
【算法/学习】双指针
双指针(Two Pointers):指的是在遍历元素的过程中,不是使用单个指针进行访问,而是使用两个指针进行访问,从而达到相应的目的。如果两个指针方向相反,则称为「对撞指针」。如果两个指针方向相同,则称为「快慢指针」。如果两个指针分别属于不同的数组 / 链表,则称为「分离双指针」。
IsLand1314
2024/10/15
1430
【算法/学习】双指针
双指齐下:那晚我与算法的不解之缘
输入:n = 19 输出:true 解释: 1² + 9² = 82 8² + 2² = 68 6² + 8² = 100 1² + 0² + 0² = 1
Undoom
2024/10/17
1120
双指齐下:那晚我与算法的不解之缘
十道腾讯算法真题解析!
大家好,我是捡田螺的小男孩。收集了腾讯常考的十道算法题(真题)。在金三银四,希望对大家有帮助呀。
捡田螺的小男孩
2022/04/06
8960
十道腾讯算法真题解析!
手撕数据结构---------顺序表和链表
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是⼀种在实际中⼴泛使 ⽤的数据结构,常⻅的线性表:顺序表、链表、栈、队列、字符串…
Undoom
2024/09/23
3150
手撕数据结构---------顺序表和链表
基础算法--双指针算法
通常我们讲的双指针就是用两个指针,两个指针可以是快慢指针,解决成环的问题,也可以是指向收尾的两个指针,来减小时间复杂度。双指针算法里的指针也不止是指针,在数组中也可以是数组元素的下标,这里双指针是一种思想,并不是单单指的是指针。 接下来我们用几道例题来看看双指针算法。
用户11305458
2024/10/09
1430
基础算法--双指针算法
LeetCode链表知识点&题型总结
刚开始准备刷算法题目的时候,感觉真的是好难,十道题目有九道是不会的。心中曾一万只草泥马跑过,自己怎么这么辣鸡。
程序员徐公
2020/08/04
1.7K0
LeetCode链表知识点&题型总结
推荐阅读
相关推荐
帅地给你总结了这份高频地算法解题技巧,助你更快速着解题!
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验