Lecture 1 (Easy Problems)¶
Assign Cookies¶
Consider a scenario where a teacher wants to distribute cookies to students, with each student receiving at most one cookie. Given two arrays, student and cookie, the ith value in the student array describes the minimum size of cookie that the ith student can be assigned. The jth value in the cookie array represents the size of the jth cookie. If cookie[j] >= student[i], the jth cookie can be assigned to the ith student. Maximize the number of students assigned with cookies and output the maximum number.
Input : Student = [1, 2, 3] , Cookie = [1, 1]
Output :1
Explanation : Only the first cookie (1) satisfies the first student (1), therefore only 1 student is content.
Input : Student = [1, 2] , Cookie = [1, 2, 3]
Output : 2
Explanation : Cookie 1 satisfies student 1 and cookie 2 satisfies student 2. Therefore, 2 students are content.
Approach 1 (Recursion Method without memorization)¶
- we will sort the students and cookies in ASC so we know to even go for next cookie or not
- we will build a recursive count cookies helper method at any cookie idx and student idx
- at any time , there are no more students or cookies , return 0
- there are only 2 branches
- Branch1 : when cookie can be picked
- Then result= 1+ max( count(next student, next cookie), count(student, next cookie))
- Branch 2 : when cookie cannot be picked
- result = cookie is not picked (next student , next cookie)
def assign_cookies(students,cookies):
students.sort()
cookies.sort()
S = len(students)
C = len(cookies)
def cookie_count(student_idx,cookie_idx):
# all the students are checked
if student_idx == S:
return 0
# all the cookies are checked
if cookie_idx == C:
return 0
count =0
cookie_size = cookies[cookie_idx]
min_size = students[student_idx]
# if cookie can be used
if cookie_size >= min_size:
# if cookie is assigned
count = 1 + cookie_count(student_idx+1,cookie_idx+1)
# if cookies cannot be used,check the next cookie
else:
count = cookie_count(student_idx,cookie_idx +1)
return count
return cookie_count(0,0)
print(assign_cookies([1,2,3],[1,1]))
print(assign_cookies([1,2],[1,2,3]))
1 2
Complexity¶
S = len(students) C = len(Cookies)
O(2^ (S+C))
O((S+C))
Approach 2 (More pythonic and cleaner)¶
def assign_cookies(students,cookies):
students.sort()
cookies.sort()
S = len(students)
C = len(cookies)
def cookie_count(student_idx,cookie_idx):
if student_idx == S or cookie_idx == C:
return 0
if cookies[cookie_idx] >= students[student_idx]:
return 1+cookie_count(student_idx+1,cookie_idx+1)
return cookie_count(student_idx+1,cookie_idx+1)
return cookie_count(0,0)
print(assign_cookies([1,2,3],[1,1]))
print(assign_cookies([1,2],[1,2,3]))
1 2
Approach 3 (Add memorization)¶
def assign_cookies(students,cookies):
S = len(students)
C = len(cookies)
students.sort()
cookies.sort()
memory = {}
def count_cookies(student_idx,cookie_idx):
if student_idx == S or cookie_idx == C:
return 0
key = f"{student_idx}_{cookie_idx}"
if key in memory:
return memory[key]
count =0
# cookie can be picked
if students[student_idx] <= cookies[cookie_idx]:
count = 1+count_cookies(student_idx+1,cookie_idx+1)
else:
count = count_cookies(student_idx,cookie_idx+1)
memory[key] = count
return count
return count_cookies(0,0)
print(assign_cookies([1,2,3],[1,1]))
print(assign_cookies([1,2],[1,2,3]))
1 2
Complexity¶
S = len(students) and C = len(cookies)
Memorization (SC) , Sorting (S log S + C log C) => Total (O(S log S) + O(C log C) + O(S C) ) => O(S*C)
Memorization (SC) , Recustion (S+C) => total (O(SC) + O(S+C)) => O(S*C)
Approach 5 (Use 2D array for memory)¶
def assign_cookies(students,cookies):
S = len(students)
C = len(cookies)
students.sort()
cookies.sort()
memory = [[-1 for _ in range(C)] for _ in range(S)]
def count_cookies(student_idx,cookie_idx):
if student_idx == S or cookie_idx == C:
return 0
if memory[student_idx][cookie_idx] != -1:
return memory[student_idx][cookie_idx]
count =0
# cookie can be picked
if students[student_idx] <= cookies[cookie_idx]:
count = max(1+count_cookies(student_idx+1,cookie_idx+1),count_cookies(student_idx,cookie_idx+1))
else:
count = count_cookies(student_idx,cookie_idx+1)
memory[student_idx][cookie_idx] = count
return count
return count_cookies(0,0)
print(assign_cookies([1,2,3],[1,1]))
print(assign_cookies([1,2],[1,2,3]))
1 2
Complexity¶
- O(S*C)
- O(S*C)
Approach 5 (Backward recursion)¶
def assign_cookies(students,cookies):
S = len(students)
C = len(cookies)
students.sort()
cookies.sort()
memory = [[-1 for _ in range(C+1)] for _ in range(S+1)]
def count_cookies(remaining_students,remaining_cookies):
if remaining_students ==0 or remaining_cookies ==0:
return 0
if memory[remaining_students][remaining_cookies] !=-1:
return memory[remaining_students][remaining_cookies]
count = 0
# try take cookie, skip cookie , skip student
if students[remaining_students-1] <= cookies[remaining_cookies-1]:
count = max(1+count_cookies(remaining_students-1,remaining_cookies-1),count_cookies(remaining_students-1,remaining_cookies),count_cookies(remaining_students,remaining_cookies-1))
# try skip cookie , skip student
else:
count = max(count_cookies(remaining_students,remaining_cookies-1),count_cookies(remaining_students-1,remaining_cookies))
memory[remaining_students][remaining_cookies] = count
return count
return count_cookies(S,C)
print(assign_cookies([1,2,3],[1,1]))
print(assign_cookies([1,2],[1,2,3]))
1 2
Complexity¶
O(S*C)
O(S*C)
Approach 5 (Tabulation)¶
def assign_cookies(students,cookies):
S = len(students)
C = len(cookies)
students.sort()
cookies.sort()
memory = [[0 for _ in range(C+1)] for _ in range(S+1)]
for remaining_students in range(S+1):
for remaining_cookies in range(C+1):
if remaining_students ==0 or remaining_cookies ==0:
memory[remaining_students][remaining_cookies] =0
elif students[remaining_students-1] <= cookies[remaining_cookies-1]:
memory[remaining_students][remaining_cookies] = max(1+ memory[remaining_students-1][remaining_cookies-1],memory[remaining_students-1][remaining_cookies],memory[remaining_students][remaining_cookies-1])
else:
memory[remaining_students][remaining_cookies] = max(memory[remaining_students][remaining_cookies-1],memory[remaining_students-1][remaining_cookies])
return memory[S][C]
print(assign_cookies([1,2,3],[1,1]))
print(assign_cookies([1,2],[1,2,3]))
1 2
Complexity¶
- O(S*C)
- O(S*C)
Approach 6 (Greedy Method)¶
def assign_cookies(students,cookies):
S = len(students)
C = len(cookies)
students.sort()
cookies.sort()
student_idx , cookie_idx = 0,0
while student_idx <S and cookie_idx <C:
if cookies[cookie_idx] >= students[student_idx]:
student_idx +=1
cookie_idx +=1
return student_idx
print(assign_cookies([1,2,3],[1,1]))
print(assign_cookies([1,2],[1,2,3]))
1 2
Complexity¶
O(S log S + C log C)
O(1)
Lemonade Change¶
Given an array representing a queue of customers and the value of bills they hold, determine if it is possible to provide correct change to each customer. Customers can only pay with 5$, 10$ or 20$ bills and we initially do not have any change at hand. Return true, if it is possible to provide correct change for each customer otherwise return false.
Example 1:
Input: bills = [5, 5, 5, 10, 20]
Output : true
First Customer pays 5$, no change required.
Second Customer pays 5$, no change required.
Third Customer pays 5$, no change required.
The Fourth Customer pays 10$, out of the three 5$ bills we have, we pay a 5$ bill and accept the 10$ bill.
Fifth Customer pays 20$, out of the two 5$ bills and one 10$ bill we have, we pay 15$ in change and have one 5$ bill left.
Example 2:
Input: bills = [5, 5, 10, 10, 20]
Output: False
Approach 1 (Greedy)¶
- we need to use $10 first , only then give away 5
- keep track of count_10 and count_5
def lemonade_change(nums):
count_10,count_5 =0,0
for num in nums:
if num == 20:
# prefer 10 +5
if count_10 >0 and count_5 >0:
count_10 -=1
count_5 -=1
elif count_5 >=3:
count_5 -=3
else:
return False
elif num ==10:
if count_5 >=1:
count_5 -=1
count_10 +=1
else:
return False
else:
count_5 +=1
return True
print(lemonade_change([5, 5, 5, 10, 20]))
print(lemonade_change([5, 5, 10, 10, 20]))
True False
Complexity¶
- N = len(nums)
O(N)
O(1)