Skip to content

Commit a0447aa

Browse files
committed
Add new hash table problems and notes
1 parent 839b0aa commit a0447aa

29 files changed

+932
-141
lines changed
Lines changed: 69 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,78 @@
1-
## [1346. Check If N and Its Double Exist](https://leetcode.com/problems/check-if-n-and-its-double-exist/)
1+
# [1346. Check If N and Its Double Exist](https://leetcode.com/problems/check-if-n-and-its-double-exist/description/)
22

3-
### Hash Table
3+
## Test Cases
4+
### Normal Cases
5+
```
6+
Input: [10, ..., 5, ...]
7+
Output: true
8+
9+
Input: [7, 1, 2]
10+
Output: false
11+
```
12+
### Edge / Corner Cases
13+
* The array contains the same number more than once.
14+
```
15+
Input: [8, 1, 2, 8, 9]
16+
Output: false
17+
```
18+
* The number is negative.
19+
```
20+
Input: [-10, 1, -20, 5]
21+
Output: true
22+
```
23+
* There is one `0's` or multiple `0's`.
24+
```
25+
Input: [0, 1, 2, 0]
26+
Output: true
27+
28+
Input: [3, 2, 0, 5]
29+
Output: false
30+
```
31+
32+
## Hash Set
33+
We iterate every number and check if it's double or half is in the set.
434

535
```kotlin
636
fun checkIfExist(arr: IntArray): Boolean {
7-
val seen = hashSetOf<Int>()
8-
arr.forEach { value ->
9-
if (seen.contains(value * 2) || (value % 2 == 0 && seen.contains(value / 2))) {
10-
return true
11-
}
12-
seen.add(value)
37+
val set = HashSet<Int>()
38+
for (num in arr) {
39+
if (num * 2 in set) return true
40+
if (num % 2 == 0 && num / 2 in set) return true
41+
set.add(num)
1342
}
1443
return false
1544
}
1645
```
1746

18-
* **Time Complexity**: `O(n)` for only one for-loop.
19-
* **Space Complexity**: `O(n)` to store the seen set.
47+
* **Time Complexity:** `O(n)`
48+
* **Space Complexity:** `O(n)`
49+
50+
## Binary Search
51+
We can sort the array, and iterate each number to find its double using binary search.
52+
53+
```kotlin
54+
fun checkIfExist(arr: IntArray): Boolean {
55+
arr.sort()
56+
for (i in arr.indices) {
57+
val num = arr[i]
58+
val foundIndex = binarySearch(arr, num * 2)
59+
if (foundIndex != -1 && foundIndex != i) return true
60+
}
61+
return false
62+
}
63+
64+
private fun binarySearch(arr: IntArray, target: Int): Int {
65+
var left = 0
66+
var right = arr.size - 1
67+
while (left <= right) {
68+
val middle = left + (right - left) / 2
69+
if (arr[middle] == target) return middle
70+
if (arr[middle] < target) left = middle + 1
71+
else right = middle - 1
72+
}
73+
return -1 // Not found
74+
}
75+
```
76+
77+
* **Time Complexity:** `O(n log n)`
78+
* **Space Complexity:** `O(log n)`

leetcode/141.linked-list-cycle.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
## [141. Linked List Cycle](https://leetcode.com/problems/linked-list-cycle/)
1+
# [141. Linked List Cycle](https://leetcode.com/problems/linked-list-cycle/)
22

3+
## Hash Set
34
```kotlin
45
fun hasCycle(head: ListNode?): Boolean {
56
val seenNode = hashSetOf<ListNode>()
@@ -15,6 +16,7 @@ fun hasCycle(head: ListNode?): Boolean {
1516
}
1617
```
1718

