070、数据挖掘常见问题
1. 1G 的文件,每一行是一个词,词的大小不超过 16 字节,内存限制大小是 1M,返回频数最高的 100 个词。使用生成器读取文件。每次读取 65536 行,一共进行 1500 次,当读取不到内容时关闭文件。每次读取,最终要得到 100 个频数最高的词。每 500 次,进行一次合并和统计,得到最多 50000 个词,对这 50000 个词提取其中频数最高的 100 个词。最终对最多...
1. 1G 的文件,每一行是一个词,词的大小不超过 16 字节,内存限制大小是 1M,返回频数最高的 100 个词。
使用生成器读取文件。每次读取 65536 行,一共进行 1500 次,当读取不到内容时关闭文件。每
次读取,最终要得到 100 个频数最高的词。每 500 次,进行一次合并和统计,得到最多 50000 个
词,对这 50000 个词提取其中频数最高的 100 个词。最终对最多 300 个筛选出来的词,进行合并
和统计,提取最终频数最高的 100 个词。
筛选出 100 个高频词的步骤:
1、统计每个词出现的次数。维护一个 Key 为词,Value 为该词出现次数的 hash 表。每次读取一个词,如果该字串不在 hash 表中,那么加入该词,并且将 Value 值设为 1;如果该字串在 hash 表中,那么将该字串的计数加一即可。
2、据每个词的引用次数进行排序。冒泡、快排等等都可以。
2. 一个大约有一万行的文本文件,每行一个词,要求统计出其中最频繁出现的前 10 个词,请给出思想和时间复杂度分析。
用 trie 树统计每个词出现的次数,时间复杂度是 O(n*le)(le 表示单词的平均长度)。然后是找出出现最频繁的前 10 个词,可以用堆来实现,时间复杂度是 O(n*lg10)。所以总的时间复杂度,是 O(n*le) 与 O(n*lg10)中较大的哪一个。
详情扫描二维码了解。http://www.mamicode.com/info-detail-1037262.html
3. 怎么在海量数据中找出重复次数最多的一个?
算法思想:
先做 hash,然后求模映射为小文件,求出每个小文件中重复次数最多的一个,并记录重复次数。然后找出上一步求出的数据中重复次数最多的一个就是所求。
参考博客一:
https://blog.csdn.net/u010601183/article/details/56481868
参考博客二:
https://www.cnblogs.com/lianghe01/p/4391804.html
4. 给 2 亿个不重复的整数,没排过序的,然后再给一个数,如何快速判断这个数是否在那 2 亿个数当中?
unsigned int 的取值范围是 0 到 2^32-1。我们可以申请连续的 2^32/8=512M 的内存,用每一个 bit 对应一个 unsigned int 数字。首先将 512M 内存都初始化为 0,然后每处理一个数字就将其对应的 bit 设置为 1。当需要查询时,直接找到对应 bit,看其值是 0 还是 1 即可。
5. 算法的特征?
1) 有穷性: 一个算法必须保证执行有限步骤之后结束;
2) 确切性: 算法的每一步骤必须有确切的定义;
3) 输入:一个算法有 0 个或多个输入,以刻画运算对象的初始情况,所谓 0 个输入是指算法本身给出了初始条件;
4) 输出:一个算法有一个或多个输出,以反映对输入数据加工后的结果。没有输出的算法是毫无意义的;
5) 可行性: 算法原则上能够精确地运行,而且人们用笔和纸做有限次数运算后即可完成。
6. 冒泡排序的思想?
冒泡思想:通过无序区中相邻记录的关键字间的比较和位置的交换,使关键字最小的记录像气泡一样逐渐向上漂至水面。整个算法是从最下面的记录开始,对每两个相邻的关键字进行比较,把关键字较小的记录放到关键字较大的记录的上面,经过一趟排序后,关键字最小的记录到达最上面,接着再在剩下的记录中找关键字次小的记录,把它放在第二个位置上,依次类推,一直到所有记录有序为止
复杂度:时间复杂度为 O(n2),空间复杂度为 O(1)
7. 快速排序的思想?
快排的基本思想:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
复杂度:快速排序是不稳定的排序算法,最坏的时间复杂度是 O(n2),最好的时间复杂度是(nlogn), 空间复杂度为 O(logn)
8. 如何判断单向链表中是否有环?
首先遍历链表,寻找是否有相同地址,借此判断链表中是否有环。如果程序进入死循环,则需要一块空间来存储指针,遍历新指针时将其和储存的旧指针比对,若有相同指针,则该链表有环,否则将这个新指针存下来后继续往下读取,直到遇见 NULL,这说明这个链表无环。
9. 基础的数据结构有哪些?
基本的算法有: 排序算法(冒泡排序,插入排序,快速排序,归并排序),查找(二分查找),搜索((DFS)深度优先搜索,(BFS)广度优先搜索),(Dijkstra 算法),动态规划算法,分类(朴素贝叶斯分类算法等)。
评价算法的好坏一般有两种: 时间复杂度和空间复杂度。时间复杂度:同样的输入规模(问题规模)花费多少时间。 空间复杂度:同样的输入规模花费多少空间(主要是内存)。以上两点越小越好。
稳定性:不会因为输入的不同而导致不稳定的情况发生。算法的思路是否简单:越简单越容易实现的越好。
10. 基本的算法有哪些,怎么评价一个算法的好坏?
基本的算法有: 排序算法(简单排序, 快速排序, 归并排序), 搜索(二分搜索), 对于其他基本数据结构, 栈, 队列,树,都有一些基本的操作。
稳定性:不会引文输入的不同而导致不稳定的情况发生。算法的思路是否简单:越简单越容易实现的越好。
11. 哪种数据结构可以实现递归?
栈可以实现,递归需要保存正在计算的上下文, 等待当前计算完成后弹出,再继续计算, 只有栈先进后出的特性才能实现。
12. 你知道哪些排序算法(一般是通过问题考算法)
冒泡, 选择, 快排, 归并。
13. 斐波那契数列
斐波那契数列:简单地说,起始两项为 0 和 1,此后的项分别为它的前两项之和。
14. 二叉树如何求两个叶节点的最近公共祖先?
二叉树是搜索二叉树 :
1、原理:二叉搜索树是排序过的 ,位于左子树的结点都比父结点小,位
于右子树的结点都比父结点大,我们只需从根节点开始和两个输入的结点进行比较,如果当前节点的值比两个结点的值都大,那么最低的公共祖先结点一定在该结点的左子树中,下一步开遍历当前结点的左子树。如果当前节点的值比两个结点的值都小,那么最低的公共祖先结点一定在该结点的右子树中,下一步开遍历当前结点的右子树。这样从上到下找到第一个在两个输入结点的值之间的结点。
15. 找出二叉树中最远结点的距离?
计算一个二叉树的最大距离有两个情况。
情况 A: 路径经过左子树的最深节点,通过根节点,再到右子树的最深节点。情况 B: 路径不穿过根节点,而是左子树或右子树的最大距离路径,取其大者。只需要计算这两个情况的路径距离,并取其大者,就是该二叉树的最大距离。
16. set 用 in 时间复杂度是多少,为什么?
O(1),因为 set 是键值相同的一个数据结构,键做了 hash 处理。
17. 深度优先遍历和广度优先遍历的区别?
1) 二叉树的深度优先遍历的非递归的通用做法是采用栈,广度优先遍历的非递归的通用做法是采用队列。
2) 深度优先遍历:对每一个可能的分支路径深入到不能再深入为止,而且每个结点只能访问一次。要特别注意的是,二叉树的深度优先遍历比较特殊,可以细分为先序遍历、中序遍历、后序遍历。具体说明如下:
先序遍历:对任一子树,先访问根,然后遍历其左子树,最后遍历其右子树。中序遍历:对任一子树,先遍历其左子树,然后访问根,最后遍历其右子树。后序遍历:对任一子树,先遍历其左子树,然后遍历其右子树,最后访问根。
广度优先遍历:又叫层次遍历,从上往下对每一层依次访问,在每一层中,从左往右(也可以从右往左)访问结点,访问完一层就进入下一层,直到没有结点可以访问为止。
3) 深度优先搜素算法:不全部保留结点,占用空间少;有回溯操作(即有入栈、出栈操作),运行速度慢。
广度优先搜索算法:保留全部结点,占用空间大; 无回溯操作(即无入栈、出栈操作),运行速度快。通常,深度优先搜索法不全部保留结点,扩展完的结点从数据库中弹出删去,这样,一般在数据库
中存储的结点数就是深度值,因此它占用空间较少。所以,当搜索树的结点较多,用其它方法易产生内存溢出时,深度优先搜索不失为一种有效的求解方法。
广度优先搜索算法,一般需存储产生的所有结点,占用的存储空间要比深度优先搜索大得多,因此, 程序设计中,必须考虑溢出和节省内存空间的问题。但广度优先搜索法一般无回溯操作,即入栈和出栈的操作,所以运行速度比深度优先搜索要快些。
18. 求和问题,给定一个数组,数组中的元素唯一,数组元素数量 N >2,若数组中的两个数相加和为 m,则认为该数对满足要求,请思考如何返回所有满足要求的数对(要求去重),并给出该算法的计算复杂度和空间复杂度
时间复杂度 O(n^2) 空间复杂度 O(n)。
19. 桶排序(最快最简单的排序)
桶排序的基本思想是将一个数据表分割成许多 buckets,然后每个 bucket 各自排序,或用不同的排序算法,或者递归的使用 bucket sort 算法。也是典型的 divide-and-conquer 分而治之的策略。它是一个分布式的排序,介于 MSD 基数排序和 LSD 基数排序之间。
1、桶排序是稳定的
2、桶排序是常见排序里最快的一种, 大多数情况下比快排还要快
3、桶排序非常快,但是同时也非常耗空间,基本上是最耗空间的一种排序算法
20. 青蛙跳台阶问题
一只青蛙要跳上 n 层高的台阶,一次能跳一级,也可以跳两级,请问这只青蛙有多少种跳上这个 n 层高台阶的方法?
思路分析:
这个问题有三种方法来解决,并在下面给出三处方法的 python 实现。
方法 一:递归
设青蛙跳上 n 级台阶有 f(n)种方法,把这 n 种方法分为两大类,第一种最后一次跳了一级台阶,这类方法共有 f(n-1)种,第二种最后一次跳了两级台阶,这种方法共有 f(n-2)种,则得出递推公式f(n)=f(n-1)+f(n-2),显然,f(1)=1,f(2)=2,递推公式如下:
方法 二: 用循环来代替递归
这种方法的原理仍然基于上面的公式,但是用循环代替了递归,比上面的代码效率上有较大的提升, 可以 AC。
代码实现如下:
方法三:建立简单数学模型,利用组合数公式
设青蛙跳上这 n 级台阶一共跳了 z 次,其中有 x 次是一次跳了两级,y 次是一次跳了一级,则有z=x+y ,2x+y=n,对一个固定的 x,利用组合可求出跳上这 n 级台阶的方法共有种方法又因为 x 在区间[0,n/2]内,所以我们只需要遍历这个区间内所有的整数,求出每个 x 对应的组合数累加到最后的结果即可。
21. 删除排序数组中的重复数字 Remove Duplicates from Sorted Array
给定一个排序数组,在原数组中删除重复出现的数字,使得每个元素只出现一次,并且返回 新的数组的长度。 不要使用额外的数组空间,必须在原地没有额外空间的条件下完成。
样例:
给出数组 A =[1,1,2],你的函数应该返回长度 2,此时 A=[1,2]。
分析:遍历,跳过重复元素。时间复杂度 O(n)。
思考:其他条件不变,如果可以允许出现两次重复将如何处理? 样例:
给出数组 nums = [1,1,1,2,2,3]。
你的函数应当返回长度 length = 5, 且前 5 个元素分别为 1, 1, 2, 2 and 3。
22. 两数之和 Two Sum
给一个整数数组,找到两个数使得他们的和等于一个给定的数 target。
你需要实现的函数 twoSum 需要返回这两个数的下标, 并且第一个下标小于第二个下标。注意这里下标的范围是 1 到 n,不是以 0 开头。
样例:
给 出 numbers = [2, 7, 11, 15], target = 9, 返 回 [1, 2]. 分析:
给定一个数列(注意不一定有序),和一个指定的数值 target。从这个数列中找出两个数相加刚好等于 target,要求给出这两个数的下标(注意数列下标是从 1 而不是从 0 开始)。
首先将数列排序。由于最后要得求的是两个数的原有下标,而不是两个数本身,因此要用一个新的对象 Item 来封装原有数列元素,并记录该元素的原有下标。排序是针对数列元素本身数值的,分别用一个index1 指针和一个index2 指针指向排序后的数列的首位,如果指向的两个数相加的和等于target, 则搜索结束;如果和小于 target,则由于 index2 此时指向的已经是数组中的最大数了,因此只能令
index1 向右移动一次;如果和大于 target,则由于此时 index1 已经指向数组中的最小数了,因此只能令 index2 向左移动一次。用一个循环重复上述过程,直到和等于 target 宣告搜索成功,或者
index1>=index2 宣告搜索失败。
空间复杂度 O(n),因为构造了一个新的 Item 序列。时间复杂度方面,如果假设 Python 的 sort 算法是用的快速排序的话,那排序的时间复杂度为 O(n*logn),搜索过程的时间复杂度为 O(n),因此总的时间复杂度为 O(n*logn)。
注意给的数列也许长度为 0,这样无论 target 是多少都是搜索失败。而且题目中给的 example 明显是是在误导人,不仔细看误以为数列原本就有序。此外,数列下标是从 1 而不是从 0 开始的。
23. 删除元素 Remove Element
给定一个数组和一个值,在原地删除与值相同的数字,返回新数组的长度。元素的顺序可以改变, 并且对新的数组不会有影响。
样例:
给出一个数组 [0,4,4,0,0,2,4,4],和值 4
返回 4 并且 4 个元素的新数组为[0,0,0,2] 分析:
题目已经暗示可以将需要删除的元素移至数组末尾,因此用两个下标 m 和 n,m 用来从左至右遍历数组寻找该元素,n 从右至左记录可以用来交换的尾部位置。
24. 加一 Plus One
给一个由包含一串数字的列表组成的非负整数加上一。
注意点:列表前面的数字表示高位 ,注意最高位也可能进位。例子:
输入: [1, 2, 3, 4, 9]
输出: [1, 2, 3, 5, 0]
解题思路:
用一个 int 数组代表一个数值,最左边是最高位,现要求加上 1,同样地返回一个 int 数组代表计算结果。非常简单的模拟加法,注意进位问题,如果是 99,999 这种,最后加上 1 后还要在数组最左边插入一个元素 1。
25. 用两个队列如何实现一个栈,用两个栈如何实现一个队列?
两个栈实现一个队列:
栈的特性是先进后出(FILO),队列的特性是先进先出(FIFO),在实现 delete 时,将一个栈中的数据依次拿出来压入到另一个为空的栈,另一个栈中数据的顺序恰好是先压入栈 1 的元素此时在栈
2 的上面,为了实现效率的提升,在 delete 时,判断栈 2 是否有数据,如果有的话,直接删除栈顶元素,在栈 2 为空时才将栈 1 的数据压入到栈 2 中,从而提高程序的运行效率,实现过程可以分为下面几个步骤:
1、push 操作时,一直将数据压入到栈 2 中
2、delete 操作时,首先判断栈 2 是否为空,不为空的情况直接删除栈 2 栈顶元素,为空的话
将栈 1 的数据压入到栈 2 中,再将栈 2 栈顶元素删除。两个队列实现一个栈 :
因为队列是先进先出,所以要拿到队列中最后压入的数据,只能每次将队列中数据 pop 到只剩一个,此时这个数据为最后压入队列的数据,在每次 pop 时,将数据压入到另一个队列中。每次执行 delete 操作时,循环往复。
26. 爬楼梯 Climbing Stairs
假设你正在爬楼梯,需要 n 步你才能到达顶部。但每次你只能爬一步或者两步,你能有多少种不同的方法爬到楼顶部?
样例:
比如 n=3,1+1+1=1+2=2+1=3,共有 3 中不同的方法
返 回 3 解题思路:
如果按照从右至左的逆序递归求解,其实就相当于搜索算法了,会造成子搜索过程的重复计算。搜索算法一般都可以用动态规划来替代,因此这里就用 1D 动态规划。
然后可以发现,f(x)的求解只依赖于 f(x-1)和 f(x-2),因此可以将空间复杂度缩小到 int[3]。于是你就会发现,这其实就是一个裴波拉契数列问题。
27. 落单的数Single Number
给出 2*n + 1 个的数字,除其中一个数字之外其他每个数字均出现两次,找到这个数字。样例:
给出 [1,2,2,1,3,4,3],返回 4。
解题思路:
异或操作,知道的人立马能做出来,不知道的人想破脑袋也想不出这个方法。当然用 hashmap/map 之类的把所有元素插一遍也能找出这个只出现过一次的元素,但是想必面试官不会很开心。位操作还是有很多技巧的,还需要继续深入学习。
28. 搜索旋转排序数组 Search in Rotated Sorted Array
假设有一个排序的按未知的旋转轴旋转的数组(比如,0 1 2 4 5 6 7 可能成为 4 5 6 7 0 1 2)。给定一个目标值进行搜索,如果在数组中找到目标值返回数组中的索引位置,否则返回-1。
你可以假设数组中不存在重复的元素。样例:
给出[4, 5, 1, 2, 3]和 target=1,返回 2
给出[4, 5, 1, 2, 3]和 target=0,返回 -1 解题思路:
采用了二分搜索,不过旋转后的数组要讨论的情况增多了。其实旋转后的数组的大小关系一般如下图:先通过中点与左顶点的关系来分类讨论中点落在了哪一部分,如果在左半边,则继续讨论目标数在中点的左边还是右边;如果在右半边,同样讨论目标数的位置。同时需要注意特殊情况只剩下两个数时, 例如[3,1],这时求出的中点也是 3,如果中点不匹配,应考虑 1。这种情况不好与上面的情况合并,单独列出。
思考:
其他条件不变,假如有重复元素又将如何?是否会影响运行时间复杂度?如何影响?为何会影响? 写出一个函数判断给定的目标值是否出现在数组中。
样例:
给出[3,4,4,5,7,0,1,2]和 target=4,返回 true。
29. 三数之和 3Sum
给出一个有 n 个整数的数组 S,在 S 中找到三个整数 a, b, c,找到所有使得 a + b + c = 0 的三元组。
注意事项:在三元组(a, b, c),要求 a <= b <= c,结果不能包含重复的三元组。样例:
如 S = {-1 0 1 2 -1 -4}, 你需要返回的三元组集合的是:
(-1, 0, 1)、(-1, -1, 2)
解题思路:
求一个列表中所有和为零的二元组的一种思路是先把列表排序,再用两个指针从两头向中间移动。如果前后两个数的和小于 0,则左指针右移;如果和大于 0,则右指针左移。求三元组时可以参考这种做法,第一个数 a 确定后,可以理解为求列表中和为-a 的二元组。由于不要考虑重复的元组,遇到重复的数可以直接跳过。
30. 四数之和 4Sum
给一个包含 n 个数的整数数组 S,在 S 中找到所有使得和为给定整数 target 的四元组(a, b, c,d)。注意事项:
四元组(a, b, c, d)中,需要满足 a <= b <= c <= d。答案中不可以包含重复的四元组。
样例:
例如,对于给定的整数数组 S=[1, 0, -1, 0, -2, 2] 和 target=0. 满足要求的四元组集合为:
(-1, 0, 0, 1)
(-2, -1, 1, 2)
(-2, 0, 0, 2)
解题思路:
想参照之前的一题 3Sum 的解法来解决,结果超时了。换个思路,空间换时间。既然有四个数,那就把前两个数之和和后两个数之和分别当做一个数。现在要求的就是在一个列表中哪两个数的和为特定值。可以先遍历一遍列表,存值和它的下标的键值对,第二遍遍历列表寻找(目标值-当前值)是否在之前的键值对中,如果在就是符合的一组解。
31. 下一个排列Next Permutation
给定一个整数数组来表示排列,找出其之后的一个排列。
注意事项:排列中可能包含重复的整数。样例:
给出排列[1,3,2,3],其下一个排列是[1,3,3,2]
给出排列[4,3,2,1],其下一个排列是[1,2,3,4] 解题思路:
通过一个例子来说明,原数组为[1,7,3,4,1]。我们想要找到比 173421 大一点的数,那就要优先考虑
将后面的数字变换顺序,而 421 从后往前是升序的(也就是这三个数字能组成的最大的数),变换了反而会变小,所以要先找到降序的点。可以看出 3 是第一个降序的点,要想整个数变大,3 就要变大,从后往前找到第一个比 3 大的数 4,将 3 和 4 交换位置得到 174321,既然原来 3 所在位置的数字变大了,那整个数肯定变大了,而它之后的数是最大的(从后往前是升序的),应转换成最小的,直接翻转。
32. 第 K 个 排 列 Permutation Sequence
给定 n 和 k,求 123..n 组成的排列中的第 k 个排列。注意事项:1 ≤ n ≤ 9。
样例:
对于 n = 3, 所有的排列如下:
解题思路:
因为 n 个不同的数字可以组成 n!个序列,那么首位确定的序列都有(n-1)!种不同的可能性,而且这些序列都根据首位的大小进行了分组,1…是最小的(n-1)!个,2…是(n-1)!+1 到 2(n-1)!个,那么现在只需要计算 k 中有几个(n-1)!就可以确定首位的数字,同样可以通过这样的方法来确定第二位、第三位…… 此外,由于列表下标从 0 开始,所以 k 要减去 1。
33. 判断数独是否合法 Valid Sudoku
请判定一个数独是否有效。该数独可能只填充了部分数字,其中缺少的数字用 . 表示。注意事项:
一个合法的数独(仅部分填充)并不一定是可解的。我们仅需使填充的空格有效即可。说明:什么是 数独?(详细解释可以扫下面二维码连接)。
解题思路:
用三个列表表示横向、纵向和小正方形的情况。特别需要注意的是小正方形内的元素的表示方法:
board[i/3*3+j/3][i%3*3+j%3]。
34. 链表求和 Add Two Numbers
你有两个用链表代表的整数,其中每个节点包含一个数字。数字存储按照在原来整数中相反的顺序, 使得第一个数字位于链表的开头。写出一个函数将两个整数相加,用链表形式返回和。
样例:
输入: (2 -> 4 -> 3) + (5 -> 6 -> 4),返回 7 -> 0 -> 8。
解题思路:
把两个链表当成是相同长度的,短的那个想象成后面都是 0;如果进位的话,在初始化下一个节点的时候将其赋值为 1 即可,所以在计算当前节点的值时要加上自己本来的值。
35. 链表划分 Partition List
给定一个单链表和数值 x,划分链表使得所有小于 x 的节点排在大于等于 x 的节点之前。你应该保留两部分内链表节点原有的相对顺序。
解题思路:
看成有一串珠子,有红和蓝两种颜色,现在要把红色和蓝色分别集中到一起。可以遍历每个珠子, 如果是蓝色就串在一条线上,红色的串在另一条线上,最后把两条线连起来就可以了。注意,在比较大的那串数中,最后的指针要置为 None,因为那是排序后的最后一个节点。
36. 旋转链表
给定一个链表,旋转链表,使得每个节点向右移动 k 个位置,其中 k 是一个非负数。样例:
给出链表 1->2->3->4->5->null 和 k=2 返回 4->5->1->2->3->null
37. 两两交换链表中的节点
例子:
输入: head = 1->2->3->4 输出: 2->1->4->3
解题思路:
比较常见的链表操作。下面看一下典型情况,如要交换链表中 A->B->C->D 中的 B 和 C 需要做如下操作:
将 A 指向 C; 将 B 指向 D; 将 C 指向 B;
在头节点之前加一个假节点就可以使所有的交换都符合上面的情况。
38. 重排链表
解题思路:
由于是一个单向链表,从尾部不断取元素比较困难,所以我们要将链表反转,又因为是同时从两头取元素,所以我们只需要反转后半段链表。我们先通过快慢指针来获得链表的中间节点,并将链表截断。接着翻转后半段链表,最后依次从两个链表中提取元素进行连接。需要注意的是,在截断时注意哪个链表比较长,合并的时候不要遗漏元素。
更多推荐
所有评论(0)