程序员代码面试指南补充

目录

 

 3 布隆过滤器

 4 一致性hash

 5 哈夫曼(霍夫曼)编码问题 

 6 贪心

7 临时补充

 

3 布隆过滤器

 

 

 

 总结:布隆过滤器的提示点:

1、黑名单问题;2、要求内存空间极为苛刻;3、单样本的大小可能很大;4、允许有失误率(可能要自己问)

5 哈夫曼(霍夫曼)编码问题 

Huffman Tree:给定n个权值作为n个叶子结点,构造一颗二叉树,若树的带权路径长度达到最小,则这棵树被称为哈夫曼树(也就是非叶子节点的权值和最小)。

具体应用:

一块金条切成两半,是需要花费和长度数值一样的铜板的。比如长度为20的金条,不管切成长度多大的两半,都要花费20个铜板。一群人想整分整块金条,怎么分最省铜板? 

例如,给定数组{10,20,30},代表一共三个人,整块金条长度为10+20+30=60.金条要分成10,20,30三个部分。如果,先把长度60的金条分成10和50,花费60再把长度50的金条分成20和30,花费50一共花费110铜板。但是如果,先把长度60的金条分成30和30,花费60再把长度30金条分成10和20,花费30一共花费90铜板。输入一个数组,返回分割的最小代价。

可以借用最小堆实现:

 

public static int lessMoney(int[] arr){
    PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
    //将数组中所有的元素放入最小堆中
    for (int i = 0; i < arr.length; i++) {
        priorityQueue.add(arr[i]);
    }
    //所需要花费的金钱
    int sum = 0;
    //存储堆顶元素之和
    int cur = 0;
    while (priorityQueue.size() > 1){
        cur = priorityQueue.poll()+priorityQueue.poll();
        sum+= cur;
        priorityQueue.add(cur);
    }
    return sum;
}

6 贪心

例1:贪心堆

输入:参数1,正数数组costs;参数2,正数数组profits;参数3,正数k;参数4,正数m

costs[i]表示i号项目的花费;profits[i]表示i号项目在扣除花费之后还能挣到的钱(利润);k表示你不能并行、只能串行的最多做k个项目;m表示你初始的资金

说明:你每做完一个项目,马上获得的收益,可以支持你去做下一个项目

输出:你最后获得的最大钱数

以下图为例,一开始初始资金为5,只能在花费为1和4中选择收益最大,当进行完第一轮后资金为11,然后在花费小于11中选择剩下的最大的。

 

在这道题目中可以用一个最小堆和最大堆来解决问题,在最小堆中按照花费排序,弹出小于启动资金的

 

 这个代码好像没找到

例2:一些项目要占用一个会议室宣讲,会议室不能同时容纳两个项目的宣讲。 给你每一个项目开始的时间和结束的时间(给你一个数组,里面 是一个个具体的项目),你来安排宣讲的日程,要求会议室进行 的宣讲的场次最多。返回这个最多的宣讲场次

这里正确的贪心方法应该是按照结束时间的早晚进行排序,先去结束时间早的再去结束时间晚的

//定义安排的类
public static class Program {
    public int start;
    public int end;

    public Program(int start, int end) {
        this.start = start;
        this.end = end;
    }
}
//自定义比较方法比较结束时间的早晚
public static class ProgramComparator implements Comparator<Program> {
    @Override
    public int compare(Program o1, Program o2) {
        return o1.end - o2.end;
    }
}

public static int bestArrange(Program[] programs, int start) {
    Arrays.sort(programs, new ProgramComparator());
    int result = 0;
    for (int i = 0; i < programs.length; i++) {
        if (start <= programs[i].start) {
            result++;
            start = programs[i].end;
        }
    }
    return result;
}

7 岛问题

出处:LeetCode200岛屿数量。博客上https://www.cnblogs.com/youngao/p/11457061.html有记录,但是代码不好,这里给出左程云的思路:利用一个感染函数,将遍历的过的感染这和博客中的算法思路感觉是很类似的

public static int countIslands(int[][] m) {
    if (m == null || m[0] == null) {
        return 0;
    }
    int N = m.length;
    int M = m[0].length;
    //保存岛屿数量
    int res = 0;
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < M; j++) {
            if (m[i][j] == 1) {
                res++;
                //从该点出发,将四周进行感染
                infect(m, i, j, N, M);
            }
        }
    }
    return res;
}

public static void infect(int[][] m, int i, int j, int N, int M) {
    if (i < 0 || i >= N || j < 0 || j >= M || m[i][j] != 1) {
        return;
    }
    //将感染后置为2
    m[i][j] = 2;
    infect(m, i + 1, j, N, M);
    infect(m, i - 1, j, N, M);
    infect(m, i, j + 1, N, M);
    infect(m, i, j - 1, N, M);
}

