前言:
大家好,我是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];
}
}
}
}