01Dynamic Array Exercise
Source notebook: Dynamic Array Exercise.ipynb
Dynamic Array Exercise
In this exercise we will create our own Dynamic Array class!
We'll be using a built in library called ctypes. Check out the documentation for more info, but its basically going to be used here as a raw array from the ctypes module. If you find yourself very interested in it, check out: Ctypes Tutorial
Also...
A quick note on public vs private methods, we can use an underscore _ before the method name to keep it non-public. For example:
class M(object): def public(self): print 'Use Tab to see me!' def _private(self): print "You won't be able to Tab to see me!"
m = M()
m.public()
Use Tab to see me!
m._private()
You won't be able to see me!
Check out PEP 8 and the Python docs for more info on this!
Dynamic Array Implementation
import ctypes class DynamicArray(object): ''' DYNAMIC ARRAY CLASS (Similar to Python List) ''' def __init__(self): self.n = 0 # Count actual elements (Default is 0) self.capacity = 1 # Default Capacity self.A = self.make_array(self.capacity) def __len__(self): """ Return number of elements sorted in array """ return self.n def __getitem__(self,k): """ Return element at index k """ if not 0 <= k <self.n: return IndexError('K is out of bounds!') # Check it k index is in bounds of array return self.A[k] #Retrieve from array at index k def append(self, ele): """ Add element to end of the array """ if self.n == self.capacity: self._resize(2*self.capacity) #Double capacity if not enough room self.A[self.n] = ele #Set self.n index to element self.n += 1 def _resize(self,new_cap): """ Resize internal array to capacity new_cap """ B = self.make_array(new_cap) # New bigger array for k in range(self.n): # Reference all existing values B[k] = self.A[k] self.A = B # Call A the new bigger array self.capacity = new_cap # Reset the capacity def make_array(self,new_cap): """ Returns a new array with new_cap capacity """ return (new_cap * ctypes.py_object)()
# Instantiate arr = DynamicArray()
# Append new element arr.append(1)
# Check length len(arr)
1
# Append new element arr.append(2)
# Check length len(arr)
2
# Index arr[0]
1
arr[1]
2
Awesome, we made our own dynamic array! Play around with it and see how it auto-resizes. Try using the same sys.getsizeof() function we worked with previously!
Great Job!
02Anagram Check
Source notebook: Anagram Check - SOLUTION.ipynb
Anagram Solution
Problem
Given two strings, check to see if they are anagrams. An anagram is when the two strings can be written using the exact same letters (so you can just rearrange the letters to get a different phrase or word).
For example:
"public relations" is an anagram of "crap built on lies."
"clint eastwood" is an anagram of "old west action"
Note: Ignore spaces and capitalization. So "d go" is an anagram of "God" and "dog" and "o d g".
Solution
There are two ways of thinking about this problem, if two strings have the same frequency of letters/element (meaning each letter shows up the same number of times in both strings) then they are anagrams of eachother. On a similar vien of logic, if two strings are equal to each other once they are sorted, then they are also anagrams of each other.
You would be able to implement this second solution pretty easily in Python:
def anagram(s1,s2): # Remove spaces and lowercase letters s1 = s1.replace(' ','').lower() s2 = s2.replace(' ','').lower() # Return boolean for sorted match. return sorted(s1) == sorted(s2)
anagram('dog','god')
True
anagram('clint eastwood','old west action')
True
anagram('aa','bb')
False
Now the above sorting approach is simple, but is actually not optimal and in an interview setting you would probably be asked to implement a more manual solution involving just counting the number of letters in each string to test your ability to understand hash tables. Let's build out a fuller solution using counting and Python dictionaries:
def anagram2(s1,s2): # Remove spaces and lowercase letters s1 = s1.replace(' ','').lower() s2 = s2.replace(' ','').lower() # Edge Case to check if same number of letters if len(s1) != len(s2): return False # Create counting dictionary (Note could use DefaultDict from Collections module) count = {} # Fill dictionary for first string (add counts) for letter in s1: if letter in count: count[letter] += 1 else: count[letter] = 1 # Fill dictionary for second string (subtract counts) for letter in s2: if letter in count: count[letter] -= 1 else: count[letter] = 1 # Check that all counts are 0 for k in count: if count[k] != 0: return False # Otherwise they're anagrams return True
anagram2('dog','god')
True
anagram2('clint eastwood','old west action')
True
anagram2('dd','aa')
False
A quick note on the second solution, the use of defaultdict form the collections module would clean up this code quite a bit, and the final for loop could be built into the second for loop, but in the above implementation every step is very clear.
Test Your Solution
Run the cell below to test your solution
""" RUN THIS CELL TO TEST YOUR SOLUTION """ from nose.tools import assert_equal class AnagramTest(object): def test(self,sol): assert_equal(sol('go go go','gggooo'),True) assert_equal(sol('abc','cba'),True) assert_equal(sol('hi man','hi man'),True) assert_equal(sol('aabbcc','aabbc'),False) assert_equal(sol('123','1 2'),False) print "ALL TEST CASES PASSED" # Run Tests t = AnagramTest() t.test(anagram)
ALL TEST CASES PASSED
t.test(anagram2)
ALL TEST CASES PASSED
Good Job!
03Array Pair Sum
Source notebook: Array Pair Sum - SOLUTION.ipynb
Array Pair Sum
Problem
Given an integer array, output all the ** unique ** pairs that sum up to a specific value k.
So the input:
pair_sum([1,3,2,2],4)
would return 2 pairs:
(1,3)
(2,2)
NOTE: FOR TESTING PURPOSES< CHANGE YOUR FUNCTION SO IT OUTPUTS THE NUMBER OF PAIRS
Solution
The O(N) algorithm uses the set data structure. We perform a linear pass from the beginning and for each element we check whether k-element is in the set of seen numbers. If it is, then we found a pair of sum k and add it to the output. If not, this element doesn’t belong to a pair yet, and we add it to the set of seen elements.
The algorithm is really simple once we figure out using a set. The complexity is O(N) because we do a single linear scan of the array, and for each element we just check whether the corresponding number to form a pair is in the set or add the current element to the set. Insert and find operations of a set are both average O(1), so the algorithm is O(N) in total.
def pair_sum(arr,k): if len(arr)<2: return # Sets for tracking seen = set() output = set() # For every number in array for num in arr: # Set target difference target = k-num # Add it to set if target hasn't been seen if target not in seen: seen.add(num) else: # Add a tuple with the corresponding pair output.add( (min(num,target), max(num,target)) ) # FOR TESTING return len(output) # Nice one-liner for printing output #return '\n'.join(map(str,list(output)))
pair_sum([1,3,2,2],4)
2
Test Your Solution
""" RUN THIS CELL TO TEST YOUR SOLUTION """ from nose.tools import assert_equal class TestPair(object): def test(self,sol): assert_equal(sol([1,9,2,8,3,7,4,6,5,5,13,14,11,13,-1],10),6) assert_equal(sol([1,2,3,1],3),1) assert_equal(sol([1,3,2,2],4),2) print 'ALL TEST CASES PASSED' #Run tests t = TestPair() t.test(pair_sum)
ALL TEST CASES PASSED
Good Job!
04Find the Missing Element
Source notebook: Find the Missing Element - SOLUTION.ipynb
Find the Missing Element
Problem
Consider an array of non-negative integers. A second array is formed by shuffling the elements of the first array and deleting a random element. Given these two arrays, find which element is missing in the second array.
Here is an example input, the first array is shuffled and the number 5 is removed to construct the second array.
Input:
finder([1,2,3,4,5,6,7],[3,7,2,1,4,6])
Output:
5 is the missing number
Solution
The naive solution is go through every element in the second array and check whether it appears in the first array. Note that there may be duplicate elements in the arrays so we should pay special attention to it. The complexity of this approach is O(N^2), since we would need two for loops.
A more efficient solution is to sort the first array, so while checking whether an element in the first array appears in the second, we can do binary search (we'll learn about binary search in more detail in a future section). But we should still be careful about duplicate elements. The complexity is O(NlogN).
If we don’t want to deal with the special case of duplicate numbers, we can sort both arrays and iterate over them simultaneously. Once two iterators have different values we can stop. The value of the first iterator is the missing element. This solution is also O(NlogN). Here is the solution for this approach:
def finder(arr1,arr2): # Sort the arrays arr1.sort() arr2.sort() # Compare elements in the sorted arrays for num1, num2 in zip(arr1,arr2): if num1!= num2: return num1 # Otherwise return last element return arr1[-1]
arr1 = [1,2,3,4,5,6,7] arr2 = [3,7,2,1,4,6] finder(arr1,arr2)
5
In most interviews, you would be expected to come up with a linear time solution. We can use a hashtable and store the number of times each element appears in the second array. Then for each element in the first array we decrement its counter. Once hit an element with zero count that’s the missing element. Here is this solution:
import collections def finder2(arr1, arr2): # Using default dict to avoid key errors d=collections.defaultdict(int) # Add a count for every instance in Array 1 for num in arr2: d[num]+=1 # Check if num not in dictionary for num in arr1: if d[num]==0: return num # Otherwise, subtract a count else: d[num]-=1
arr1 = [5,5,7,7] arr2 = [5,7,7] finder2(arr1,arr2)
5
One possible solution is computing the sum of all the numbers in arr1 and arr2, and subtracting arr2’s sum from array1’s sum. The difference is the missing number in arr2. However, this approach could be problematic if the arrays are too long, or the numbers are very large. Then overflow will occur while summing up the numbers.
By performing a very clever trick, we can achieve linear time and constant space complexity without any problems. Here it is: initialize a variable to 0, then XOR every element in the first and second arrays with that variable. In the end, the value of the variable is the result, missing element in array2.
def finder3(arr1, arr2): result=0 # Perform an XOR between the numbers in the arrays for num in arr1+arr2: result^=num print result return result
finder3(arr1,arr2)
5 0 7 0 5 2 5 5
Test Your Solution
""" RUN THIS CELL TO TEST YOUR SOLUTION """ from nose.tools import assert_equal class TestFinder(object): def test(self,sol): assert_equal(sol([5,5,7,7],[5,7,7]),5) assert_equal(sol([1,2,3,4,5,6,7],[3,7,2,1,4,6]),5) assert_equal(sol([9,8,7,6,5,4,3,2,1],[9,8,7,5,4,3,2,1]),6) print 'ALL TEST CASES PASSED' # Run test t = TestFinder() t.test(finder)
ALL TEST CASES PASSED
Good Job!
05Largest Continuous Sum
Source notebook: Largest Continuous Sum - SOLUTION.ipynb
Largest Continuous Sum
Problem
Given an array of integers (positive and negative) find the largest continuous sum.
Solution
If the array is all positive, then the result is simply the sum of all numbers. The negative numbers in the array will cause us to need to begin checking sequences.
The algorithm is, we start summing up the numbers and store in a current sum variable. After adding each element, we check whether the current sum is larger than maximum sum encountered so far. If it is, we update the maximum sum. As long as the current sum is positive, we keep adding the numbers. When the current sum becomes negative, we start with a new current sum. Because a negative current sum will only decrease the sum of a future sequence. Note that we don’t reset the current sum to 0 because the array can contain all negative integers. Then the result would be the largest negative number.
Let's see the code:
def large_cont_sum(arr): # Check to see if array is length 0 if len(arr)==0: return 0 # Start the max and current sum at the first element max_sum=current_sum=arr[0] # For every element in array for num in arr[1:]: # Set current sum as the higher of the two current_sum=max(current_sum+num, num) # Set max as the higher between the currentSum and the current max max_sum=max(current_sum, max_sum) return max_sum
large_cont_sum([1,2,-1,3,4,10,10,-10,-1])
29
Many times in an interview setting the question also requires you to report back the start and end points of the sum. Keep this in mind and see if you can solve that problem, we'll see it in the mock interview section of the course!
Test Your Solution
from nose.tools import assert_equal class LargeContTest(object): def test(self,sol): assert_equal(sol([1,2,-1,3,4,-1]),9) assert_equal(sol([1,2,-1,3,4,10,10,-10,-1]),29) assert_equal(sol([-1,1]),1) print 'ALL TEST CASES PASSED' #Run Test t = LargeContTest() t.test(large_cont_sum)
ALL TEST CASES PASSED
Good Job!
06Sentence Reversal
Source notebook: Sentence Reversal - SOLUTION.ipynb
Sentence Reversal
Problem
Given a string of words, reverse all the words. For example:
Given:
'This is the best'
Return:
'best the is This'
As part of this exercise you should remove all leading and trailing whitespace. So that inputs such as:
' space here' and 'space here '
both become:
'here space'
Solution
We could take advantage of Python's abilities and solve the problem with the use of split() and some slicing or use of reversed:
def rev_word1(s): return " ".join(reversed(s.split())) #Or def rev_word2(s): return " ".join(s.split()[::-1])
rev_word1('Hi John, are you ready to go?')
'go? to ready you are John, Hi'
rev_word2('Hi John, are you ready to go?')
'go? to ready you are John, Hi'
While these are valid solutions, in an interview setting you'll have to work out the basic algorithm that is used. In this case what we want to do is loop over the text and extract words form the string ourselves. Then we can push the words to a "stack" and in the end opo them all to reverse. Let's see what this actually looks like:
def rev_word3(s): """ Manually doing the splits on the spaces. """ words = [] length = len(s) spaces = [' '] # Index Tracker i = 0 # While index is less than length of string while i < length: # If element isn't a space if s[i] not in spaces: # The word starts at this index word_start = i while i < length and s[i] not in spaces: # Get index where word ends i += 1 # Append that word to the list words.append(s[word_start:i]) # Add to index i += 1 # Join the reversed words return " ".join(reversed(words))
rev_word3(' Hello John how are you ')
'you are how John Hello'
rev_word3(' space before')
'before space'
If you want you can further develop this solution so its all manual, you can create your own reversal function.
Test Your Solution
""" RUN THIS CELL TO TEST YOUR SOLUTION """ from nose.tools import assert_equal class ReversalTest(object): def test(self,sol): assert_equal(sol(' space before'),'before space') assert_equal(sol('space after '),'after space') assert_equal(sol(' Hello John how are you '),'you are how John Hello') assert_equal(sol('1'),'1') print "ALL TEST CASES PASSED" # Run and test t = ReversalTest() t.test(rev_word)
ALL TEST CASES PASSED
Good Job!
07String Compression
Source notebook: String Compression -SOLUTION.ipynb
String Compression
Problem
Given a string in the form 'AAAABBBBCCCCCDDEEEE' compress it to become 'A4B4C5D2E4'. For this problem, you can falsely "compress" strings of single or double letters. For instance, it is okay for 'AAB' to return 'A2B1' even though this technically takes more space.
The function should also be case sensitive, so that a string 'AAAaaa' returns 'A3a3'.
Solution
Since Python strings are immutable, we'll need to work off of a list of characters, and at the end convert that list back into a string with a join statement.
The solution below should yield us with a Time and Space complexity of O(n). Let's take a look with careful attention to the explanatory comments:
def compress(s): """ This solution compresses without checking. Known as the RunLength Compression algorithm. """ # Begin Run as empty string r = "" l = len(s) # Check for length 0 if l == 0: return "" # Check for length 1 if l == 1: return s + "1" #Intialize Values last = s[0] cnt = 1 i = 1 while i < l: # Check to see if it is the same letter if s[i] == s[i - 1]: # Add a count if same as previous cnt += 1 else: # Otherwise store the previous data r = r + s[i - 1] + str(cnt) cnt = 1 # Add to index count to terminate while loop i += 1 # Put everything back into run r = r + s[i - 1] + str(cnt) return r
compress('AAAAABBBBCCCC')
'A5B4C4'
Test Your Solution
""" RUN THIS CELL TO TEST YOUR SOLUTION """ from nose.tools import assert_equal class TestCompress(object): def test(self, sol): assert_equal(sol(''), '') assert_equal(sol('AABBCC'), 'A2B2C2') assert_equal(sol('AAABCCDDDDD'), 'A3B1C2D5') print 'ALL TEST CASES PASSED' # Run Tests t = TestCompress() t.test(compress)
ALL TEST CASES PASSED
Good Job!
08Unique Characters in String
Source notebook: Unique Characters in String - SOLUTION.ipynb
Unique Characters in String
Problem
Given a string,determine if it is compreised of all unique characters. For example, the string 'abcde' has all unique characters and should return True. The string 'aabcde' contains duplicate characters and should return false.
Solution
We'll show two possible solutions, one using a built-in data structure and a built in function, and another using a built-in data structure but using a look-up method to check if the characters are unique.
def uni_char(s): return len(set(s)) == len(s)
def uni_char2(s): chars = set() for let in s: # Check if in set if let in chars: return False else: #Add it to the set chars.add(let) return True
Test Your Solution
""" RUN THIS CELL TO TEST YOUR CODE> """ from nose.tools import assert_equal class TestUnique(object): def test(self, sol): assert_equal(sol(''), True) assert_equal(sol('goo'), False) assert_equal(sol('abcdefg'), True) print 'ALL TEST CASES PASSED' # Run Tests t = TestUnique() t.test(uni_char)
ALL TEST CASES PASSED