Skip to content

Commit 384880f

Browse files
authored
Merge pull request algorithm004-01#419 from 1024burning/master
751-week 02
2 parents bf743b2 + 4fed2fc commit 384880f

File tree

4 files changed

+372
-1
lines changed

4 files changed

+372
-1
lines changed

Week 02/id_751/LeetCode_1_751.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/**
2+
* 两数之和
3+
*
4+
* 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
5+
*
6+
* 你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
7+
*
8+
* 示例:
9+
*
10+
* 给定 nums = [2, 7, 11, 15], target = 9
11+
*
12+
* 因为 nums[0] + nums[1] = 2 + 7 = 9
13+
* 所以返回 [0, 1]
14+
*
15+
*/
16+
17+
//1.暴力求解
18+
// class Solution {
19+
// public int[] twoSum(int[] nums, int target) {
20+
// for (int i = 0; i < nums.length - 1; ++i) {
21+
// for (int j = i + 1; j < nums.length; ++j) {
22+
// if (nums[i] + nums[j] == target)
23+
// return new int[]{i, j};
24+
// }
25+
// }
26+
// return new int[0];
27+
// }
28+
// }
29+
class Solution {
30+
public int[] twoSum(int[] nums, int target) {
31+
HashMap<Integer,Integer> h = new HashMap<>();
32+
for (int i = 0; i < nums.length; ++i) {
33+
h.put(nums[i], i);
34+
}
35+
for (int j = 0; j < nums.length; ++j) {
36+
int scd = target - nums[j];
37+
if (h.containsKey(scd) && h.get(scd) != j) {
38+
return new int[]{j, h.get(scd)};
39+
}
40+
}
41+
return new int[0];
42+
}
43+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
*给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
3+
*
4+
* 示例 1:
5+
*
6+
* 输入: s = "anagram", t = "nagaram"
7+
* 输出: true
8+
* 示例 2:
9+
*
10+
* 输入: s = "rat", t = "car"
11+
* 输出: false
12+
* 说明:
13+
* 你可以假设字符串只包含小写字母。
14+
*/
15+
// class Solution {
16+
// public boolean isAnagram(String s, String t) {
17+
// char[] charS = s.toCharArray();
18+
// char[] charT = t.toCharArray();
19+
// Arrays.sort(charS);
20+
// Arrays.sort(charT);
21+
// return Arrays.equals(charS, charT);
22+
// }
23+
// }
24+
class Solution {
25+
public boolean isAnagram(String s, String t) {
26+
if (s.length() != t.length())
27+
return false;
28+
int[] counts = new int[26];
29+
for (int i = 0; i < s.length(); ++i) {
30+
counts[s.charAt(i) - 'a']++;
31+
counts[t.charAt(i) - 'a']--;
32+
}
33+
for (int count : counts) {
34+
if (count != 0 ) return false;
35+
}
36+
return true;
37+
}
38+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
*给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。
3+
*
4+
* 示例:
5+
*
6+
* 输入: ["eat", "tea", "tan", "ate", "nat", "bat"],
7+
* 输出:
8+
* [
9+
* ["ate","eat","tea"],
10+
* ["nat","tan"],
11+
* ["bat"]
12+
* ]
13+
* 说明:
14+
*
15+
* 所有输入均为小写字母。
16+
* 不考虑答案输出的顺序。
17+
*/
18+
//暴力求解 567ms 击败 5.01%的同学!优秀
19+
class Solution {
20+
public List<List<String>> groupAnagrams(String[] strs) {
21+
if (strs.length == 0) return new ArrayList();
22+
List<List<String>> res = new ArrayList<>();
23+
for (int i = 0; i < strs.length; ++i) {
24+
if(strs[i] == null) continue;
25+
List<String> group = new ArrayList<>();
26+
group.add(strs[i]);
27+
for (int j = i +1; j < strs.length; ++j) {
28+
if (isYW(strs[i], strs[j])) {
29+
group.add(strs[j]);
30+
strs[j] = null;
31+
}
32+
}
33+
res.add(group);
34+
}
35+
return res;
36+
}
37+
private boolean isYW(String s1, String s2) {
38+
if (s1 == null || s2 == null || s1.length() != s2.length())
39+
return false;
40+
int[] counts = new int[26];
41+
for (int i = 0; i < s1.length(); ++i) {
42+
counts[s1.charAt(i) - 'a']++;
43+
counts[s2.charAt(i) - 'a']--;
44+
}
45+
for (int count : counts) {
46+
if (count != 0 ) return false;
47+
}
48+
return true;
49+
}
50+
}

Week 02/id_751/NOTE.md

Lines changed: 241 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,244 @@
11
# NOTE
2+
HashMap
3+
存储结构
4+
5+
内部包含了一个 Entry 类型的数组 table。
6+
7+
transient Entry[] table;
8+
Entry 存储着键值对。它包含了四个字段,从 next 字段我们可以看出 Entry 是一个链表。即数组中的每个位置被当成一个桶,一个桶存放一个链表。HashMap 使用拉链法来解决冲突,同一个链表中存放哈希值和散列桶取模运算结果相同的 Entry。
9+
10+
11+
12+
static class Entry<K,V> implements Map.Entry<K,V> {
13+
final K key;
14+
V value;
15+
Entry<K,V> next;
16+
int hash;
17+
18+
Entry(int h, K k, V v, Entry<K,V> n) {
19+
value = v;
20+
next = n;
21+
key = k;
22+
hash = h;
23+
}
24+
25+
public final K getKey() {
26+
return key;
27+
}
28+
29+
public final V getValue() {
30+
return value;
31+
}
32+
33+
public final V setValue(V newValue) {
34+
V oldValue = value;
35+
value = newValue;
36+
return oldValue;
37+
}
38+
39+
public final boolean equals(Object o) {
40+
if (!(o instanceof Map.Entry))
41+
return false;
42+
Map.Entry e = (Map.Entry)o;
43+
Object k1 = getKey();
44+
Object k2 = e.getKey();
45+
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
46+
Object v1 = getValue();
47+
Object v2 = e.getValue();
48+
if (v1 == v2 || (v1 != null && v1.equals(v2)))
49+
return true;
50+
}
51+
return false;
52+
}
53+
54+
public final int hashCode() {
55+
return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
56+
}
57+
58+
public final String toString() {
59+
return getKey() + "=" + getValue();
60+
}
61+
}
62+
63+
64+
put操作
65+
66+
public V put(K key, V value) {
67+
if (table == EMPTY_TABLE) {
68+
inflateTable(threshold);
69+
}
70+
// 键为 null 单独处理
71+
if (key == null)
72+
return putForNullKey(value);
73+
int hash = hash(key);
74+
// 确定桶下标
75+
int i = indexFor(hash, table.length);
76+
// 先找出是否已经存在键为 key 的键值对,如果存在的话就更新这个键值对的值为 value
77+
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
78+
Object k;
79+
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
80+
V oldValue = e.value;
81+
e.value = value;
82+
e.recordAccess(this);
83+
return oldValue;
84+
}
85+
}
86+
modCount++;
87+
// 插入新键值对
88+
addEntry(hash, key, value, i);
89+
return null;
90+
}
91+
92+
HashMap 允许插入键为 null 的键值对。但是因为无法调用 null 的 hashCode() 方法,也就无法确定该键值对的桶下标,只能通过强制指定一个桶下标来存放。HashMap 使用第 0 个桶存放键为 null 的键值对。
293

