面试复习之基础数据结构与算法第二篇
一、堆
1.Priority Queue是堆数据结构的代表
Priority Queue,优先级队列,正常入队,按照优先级出队。
2.Priority Queue的两种实现机制:
Heap:二叉堆(大顶堆、小顶堆),二项堆,斐波那契
小顶堆,值小的始终排在前面,堆顶是最小值
大顶堆,值大的始终排在前面,堆顶是最大值
Binary Search Tree
3.Heap不同实现的算法复杂度
4.返回数据流中第K大元素?
思路1:记录前K大元素,每次对记录的元素排序
N个元素,每个元素计算一次,复杂度是O(N)
K个元素快速排序,复杂度是O(K * log K)
算法的复杂度是O(N * K * log K)
思路2:利用优先级队列,小顶堆,最小元素(即第K大元素)始终在堆顶
/**
* N个元素,每个元素计算一次,复杂度是O(N)
* K个元素快速排序及堆顶比较,复杂度是O(log2 k)
* 算法的复杂度是O(N * log2 K)
*/
class KthLargest {
private PriorityQueue<Integer> priorityQueue;
private int k;
public KthLargest(int k, int[] nums) {
//利用小顶堆(Java默认实现)
priorityQueue = new PriorityQueue<>(k);
this.k = k;
//遍历数组把num加入
for (int num : nums) {
add(num);
}
}
public int add(int num) {
if (priorityQueue.size() < k){
//加入元素到小顶堆
priorityQueue.offer(num);
}else if(priorityQueue.peek() < num){
//如果小顶堆的堆顶元素值 < num
priorityQueue.poll();
priorityQueue.offer(num);
}else{
//返回第k大元素,即小顶堆的堆顶
return priorityQueue.peek();
}
}
5.计算滑动窗口中最大值?
思路1:优先级队列,大顶堆,最大元素始终在堆顶
维护Max Heap,时间复杂度是O(log k)
查询堆顶,时间复杂度是O(1)
算法复杂度是O(N * log K)
思路2:双端队列
/**
* 输入
* nums [1,3,-1,-3,5,3,6]
* k = 3
* 输出(每次计算连续的k个元素最大值)
* [3,3,5,5,6]
*
* 特征分析,每次滑动后,可以放弃左侧已经计算过的部分
* N个元素入队,时间复杂度是O(N)
* 保持队列左侧永远是最大值
* 新元素入队维护,时间复杂度是O(N)
* 算法复杂度是O(N * log K)
*/
public int[] maxSlidingWindow(int[] nums, int k) {
if(nums.length == 0) {
return new int[0];
}
//保存数组元素的下标
Deque<Integer> window = new ArrayDeque<>();
//计算结果
int[] result = new int[nums.length - k + 1];
for(int i = 0; i < nums.length; i++) {
//从k开始,window.peek()查询队首元素
//删除队列中小于窗口左边下标的元素
if(i >= k && window.peek() <= i - k) {
//移除队首元素
window.remove();
}
//新入队元素nums[i]
//从队列右侧开始,删除小于nums[i]的元素
while(!window.isEmpty() && nums[window.peekLast()] < nums[i]){
window.removeLast();
}
window.add(i);
//队列左侧是最大值,加入结果
if(i >= k - 1){
result[i - k + 1] = nums[window.peek()];
}
}
return result;
}
二、哈希表
1.哈希表、哈希函数、哈希碰撞。
哈希表、哈希函数
解决的问题是方便查找
查询的复杂度是O(1)
哈希碰撞
99%情况会存在碰撞
拉链法解决冲突,即数组元素退化为一个链表的数据结构存储
2.不同语言的内置实现:
3.映射(Map),元素的Key值不允许重复
HashMap
哈希表的实现
无序
插入、删除、查询的平均复杂度是O(1)
TreeMap
二叉搜索树的实现
有序
查询的复杂度是O(log N)
4.集合(Set),元素不允许重复
HashSet
哈希表的实现
无序
插入、删除、查询的平均复杂度是O(1)
TreeSet
二叉搜索树的实现
有序
查询的复杂度是O(log N)
5.有效的字母异位词?
思路1:数组排序,对不同字符串的排序结果做比较,相同即可。
/**
* 时间复杂度是O(N * log N)
*/
public boolean isAnagram(String a, String b) {
if (a.length() != b.length()) {
return false;
}
char[] c1 = a.toCharArray();
char[] c2 = b.toCharArray();
Arrays.sort(c1);
Arrays.sort(c2);
return String.copyValueOf(c1).equals(String.copyValueOf(c2));
}
思路2:Map计数
/**
* 时间复杂度是O(N)
*/
public boolean isAnagram(String a, String b) {
if (a.length() != b.length()) {
return false;
}
char[] c1 = a.toCharArray();
char[] c2 = b.toCharArray();
//a.length() == b.length()
int length = a.length();
Map<Character, Integer> map1 = new HashMap<>();
Map<Character, Integer> map2 = new HashMap<>();
for (int i = 0; i < length; i++) {
if (map1.get(c1[i]) == null) {
map1.put(c1[i], 0);
} else {
map1.put(c1[i], map1.get(c1[i]) + 1);
}
if (map2.get(c2[i]) == null) {
map2.put(c2[i], 0);
} else {
map2.put(c2[i], map2.get(c2[i]) + 1);
}
}
return map1.equals(map2);
}
6.数组中两数之和等于目标值?
/**
* 输入
* nums = [2,7,11,15]
* target = 9
* 输出
* 因为nums[0] + nums[1] = 9
* 因此输出数组下标[0,1]
*/
思路1:暴力求解,两层循环
/**
* 时间复杂度是O(N^2)
*/
public int[] twoSum(int[] nums, int target) {
if (nums == null) {
return new int[]{};
}
int[] result = new int[2];
for(int i = 0; i < nums.length-1; i++){
for(int j = i+1; j < nums.length-1; j++){
if(nums[i] + nums[j] == 9){
result[0] = i;
result[1] = j;
return result;
}
}
}
return result;
}
思路2:Set,一次循环 + 一次查找
/**
* 时间复杂度是O(2N)
*/
public int[] twoSum(int[] nums, int target) {
if (nums == null) {
return new int[]{};
}
int[] result = new int[2];
HashSet<Integer> set = new HashSet<>();
for (int i = 0; i < nums.length-1; i++) {
set.add(nums[i]);
}
int temp;
for (int i = 0; i<nums.length-1; i++){
temp = target - nums[i];
if(set.contains(temp)){
result[0] = i;
//找到result[1]即可;
return result;
}
}
return result;
}
思路3:Map,key用来记录nums[i]的值,value用来记录对应的下标。
/**
* 时间复杂度是O(2N)
*/
public int[] twoSum(int[] nums, int target) {
if (nums == null) {
return new int[]{};
}
int[] result = new int[2];
Map<Integer,Integer> map = new HashMap<>();
for (int i = 0; i < nums.length-1; i++) {
map.put(nums[i], i);
}
for (int i = 0; i < nums.length-1; i++) {
int temp = target - nums[i];
if(map.containsKey(temp) && map.get(temp) != i){
result[0] = i;
result[1] = map.get(temp);
}
}
return result;
}
7.数组中三数之和等于目标值?
/**
* 输入
* nums = [-1,0,1,2,-1,-4]
* target = 0
*
* 输出数组元素
* [-1,0,1]、[-1,2,-1]
*/
思路1:暴力求解,三层循环
/**
* 时间复杂度是O(N^3)
*/
思路2:Set,两次循环 + 一次查找
/**
* 使用Map/Set消除一层循环
* 时间复杂度是O(N^2),空间复杂度是O(N)
*/
public List<Integer[]> threeSum(int[] arr) {
if(arr ==null || arr.length == 0){
return new ArrayList();
}
List<Integer[]> resultList = new ArrayList();
Arrays.sort(arr);
for(int i=0; i<arr.length-1; i++){
//重复的过滤掉
if(i>0 && arr[i] == arr[i-1]){
continue;
}
Map<Integer,Integer> targetMap = new HashMap<>();
for(int j=i+1; j<arr.length; j++){
if(targetMap.containsKey(arr[j])){
if(targetMap.get(arr[j]) == 0){
resultList.add(new Integer[]{arr[i], arr[j], -arr[i] - arr[j]});
targetMap.put(arr[j],1);
}
}else{
targetMap.put(-arr[i] - arr[j],0);
}
}
}
return resultList;
}
思路3:Sort + Find
/**
* 时间复杂度是O(N^2)
*/
public List<Integer[]> threeSum(int[] arr) {
if (arr == null || arr.length < 3) {
return new ArrayList<>();
}
Arrays.sort(arr);
List<Integer[]> resultList = new ArrayList<>();
for(int i=0; i<arr.length-2; i++){
if(i>0 && arr[i]==arr[i-1]){
continue;
}
int left = i+1;
int right = arr.length-1;
while(left < right){
int s = arr[i] + arr[left] + arr[right];
if(s < 0){
left += 1;
}else if(s > 0){
right -= 1;
}else{
resultList.add(new Integer[]{i, left, right});
while(left < right && arr[left] == arr[left+1]){
left += 1;
}
while(left < right && arr[right] == arr[right-1]){
right -= 1;
}
left += 1;
right -= 1;
}
}
}
return resultList;
}