## arrays and strings
* arrays and strings hold values of the same type at contiguous memory locations with the advantage of storing multiple elements of the same type with one single variable name.
* we are usually concerned with two things: the index of an element, and the element itself.
* accessing elements is fast as long as you have the index (as opposed to linked lists that need to be traversed from the head).
* addition or removal of elements into/from the middle of an array is slow because the remaining elements need to be shifted to accommodate the new/missing element (unless they are inserted/removed at the end of the list).
* **subarray**: a range of contiguous values within an array (for example, in `[2, 3, 6, 1, 5, 4]`, `[3, 6, 1]` is a subarray while `[3, 1, 5]` is not).
* **subsequence**: a sequence derived from the given sequence without changing the order (for example, in `[2, 3, 6, 1, 5, 4]`, `[3, 1, 5]` is a subsequence but `[3, 5, 1]` is not).
----
### two-pointer technique
* a typical scenario is when you want to iterate the array from two ends to the middle or when pointers can cross each others (or even be in different arrays).
* another scenario is when you need one slow-runner and one fast-runner at the same time (so that you can determine the movement strategy for both pointers).
* in any case, this technique is usually used when the array is sorted.
* in the **sliding window** technique, the two pointers usually move in the same direction and never overtake each other. examples are: longest substring without repeating characters, minimum size subarray sum, and minimum window substring.
----
### intervals
* checking if two intervals overlap:
```python
def is_overlap(a, b):
return a[0] < b[1] and b[0] < a[1]
```
* merging two intervals:
```python
def merge_overlapping_intervals(a, b):
return [min(a[0], b[0]), max(a[1], b[1])]
```
---
### two-dimensional arrays
* a matrix is a 2d array. they can be used to represent graphs where each node is a cell on the matrix which has 4 neighbors (except those cells on the edge and corners).
* in some languages (like C++), 2d arrays are represented as 1d, so an array of `m * n` elements represents `array[i][j]` as `array[i * n + j]`.
* creating an empty matrix:
```python
zero_matrix = [ [0 for _ in range(len(matrix[0]))] for _ in range(len(matrix)) ]
```
* copying a matrix:
```python
copied_matrix = [ row[:] for row in mattrix ]
```
* the transpose of a matrix can be found by interchanging its rows into columns or columns into rows:
```python
transposed = zip(*matrix)
```
---
### check if mountain
```python
def valid_mountain_array(arr: list[int]) -> bool:
last_number, mountain_up = arr[0], True
for i, n in enumerate(arr[1:]):
if n > last_number:
if mountain_up == False:
return False
elif n < last_number:
if i == 0:
return False
mountain_up = False
else:
return False
last_number = n
return not mountain_up
```
---
### duplicate zeros in place
```python
def duplicate_zeros(arr: list[int]) -> list[int]:
i = 0
while i < len(arr):
if arr[i] == 0 and i != len(arr) - 1:
range_here = len(arr) - (i + 2)
while range_here > 0:
arr[i + range_here + 1] = arr[i + range_here]
range_here -= 1
arr[i+1] = 0
i += 2
else:
i += 1
return arr
```
----
### remove duplicates in place
```python
def remove_duplicates(nums: list[int]) -> int:
arr_i, dup_i = 0, 1
while arr_i < len(nums) and dup_i < len(nums):
if nums[arr_i] == nums[dup_i]:
dup_i += 1
else:
arr_i += 1
nums[arr_i] = nums[dup_i]
for i in range(arr_i + 1, dup_i):
nums[i] = '_'
return dup_i - arr_i - 1, nums
```
---
### anagrams
* to determine if two strings are anagrams, there are a few approaches:
- sorting both strings should produce the same string (`O(N log(N))` time and `O(log(N))` space.
- if we map each character to a prime number and we multiply each mapped number together, anagrams should have the same multiple (prime factor decomposition, `O(N)`).
- frequency counting of characters can determine whether they are anagram (`O(N)`).
```python
def is_anagram(string1, string2) -> bool:
string1 = string1.lower()
string2 = string2.lower()
if len(string1) != len(string2):
return False
for c in string1:
if c not in string2:
return False
return True
```
----
### palindromes
* ways to determine if a string is a palindrome:
* reverse the string and they should be equal.
* have two pointers at the start and end of the string, moving the pointers until they meet.
```python
def is_palindrome(sentence):
sentence = sentence.strip(' ')
if len(sentence) < 2:
return True
if sentence[0] == sentence[-1]:
return is_palindrome(sentence[1:-1])
return False
```
```python
def is_permutation_of_palindromes(some_string):
aux_dict = {}
for c in some_string.strip():
if c in aux_dict.keys():
aux_dict[c] -= 1
else:
aux_dict[c] = 1
for v in aux_dict.values():
if v != 0:
return False
return True
```
---
### intersection of two arrays
```python
def intersect(nums1: list[int], nums2: list[int]) -> list[int]:
result = []
set_nums = set(nums1) & set(nums2)
counter = Counter(nums1) & Counter(nums2)
for n in set_nums:
result.extend([n] * counter[n])
return result
```
---
### check if isomorphic
```python
def is_isomorphic(s: str, t: str) -> bool:
map_s_to_t = {}
map_t_to_s = {}
for ss, tt in zip(s, t):
if (ss not in map_s_to_t) and (tt not in map_t_to_s):
map_s_to_t[ss] = tt
map_t_to_s[tt] = ss
elif (map_s_to_t.get(ss) != tt) or (map_t_to_s.get(tt) != ss):
return False
return True
```
---
### absolute difference between the sums of a matrix's diagonals
```python
def diagonal_difference(arr):
diag_1 = 0
diag_2 = 0
i, j = 0, len(arr) - 1
while i < len(arr) and j >= 0:
diag_1 += arr[i][i]
diag_2 += arr[i][j]
i += 1
j -= 1
return diag_1, diag_2, abs(diag_1 - diag_2)
```
---
### find permutations
```python
def permutations(string) -> list:
if len(string) == 1:
return [string]
result = []
for i, char in enumerate(string):
for perm in permutation(string[:i] + string[i+1:]):
result += [char + perm]
return result
```
---
### length of the longest substring
```python
def length_longest_substring(s) -> int:
result = ""
this_longest_string = ""
i = 0
for c in s:
j = 0
while j < len(this_longest_string):
if c == this_longest_string[j]:
if len(this_longest_string) > len(result):
result = this_longest_string
this_longest_string = this_longest_string[j+1:]
j += 1
this_longest_string += c
return result, this_longest_string
```