3-
94+
private V putForNullKey(V value) {
95+
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
96+
if (e.key == null) {
97+
V oldValue = e.value;
98+
e.value = value;
99+
e.recordAccess(this);
100+
return oldValue;
101+
}
102+
}
103+
modCount++;
104+
addEntry(0, null, value, 0);
105+
return null;
106+
}
107+
108+
使用链表的头插法,也就是新的键值对插在链表的头部,而不是链表的尾部。
109+
110+
void addEntry(int hash, K key, V value, int bucketIndex) {
111+
if ((size >= threshold) && (null != table[bucketIndex])) {
112+
resize(2 * table.length);
113+
hash = (null != key) ? hash(key) : 0;
114+
bucketIndex = indexFor(hash, table.length);
115+
}
116+
117+
createEntry(hash, key, value, bucketIndex);
118+
}
119+
120+
void createEntry(int hash, K key, V value, int bucketIndex) {
121+
Entry<K,V> e = table[bucketIndex];
122+
// 头插法,链表头部指向新的键值对
123+
table[bucketIndex] = new Entry<>(hash, key, value, e);
124+
size++;
125+
}
126+
127+
128+
Entry(int h, K k, V v, Entry<K,V> n) {
129+
value = v;
130+
next = n;
131+
key = k;
132+
hash = h;
133+
}
134+
135+
确定桶下标
4136