还有一种是利用多CPU和并查集实现的方法,这里只给出思路

8 递归与回溯

例1:打印一个字符串的全部子序列,包括空字符串

 

 在每一个位置都可以选择要还是不要,并且沿着这条路继续下去

public static void printAllSubsquence(char[] str,int i,String res){
    if (i == str.length){
        //当前遍历的位置到达字符数组结尾时便打印结果字符串
        System.out.println(res);
        return;
    }
    //不要第i个位置的字符
    printAllSubsquence(str,i+1,res);
    //要第i个位置的字符
    printAllSubsquence(str,i+1,res+String.valueOf(str[i]));
}

 例2:给你一个数组arr,和一个整数aim。如果可以任意选择arr中的数字,能不能累加得到aim,返回true或者false。

这里为了简单分析,假设数组中全为正数,则数组如下,递归分析如下图所示,一开始记录位置f(0,0)标示从0位置开始向后寻找,在寻找之前和为0。

 

public static boolean isSum(int[] arr,int i,int sum,int aim){
    //当遍历到数组最后位置时要判断是否相等返回,因为一开始0不代表算上自身位置的和所以i应该一直到arr.length而不是arr.length-1
    if (i == arr.length){
        return sum == aim;
    }
    //两种情况,要当前数字和不要当前数字
    return isSum(arr,i+1,sum,aim)||isSum(arr,i+1,sum+arr[i],aim);
}

分析上述代码,arr、aim是确定的,可变的是i和sum,如:F(3,5)当这个位置确定了以后和前面是怎么来到F(3,5)位置无关,因此可以用动态规划进行优化,i与sum是两个变量因此需要一个二维表维护

 根据下面的代码分析出最后一行的结尾,确定只有当sum==aim时才为true

if (i == arr.length){
     return sum == aim;
}

对任意位置有:

isSum(arr,i+1,sum,aim)||isSum(arr,i+1,sum+arr[i],aim);

需要下面一行的sum位置和sum移动arr[i]即a的位置,根据这些条件可以从下至上递推至(0,0)位置的值为true或false

7 临时补充

1 在LeetCode上有,题号未知

给定一个N行3列二维数组,每一行表示有一座大楼,一共有N座大楼。所有大楼的底部都坐落在X轴上,每一行的三个值(a,b,c)代表每座大楼的从(a,0)点开始,到(b,0)点结束,高度为c。
        输入的数据可以保证a<b,且a,b,c均为正数。大楼之间可以有重合。请输出整体的轮廓线。
        例子:给定一个二维数组
        [
                [1, 3, 3],
                [2, 4, 4],
                [5, 6, 1]
        ]
        输出为轮廓线
        [
                [1, 2, 3],
                [2, 4, 4],
                [5, 6, 1]
        ]

解题分析:

示例分析如下图

稍微复杂一点的例子

 

 轮廓线的产生是因为有高度的变化,即只要有了高度信息那么便可以产生轮廓,采用TreeMap结构进行排序查找。TreeMap本身是有序的,底层采用红黑树实现

 

//节点中保存上或下,位置,高度信息
public static class Node {
    public boolean isUp;
    public int posi;
    public int h;

    public Node(boolean bORe, int position, int height) {
        isUp = bORe;
        posi = position;
        h = height;
    }
}

//自己定义的一个比较器
public static class NodeComparator implements Comparator<Node> {
    @Override
    public int compare(Node o1, Node o2) {
        //当位置不同时,位置小的在前
        if (o1.posi != o2.posi) {
            return o1.posi - o2.posi;
        }
        //当位置相同时,约定上的排在前面,这个排序策略没有严格要求,排在后面也可
        if (o1.isUp != o2.isUp) {
            return o1.isUp ? -1 : 1;
        }
        return 0;
    }
}

