算法基础面试题(三)

二叉树

//迭代实现前序遍历

// 前序单层循环,要先压入右子节点,再压入左子节点
    public static void preOrder1(TreeNode root) {
        if (root == null) return;

        Deque<TreeNode> stack = new LinkedList<>();
        stack.push(root);
        while (!stack.isEmpty()) {
            TreeNode node = stack.pop();
            System.out.print(node.val + " ");
            if (node.right != null) stack.push(node.right);
            if (node.left != null) stack.push(node.left);
        }
    }

    //前序(迭代,双层循环) 内循环中压入子树的所有左子节点
    public static void preOrder2(TreeNode root) { // 前序双层循环
        if (root == null) return;
        TreeNode node = root;
        Deque<TreeNode> stack = new LinkedList<>();
        while (!stack.isEmpty() || node != null) {
            while (node != null) {
                System.out.print(node.val + " ");
                stack.push(node);
                node = node.left;
            }
            node = stack.pop();
            node = node.right;
        }
    }

//迭代实现后序遍历,先序遍历是中左右,后续遍历是左右中,那么我们只需要调整一下先序遍历的代码顺序,就变成中右左的遍历顺序,然后在反转result数组,输出的结果顺序就是左右中了

//迭代实现中序遍历

前缀树

Trie,又称前缀树或字典树,是一棵有根树,其每个节点包含以下字段:

指向子节点的指针数组 children对于本题而言,数组长度为 26,即小写英文字母的数量。

布尔字段 isEnd 表示该节点是否为字符串的结尾。

图的遍历

并查集?

DFS、BFS 和 Floyd 算法

拓扑排序

拓扑排序常常应用于表示任务之间先后关系的有向无环图(DAG),例如任务调度、编译顺序等场景。

拓扑排序的经典算法有深度优先搜索(DFS)和广度优先搜索(BFS)。

DFS:

BFS:

海量数据处理

Hash法

BIt-map法

Bloom filter法

数据库优化法

倒排索引法

外排序法

Trie树

双层桶

Map-reduce法

TOP K问题

针对TOP K问题,通常使用分治+Trie树/hash+小顶堆。首先把数据集按照hash方法分解成多个小数据集,然后使用Tire树或者hash统计每个小数据集中的query词频,之后用小顶堆求出每个数据集中出现频率最高的前K个树,最后在所有的top K中求出最终的top K。

一亿个浮点数,如何找出其中最大的10000个 :

解法:建立最小堆

有1000万个记录,这些查询串的重复度比较高,如果除去重复后,不超过300万个。请tongji最热门的10个查询串,要求使用的内存不超过1GB。

300万,1G内存,一个大概400字节,对于查询串来说,够用了。

先用hash统计次数,然后最小堆维护Top K

有10个文件,每个文件1GB,每个文件的每一行都存放的是用户的query,每个文件的query都有可能重复,请按照query的频度排序

  1. 切分文件: 将每个1GB的文件切分成适当大小的块,每个块可以装入内存。

  2. 内部排序: 对每个块进行内部排序,可以使用快速排序、归并排序等算法。

  3. 归并排序: 对排序后的块进行归并排序,生成一个全局有序的大文件。

  4. 归并处理: 逐个读取每个文件中的每一行 query,使用哈希表进行频度统计。

  5. 最小堆维护 Top K: 维护一个最小堆,用于存储当前频度最高的 K 个 query。

  6. 输出结果: 当处理完所有文件后,堆中的元素即为频度最高的 K 个 query。

有一个 1GB 大小的文件,文件里每一行是一个词,每个词的大小不超过 16B,内存大小限制是 1MB,要求返回频数最高的 100 个词(Top 100)。

由于内存限制,我们无法把每个单词读进内存。因此一般采用分治的办法。例如我们将1GB文件分开存储在2000个0.512MB的小文件,思路如下:

  • 首先逐行读取大文件,对单词word取哈希和取模hash(word) % 2000, 然后分别保存到小文件file_i 中。

  • 对所有小文件进行词频统计。可以采用小顶堆获得每个文件词频最高的100个词。(为什么用小顶堆?因为小顶堆满了之后,每次pop的是最小的元素。)

  • 对所有小文件的结果进行合并,同样采用小顶堆。