19+
## Two Pointers
1820
We can use two pointers approach to solve this with `O(1)` space:
1921
1. Slow pointer goes 1 step, fast pointer goes 2 step.
2022
2. Traverse the linked list.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# [1636. Sort Array by Increasing Frequency](https://leetcode.com/problems/sort-array-by-increasing-frequency/description/)
2+
3+
## Hash Table + Sorting
4+
```kotlin
5+
// Sort the map directly
6+
fun frequencySort(nums: IntArray): IntArray {
7+
val countMap = HashMap<Int, Int>()
8+
for (num in nums) {
9+
countMap[num] = (countMap[num] ?: 0) + 1
10+
}
11+
val sortedMap =
12+
countMap
13+
.toList()
14+
.sortedWith(compareBy<Pair<Int, Int>> { (_, value) -> value }
15+
.thenByDescending { it.first })
16+
.toMap()
17+
val ans = IntArray(nums.size)
18+
var i = 0
19+
for ((k, v) in sortedMap) {
20+
repeat(v) {
21+
ans[i++] = k
22+
}
23+
}
24+
return ans
25+
}
26+
27+
// Using Java Collections API
28+
fun frequencySort(nums: IntArray): IntArray {
29+
val countMap = HashMap<Int, Int>()
30+
for (num in nums) {
31+
countMap[num] = (countMap[num] ?: 0) + 1
32+
}
33+
val ans = MutableList(nums.size) { nums[it] }
34+
Collections.sort(ans) { n1, n2 ->
35+
val count1 = countMap[n1]!!
36+
val count2 = countMap[n2]!!
37+
if (count1 == count2) n2 - n1
38+
else count1 - count2
39+
}
40+
return ans.toIntArray()
41+
}
42+
43+
// Using Kotlin APIs
44+
fun frequencySort(nums: IntArray): IntArray {
45+
val countMap = HashMap<Int, Int>()
46+
for (num in nums) {
47+
countMap[num] = (countMap[num] ?: 0) + 1
48+
}
49+
50+
val ans = nums.sortedWith(compareBy<Int> { countMap[it] }.thenByDescending { it })
51+
// Or equivalently
52+
// val ans = nums.sortedWith(compareBy({ countMap[it] }, {-it}))
53+
54+
return ans.toIntArray()
55+
}
56+
```

leetcode/1876.substrings-of-size-three-with-distinct-characters.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
## [1876. Substrings of Size Three with Distinct Characters](https://leetcode.com/problems/substrings-of-size-three-with-distinct-characters)
22

