Hash 散列 哈希 杂凑把任意长度的输入 通过算法 变换成固定长度的输出相较于 顺序存储结构而言 当存储量达到一定程度时 查找效率得到提高“ 空间换时间 ”映射关系,根据关键字 key 访问到具体值 value不同 key 映射到同一个地址 哈希碰撞 或 哈希冲突
哈希函数
1 )直接寻址法取关键字或关键字的线性函数 作为散列地址2 )除留取余法对关键字或关键字的部分取模 作为散列地址 取模的除数 一般为素数 / 质数取模的除数 一般为素数 / 质数3 )取随机数法使用随机函数,取关键字的随机值 作为散列地址4 )数字分析法根据数字的特性,经过分析,取部分进行计算(如手机号后四位 身份证后四位等等)5 )平方取中法先求平方,取中间几位 作为散列地址6 )折叠法取关键字的几部分 取叠加和 作为散列地址
发生哈希冲突的原因 —— 抽屉原理解决冲突的办法: 再找一个空闲位置具体如下1 )线性探测key 01 —— 1 号柜子如果满了 顺延到下一个位置 —— 2 号柜子2 )二次探测如果满了 按照一定规律顺延如以二次方顺延 value value+1^2 value+2^2 3 )双重哈希使用两种哈希函数 第一个位置被占用时 计算第二个4 )链表法让一个位置 存储多个 value ( 用链表串联起来 )
1. 两数之和给定一个整数数组 nums 和一个目标值 target ,请你在该数组中找出和为目标值的那 两个整数,并返回他们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。示例 :给定 nums = [2, 7, 11, 15], target = 4因为 nums[0] + nums[1] = 2 + 7 = 9所以返回 [0, 1]
一、暴力破解法2 [7, 11, 15]7 [11, 15]11 [15]遍历每个元素 查找后续元素与其相加的和 是否等于 target依次遍历出 每两个元素之和二、倒推法使用额外容器存储 快速找到是否存在某个值< 元素值,索引位置 > hashmap[2, 7, 11, 15]<2,0> <7,1> <11,2> <15,3>262 26-2 = 247 26-7 = 1911 26-11 = 15三、一次哈希法[2, 7, 11, 15] 426 map2 26-2 = 24 <2,0>7 26-7 = 19 <2,0> <7,1>11 26-11 = 15 <2,0> <7,1> <11,2>15 26-15 = 11边遍历边修改 map 的值 能达到最好效率
public static int[] twoSum(int[] nums, int target) {
//遍历每个元素 查找后续元素与其相加的和 是否等于target
for (int i = 0; i < nums.length; i++) {
for (int j = i + 1; j < nums.length; j++) {
if (nums[i] + nums[j] == target) {
return new int[]{i, j};
}
}
}
return new int[]{-1, -1};
}
public static int[] twoSum1(int[] nums, int target) {
// 使用map 存储 <元素值,索引位置>
Map map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
map.put(nums[i], i);
}
for (int i = 0; i < nums.length; i++) {
int needNum = target - nums[i];
// 数组中同一个元素不能使用两遍
if (map.containsKey(needNum) && map.get(needNum) != i) {
return new int[]{i, map.get(needNum)};
}
}
return new int[]{-1, -1};
}
public static int[] twoSum2(int[] nums, int target) {
// 边遍历边修改map的值 能达到最好效率
Map map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int needNum = target - nums[i];
if (map.containsKey(needNum)) {
return new int[]{map.get(needNum), i};
}
map.put(nums[i], i);
}
return new int[]{-1, -1};
}
389. 找不同给定两个字符串 s 和 t ,它们只包含小写字母。字符串 t 由字符串 s 随机重排,然后在随机位置添加一个字母。请找出在 t 中被添加的字母。示例 1 :输入: s = "abcd", t = "abcde"输出: "e"解释: 'e' 是那个被添加的字母。示例 2 :输入: s = "", t = "y"输出: "y"示例 3 :输入: s = "a", t = "aa"输出: "a"示例 4 :输入: s = "ae", t = "aea"输出: "a
方案一:使用 map 分别记录 s 和 t 中 < 字母,出现的次数 >查找 t 中出现次数多了一次 或者在 s 中从未出现的字母s = "abcd", t = "abcde"s = "abcd", t = "abcda"
public static char findTheDifference(String s, String t) {
Map map = new HashMap<>();
// 存储s中的字母 及其出现次数
for (Character c : s.toCharArray()) {
if (map.containsKey(c)) {
int newNum = map.get(c) + 1;
map.put(c, newNum);
continue;
}
map.put(c, 1);
}
for (char tc : t.toCharArray()) {
// 在s中从未出现的字母
if (!map.containsKey(tc)) {
return tc;
}
// 查找t中出现次数多了一次
if (map.get(tc) == 0) {
return tc;
}
int newNum = map.get(tc) - 1;
map.put(tc, newNum);
}
return '-';
}
方案二:字符串的替换方法 replace()遍历 s 中每个字母 将其在 t 中替换为空 t 最后只剩一个字母
public static char findTheDifference1(String s, String t) {
for (Character c : s.toCharArray()) {
// 替换第一个出现的位置
t = t.replaceFirst(c.toString(), "");
}
return t.toCharArray()[0];
}
方案三:根据 ascii 码表的特性分别遍历 s 和 t 将每个字母的值相加 所得结果相减 差值就是要找的值a + b + c + d = 97 + 98 + 99 + 100 = 394a + b + c + d + e = 97 + 98 + 99 + 100 + 101 = 495
// 根据ascii码表的特性
public static char findTheDifference2(String s, String t) {
int sSum = 0, tSum = 0;
for (Character c : s.toCharArray()) {
sSum += c;
}
for (Character c : t.toCharArray()) {
tSum += c;
}
return (char) (tSum - sSum);
}
方案四:异或运算 二进制运算0 ^ 1 = 1 ^ 0 = 1 两者不同0 ^ 0 = 1 ^ 1 = 0 两者相同a ^ b ^ c ^ a ^ b = (a ^ a) ^ (b ^ b) ^ c = cs = "abcd", t = "abcde"(a ^ b ^ c ^ d) ^ (a ^ b ^ c ^ d ^ e) = e
//异或运算
public static char findTheDifference3(String s, String t) {
int result = 0;
for (Character c : s.toCharArray()) {
result ^= c;
}
for (Character c : t.toCharArray()) {
result ^= c;
}
return (char) result;
}
将复杂问题 递推分解为最简问题 然后将结果回归的过程Windows - LinuxLinux = Linux is not Unix使用方法: 自己调用自己
兔子问题有一对大兔子 每个月繁衍 一对小兔子(一公一母)小兔子 每个月生长为 大兔子现有一对小兔子 一年后 有多少对?M1 1 AM2 1 A~M3 2 A->BM4 3 A->C + B~M5 5 A->D + B->E + C~M6 8 A->F + B->G + C->H + D~ + E~
当前的所有兔子 = 上个月的所有兔子 + 这个月新生的兔子(可以繁衍的兔子)= 上个月的所有兔子数量 + 上上个月的所有兔子数量(经过了一个月的生长周期)Mn = M(n-1) + M(n-2)M5 = M4 + M3= (M3+M2) + (M2+M1)= (M2+M1 + M2) + (M2+M1)= 1+1+1 + 1+1 = 5M1=1 M2=1使用方式:1 )推导出递推公式 —— 找规律2 )找到递推的出口 —— 找出口
public static int fib(int N) {
if (N == 1) return 1;
if (N == 2) return 1;
System.out.println("求第" + N + "个月的兔子数量");
System.out.println("转化为求第" + (N - 1) + "个月和第" + (N - 2) + "个月的兔 子数量");
return fib(N - 1) + fib(N - 2);
}
大部分递归 可以转化为迭代处理Make it work,Make it right,Make it fast思路:使用数组存储,通过 n-1 和 n-2 的值进行计算
public static int fib1(int N) {
// 6 —— 0 1 2 3 4 5
// fib(0) = 0 有时需要处理
if (N <= 1) return 1;
int[] arr = new int[N];
arr[0] = 1;
arr[1] = 1;
for (int i = 2; i < arr.length; i++) {
arr[i] = arr[i - 1] + arr[i - 2];
}
return arr[N - 1];
}
印度的恒河 瓦拉那西 诞生婆罗门教放了三根柱子,其中一个根柱子上放了 64 个圆盘需要将全部圆盘 移动到另一根柱子上并且 每次只能移动一个 移动过程中 小圆盘必须在大圆盘之上为何不可完成?
分析:一个圆盘 A->C两个圆盘 A->B A->C B->C三个圆盘 A->C A->B C->B ( 把前两个圆盘 从 A 移动到 B)A->C ( 移动最大的圆盘 )B->A B->C A->C ( 再把前两个圆盘 从 B 移动到 C)N 个圆盘先把前 N-1 个圆盘 从 A 移动到 B ( 经由 C)再把最大的圆盘 从 A 移动到 C最后把前 N-1 个圆盘 从 B 移动到 C ( 经由 A)移动次数H(1) = 1H(2) = 3H(3) = H(2) + 1 + H(2) = 7H(4) = 7 + 1 + 7 = 15H(N) = H(N-1) + 1 + H(N-1) = 2^N - 1
// 四个参数 有n个圆盘 需要从A柱子移动到C 经由B
// 起始 中间 终点
public static void hanoi(int n, char A, char B, char C) {
// 出口
if (n == 1) {
System.out.println(A + "->" + C);
return;
}
// 先把前N-1个圆盘 从A移动到B (经由C)
// 再把最大的圆盘 从A移动到C
// 最后把前N-1个圆盘 从B移动到C (经由A)
hanoi(n - 1, A, C, B);
System.out.println(A + "->" + C);
hanoi(n - 1, B, A, C);
}