算法设计题

47 阅读3分钟

前言:

大家好,我是Felix, 好久没有更新了,最近一直在准备一些面试,想着跳槽,就比较忙。

在学习的过程中,碰到一些数据结构设计题,如果平时没有遇到,可能还是真的比较难想到。今天和大家分享一下。

题目1:

请你设计一个数据结构,可以提供添加,删除元素的方法,并且还可以提供随机弹出一个元素的方法,且要求每个弹出元素的概率要一样。并且每个方法的时间复杂度都是O(1)。

思路:

首先,添加,删除方法,实现复杂度为O(1),很容易想到HASH表,但是随机弹出一个怎么设计呢?

可以这样想,我们在哈希表里存入,key为第几个放入的,value为放入的值。这样在弹出元素的时候,可以通过Math的random方法,弹出第几号元素即可。但是它还是会有一个问题,那就是,如果把某个元素删除了,通过随机运算,要弹出的元素就是要删除的元素,那岂不是又要进行运算,极端情况下,时间复杂度会达到O(n)。

那该如何是好呢?可以这样想,用最后一个元素去堵删除哪个元素的洞!这个方法是不是很巧妙?

那我们来看下代码。

/**
 * 实现一个删除,添加,弹出随机元素都为O(1)的hash表
 */
public class MyHashTable {

    // 记录下这个map的大小,便于我们找到最后一个元素的索引
    private int size;

    // 该map记录的是  第几个放入  - 放入的值
    private Map<Integer, Integer> keyValueMap;

    // 该map记录的是  放入的值  - 第几个放入 
    private Map<Integer, Integer> valueKeyMap;


    public MyHashTable() {
        this.size = 0;

        keyValueMap = new HashMap<>();

        valueKeyMap = new HashMap<>();
    }

    // 新增方法很简单,维护3个变量即可
    public boolean add(int value) {
        if (!valueKeyMap.containsKey(value)) {
            keyValueMap.put(size, value);
            valueKeyMap.put(value, size++);
            return true;
        }
        return false;
    }

    // 删除元素,一定要用最后一个元素去堵删除掉的哪个元素即可
    public boolean remove(int removeValue) {
        // 表里有这个元素才去删除,否则返回false
        if (valueKeyMap.containsKey(removeValue)) {
            // 要删除元素的索引
            int removeIndex = valueKeyMap.get(removeValue);

            // 在拿到最后一个元素的值和索引, 同时维护size的值
            int lastIndex = -- size;
            int lastValue = keyValueMap.get(lastIndex);
            // 删除元素
            valueKeyMap.remove(removeValue);
            keyValueMap.put(removeIndex, lastValue);

            keyValueMap.remove(lastIndex);

        } 
        return false;
    }
}

题目二:

请你实现并查集:有一个数据结构,他们有很多结合,实现一个方法可以合并两个集合,再实现一个方法可以判断两个元素是否在一个集合内。时间复杂度都要为O(1).

/**
 * 实现并查集,要求提供一下几个方法,且时间复杂度都是O(1)
 * 判断两个元素是否在同一个结合
 * 合并两个元素所在的集和
 */
public class UnionSet {

    // 该并查集中的元素
    private int[] values;

    // 每个元素的最顶层元素
    private int[] father;
    
    // 记录每个顶层元素的集合大小,但是对于不是顶层元素,这个字段无意义。
    private int[] size;

    // 记录每个元素到达最顶层时候经过的元素,用于合并一个并查集
    private int[] help;

    /**
     * 并查集的构造方法
     * @param n
     */
    public UnionSet(int n) {
        // 初始化这个并查集,每个元素的顶层都是自己
        values = new int[n];
        father = new int[n];
        size = new int[n];
        for (int i = 0; i < values.length; i++) {
            values[i] = i;
            father[i] = i;
            size[i] = 1;
        }
    }

    /**
     * 该方法用来找到某个元素的最顶层的父亲,但是在过程中
     * 借助help数组,把这个层级变得扁平化,利于管理
     * @param x
     * @return
     */
    public int find(int x) {
        int [] help = new int[values.length];
        int index = 0;
        while (father[x] != x) {
            help[index ++] = x;
            x = father[x];
        }

        while (index > 0) {
            help[--index] = x;
        }

        return x;
    }

    public boolean isSameSet(int x, int y) {
        return find(x) == find(y);
    }

    public void union(int x, int y) {
        if (!isSameSet(x, y)) {
            if (size[x] > size[y]) {
                int yFather = father[y];
                int xFather = father[x];
                father[yFather] = xFather;
                size[xFather] = size[xFather] + size[yFather];
            } else {
                int xFather = father[x];
                int yFather = father[y];
                father[xFather] = yFather;
                size[yFather] = size[yFather] + size[xFather];
            }
            
        }
    }
}