137+
int hash = hash(key);
138+
int i = indexFor(hash, table.length);
139+
140+
计算 hash 值
141+
142+
final int hash(Object k) {
143+
int h = hashSeed;
144+
if (0 != h && k instanceof String) {
145+
return sun.misc.Hashing.stringHash32((String) k);
146+
}
147+
148+
h ^= k.hashCode();
149+
150+
// This function ensures that hashCodes that differ only by
151+
// constant multiples at each bit position have a bounded
152+
// number of collisions (approximately 8 at default load factor).
153+
h ^= (h >>> 20) ^ (h >>> 12);
154+
return h ^ (h >>> 7) ^ (h >>> 4);
155+
}
156+
157+
public final int hashCode() {
158+
return Objects.hashCode(key) ^ Objects.hashCode(value);
159+
}
160+
161+
扩容-基本原理
162+
设 HashMap 的 table 长度为 M,需要存储的键值对数量为 N,如果哈希函数满足均匀性的要求,那么每条链表的长度大约为 N/M,因此平均查找次数的复杂度为 O(N/M)。
163+
164+
为了让查找的成本降低,应该尽可能使得 N/M 尽可能小,因此需要保证 M 尽可能大,也就是说 table 要尽可能大。HashMap 采用动态扩容来根据当前的 N 值来调整 M 值,使得空间效率和时间效率都能得到保证。
165+
166+
和扩容相关的参数主要有:capacity、size、threshold 和 load_factor。
167+
168+
参数 含义
169+
capacity table 的容量大小,默认为 16。需要注意的是 capacity 必须保证为 2 的 n 次方。
170+
size 键值对数量。
171+
threshold size 的临界值,当 size 大于等于 threshold 就必须进行扩容操作。
172+
loadFactor 装载因子,table 能够使用的比例,threshold = (int)(newCapacity * loadFactor)。
173+
174+
static final int DEFAULT_INITIAL_CAPACITY = 16;
175+
176+
static final int MAXIMUM_CAPACITY = 1 << 30;
177+
178+
static final float DEFAULT_LOAD_FACTOR = 0.75f;
179+
180+
transient Entry[] table;
181+
182+
transient int size;
183+
184+
int threshold;
185+
186+
final float loadFactor;
187+
188+
transient int modCount;
189+
从下面的添加元素代码中可以看出,当需要扩容时,令 capacity 为原来的两倍。
190+
191+
void addEntry(int hash, K key, V value, int bucketIndex) {
192+
Entry<K,V> e = table[bucketIndex];
193+
table[bucketIndex] = new Entry<>(hash, key, value, e);
194+
if (size++ >= threshold)
195+
resize(2 * table.length);
196+
}
197+
扩容使用 resize() 实现,需要注意的是,扩容操作同样需要把 oldTable 的所有键值对重新插入 newTable 中,因此这一步是很费时的。
198+
199+
void resize(int newCapacity) {
200+
Entry[] oldTable = table;
201+
int oldCapacity = oldTable.length;
202+
if (oldCapacity == MAXIMUM_CAPACITY) {
203+
threshold = Integer.MAX_VALUE;
204+
return;
205+
}
206+
Entry[] newTable = new Entry[newCapacity];
207+
transfer(newTable);
208+
table = newTable;
209+
threshold = (int)(newCapacity * loadFactor);
210+
}
211+
212+
void transfer(Entry[] newTable) {
213+
Entry[] src = table;
214+
int newCapacity = newTable.length;
215+
for (int j = 0; j < src.length; j++) {
216+
Entry<K,V> e = src[j];
217+
if (e != null) {
218+
src[j] = null;
219+
do {
220+
Entry<K,V> next = e.next;
221+
int i = indexFor(e.hash, newCapacity);
222+
e.next = newTable[i];
223+
newTable[i] = e;
224+
e = next;
225+
} while (e != null);
226+
}
227+
}
228+
}
229+
230+
231+
扩容-重新计算桶下标
232+
在进行扩容时,需要把键值对重新计算桶下标,从而放到对应的桶上。在前面提到,HashMap 使用 hash%capacity 来确定桶下标。HashMap capacity 为 2 的 n 次方这一特点能够极大降低重新计算桶下标操作的复杂度。
233+
234+
假设原数组长度 capacity 为 16,扩容之后 new capacity 为 32:
235+
git
236+
capacity : 00010000
237+
new capacity : 00100000
238+
239+
对于一个 Key,它的哈希值 hash 在第 5 位:
240+
241+
为 0,那么 hash%00010000 = hash%00100000,桶位置和原来一致;
242+
为 1,hash%00010000 = hash%00100000 + 16,桶位置是原位置 + 16
243+
244+

0 commit comments

Comments
 (0)