3+
> TODO: Add solution of fixed size sliding window.
4+
35
```kotlin
46
fun countGoodSubstrings(s: String): Int {
57
if (s.length < 3) return 0

leetcode/202.happy-number.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# [202. Happy Number](https://leetcode.com/problems/happy-number/description/)
2+
3+
## Two Pointers
4+
We use the same idea of [141. Linked List Cycle](../leetcode/141.linked-list-cycle.md) to solve this problem. We use two pointers to detect the cycle.
5+
6+
```kotlin
7+
fun isHappy(n: Int): Boolean {
8+
var fast = n
9+
var slow = n
10+
do {
11+
fast = sumDigits(sumDigits(fast))
12+
slow = sumDigits(slow)
13+
} while (fast != slow)
14+
return slow == 1
15+
}
16+
private fun sumDigits(n: Int): Int {
17+
var sum = 0
18+
var nn = n
19+
while (nn > 0) {
20+
sum += (nn * 1.0 % 10).pow(2.0).toInt()
21+
nn /= 10
22+
}
23+
return sum
24+
}
25+
```
26+
27+
```js
28+
29+
```

leetcode/209.minimum-size-subarray-sum.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Output: 0
2121
### Edge / Corner Cases
2222
> TODO: Can't think of any edge cases.
2323
24-
## Prefix Sum
24+
## Prefix Sum (Not Optimal)
2525
We can apply prefix sum approach to find the sum of subarray.
2626

2727
```js
@@ -64,6 +64,23 @@ fun minSubArrayLen(target: Int, nums: IntArray): Int {
6464
* Then we reduce the window size by increasing the starting index until `[start..end]` < `target` so that we can get the **minimum** size of subarray that meets the requirement.
6565

6666
```kotlin
67+
fun minSubArrayLen(target: Int, nums: IntArray): Int {
68+
var sum = 0
69+
var left = 0
70+
var ans = Int.MAX_VALUE
71+
for (right in nums.indices) {
72+
sum += nums[right]
73+
while (sum >= target) {
74+
// We update the result here, because the window is valid
75+
ans = minOf(ans, right - left + 1)
76+
sum -= nums[left]
77+
left++
78+
}
79+
}
80+
return if (ans == Int.MAX_VALUE) 0 else ans
81+
}
82+
83+
// Or equivalently, followed our general sliding window template.
6784
fun minSubArrayLen(target: Int, nums: IntArray): Int {
6885
var minLength = Int.MAX_VALUE
6986
var start = 0
@@ -91,4 +108,4 @@ fun minSubArrayLen(target: Int, nums: IntArray): Int {
91108
* **Time Complexity**: `O(n)`, we move ending index and then starting index, that just scaned through array.
92109
* **Space Complexity**: `O(1)` for no extra space.
93110

94-
> TODO: Add binary search and two pointer solution. https://leetcode.com/problems/minimum-size-subarray-sum/editorial/
111+
> TODO: Add binary search and two pointer solution. https://leetcode.com/problems/minimum-size-subarray-sum/solutions/59090/c-o-n-and-o-nlogn/

leetcode/290.word-pattern.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ fun wordPattern(pattern: String, s: String): Boolean {
66
if (pattern.length != split.size) return false
77

88
val match = Array<String>(26) { "" }
9+
10+
// Or we can use reversed map to check if the word is already mapped to a character.
911
val seen = hashSetOf<String>()
1012
for (i in 0 until pattern.length) {
1113
val c = pattern[i]

leetcode/3325.count-substrings-with-k-frequency-characters-i.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# [3325. Count Substrings With K-Frequency Characters I](https://leetcode.com/problems/count-substrings-with-k-frequency-characters-i/description/)
22

33
## Sliding Window
4-
We maintain a window that contains at least one `k` frequency characters. We expand the window by moving the `right` pointer until the substrings has at least one `k` frequency characters.
4+
* Window: the substrings that contain at least `k` frequency characters.
5+
* We expand the window by moving the `right` pointer until the substrings has at least one `k` frequency characters.
56

67
```js
78
k = 2

leetcode/350.intersection-of-two-arrays-ii.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
## [350. Intersection of Two Arrays II](https://leetcode.com/problems/intersection-of-two-arrays-ii/)
2-
### Hash Table
3-
**Idea!!** Count the occurrence of every elements in both arrays, and iterate the unique elements to find the common part. For example, `[1, 2, 3, 2, 4]` and `[2, 2, 2, 3, 3]`, we will get `count1 = {1: 1, 2: 2, 3: 1, 4: 1}` and `count2 = {2: 3, 3: 2}`. Then we iterate `count1` and find the common part.
1+
# [350. Intersection of Two Arrays II](https://leetcode.com/problems/intersection-of-two-arrays-ii/)
2+
3+
## Hash Table
4+
**Idea!!** Count the occurrence of every elements in both arrays, and iterate the unique elements to find the common part. For example, `[1, 2, 3, 2, 4]` and `[2, 2, 2, 3, 3]`, we will get `count1 = {1: 1, 2: 2, 3: 1, 4: 1}` and `count2 = {2: 3, 3: 2}`. Then we iterate `count1` and find the common part = `min(count1[key], count2[key])`.
45

56
```js
67
value count1 count2 common
@@ -88,7 +89,7 @@ fun intersect(nums1: IntArray, nums2: IntArray): IntArray {
8889
* **Time Complexity**: `O(m + n)`.
8990
* **Space Complexity**: `O(min(m, n))` for hash table.
9091

91-
### Two Pointers
92+
## Two Pointers
9293
Or we can sort both the arrays, then use two pointers to find the common part.
9394

9495
```js
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# [381. Insert Delete GetRandom O(1) - Duplicates allowed](https://leetcode.com/problems/insert-delete-getrandom-o1-duplicates-allowed/description/)
2+
3+
## Hash Table
4+
We extend the same idea from [380. Insert Delete GetRandom O(1)](../leetcode/380.insert-delete-getrandom-o1.md).
5+
6+
7+
We store all the index of each element in a hash table. When we remove an element, we swap it with the last element in the list and update the index of the last element in the hash table. This way, we can remove an element in O(1) time.
8+
9+
```kotlin
10+
class RandomizedCollection() {
11+
12+
// {value: {index}}
13+
private val valueIndexMap = HashMap<Int, HashSet<Int>>()
14+
private val values = mutableListOf<Int>()
15+
16+
// Return true if not exist, false if exist.
17+
fun insert(`val`: Int): Boolean {
18+
values.add(`val`)
19+
if (`val` in valueIndexMap) {
20+
valueIndexMap[`val`]!!.add(values.lastIndex)
21+
return false
22+
} else {
23+
valueIndexMap[`val`] = HashSet<Int>()
24+
valueIndexMap[`val`]!!.add(values.lastIndex)
25+
return true
26+
}
27+
}
28+
29+
// Return true if exist, false if not exist.
30+
fun remove(`val`: Int): Boolean {
31+
if (`val` !in valueIndexMap) {
32+
return false
33+
}
34+
val indexes = valueIndexMap[`val`]!!
35+
36+
// Take one index of the value to remove
37+
val index = indexes.iterator().next()
38+
39+
// Remove the index of `val` from map, and remove key if the set becomes empty
40+
indexes.remove(index)
41+
if (indexes.isEmpty()) {
42+
valueIndexMap.remove(`val`)
43+
}
44+
45+
// We move the last element to the current index, and update the index of last element in map
46+
val lastValue = values.last()
47+
values[index] = lastValue
48+
49+
// Update the index of last element in map
50+
valueIndexMap[lastValue]?.remove(values.lastIndex)
51+
valueIndexMap[lastValue]?.add(index)
52+
53+
// Remove the last element from list
54+
values.removeAt(values.lastIndex)
55+
return true
56+
}
57+
58+
fun getRandom(): Int {
59+
val index = (Math.random() * values.size).toInt()
60+
return values[index]
61+
}
62+
63+
}
64+
```

0 commit comments

Comments
 (0)