用到一些典型数据结构的题将在此处更新。
一、哈希表
笔记
1.什么是哈希表
哈希表离不开哈希函数,哈希函数就是根据key计算出应该存储地址的位置,而哈希表是基于哈希函数建立的一种查找表。
2.哈希函数构造方法
(1)直接定址法
取关键字或关键字的某个线性函数值为哈希地址。即H(key)=key 或 H(key)=a*key+b (a,b为常数)。
(2)数字分析法
若关键字是以R为基的数(如:以10为基的十进制数),并且哈希表中可能出现的关键字都是事先知道的,则可取关键字的若干数位组成哈希地址.
(3)平方取中法(常用)
取关键字平方后的中间几位为哈希地址。
(4)折叠法(适用于关键字位数比较多,且关键字中每一位上数字分布大致均匀的情况)
将关键字分割成位数相同的几部分(最后一部分的位数可不同),然后取这几部分的叠加和(舍去进位)作为哈希地址。
(5)除留余数法
取关键字被某个不大于哈希表表长m的数p除后所得余数为哈希地址(p为素数)
(6)随机数法(适用于关键字长度不等时)
选择一个随机函数,取关键字的随机函数值为它的哈希地址。即H(key)=random(key),其中random为随机函数。
实际应用时根据情况不同选用的哈希函数不同,通常,考虑因素如下:
- 计算哈希函数所需时间(包括硬件指令的因素)
- 关键字的长度
- 哈希表的大小
- 关键字的分布情况
- 记录的查找频率
3.哈希冲突
哈希函数用不同key值却产生相同的地址,即H(key1)=H(key2)
如何解决?
(1)开放寻址法
所有输入的元素全部存放在哈希表里。当发生冲突时,使用某种探查技术在散列表中形成一个探查序列。沿此序列逐个单元地查找,直到找到给定的关键字,或者碰到一个开放的地址(即该地址单元为空)为止,插入只需将待插入的新结点存人该地址单元即可。主要有三种探查方法:
- 线性探测
- 平方探测
- 伪随机探测
(2)链表法
将所有哈希地址相同的结点链接在同一个单链表中。
(3)公共溢出区法
建立一个特殊存储空间,专门存放冲突的数据。此种方法适用于数据和冲突较少的情况。
(4)再哈希法
准备若干个哈希函数,产生冲突时就采用另一个哈希函数,直到没有冲突。
相关题目
1. 两数之和
- 链接:https://leetcode-cn.com/problems/two-sum/
- 题目描述:
- 解题思路
很容易想到使用暴力枚举的方法,先选定一个数,然后逐个判断剩下的数和否和选定的组成target,可以就返回对应的下标即可。单这样时间复杂度为O(n2),看了下数组长度,最长有10000,必定超时。因此自然想到优化下。事实上,其实遍历一次我们即可知道所有数据,如果我们记忆这些数据,在遇到下一个新的数是,就回忆下之前的某个数能否和其组成target,这样,只需遍历一次数据即可得到结果。那么怎么记忆?这里就用到哈希表。
- Java
class Solution {
public int[] twoSum(int[] nums, int target) {
//用哈希表存储nums和下标的映射关心
Map<Integer, Integer> map=new HashMap<>();
int a[]=new int[2];
for (int i = 0; i < nums.length; i++) {
int diff = target-nums[i];
if (map.containsKey(diff)) {
a[0]=map.get(diff);
a[1]=i;
return a;
}
map.put(nums[i], i);
}
return a;
}
}
- C++
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> m; //第一个数是key,key可以作为索引
for (int i = 0; i < nums.size(); i++)
{
int diff = target - nums[i];
if (m.count(diff)) //判断哈希表是不是存在这个差值
return { m[diff],i }; //返回他们的下标
m[nums[i]] = i; //扩展哈希表
}
return {};
}
};
二、栈
笔记
栈是一种只能在一端进行插入和删除操作的特殊线性表。它按照先进后出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。栈具有记忆作用,对栈的插入与删除操作中,不需要改变栈底指针。
相关题目
20.有效括号
- 链接:https://leetcode-cn.com/problems/valid-parentheses
- 题目描述:
- 解题思路:
根据题目的有效性描述,左右括号必须匹配,且括号内的括号也是有效的才行,因此合法的字符串必定包含紧紧挨在一起的同类型括号,且消除此对括号又能找到下一对同类型括号。重复上述操作最终可以使字符串为空。怎么消除呢?充分利用栈先进后出的性质,左括号直接进栈,如果下一个括号与栈顶括号匹配,就将栈顶括号出栈,实现消除,若不匹配,说明非法,返回假即可。
- Java
static class Solution {
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
char c[]=s.toCharArray();
for (int i = 0; i < c.length; i++) {
//左括号直接进栈
if (stack.isEmpty()||c[i]=='('||c[i]=='['||c[i]=='{') {
stack.push(c[i]);
continue;
}
//遇到右括号就进行匹配验证
if (!stack.isEmpty()) {
if ((c[i]==')'&&stack.peek()=='(')||
(c[i]==']'&&stack.peek()=='[')||
(c[i]=='}'&&stack.peek()=='{')) stack.pop();
else return false;
}
}
//最后栈是空的,说明合法
return stack.isEmpty();
}
}