总结:最大的 K 个用小顶堆;最小的 K 个用大顶堆

重复问题

在海量数据中找出重复出现的元素或者去重重复出现的元素,一般可以通过位图法实现。

给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中。

  • 哈希分治(hash & divide and conquer)

我们可以将40亿个数保存到N个小文件中。然后遍历40亿个数,将哈希值hash(num)保存到下标为hash(num) % N 的文件中。然后遍历小文件,如果有一个文件存在x,就可以返回true。

  • 位图(bitmap)

位图的原理是用unsigned int整数的每一位来表示一个数是否存在,例如在unsigned int表示32位的机器上,能表示2<<32的数据,占用的空间约为512MB。

通常一个位图底层是一个数组,保存很多数字。40亿个数差不多需要长度为10的数组来表示。

给定A、B两个文件,各存放50亿个url,每个url各占64字节,内存限制是4G,让你找出A、B文件共同的url?

  • 分治思想

  1. 遍历文件A,对每个url求hash(url)%N,然后将取得的值分布到N个小文件中,每个小文件可以放在内存中。

  2. 遍历文件B,采取和A相同的方式将url存储到N个小文件中.

  3. 分布取两个小文件,把一个小文件加入哈希表,然后遍历另一个,如果url在哈希表,则为共同url,汇总到结果中。

有可能出现哈希碰撞,可以采用多个哈希函数减少碰撞概率。

  • bloom过滤器

bloom过滤器非常适合查找不存在的数据。

如果允许一定的误判率,4G内存可以表示340亿bit,我们将其中一个文件映射到340亿bit上,然后读取另外一个文件的url,检查元素是否在bloom过滤器中.

优化:

可以采用前缀树进行优化。

在 2.5 亿个整数中找出不重复的整数。注意:内存不足以容纳这 2.5 亿个整数。

根据“内存不足以容纳这2.5亿个整数”,可以知道我们需要用bitmap。

SetBit(x):将整数存入位图。 如果一个数状态为NONE,则变为SINGLE;如果为SINGLE则变为MULTIPLE。

在某个项目中,我们需要对2亿条手机号码删除重复记录(过滤号码黑名单同样有效)

解决方案: 将电话号码由12位单个数字组成的字符串转换为一个unsigned int型数据(这个完全可能,手机号码由前三位数字和后面八位数字组成,后面八位需要占到11000万的空间,而前面用0100的数字存储已经足够)为简单起见,默认为0~4G的数字都有可能分布号码,为此我们分配4G/32=512M的内存将这2亿个号码整理成unsigned int类型后按上述办法存放在这块内存中(比如13512345678我们整理后为112345678,我们找到内存中112345678bit的下标,并将此bit值设为1)遍历整个bit数组,记录下所有的号码,这些号码即是不重复的手机号码

总结 建立一个足够大的bit数组当作hash表 以bit数组的下标来表示一个整数 以bit位中的0或1来表示这个整数是否在这个数组中存在 适用于无重复原始数据的搜索 原来每个整数需要4byte空间变为1bit,空间压缩率为32倍 扩展后可实现其他类型(包括重复数据)的搜索

排序问题

分治法

位图法

假设一个文件中有9亿条不重复的9位整数,现在要求对这个文件进行排序。

  1. 分段排序

先将整个9位整数进行分段,亿条数据进行分成20段,每段5000万条 在文件中依次搜索05000万,500000011亿…… 将排序的结果存入文件

  1. 位图排序

声明一个可以包含9位整数的bit数组(10亿),一共需要10亿/8=120M内存 把内存中的数据全部初始化为0, 读取文件中的数据,并将数据放入内存。比如读到一个数据为341245909这个数据,那就先在内存中找到341245909这个bit,并将bit值置为1遍历整个bit数组,将bit为1的数组下标存入文件

最后更新于

这有帮助吗?