mirror of
https://github.com/dholerobin/Lecture_Notes.git
synced 2025-07-01 13:06:29 +00:00
Merge branch 'master' of https://github.com/KingsGambitLab/Lecture_Notes
This commit is contained in:
commit
4939458348
@ -1,237 +0,0 @@
|
|||||||
|
|
||||||
Recursion
|
|
||||||
----------
|
|
||||||
Recursion - process of function calling itself
|
|
||||||
directly or indirectly.
|
|
||||||
|
|
||||||
__Steps involved:__
|
|
||||||
- Base case
|
|
||||||
- Self Work
|
|
||||||
- Recursive Calls
|
|
||||||
|
|
||||||
```python
|
|
||||||
def fib(n):
|
|
||||||
if n <= 1 : return n # base case
|
|
||||||
return fib(n-1) + fib(n-2) # recursive calls
|
|
||||||
|
|
||||||
fib(10) # initial call
|
|
||||||
```
|
|
||||||
In the recursive program, the solution to the base case is provided and the solution of the bigger problem is expressed in terms of smaller problems.
|
|
||||||
In the above example, base case for n < = 1 is defined and larger value of number can be solved by converting to smaller one till base case is reached.
|
|
||||||
|
|
||||||
__Intuition__
|
|
||||||
|
|
||||||
The main idea is to represent a problem in terms of one or more smaller problems, and add one or more base conditions that stop the recursion.
|
|
||||||
_For example_, we compute factorial n if we know factorial of (n-1). The base case for factorial would be n = 0. We return 1 when n = 0.
|
|
||||||
-- --
|
|
||||||
|
|
||||||
Power
|
|
||||||
-----
|
|
||||||
|
|
||||||
> Given n, k.
|
|
||||||
Find $n^k$
|
|
||||||
|
|
||||||
```python
|
|
||||||
def pow(n, k):
|
|
||||||
if k == 0: return 1
|
|
||||||
return n*pow(n, k - 1)
|
|
||||||
```
|
|
||||||
__Time Complexity__: $O(n)$
|
|
||||||
|
|
||||||
__Optimised solution:__
|
|
||||||
```python
|
|
||||||
def pow(n, k):
|
|
||||||
if k == 0: return 1
|
|
||||||
|
|
||||||
nk = pow(n, k//2)
|
|
||||||
if k % 2 == 0:
|
|
||||||
return nk * nk
|
|
||||||
else:
|
|
||||||
return nk * nk * n
|
|
||||||
```
|
|
||||||
|
|
||||||
Why not f(n, k/2) * f(n, k/2+1) in the else condition?
|
|
||||||
To allow reuse of answers.
|
|
||||||
|
|
||||||
<img src="https://user-images.githubusercontent.com/35702912/66316190-d30e1f00-e934-11e9-8089-85c6dc69baa7.jpg" data-canonical-src="https://user-images.githubusercontent.com/35702912/66316190-d30e1f00-e934-11e9-8089-85c6dc69baa7.jpg" width="400" />
|
|
||||||
|
|
||||||
__Time Complexity__ (assuming all multiplications are O(1))? $O(\log_2 k)$
|
|
||||||
|
|
||||||
|
|
||||||
Break it into 3 parts? k//3 and take care of mod1 and mod2.
|
|
||||||
|
|
||||||
Binary is still better, just like in binary search.
|
|
||||||
|
|
||||||
-- --
|
|
||||||
All Subsets
|
|
||||||
-----------
|
|
||||||
|
|
||||||
> Given A[N], print all subsets
|
|
||||||
|
|
||||||
The idea is to consider two cases for every element.
|
|
||||||
(i) Consider current element as part of current subset.
|
|
||||||
(ii) Do not consider current element as part of current subset.
|
|
||||||
|
|
||||||
Number of subsets? $2^n$
|
|
||||||
|
|
||||||
Explain that we want combinations, and not permutations. [1, 4] = [4, 1]
|
|
||||||
|
|
||||||
Number of permutations will be much larger than combinations.
|
|
||||||
|
|
||||||
```python
|
|
||||||
def subsets(A, i, aux):
|
|
||||||
if i == len(A):
|
|
||||||
print(aux)
|
|
||||||
return
|
|
||||||
take = subsets(A, i+1, aux + [A[i]]) # Case 1
|
|
||||||
no_take = subsets(A, i+1, aux) # Case 2
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
<img src="https://user-images.githubusercontent.com/35702912/66323471-7a914e80-e941-11e9-84a9-11a333ac4f77.jpg" width="400"
|
|
||||||
/>
|
|
||||||
|
|
||||||
How many leaf nodes? $2^n$ - one for each subset
|
|
||||||
How many total nodes? $2^{n+1} - 1$
|
|
||||||
|
|
||||||
__Time Complexity__: $O(2^n)$
|
|
||||||
|
|
||||||
Subsets using Iteration
|
|
||||||
-----------------------
|
|
||||||
|
|
||||||
Look at recursion Tree.
|
|
||||||
Going left = 0
|
|
||||||
Going right = 1
|
|
||||||
|
|
||||||
Basically, for each element, choose = 1, skip = 0
|
|
||||||
|
|
||||||
So, generate numbers from 0 to $2^n-1$ and look at the bits of the numbers. Each subset is formed using each number.
|
|
||||||
```
|
|
||||||
For A = [1 2 3]
|
|
||||||
|
|
||||||
000 []
|
|
||||||
001 [c]
|
|
||||||
010 [b]
|
|
||||||
011 [b c]
|
|
||||||
100 [a]
|
|
||||||
101 [a c]
|
|
||||||
110 [a b]
|
|
||||||
111 [a b c]
|
|
||||||
```
|
|
||||||
|
|
||||||
Lexicographic subsets
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
Explain what is lexicographic order.
|
|
||||||
|
|
||||||
```
|
|
||||||
For Array [0,1,2,3,4] Subsets in Lexicographical order,
|
|
||||||
[]
|
|
||||||
[0]
|
|
||||||
[0, 1]
|
|
||||||
[0, 1, 2]
|
|
||||||
[0, 1, 2, 3]
|
|
||||||
[0, 1, 3]
|
|
||||||
[0, 2]
|
|
||||||
[0, 2, 3]
|
|
||||||
[0, 3]
|
|
||||||
[1]
|
|
||||||
[1, 2]
|
|
||||||
[1, 2, 3]
|
|
||||||
[1, 3]
|
|
||||||
[2]
|
|
||||||
[2, 3]
|
|
||||||
[3]
|
|
||||||
```
|
|
||||||
- The idea is to sort the array first.
|
|
||||||
- After sorting, one by one fix characters and recursively generates all subsets starting from them.
|
|
||||||
- After every recursive call, we remove current character so that next permutation can be generated.
|
|
||||||
- Basically, we're doing DFS. Print when encountering node
|
|
||||||
But don't print when going left - because already printed in parent.
|
|
||||||
|
|
||||||
|
|
||||||
<img src="https://user-images.githubusercontent.com/35702912/66468106-3d44d200-eaa3-11e9-96e7-c6a050be1219.jpg" width="400"
|
|
||||||
/>
|
|
||||||
|
|
||||||
|
|
||||||
<img src="https://user-images.githubusercontent.com/35702912/66468119-42098600-eaa3-11e9-8f24-237be2a91d12.jpg" width="400"
|
|
||||||
/>
|
|
||||||
```python
|
|
||||||
def subsets(A, i, aux, p):
|
|
||||||
if p: print(aux)
|
|
||||||
if i == len(A):
|
|
||||||
return
|
|
||||||
take = subsets(A, i+1, aux + [A[i]], True)
|
|
||||||
no_take = subsets(A, i+1, aux, False)
|
|
||||||
```
|
|
||||||
|
|
||||||
__Time Complexity__: $O(2^n)$
|
|
||||||
__Space Complexity__: $O(n^2)$, because we're creating new aux arrays.
|
|
||||||
|
|
||||||
-- --
|
|
||||||
Number of Subsets with a given Sum
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
> Given an array of integers and a sum, the task is to print all subsets of given array with sum equal to given sum.
|
|
||||||
> A = [2, 3, 5, 6, 8, 10] Sum = 10
|
|
||||||
> Output: [5, 2, 3] [2, 8] [10]
|
|
||||||
|
|
||||||
The subsetSum problem can be divided into two subproblems.
|
|
||||||
- Include the current element in the sum and recur (i = i + 1) for the rest of the array
|
|
||||||
- Exclude the current element from the sum and recur (i = i + 1) for the rest of the array.
|
|
||||||
|
|
||||||
|
|
||||||
<img src="https://user-images.githubusercontent.com/35702912/66469192-11c2e700-eaa5-11e9-9094-252ce842464a.jpg" width="400"
|
|
||||||
/>
|
|
||||||
```python
|
|
||||||
def subsetSum(A,N,cur_sum, i, target):
|
|
||||||
if i == N:
|
|
||||||
if cur_sum == target:
|
|
||||||
return 1
|
|
||||||
else :
|
|
||||||
return 0
|
|
||||||
take = subsetSum(A,N,cur_sum + A[i], i+1, target)
|
|
||||||
no_take = subsetSum(A,N,cur_sum, i+1, target)
|
|
||||||
return take + no_take
|
|
||||||
```
|
|
||||||
|
|
||||||
Why can't terminate earlier when cur_sum == target?
|
|
||||||
Because we can have negative values in the array as well and this condition will prevent us from considering negative values.
|
|
||||||
For eg,
|
|
||||||
target = 6
|
|
||||||
1, 2, 3 is good, but
|
|
||||||
1, 2, 3, -1, 1 is also good. <br>
|
|
||||||
__Time Complexity__: O(2^n)
|
|
||||||
<br>
|
|
||||||
__Space Complexity__: O(n)
|
|
||||||
|
|
||||||
Number of Subsets with a given Sum (Repetition Allowed)
|
|
||||||
---------------
|
|
||||||
> Given a set of m distinct positive integers and a value ‘N’. The problem is to count the total number of ways we can form ‘N’ by doing sum of the array elements. Repetitions and different arrangements are allowed.
|
|
||||||
> All array elements are positive.
|
|
||||||
|
|
||||||
The subsetSum2 problem can be divided into two subproblems.
|
|
||||||
- Include the current element in the sum and recur for the rest of the array. Here the value of i is not incremented to incorporate the condition of including multiple occurances of a element.
|
|
||||||
- Exclude the current element from the sum and recur (i = i + 1) for the rest of the array.
|
|
||||||
|
|
||||||
|
|
||||||

|
|
||||||
```python
|
|
||||||
def subsetSum2(A,N,cur_sum, i, target):
|
|
||||||
if i == N:
|
|
||||||
if cur_sum == target:
|
|
||||||
return 1
|
|
||||||
else :
|
|
||||||
return 0
|
|
||||||
elif cur_sum > target:
|
|
||||||
return 0;
|
|
||||||
take = subsetSum2(A,N,cur_sum + A[i], i, target)
|
|
||||||
no_take = subsetSum2(A,N,cur_sum, i+1, target)
|
|
||||||
return take + no_take
|
|
||||||
```
|
|
||||||
__Time Complexity__ : _O(2 *(Target/MinElement))_
|
|
||||||
<br>
|
|
||||||
__Space Complexity__: _O(Target/Min Element)_
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,164 +0,0 @@
|
|||||||
Backtracking
|
|
||||||
------------
|
|
||||||
|
|
||||||
Backtracking is a methodical way of trying out various sequences of decisions, until you find one that “works”. It is a systematic way to go through all the possible configurations of a search space.
|
|
||||||
- do
|
|
||||||
- recurse
|
|
||||||
- undo
|
|
||||||
|
|
||||||
Backtracking is easily implemented with recursion because:
|
|
||||||
- The run-time stack takes care of keeping track of the choices that got us to a given point.
|
|
||||||
- Upon failure we can get to the previous choice simply by returning a failure code from the recursive call.
|
|
||||||
|
|
||||||
Backtracking can help reduce the space complexity, because we're reusing the same storage.
|
|
||||||
|
|
||||||
__Backtracking Algorithm__:
|
|
||||||
Backtracking is really quite simple--we “explore” each node, as follows:
|
|
||||||
```python
|
|
||||||
To “explore” node N:
|
|
||||||
1. If N is a goal node, return “success”
|
|
||||||
2. If N is a leaf node, return “failure”
|
|
||||||
3. For each child C of N,
|
|
||||||
3.1. Explore C
|
|
||||||
3.1.1. If C was successful, return “success”
|
|
||||||
4. Return “failure”
|
|
||||||
```
|
|
||||||
Print all Permutations of a String
|
|
||||||
-------------
|
|
||||||
> A permutation, also called an “arrangement number” or “order,” is a rearrangement of the elements of an ordered string S into a one-to-one correspondence with S itself. <br>
|
|
||||||
String: ABC <br>
|
|
||||||
Permutations: ABC ACB BAC BCA CBA CAB
|
|
||||||
|
|
||||||
Total permutations = n!
|
|
||||||
|
|
||||||
<img src="https://user-images.githubusercontent.com/35702912/66570095-7e63e180-eb8a-11e9-8e3c-31d8e04f2d67.jpg" width="500"
|
|
||||||
/>
|
|
||||||
<img src="https://user-images.githubusercontent.com/35702912/66570104-83c12c00-eb8a-11e9-802d-f0f0ede4a14a.jpg" width="500"
|
|
||||||
/>
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
def permute(S, i):
|
|
||||||
if i == len(S):
|
|
||||||
print(S)
|
|
||||||
for j in range(i, len(S)):
|
|
||||||
S[i], S[j] = S[j], S[i]
|
|
||||||
permute(S, i+1)
|
|
||||||
S[i], S[j] = S[j], S[i] # backtrack
|
|
||||||
```
|
|
||||||
__Time Complexity:__ _O(n*n!)_ because there are n! permutations and it requires _O(n)_ to print a permutation.
|
|
||||||
<br>
|
|
||||||
__Space Complexity:__ _O(n)_
|
|
||||||
|
|
||||||
_Note: Output not in Lexicographic Order._
|
|
||||||
|
|
||||||
Print all Unique Permutations of a String
|
|
||||||
--------------------
|
|
||||||
> String: AAB
|
|
||||||
> Permutations: AAB ABA BAA
|
|
||||||
|
|
||||||
Basically, if we're swapping S[i] with S[j], but S[j] already occured earlier from S[i] .. S[j-1], then swapping will result in repetition.
|
|
||||||
|
|
||||||
|
|
||||||
<img src="https://user-images.githubusercontent.com/35702912/66570690-bc153a00-eb8b-11e9-8a00-9dfb728df5f9.jpg" width="500"
|
|
||||||
/>
|
|
||||||
|
|
||||||
```python
|
|
||||||
def permute_distinct(S, i):
|
|
||||||
if i == len(S):
|
|
||||||
print(S)
|
|
||||||
for j in range(i, len(S)):
|
|
||||||
if S[j] in S[i:j]:
|
|
||||||
continue
|
|
||||||
S[i], S[j] = S[j], S[i]
|
|
||||||
permute_distinct(S, i+1)
|
|
||||||
S[i], S[j] = S[j], S[i] # backtrack
|
|
||||||
```
|
|
||||||
__Time Complexity:__ _O(n*n!)_ <br>
|
|
||||||
__Space Complexity:__ _O(n)_
|
|
||||||
|
|
||||||
Print Permutations Lexicographically
|
|
||||||
---
|
|
||||||
> Given a string, print all permutations of it in sorted order. <br>
|
|
||||||
For example, if the input string is “ABC”, then output should be “ABC, ACB, BAC, BCA, CAB, CBA”.
|
|
||||||
|
|
||||||
- Right shift the elements before making the recursive call.
|
|
||||||
- Left shift the elements while backtracking.
|
|
||||||
|
|
||||||
```python
|
|
||||||
def permute(S, i):
|
|
||||||
if i == len(S):
|
|
||||||
print(S)
|
|
||||||
for j in range(i, len(S)):
|
|
||||||
S[i], S[j] = S[j], S[i]
|
|
||||||
permute(S, i+1)
|
|
||||||
S[i], S[j] = S[j], S[i] # backtrack
|
|
||||||
```
|
|
||||||
|
|
||||||
__Time Complexity:__ _O(n* n*n!)_ <br>
|
|
||||||
__Space Complexity:__ _O(n)_
|
|
||||||
|
|
||||||
Kth Permutation Sequence (Optional)
|
|
||||||
----
|
|
||||||
> Given a string of length n containing lowercase alphabets only. You have to find the k-th permutation of string lexicographically.
|
|
||||||
|
|
||||||
|
|
||||||
$\dfrac{k}{(n-1)!}$ will give us the index of the first digit. Remove that digit, and continue.
|
|
||||||
|
|
||||||
```python
|
|
||||||
def get_perm(A, k):
|
|
||||||
perm = []
|
|
||||||
while A:
|
|
||||||
# get the index of current digit
|
|
||||||
div = factorial(len(A)-1)
|
|
||||||
i, k = divmod(k, div)
|
|
||||||
perm.append(A[i])
|
|
||||||
# remove handled number
|
|
||||||
del A[index]
|
|
||||||
return perm
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Sorted Permutation Rank (Optional)
|
|
||||||
--
|
|
||||||
> Given S, find the rank of the string amongst its permutations sorted lexicographically.
|
|
||||||
Assume that no characters are repeated.
|
|
||||||
|
|
||||||
```python
|
|
||||||
Input : 'acb'
|
|
||||||
Output : 2
|
|
||||||
The order permutations with letters 'a', 'c', and 'b' :
|
|
||||||
abc
|
|
||||||
acb
|
|
||||||
bac
|
|
||||||
bca
|
|
||||||
cab
|
|
||||||
cba
|
|
||||||
```
|
|
||||||
**Hint:**
|
|
||||||
If the first character is X, all permutations which had the first character less than X would come before this permutation when sorted lexicographically.
|
|
||||||
|
|
||||||
Number of permutation with a character C as the first character = number of permutation possible with remaining $N-1$ character = $(N-1)!$
|
|
||||||
|
|
||||||
**Approach:**
|
|
||||||
rank = number of characters less than first character * (N-1)! + rank of permutation of string with the first character removed
|
|
||||||
```
|
|
||||||
Lets say out string is “VIEW”.
|
|
||||||
Character 1 : 'V'
|
|
||||||
All permutations which start with 'I', 'E' would come before 'VIEW'.
|
|
||||||
Number of such permutations = 3! * 2 = 12
|
|
||||||
Lets now remove ‘V’ and look at the rank of the permutation ‘IEW’.
|
|
||||||
|
|
||||||
Character 2 : ‘I’
|
|
||||||
All permutations which start with ‘E’ will come before ‘IEW’
|
|
||||||
Number of such permutation = 2! = 2.
|
|
||||||
Now, we will limit ourself to the rank of ‘EW’.
|
|
||||||
|
|
||||||
Character 3:
|
|
||||||
‘EW’ is the first permutation when the 2 permutations are arranged.
|
|
||||||
So, we see that there are 12 + 2 = 14 permutations that would come before "VIEW".
|
|
||||||
Hence, rank of permutation = 15.
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
@ -1,111 +0,0 @@
|
|||||||
Number of Squareful Arrays
|
|
||||||
--------------------------
|
|
||||||
> Given A[N]
|
|
||||||
> array is squareful if for every pair of adjacent elements, their sum is a perfect square
|
|
||||||
> Find and return the number of permutations of A that are squareful
|
|
||||||
>
|
|
||||||
Example:
|
|
||||||
A = [2, 2, 2]
|
|
||||||
output: 1
|
|
||||||
A = [1, 17, 8]
|
|
||||||
output: 2
|
|
||||||
[1, 8, 17], [17, 8, 1]
|
|
||||||
|
|
||||||
|
|
||||||
```python
|
|
||||||
def check(a, b):
|
|
||||||
sq = int((a + b) ** 0.5)
|
|
||||||
return (sq * sq) == (a + b)
|
|
||||||
|
|
||||||
if len(A) == 1: # corner case
|
|
||||||
return int(check(A[0], 0))
|
|
||||||
|
|
||||||
count = 0
|
|
||||||
def permute_distinct(S, i):
|
|
||||||
global count
|
|
||||||
if i == len(S):
|
|
||||||
count += 1
|
|
||||||
|
|
||||||
for j in range(i, len(S)):
|
|
||||||
if S[j] in S[i:j]: # prevent duplicates
|
|
||||||
continue
|
|
||||||
|
|
||||||
if i > 0 and (not check(S[j], S[i-1])): # invalid solution - branch and bound
|
|
||||||
continue
|
|
||||||
|
|
||||||
S[i], S[j] = S[j], S[i]
|
|
||||||
permute_distinct(S, i+1)
|
|
||||||
|
|
||||||
S[i], S[j] = S[j], S[i] # backtrack
|
|
||||||
permute_distinct(A, 0)
|
|
||||||
return count
|
|
||||||
```
|
|
||||||
|
|
||||||
Gray Code
|
|
||||||
---------
|
|
||||||
|
|
||||||
> Given a non-negative integer N representing the total number of bits in the code, print the sequence of gray code. A gray code sequence must begin with 0.
|
|
||||||
|
|
||||||
> The gray code is a binary numeral system where two successive values differ in only one bit.
|
|
||||||
|
|
||||||
|
|
||||||
G(n+1) can be constructed as:
|
|
||||||
0 G(n)
|
|
||||||
1 R(n)
|
|
||||||
|
|
||||||
```
|
|
||||||
Example G(2) to G(3):
|
|
||||||
0 00
|
|
||||||
0 01
|
|
||||||
0 11
|
|
||||||
0 10
|
|
||||||
----
|
|
||||||
1 10
|
|
||||||
1 11
|
|
||||||
1 01
|
|
||||||
1 00
|
|
||||||
```
|
|
||||||
```python
|
|
||||||
def gray(self, n):
|
|
||||||
codes = [0, 1] # length 1
|
|
||||||
for i in range(1, n):
|
|
||||||
new_codes = [s | (1 << i) for s in reversed(codes)]
|
|
||||||
codes += new_codes
|
|
||||||
return codes
|
|
||||||
```
|
|
||||||
|
|
||||||
N Queens
|
|
||||||
--------
|
|
||||||
|
|
||||||
[NQueens - InterviewBit](https://www.interviewbit.com/problems/nqueens/)
|
|
||||||
Backtracking
|
|
||||||
- Place one queen per row
|
|
||||||
- backtrack if failed
|
|
||||||
|
|
||||||
Word Break II
|
|
||||||
-------------
|
|
||||||
> Given a string A and a dictionary of words B, add spaces in A to construct a sentence where each word is a valid dictionary word.
|
|
||||||
```
|
|
||||||
Input 1:
|
|
||||||
A = "catsanddog",
|
|
||||||
B = ["cat", "cats", "and", "sand", "dog"]
|
|
||||||
|
|
||||||
Output 1:
|
|
||||||
["cat sand dog", "cats and dog"]
|
|
||||||
```
|
|
||||||
```python
|
|
||||||
def wordBreak(A, B):
|
|
||||||
B = set(B)
|
|
||||||
sents = []
|
|
||||||
def foo(i, start, sent):
|
|
||||||
word = A[start:i+1]
|
|
||||||
if i == len(A):
|
|
||||||
if word in B:
|
|
||||||
sents.append((sent + ' ' + word).strip())
|
|
||||||
return
|
|
||||||
|
|
||||||
if word in B:
|
|
||||||
foo(i+1, i+1, sent + ' ' + word)
|
|
||||||
foo(i+1, start, sent)
|
|
||||||
foo(0, 0, '')
|
|
||||||
```
|
|
152
Sorting/1.md
Normal file
152
Sorting/1.md
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
# Sorting
|
||||||
|
|
||||||
|
- define sorting: permuting the sequence to enforce order. todo
|
||||||
|
- brute force: $O(n! \times n)$
|
||||||
|
|
||||||
|
|
||||||
|
Stability
|
||||||
|
---------
|
||||||
|
- definition: if two objects have the same value, they must retain their original order after sort
|
||||||
|
- importance:
|
||||||
|
- preserving order - values could be orders and chronological order may be important
|
||||||
|
- sorting tuples - sort on first column, then on second column
|
||||||
|
|
||||||
|
-- --
|
||||||
|
|
||||||
|
Insertion Sort
|
||||||
|
--------------
|
||||||
|
- explain:
|
||||||
|
- 1st element is sorted
|
||||||
|
- invariant: for i, the array uptil i-1 is sorted
|
||||||
|
- take the element at index i, and insert it at correct position
|
||||||
|
- pseudo code:
|
||||||
|
```c++
|
||||||
|
void insertionSort(int arr[], int length) {
|
||||||
|
int i, j, key;
|
||||||
|
for (i = 1; i < length; i++) {
|
||||||
|
key = arr[i];
|
||||||
|
j = i-1;
|
||||||
|
while (j >= 0 && arr[j] > key) {
|
||||||
|
arr[j+1] = arr[j];
|
||||||
|
j--;
|
||||||
|
}
|
||||||
|
arr[j + 1] = key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **Stablility:** Stable, because swap only when strictly >. Had it been >=, it would be unstable
|
||||||
|
- **Complexity:** $O(n^2)$
|
||||||
|
|
||||||
|
-- --
|
||||||
|
|
||||||
|
|
||||||
|
Bubble Sort
|
||||||
|
-----------
|
||||||
|
- explain:
|
||||||
|
- invariant: last i elements are the largest one and are in correct place.
|
||||||
|
- why "bubble": largest unsorted element bubbles up - just like bubbles
|
||||||
|
- pseudo code:
|
||||||
|
```c++
|
||||||
|
void bubbleSort(int arr[], int n) {
|
||||||
|
for (int i = 0; i < n-1; i++)
|
||||||
|
for (int j = 0; j < n-i-1; j++)
|
||||||
|
if (arr[j] > arr[j+1])
|
||||||
|
swap(&arr[j], &arr[j+1]);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **Stability:** Stable
|
||||||
|
- **Complexity:** $O(n^2)$
|
||||||
|
|
||||||
|
-- --
|
||||||
|
|
||||||
|
|
||||||
|
Bubble Sort with window of size 3
|
||||||
|
---------------------------------
|
||||||
|
- explain bubble sort as window of size 2
|
||||||
|
- propose window of size 3
|
||||||
|
- does this work?
|
||||||
|
- no - even and odd elements are never compared
|
||||||
|
|
||||||
|
|
||||||
|
-- --
|
||||||
|
|
||||||
|
|
||||||
|
Counting Sort
|
||||||
|
-------------
|
||||||
|
- explain:
|
||||||
|
- given array, first find min and max in O(n) time
|
||||||
|
- create space of O(max-min)
|
||||||
|
- count the number of elements
|
||||||
|
- take prefix sum
|
||||||
|
- constraint: can only be used when the numbers are bounded.
|
||||||
|
- pseudo code:
|
||||||
|
```c++
|
||||||
|
void counting_sort(char arr[]) {
|
||||||
|
// find min, max
|
||||||
|
// create output space
|
||||||
|
// count elements
|
||||||
|
// take prefix sum
|
||||||
|
// To make it stable we are operating in reverse order.
|
||||||
|
for (int i = n-1; i >= 0; i--) {
|
||||||
|
output[count[arr[i]] - 1] = arr[i];
|
||||||
|
-- count[arr[i]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **Stability:** Stable, if imlpemented correctly
|
||||||
|
- **Complexity**: $O(n + \max(a[i]))$
|
||||||
|
- why not just put the element there? if numbers/value, can do. Else, could be objects
|
||||||
|
|
||||||
|
-- --
|
||||||
|
|
||||||
|
|
||||||
|
Radix Sort
|
||||||
|
----------
|
||||||
|
- sort elements from lowest significant to most significant values
|
||||||
|
- explain: basically counting sort on each bit / digit
|
||||||
|
- **Stability:** inherently stable - won't work if unstable
|
||||||
|
- **complexity:** $O(n \log\max a[i])$
|
||||||
|
|
||||||
|
-- --
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Partition Array
|
||||||
|
---------------
|
||||||
|
> Array of size $n$
|
||||||
|
> Given $k$, $k <= n$
|
||||||
|
> Partition array into two parts $A, ||A|| = k$ and $B, ||B|| = n-k$ elements, such that $|\sum A - \sum B|$ is maximized
|
||||||
|
|
||||||
|
- Sort and choose smallest k?
|
||||||
|
- Counterexample
|
||||||
|
```
|
||||||
|
1 2 3 4 5
|
||||||
|
k = 3
|
||||||
|
|
||||||
|
bad: {1, 2, 3}, {4, 5}
|
||||||
|
good: {1, 2}, {3, 4, 5}
|
||||||
|
```
|
||||||
|
- choose based on n/2 - because we want the small sum to be smaller, so choose less elements, and the larger sum to be larger, so choose more elements
|
||||||
|
|
||||||
|
-- --
|
||||||
|
|
||||||
|
|
||||||
|
Sex-Tuples
|
||||||
|
----------
|
||||||
|
> Given A[n], all distinct
|
||||||
|
> find the count of sex-tuples such that
|
||||||
|
> $$\frac{a b + c}{d} - e = f$$
|
||||||
|
> Note: numbers can repeat in the sextuple
|
||||||
|
|
||||||
|
- Naive: ${n \choose 6} = O(n^6)$
|
||||||
|
- Optimization. Rewrite the equation as $ab + c = d(e + f)$
|
||||||
|
- Now, we only need ${n \choose 3} = O(n^3)$
|
||||||
|
- Caution: $d \neq 0$
|
||||||
|
- Once you have array of RHS, sort it in $O(\log n^3)$ time.
|
||||||
|
- Then for each value of LHS, count using binary search in the sorted array in $\log n$ time.
|
||||||
|
- Total: $O(n^3 \log n)$
|
||||||
|
|
||||||
|
-- --
|
||||||
|
|
||||||
|
Anagrams
|
||||||
|
--------
|
139
Sorting/2.md
Normal file
139
Sorting/2.md
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
|
||||||
|
# Sorting 2
|
||||||
|
-- --
|
||||||
|
|
||||||
|
Merge Sort
|
||||||
|
----------
|
||||||
|
- Divide and Conquer
|
||||||
|
- didive into 2
|
||||||
|
- sort individually
|
||||||
|
- combine the solution
|
||||||
|
- Merging takes $O(n+m)$ time.
|
||||||
|
- needs extra space
|
||||||
|
- code for merging:
|
||||||
|
```c++
|
||||||
|
// arr1[n1]
|
||||||
|
// arr2[n2]
|
||||||
|
int i = 0, j = 0, k = 0;
|
||||||
|
|
||||||
|
// output[n1+n2]
|
||||||
|
|
||||||
|
while (i<n1 && j <n2) {
|
||||||
|
if (arr1[i] <= arr2[j]) // if <, then unstable
|
||||||
|
output[k++] = arr1[i++];
|
||||||
|
else
|
||||||
|
output[k++] = arr2[j++];
|
||||||
|
}
|
||||||
|
// only one array can be non-empty
|
||||||
|
while (i < n1)
|
||||||
|
output[k++] = arr1[i++];
|
||||||
|
|
||||||
|
while (j < n2)
|
||||||
|
output[k++] = arr2[j++];
|
||||||
|
```
|
||||||
|
- stable? Yes
|
||||||
|
- in-place? No
|
||||||
|
- Time complexity recurrence: $T(n) = 2T(n/2) + O(n)$
|
||||||
|
- Solve by Master Theorem.
|
||||||
|
- Solve by algebra
|
||||||
|
- Solve by Tree height ($\log n$) * level complexity ($O(n)$)
|
||||||
|
|
||||||
|
|
||||||
|
-- --
|
||||||
|
|
||||||
|
|
||||||
|
Intersection of sorted arrays
|
||||||
|
-----------------------------
|
||||||
|
> 2 sorted arrays
|
||||||
|
> ```
|
||||||
|
> 1 2 2 3 4 9
|
||||||
|
> 2 3 3 9 9
|
||||||
|
>
|
||||||
|
> intersection: 2 3 9
|
||||||
|
> ```
|
||||||
|
- calculate intersection. Report an element only once
|
||||||
|
- Naive:
|
||||||
|
- Search each element in the other array. $O(n \log m)$
|
||||||
|
- Optimied:
|
||||||
|
- Use merge.
|
||||||
|
- Ignore unequal.
|
||||||
|
- Add equal.
|
||||||
|
- Move pointer ahead till next element
|
||||||
|
|
||||||
|
|
||||||
|
-- --
|
||||||
|
|
||||||
|
|
||||||
|
Merging without extra space
|
||||||
|
---------------------------
|
||||||
|
- can use extra time
|
||||||
|
- if a[i] < b[j], i++
|
||||||
|
- else: swap put b[i] in place of a[i]. Sorted insert a[i] in b array
|
||||||
|
- so, $O(n^2)$ time
|
||||||
|
|
||||||
|
|
||||||
|
-- --
|
||||||
|
|
||||||
|
|
||||||
|
Count inversions
|
||||||
|
---------------
|
||||||
|
> inversion:
|
||||||
|
> i < j, but a[i] > a[j] (strict inequalities)
|
||||||
|
- naive: $O(n^2)$
|
||||||
|
- Split array into 2.
|
||||||
|
- Number of inversions = number of inversions in A + B + cross terms
|
||||||
|
- count the cross inversions by example
|
||||||
|
- does number of cross inversions change when sub-arrays are permuted?
|
||||||
|
- no
|
||||||
|
- can we permute so that it becomes easier to count cross inversions?
|
||||||
|
- sort both subarrays and count inversions in A, B recursively
|
||||||
|
- then, merge A and B and during the merge count the number of inversions
|
||||||
|
- A_i B_j
|
||||||
|
- if A[i] > B[j], then there are inversions
|
||||||
|
- num inversions for A[i], B[j] = |A| - i
|
||||||
|
- intra array inversions? Counted in recursive case.
|
||||||
|
|
||||||
|
|
||||||
|
-- --
|
||||||
|
|
||||||
|
|
||||||
|
Find doubled-inversions
|
||||||
|
-----------------------
|
||||||
|
> same as inversion
|
||||||
|
> just i < j, a[i] > 2 * a[j]
|
||||||
|
|
||||||
|
- same as previous. Split and recursilvely count
|
||||||
|
- while merging, for some b[j], I need to find how many elements in A are greater than 2 * b[j]
|
||||||
|
- linear search for that, but keep index
|
||||||
|
- linear search is better than binary search
|
||||||
|
|
||||||
|
|
||||||
|
-- --
|
||||||
|
|
||||||
|
Sort n strings of length n each
|
||||||
|
- $T(n) = 2T(n/2) + O(n^2) = O(n^2)$ is wrong
|
||||||
|
- $T(n) = 2T(n/2) + O(n) * O(m) = O(nm\log n)$ is correct. Here m = the initial value of n
|
||||||
|
|
||||||
|
|
||||||
|
-- --
|
||||||
|
> .
|
||||||
|
>
|
||||||
|
> I G N O R E
|
||||||
|
>
|
||||||
|
> .
|
||||||
|
|
||||||
|
|
||||||
|
Bounded Subarray Sum Count
|
||||||
|
--------------------------
|
||||||
|
> given A[N]
|
||||||
|
> can have -ve
|
||||||
|
> given lower <= upper
|
||||||
|
> find numbe of subarrays such that lower <= sum <= upper
|
||||||
|
|
||||||
|
- naive: $O(n^2)$ (keep prefix sum to calculate sum in O(1), n^2 loop)
|
||||||
|
- if only +ve, $O(n\log n)$ using prefix sum
|
||||||
|
- but what if -ve?
|
||||||
|
-
|
||||||
|
|
||||||
|
|
||||||
|
-- --
|
Loading…
x
Reference in New Issue
Block a user