public static List<List<Integer>> buildingOutline(int[][] buildings) {
    //声明信息数组,一座大楼会被拆成两条信息
    Node[] nodes = new Node[buildings.length * 2];
    //生成信息数组
    for (int i = 0; i < buildings.length; i++) {
        //存放大楼上的信息
        nodes[i * 2] = new Node(true, buildings[i][0], buildings[i][2]);
        //存放大楼下的信息
        nodes[i * 2 + 1] = new Node(false, buildings[i][1], buildings[i][2]);
    }
    //先按照位置排序
    Arrays.sort(nodes, new NodeComparator());
    //key高度,value出现的次数
    TreeMap<Integer, Integer> htMap = new TreeMap<>();
    //收集每个位置对应的高度信息key位置,value:高度
    TreeMap<Integer, Integer> pmMap = new TreeMap<>();
    for (int i = 0; i < nodes.length; i++) {
        if (nodes[i].isUp) {
            if (!htMap.containsKey(nodes[i].h)) {
                htMap.put(nodes[i].h, 1);
            } else {
                htMap.put(nodes[i].h, htMap.get(nodes[i].h) + 1);
            }
        } else {
            if (htMap.containsKey(nodes[i].h)) {
                //如果是下,并且频率为1再出现的时候就直接删除了
                if (htMap.get(nodes[i].h) == 1) {
                    htMap.remove(nodes[i].h);
                } else {
                    htMap.put(nodes[i].h, htMap.get(nodes[i].h) - 1);
                }
            }
        }
        //存放当前位置对应的最大高度信息
        if (htMap.isEmpty()) {
            pmMap.put(nodes[i].posi, 0);
        } else {
            pmMap.put(nodes[i].posi, htMap.lastKey());
        }
    }
    List<List<Integer>> res = new ArrayList<>();
    int start = 0;
    int height = 0;//之前的高度
    for (Entry<Integer, Integer> entry : pmMap.entrySet()) {
        //在遍历的过程中,位置信息是从小到大的
        int curPosition = entry.getKey();
        int curMaxHeight = entry.getValue();
        if (height != curMaxHeight) {
            //若之前的高度不为0则生成轮廓线
            if (height != 0) {
                List<Integer> newRecord = new ArrayList<Integer>();
                newRecord.add(start);//开始位置
                newRecord.add(curPosition);//结束位置
                newRecord.add(height);//高度
                //添加记录
                res.add(newRecord);
            }
            //若为0,轮廓线开始的位置
            start = curPosition;
            //轮廓线开始的高度
            height = curMaxHeight;
        }
    }
    return res;
}

 2

给定一个字符串str, str表示一个公式, 公式里可能有整数、 加减乘除符号和左右括号, 返回公式的计算结果。
【举例】
str="48*((70-65)-43)+8*1", 返回-1816
str="3+1*4", 返回7。 str="3+(1*4)", 返回7
【说明】
1. 可以认为给定的字符串一定是正确的公式, 即不需要对str做公式有效性检查。
2. 如果是负数, 就需要用括号括起来, 比如"4*(-3)"。 但如果负数作为公式的开头或括号部分的开头, 则可以没有括号, 比如"-3*4""(-3*4)"都是合法的。
3. 不用考虑计算过程中会发生溢出的情况

public static int getValue(String str) {
    return value(str.toCharArray(), 0)[0];
}

//递归过程,返回值为数组,长度为2arr[0]代表计算结果,arr[1]代表计算到的位置
//i表示当前计算的位置
public static int[] value(char[] str, int i) {
    //用一个双向链表进行收集,即分析中的栈
    LinkedList<String> que = new LinkedList<String>();
    int pre = 0;
    int[] bra = null;
    while (i < str.length && str[i] != ')') {
        if (str[i] >= '0' && str[i] <= '9') { //当一直遇到数字时转换成可以计算的类型
            pre = pre * 10 + str[i++] - '0';
        } else if (str[i] != '(') { //遇到的不是数字且不是左括号即遇到了加减乘除
            addNum(que, pre);
            que.addLast(String.valueOf(str[i++]));
            pre = 0;
        } else {//遇到了左括号,进入递归
            bra = value(str, i + 1);
            pre = bra[0];
            i = bra[1] + 1;//跳过左括号的位置
        }
    }
    addNum(que, pre);
    return new int[] { getNum(que), i };
}

public static void addNum(LinkedList<String> que, int num) {
    if (!que.isEmpty()) {
        int cur = 0;
        String top = que.pollLast();
        if (top.equals("+") || top.equals("-")) {
            que.addLast(top);
        } else {
            cur = Integer.valueOf(que.pollLast());
            num = top.equals("*") ? (cur * num) : (cur / num);
        }
    }
    que.addLast(String.valueOf(num));
}

public static int getNum(LinkedList<String> que) {
    int res = 0;
    boolean add = true;
    String cur = null;
    int num = 0;
    while (!que.isEmpty()) {
        cur = que.pollFirst();
        if (cur.equals("+")) {
            add = true;
        } else if (cur.equals("-")) {
            add = false;
        } else {
            num = Integer.valueOf(cur);
            res += add ? num : (-num);
        }
    }
    return res;
}

3 跳表

功能类似于红黑树,其key是有序的,O(logN)

主要是利用层数来实现的,层数与key值的大小无关而是利用随机函数生成的,该函数只产生两个值0 1,且是等概率的,当产生1时停止。如:1 一层;0 1两层;0001四层。在产生的所有层数中,最大的层数为系统最小。在移动中,遇到比待插入大的向下移动,比待插入小的向右移动

如下图,假设8为三层,先从系统最小的顶层开始,右侧是19,比19小因此向下来到4层,右侧为5,8比5大因此向右移动。重复以上规律最终插入位置如下。

 

 利用上的规律可以跳过许多没必要检索的数据

0

原文地址:https://www.cnblogs.com/youngao/p/12077764.html