Skip to content

Commit 2c85ac1

Browse files
More practice problems
1 parent f6753cc commit 2c85ac1

4 files changed

Lines changed: 185 additions & 1 deletion

File tree

OMSCS/Courses/GA/Practice Problems/6.2 - Hotel Stops.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ So for each hotel $A_i$, you need to calculate the penalty of stopping at that h
7878
- Is there a risk with using a greedy algorithm? You want to travel exactly 200 miles in a day. Sometimes it might be worth stopping early for a larger penalty to avoid a bigger penalty later. Does the math check out? The penalty is the distance squared.
7979
- Making the algorithm $n^2$ seems to account for possible greediness.
8080

81-
$P(i)=min\{(200-A_i)^2,\frac{min}{0 \le j \lt i}\{P(j)+(200-(A_i-A_j))^2\}\}$
81+
$P(i)=min\bigg\{(200-A_i)^2,min\space\Big\{P(j)+\big(200-A_i+A_j\big)^2:0 \le j \lt i\Big\}\bigg\}$
8282

8383
## Code
8484
```python

OMSCS/Courses/GA/Practice Problems/6.3 - Yuck Donald's.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,76 @@ tags:
77
# 6.3 - Yuck Donald's
88
![[Pasted image 20260114125811.png]]
99

10+
- Inputs:
11+
- $\{M=m_1,m_2,...,m_n\}$
12+
- An increasing sequence of positive integers indicating positions along QVH which can accept the placement of a single YD.
13+
- Likely excludes $m_0=0$, the start of QVH, which doesn't support the placement of a YD on its own. That would require $m_1=0$.
14+
- $\{P=p_1,p_2,...,p_n\}$ A sequence of profits, where $p_i$ is the expected profit for opening a YD at location $m_i$.
15+
- $k$ - A positive integer indicating the minimum distance between YD locations. This is set by corporate, as part of the franchisee agreement.
16+
- Outputs:
17+
- The max expected profit.
18+
- The list of indices of M/P
19+
20+
## Step 1: Define the Subproblem in Words
21+
```
22+
Given
23+
a list of mile-markers "M" of length n
24+
a list of expected profits "P" of length n
25+
an integer "k"
26+
for i=1 -> n
27+
28+
TP(i) = the total max profit of the subsequence of YD locations in (m_1, p_1), ..., (m_i, p_i) which includes (m_i, p_i)
29+
```
30+
31+
This definition is sloppy, but will probably work.
32+
33+
## Step 2: Define the Recurrence Relation
34+
This is similar to [[6.2 - Hotel Stops]], in fact, I'm seeing now why the instructor said that practicing DP algorithms was important. 99% of the trick is "make sure that the subproblem you're solving always includes the last element in the input."
35+
36+
- Base case, the total max profit for a given mile-marker $M(i)$ is just the potential profit of that mile-marker $P(i)$, assuming that no prior YD's can be built before it.
37+
- Assuming that there are mile-markers $M(j:1 \le j<i)$ with values at least $k$ less than $M(i)$, we need to take the maximum of $TP(j) + P(i)$, wrt the current best $P(i)$ (including the base-case).
38+
- If we initialize $TP$ to $P$, we can omit the outer $max$.
39+
$TP(i)=max\space \bigg\{P(i), \space max \space \Big\{ TP(j) + P(i) : \big(1 \le j \lt i\big) \text{ and } \big(M(i) \ge M(j) + k \big)\Big\} \bigg\}$
40+
## Code
41+
```python
42+
def yuck_donalds_optimization(
43+
mile_markers: tuple[int, ...],
44+
expected_profits: tuple[int, ...],
45+
min_distance_between: int,
46+
):
47+
# Start by calculating the potential profits for opening only a single
48+
# YD at each mile marker
49+
potential_profits = [p for p in expected_profits]
50+
51+
# Keeps track of the index of the preceeding mile-marker that contains
52+
# a YD location, allowing for an efficient reconstruction of the list
53+
# of YD locations. -1 indicates mile-marker 0, which cannot inherently
54+
# contain a YD location (unless MM_0 happens to be 0).
55+
previous_yd_location = [-1 for _ in mile_markers]
56+
57+
max_profit = 0
58+
max_profix_idx = -1
59+
60+
# Determine the maximum profit of opening a YD at
61+
for i in range(len(mile_markers)):
62+
for j in range(i):
63+
if (mile_markers[i] - min_distance_between) < mile_markers[j]:
64+
break
65+
66+
potential_profit_i = potential_profits[j] + expected_profits[i]
67+
if potential_profits[i] < potential_profit_i:
68+
potential_profits[i] = potential_profit_i
69+
previous_yd_location[i] = j
70+
71+
if potential_profits[i] > max_profit:
72+
max_profit = potential_profits[i]
73+
max_profix_idx = i
74+
75+
# Reconstruct the route.
76+
yd_indices = [max_profix_idx]
77+
while previous_yd_location[yd_indices[0]] != -1:
78+
yd_indices.insert(0, previous_yd_location[yd_indices[0]])
79+
80+
return max_profit, yd_indices
81+
82+
```

OMSCS/Courses/GA/Practice Problems/6.4 - String of Words.md

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,114 @@ tags:
55
- Practice
66
---
77
# 6.4 - String of Words
8+
![[Pasted image 20260116141341.png]]
9+
10+
## Exploration
11+
First, we need some examples. Specifically we need an example which will help identify how the DP algorithm performs backtracking. This will occur in cases where a word that is identified earlier needs to be broken up to form
12+
13+
1. one whole word, and the beginning of the next word
14+
2. two or more whole words
15+
3. two or more whole words, and the beginning of the next word
16+
17+
For case 2, this algorithm doesn't seek to identify grammatically correct sentences, just strings of whole words that exist in a given dictionary. Therefore, case 2 won't actually result in any backtracking.
18+
19+
If we don't have such an example, then we may accidentally develop a greedy $O(n)$ algorithm, which runs forward collecting letters into the longest word available in the dictionary.
20+
21+
### Greedy Algorithm Failure Example
22+
To avoid coming up with an example which uses real words, let's review this contrived one.
23+
24+
- $dict(w)=w \in \{a, ab, bc\}$
25+
- $S=abc$
26+
27+
In this example, a greedy forward-only DP algorithm with no backtracking would find the following substrings. How do we force $s(3)$ to find $T,\{a, bc\}$? In addition to $s(2)=T,\{ab\}$, we would also need to keep track of $s(2)=F,\{a,b\}$.
28+
29+
- $s(1)=T,\{a\}$
30+
- $s(2)=T,\{ab\}$
31+
- $s(3)=F,\{ab,c\} \leftarrow \text{error}$
32+
33+
### Ambiguity Example
34+
Note that there is possible ambiguity for a given dictionary and substring. In this contrived example below, there are many possible input word strings that can construct the given $S$.
35+
36+
- $dict(w)=w \in \{a, b, c, d, ab, bc, cd, abc, bcd, abcd\}$
37+
- $S=abcdabcdabcd$
38+
39+
Sample of possible results:
40+
- $\{a,b,c,d,a,b,c,d,a,b,c,d\}$
41+
- $\{ab,cd,ab,cd,ab,cd\}$
42+
- $\{abcd,abcd,abcd\}$
43+
- $\{abc,d,a,bcd,abcd\}$
44+
- $\{abc,d,a,bcd,ab,cd\}$
45+
- $\{a,b,c,d,abcd,abc,d\}$
46+
- ...
47+
48+
This problem only asks whether a given string "can be reconstituted as a series of valid words." This allows for the solution to be less complicated, though it's likely possible to construct an algorithm which returns all possible results while staying within a DP solution space.
49+
50+
## Step 1: Define the Subproblem in Words
51+
```
52+
Given
53+
a function dict(S') which returns T or F for arbitrary length S'
54+
a sequence of characters S of length n
55+
56+
for i=1 -> n:
57+
for j=1 -> i:
58+
VS(i,j) contains T or F, indicating whether S_1, ..., S_j contains valid word strings.
59+
```
60+
61+
Why does this need to be 2D? What am I using $i$ for? Let's simplify (I found an example solution online.)
62+
63+
```
64+
Given
65+
a sequence of characters S of length n
66+
a function dict(S'), which returns T or F for arbitrary lengh contiguous subsequence (i.e. substring) S' of S
67+
68+
for i=1 -> n:
69+
VS(i) returns T or F, indicating whether S_1, ..., S_i only contains sequential valid word strings.
70+
P(i) returns the ending index of the prior word.
71+
```
72+
73+
## Step 2: Define the Recurrence Relation
74+
For $VS(i)$, we need to find a previous index $0\lt j \lt i$ for which there are only valid words ($VS(j)=True$). For convenience and completeness, we can assert that $VS(0)=True$, because an empty string contains no _invalid_ words. Then we need to check $dict(\{s_{j+1},\space ... \space,s_i\})$. If $True$, $VS(i)=T$ and $P(i)=j$. If there is no $j$ for which $dict(s_{j+1},\space ... \space, s_i)$, then $VS(i)=False$.
75+
76+
It's unclear to me how you would convert this into a clean mathematical expression. Anyway, here's the code.
77+
## Code
78+
This code models `is_in_dict` as a function which takes $S$, along with the starting and ending indexes of a given substring. This allows for the dictionary to be modeled as a trie, or some other graph structure, as opposed to a hash-table with non-guaranteed $O(1)$ lookup performance. Even with guaranteed $O(1)$ lookup performance, you still need to build substrings from each given range.
79+
80+
```python
81+
import typing
82+
83+
character = int | str
84+
string = list[character] | tuple[character]
85+
86+
def string_of_words(
87+
S: string,
88+
is_in_dict: typing.Callable[[string, int, int], bool]
89+
) -> tuple[bool, list[string] | None]:
90+
VS: list[bool] = [False for _ in S]
91+
P: list[int] = [-1 for _ in S]
92+
93+
for i in range(len(S)):
94+
# Iterating j forward will tend to find longer words.
95+
# Iterating j in reverse will tend to find shorter words.
96+
# If we want to find all possible strings of words, we probably
97+
# need to use 2D "VS" and "P" arrays.
98+
for j in range(-1, i):
99+
if j == -1 or VS[j]:
100+
if is_in_dict(S, j+1, i):
101+
VS[i] = True
102+
P[i] = j
103+
break
104+
105+
if not VS[-1]:
106+
return False, None
107+
108+
sentence: list[string] = []
109+
current_end = len(P)
110+
previous_end = P[-1]
111+
while True:
112+
sentence.insert(0, list(S[previous_end+1:current_end]))
113+
current_end = previous_end+1
114+
if previous_end == -1:
115+
return True, sentence
116+
previous_end = P[previous_end]
117+
118+
```
172 KB
Loading

0 commit comments

Comments
 (0)