- THE MATRIX
- Count paths
- Rotate matrix clockwise, in place
- Rotate matrix
- Rotate matrix, Python way (not in place)
- Search in sorted matrix
- Spiral traversal
- ARRAYS
- Max subarray sum (only sum)
- Max subarray sum (sum + indices)
- All unique non-contiguous pairs that sum up to k (unsorted array)
- One non-contigous pair whose sum is closest to k (sorted array)
- Max (min) sum of subarray of size k
- All contiguous subarrays with sum k (neg or pos)
- All non-contiguous triplets with sum < given value
- All non-contiguous triplets with sum = 0 (sub-type of previous)
- Equilibrium index of an array
- Find the Missing Element
- Arrays - supplementary
- Partition problem 1
- Partition problem 2 (subtype of 1)
- Longest subarray with contiguous elements
- Smallest positive integer that cannot be represented as sum of any subset of a given array
- Maximum j – i such that arr[j] > arr[i]
- Find kth Smallest Value Among m Sorted Arrays (FB)
- brute foce: combine all arrays and find kth smallest: O(n)
- Code testing
- Type Hints
- REFERENCES
THE MATRIX
Count paths
Can move only right or down from a cell in the matrix - count the ways
(i) Recursion - Recurse starting from a[m-1][n-1], upwards and leftwards, add the path count of both recursions and return count;
(ii) Dynamic Programming- Start from a[0][0].Store the count in a count matrix. Return count[m-1][n-1]
Time c. O(mn), space c. O(mn)
# Time c. - exponential O(c**n) def count_paths_rec(m, n): if(m == 1 or n == 1): return 1 return count_paths_rec(m-1, n) + count_paths_rec(m, n-1) # If diagonal movements - add last term # Time & space = O(mn) def count_paths(m, n): if m < 1 or n < 1: return -1 # Count matrix count = [[None for j in range(n)] for i in range(m)] # Edge cases - matrix of size 1xn or mx1 for i in range(n): count[0][i] = 1 for j in range(m): count[j][0] = 1 for i in range(1, m): for j in range(1, n): # Number of ways to reach a[i][j] = number of ways to reach a[i-1][j] + a[i][j-1] count[i][j] = count[i - 1][j] + count[i][j - 1] return count[m - 1][n - 1] # Time O(mn), space O(n) # Topmost leftmost cell 1, 1 def count_paths_dp(m, n): dp = [1 for i in range(n)] # Store results of subproblems for i in range(1, m): # originally - for i in range(0, m - 1), why? for j in range(1, n): dp[j] = dp[j] + dp[j - 1] return dp[n - 1]
m, n = 5, 5
print('Recursive:', count_paths_rec(m, n)) print(' Optim:', count_paths(m, n)) print(' Optim DP:', count_paths_dp(m, n))
Recursive: 70
Optim: 70
[1, 2, 1, 1, 1]
[1, 2, 3, 1, 1]
[1, 2, 3, 4, 1]
[1, 2, 3, 4, 5]
[1, 3, 3, 4, 5]
[1, 3, 6, 4, 5]
[1, 3, 6, 10, 5]
[1, 3, 6, 10, 15]
[1, 4, 6, 10, 15]
[1, 4, 10, 10, 15]
[1, 4, 10, 20, 15]
[1, 4, 10, 20, 35]
[1, 5, 10, 20, 35]
[1, 5, 15, 20, 35]
[1, 5, 15, 35, 35]
[1, 5, 15, 35, 70]
Optim DP: 70Rotate matrix clockwise, in place
# Each operation is O(M): time O(2M) -> O(M), space O(1) where m=num cells in matrix OR n**2 class Solution(object): def rotate(self, matrix): self.transpose(matrix) self.reflect(matrix) def transpose(self, matrix): n = len(matrix) for i in range(n): for j in range(i+1, n): matrix[i][j], matrix[j][i] =\ matrix[j][i], matrix[i][j] def reflect(self, matrix): n = len(matrix) for i in range(n): for j in range(n//2): matrix[i][j], matrix[i][-j-1] =\ matrix[i][-j-1], matrix[i][j] matrix = [[1,2,3],[4,5,6],[7,8,9]] s = Solution() s.rotate(matrix) print(matrix)
[[7, 4, 1], [8, 5, 2], [9, 6, 3]]
Rotate matrix
# transpose (top left invert) def top_left_invert(matrix): new = [] for row in matrix: for i, elem in enumerate(row): try: new[i].append(elem) except IndexError: new.insert(i, []) new[i].append(elem) return new # OR new = list(zip(*matrix)) for i in new: print(i)
(1, 4, 7) (2, 5, 8) (3, 6, 9)
def rotate_clockwise(matrix): new = [] for row in reversed(matrix): for i, elem in enumerate(row): try: new[i].append(elem) except IndexError: new.insert(i, []) new[i].append(elem) return new def rotate_counterclockwise(matrix): new = [] for row in matrix: for i, elem in enumerate(reversed(row)): try: new[i].append(elem) except IndexError: new.insert(i, []) new[i].append(elem) return new def top_left_invert(matrix): new = [] for row in matrix: for i, elem in enumerate(row): try: new[i].append(elem) except IndexError: new.insert(i, []) new[i].append(elem) return new def bottom_left_invert(matrix): new = [] for row in reversed(matrix): for i, elem in enumerate(reversed(row)): try: new[i].append(elem) except IndexError: new.insert(i, []) new[i].append(elem) return new def print_matrix(matrix, name): print('{}:\n['.format(name)) for row in matrix: print(' {}'.format(row)) print(']\n')
Rotate matrix, Python way (not in place)
matrix = [ [1, 2, 3], [4, 5, 6], [7, 8, 9], ]
# transpose (top left invert) new = list(zip(*matrix)) for i in new: print(i)
(1, 4, 7) (2, 5, 8) (3, 6, 9)
# bottom left invert new = list(zip(*reversed([reversed(i) for i in matrix]))) for i in new: print(i)
(9, 6, 3) (8, 5, 2) (7, 4, 1)
# clockwise new = list(zip(*reversed(matrix))) for i in new: print(i)
(7, 4, 1) (8, 5, 2) (9, 6, 3)
# counter clockwise new = list(zip(*[reversed(i) for i in matrix])) for i in new: print(i)
(3, 6, 9) (2, 5, 8) (1, 4, 7)
Search in sorted matrix
# Search a key in a row wise and column wise sorted (non-decreasing) matrix. # m - Number of rows in the matrix # n - Number of columns in the matrix # T(n)- O(m+n) # def search_in_a_sorted_matrix(mat, m, n, key): i, j = m-1, 0 while i >= 0 and j < n: if key == mat[i][j]: print ('Key %s found at row %s, column %s' % (key, i+1, j+1)) return if key < mat[i][j]: i -= 1 else: j += 1 print ('Key %s not found' % (key))
mat = [ [2, 5, 7], [4, 8, 14], [9, 11, 15], [12, 17, 20] ] key = 14 search_in_a_sorted_matrix(mat, len(mat), len(mat[0]), key)
Key 14 found at row 2, column 3
Spiral traversal
def spiral_traversal(matrix): res = [] if len(matrix) == 0: return res row_begin = 0 row_end = len(matrix) - 1 col_begin = 0 col_end = len(matrix[0]) - 1 while row_begin <= row_end and col_begin <= col_end: for i in range(col_begin, col_end+1): res.append(matrix[row_begin][i]) row_begin += 1 for i in range(row_begin, row_end+1): res.append(matrix[i][col_end]) col_end -= 1 if row_begin <= row_end: for i in range(col_end, col_begin-1, -1): res.append(matrix[row_end][i]) row_end -= 1 if col_begin <= col_end: for i in range(row_end, row_begin-1, -1): res.append(matrix[i][col_begin]) col_begin += 1 return res
mat = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] print(spiral_traversal(mat))
[1, 2, 3, 6, 9, 8, 7, 4, 5]
ARRAYS
Max subarray sum (only sum)
From "Competitive Programmer’s Handbook" - very interesting book: terse and to the point. We don’t reset current_sum to 0 because arr can be all negatives => the result will be the largest negative
# O(n) while "traditional" solutions use 2 or 3 nested loops => O(n^2) or O(n^3) def maximum_sum(arr): if len(arr)==0: return 0 # edge case max_sum = curr_sum = arr[0] for i in range(1, len(arr)): curr_sum = max(arr[i], curr_sum + arr[i]) max_sum = max(max_sum, curr_sum) return max_sum arr = [-1, 5, 3, -3, 5, -9, 5, ] maximum_sum(arr)
10
Max subarray sum (sum + indices)
Largest continuous sum in array of integers (positive and negative).
If arr = all positive => sum(arr).
Algo: start summing up in current_sum. After each addition check if current_sum > max_sum; update max_sum if yes. Keep adding as long as current_sum > 0. If current_sum < 0, start new current_sum (negative current_sum will only decrease sum of future sequence). We don’t reset current_sum to 0 because arr can be all negatives => the result will be the largest negative
def large_cont_sum(arr): if len(arr)==0: return 0 # edge case max_sum = curr_sum = arr[0] start, tstart, end = 0, 0, 0 # store start & end indices for i in range(1, len(arr)): # two-liner for this for loop if not keeping the start and end points #curr_sum = max(curr_sum + num, num) # current sum = the higher of the two #max_sum = max(curr_sum, max_sum) # max = the higher of current sum & current max if arr[i] > curr_sum + arr[i]: curr_sum = arr[i] tstart = i else: curr_sum += arr[i] if curr_sum > max_sum: max_sum = curr_sum start = tstart end = i return max_sum, arr[start:end+1]
a = [1, 2, -1, 3, 4, 10, 10, -10, -1] b = [1, 2, -1, 3, 4, -1] c = [-1, 1] for arr in [a, b, c]: print('Largerst sum = {:2} in subarray {}'.format(*large_cont_sum(arr)))
Largerst sum = 29 in subarray [1, 2, -1, 3, 4, 10, 10] Largerst sum = 9 in subarray [1, 2, -1, 3, 4] Largerst sum = 1 in subarray [1]
All unique non-contiguous pairs that sum up to k (unsorted array)
Insert and find operations of a set are O(1) => O(N).
Linear pass, for each elem: if (k - element) not in seen, add it to seen; if yes - found a pair
def pair_sum(arr, k): if len(arr) < 2: return seen, output = set(), set() # for tracking for num in arr: if k-num not in seen: # add to set if not seen seen.add(num) else: output.add( (min(num,k-num), max(num,k-num)) ) # otherwise add pair return '\n'.join(map(str,list(output)))
print(pair_sum([1,3,2,2],4)) print() print(pair_sum([1,9,2,8,3,7,4,6,5,5,13,14,11,14,1],10))
(1, 3) (2, 2) (4, 6) (5, 5) (2, 8) (1, 9) (3, 7)
One non-contigous pair whose sum is closest to k (sorted array)
Examples:
Input: arr[] = {1, 3, 4, 7, 10}, x = 15
Output: 4 and 10
Simple solution - every pair, time c. O(n2)
Efficient solution - O(n) time:
1) Initialize var diff as infinite (difference between pair and x) => need to find min diff
2) Initialize leftmost index l = 0
3) Initialize rightmost index r = n-1
3) While l < r
(a) If abs(arr[l] + arr[r] - k) < diff => update diff, result
(b) If (arr[l] + arr[r]) > k: r++, else l-- (sorted array)
MAX_VAL = 1000000000 def closest_sum(arr, n, k): res = 0, 0 # indices of correct pair l, r, diff = 0, n-1, MAX_VAL # left and right indexes, difference between pair sum and x while l < r: if abs(arr[l] + arr[r] - k) < diff: # is this pair closer than the prev one res = l, r diff = abs(arr[l] + arr[r] - k) if arr[l] + arr[r] > k: # if this sum greater, move to smaller, else to greater r -= 1 else: l += 1 return arr[res[0]], arr[res[1]] arr = [10, 22, 28, 29, 30, 40] n = len(arr) k = 52 closest_sum( arr, n, k )
(22, 30)
Max (min) sum of subarray of size k
Simple Solution - generate all subarrays of size k with time c. O(n*k)
Efficient Solution - sum of subarray (window) of size k is obtained in O(1) time using sum of previous subarray (window) of size k. Except for the first k-length subarray, compute sum by removing first element of last window and adding last element of current window
Time c. O(n), space c. O(1)
# O(n) def max_sum(arr, n, k): if (n < k): # edge case return -1 res = 0 for i in range(k): # sum of first window of size k res += arr[i] # remaining sums - remove first elem of prev window, add last elem of current window curr_sum = res for i in range(k, n): curr_sum += arr[i] - arr[i-k] res = max(res, curr_sum) return res arr = [1, 4, 2, 10, 2, 3, 1, 0, 20] k = 4 n = len(arr) print(max_sum(arr, n, k))
24
All contiguous subarrays with sum k (neg or pos)
Sub-case: k=0
Time c. O(n), space c. O(n)
Works for all cases (neg or pos); taken from here. The version on geeksforgeeks doesn't work for all of their own cases (their all-positives case)
How it works:
* Maintain curr_sum of elems in arr[:i]
* If it's k, found a subarray
* Also, if curr_sum-k in hash_map => sum(arr[0:j]) was curr_sum-k, while arr[0:i] is curr_sum => sum([ j:i+1 ]) is k
* Insert current sum into the hash map
from collections import defaultdict def findSubarraysWithSumK(arr, k): hash_map = defaultdict(list) # key = sum, value = end indices of subarray w/sum res = [] # subarrays curr_sum = 0 for i in range( len(arr) ): curr_sum += arr[i] if curr_sum == k: res.append(arr[:i+1]) # or res.append((0, i+1)) if curr_sum-k in hash_map: for value in hash_map[ curr_sum-k ]: res.append( arr[ value+1:i+1 ] ) # or res.append((value+1, i+1)) hash_map[ curr_sum ].append(i) return res
arr = [3, 4, -7, 1, 3, 3, 1, -4] k = 7 print(findSubarraysWithSumK(arr, k)) print() # Geeksforgeeks cases that worked only for one of the solutions, but there was no solution that would cover all of these! arr = [-10, 10, -12, 2, -2, -20, 10] k = -10 print(findSubarraysWithSumK(arr, k)) print() # previous cases from a) and b) arr = [15, 2, 4, 8, 9, 5, 10, 23] k = 23 print(findSubarraysWithSumK(arr, k)) print() arr = [10, 2, -2, -20, 10] k = -10 print(findSubarraysWithSumK(arr, k))
[[3, 4], [3, 4, -7, 1, 3, 3], [1, 3, 3], [3, 3, 1]] [[-10], [-10, 10, -12, 2], [-12, 2], [2, -2, -20, 10], [-20, 10]] [[2, 4, 8, 9], [23]] [[10, 2, -2, -20], [2, -2, -20, 10], [-20, 10]]
All non-contiguous triplets with sum < given value
Simple solution - brute force with three nested loops to get all triplets and their sums. Time c. = O(n^3)
res = 0
for i in range( 0 ,n-2):
for j in range( i+1 ,n-1):
for k in range( j+1, n):
if (arr[i] + arr[j] + arr[k]) < sum:
res += 1
A O(n^2) solution is possible with below:
# Time c. O(n^2); uses sort() and smart iteration def count_triplets(arr, k): arr.sort() n = len(arr) count = 0 for i in range(n-2): # first elem of triplet = arr[i] start = i + 1 # corner elements end = n - 1 while start < end: # meet in the middle if (arr[i] + arr[start] + arr[end]) >= k: # decrease index end -= 1 else: count += (end-start) # array sorted => (end-start) third elements w/sum < k start += 1 return count arr = [5, 1, 3, 4, 7] sum = 11 print(count_triplets(arr, sum))
3
All non-contiguous triplets with sum = 0 (sub-type of previous)
# Time c. O(n^2), space c. O(1) def findTriplets(arr): arr.sort() n = len(arr) count = 0 for i in range(0, n-2): start = i + 1 end = n - 1 while start < end: if arr[i] + arr[start] + arr[end] == 0: count += 1 start += 1 end -= 1 elif (arr[i] + arr[start] + arr[end] < 0): # If sum < 0, increment on left side start += 1 else: # if sum > 0, decrement on right side end -= 1 return count arr = [0, -1, 2, -3, 1] findTriplets(arr)
2
Equilibrium index of an array
Equilibrium index - sum of elements at lower indexes is equal to the sum of elements at higher indexes.
Example:
Input: A = [-7, 1, 5, 2, -4, 3, 0}] => Output: 3 because A[0] + A[1] + A[2] = A[4] + A[5] + A[6] Input: B = [1, 2, 3] => Output: -1
Time c.: O(n)
def equilibrium(arr): rightsum = sum(arr) leftsum = 0 for i, num in enumerate(arr): rightsum -= num if leftsum == rightsum: return i leftsum += num return -1 arr = [-7, 1, 5, 2, -4, 3, 0] print ('First equilibrium index is ', equilibrium(arr))
First equilibrium index is 3
Find the Missing Element
- array of non-negative integers;
- second array of the same shuffled elements w/one random element deleted;
- find missing element
- Best solution (O(n)): 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 missing element in arr2
def finder(arr1, arr2): res = 0 for num in arr1 + arr2: res ^= num print(res, end=' ') # to see the result of XOR return res
Other solutions: * go through every element in second array and check if it's in the first array - O(n**2) as 2 for loops; mind duplicates! * sorth both arr, iterate simulataneously, once iterators are not equal - missing number; O(NlogN) * using hashing (O(n)):
from collections import defaultdict def finder2(arr1, arr2): d = defaultdict(int) # dict of counts for num in arr2: # count for elements in arr2 d[num]+=1 for num in arr1: # check if num not in dict if d[num]==0: return num else: d[num]-=1 # otherwise, decrease count
arr1 = [5,4,4,7,7] arr2 = [5,4,7,7] print('Missing per finder1:', finder(arr1,arr2)) print() print('\nMissing per finder2:', finder2(arr1,arr2))
Missing per finder1: 4 5 1 5 2 5 0 4 3 4 Missing per finder2: 4
arr1, arr2 = [1,2,3,4,5,6,7], [5,7,2,1,4,6] print('Missing per finder1:', finder(arr1,arr2)) print() print('\nMissing per finder2:', finder2(arr1,arr2))
Missing per finder1: 3 1 3 0 4 1 7 0 5 2 0 1 5 3 Missing per finder2: 3
How it works: XOR works in binary, but for decimals XOR of a number with itself results in 0 => x ^ x = 0, then y ^ 0 = y
Example: if we have [5,8,12,5,12] (one element doesn't have a pair as in our case):
* 1st iter: res = 0^5 = 5
* 2nd iter: res = 5^8
* 3rd iter: res = 5^8^12
* 4th iter: res = 5^8^12^5 = 0^8^12 = 8^12
* 5th iter: res = 8^12^12 = 8^0 = 8
Arrays - supplementary
Partition problem 1
Determine whether a given set can be partitioned into two subsets such that the sum of elements in both subsets is the same
1) Calculate sum of the array. If sum is odd, there can not be two subsets with equal sum, so return false.
2) If sum of array elements is even, calculate sum/2 and find a subset of array with sum equal to sum/2
# Recursive, t. complexity = 2**n def isSubsetSum(arr, n, sum_): # return true if there is a subset of arr[] with sum = target sum # Base cases if sum_ == 0: return True if n == 0 and sum_ != 0: return False # If last elem > sum, ignore it if arr[n-1] > sum_: return isSubsetSum(arr, n-1, sum_) # else, check if sum is obtained by (a) including last elem, (b) excluding it return isSubsetSum(arr, n-1, sum_) or isSubsetSum(arr, n-1, sum_-arr[n-1])
# t.c.= O(n*sum) def isSubsetSum_dp(arr, n): sum_ = 0 for i in range(n) : sum_ += arr[i] if sum_ % 2 != 0: #if sum is odd, cannot find two subsets return 0 part = [0] * ((sum_ // 2) + 1) #initialize with 0 (False) # part[j] = true if there is a subset with sum equal to j, otherwise false for i in range(n): # fill in bottom up for j in range(sum_ // 2, arr[i]-1, -1) : if (part[j - arr[i]] == 1 or j == arr[i]): # elem to be included in sum cannot be > sum part[j] = 1 # can sum-arr[i] be formed from subset before indecx i return part[sum_ // 2]
arr = [3, 1, 5, 9, 12, 1, 1] n = len(arr) sum_ = 0 for i in range(0, n): sum_ += arr[i] if sum_ % 2 != 0: #if sum is odd, cannot find two subsets print("Can't be done") res = isSubsetSum(arr, n, sum_ // 2) if res: print("Can be done") else: print("Can't be done")
Can be done
arr = [3, 1, 5, 9, 12, 1, 1] n = len(arr) isSubsetSum_dp(arr, n)
1
Partition problem 2 (subtype of 1)
Partition a set into two subsets such that the difference of subset sums is minimum
# t.c.= O(2**n) def findMinRec(arr, i, sumCalculated, sumTotal): # If reached last elem, sum of one subset = sumCalculated, # sum of other subset = (sumTotal-sumCalculated). Return absolute difference if i == 0: return abs( (sumTotal-sumCalculated) - sumCalculated ) # For each arr[i], (1) we can exclude it from first set, (2) we can include it # return min of two choices return min( findMinRec( arr, i-1, sumCalculated + arr[i-1], sumTotal ), findMinRec( arr, i-1, sumCalculated, sumTotal ) ) def findmin_recursive(arr, n): # total sum of arr sumTotal = 0 for i in range(n): sumTotal += arr[i] return findMinRec(arr, n, 0, sumTotal)
# t.c,= O(n*sum) def findmin(arr, n): pass
arr = [3, 1, 4, 2, 2, 1, 125] n = len(arr) print("Minimum possible difference =", findmin_recursive(arr, n)) #print("Minimum possible difference =", findmin(arr, n))
Minimum possible difference = 112
K’th Smallest/Largest Element in Unsorted Array
O(N logN) solution - quicksort then get kth element
Other solutions are either long or have no Python implementation
Convert array in zigzag fashion
Array of DISTINCT elements, rearrange its elements in zig-zag fashion in O(n) time: a < b > c < d > e < f
A traditional approach would first sort, then do the zigzag - O(n logn). To convert to O(n): * Maintain an alternating flag representing the order (< or >). * If current 2 elements are not in that order, swap them; otherwise not
# Time c. = O(n) def zigzag(arr): # Flag true, then "<" is expected; else ">" is expected. First expected "<" flag = True for i in range(len(arr) - 1): if flag is True: # If A > B > C, then swap B and C if arr[i] > arr[i+1]: arr[i],arr[i+1] = arr[i+1],arr[i] else: # If A < B < C, then swap B and C if arr[i] < arr[i+1]: arr[i],arr[i+1] = arr[i+1],arr[i] flag = bool(1 - flag) print(arr) arr = [4, 3, 7, 8, 6, 2, 1] zigzag(arr)
[3, 7, 4, 8, 2, 6, 1]
Longest subarray with contiguous elements
Array of DISTINCT integers, find length of the longest subarray consisting of a continuous sequence of numbers.
Examples: [10, 12, 11] => 3; [14, 12, 11, 20] => 2
Trick: if all elements are distinct, then a subarray has contiguous elements if and only if the difference between max and min elements in subarray = difference between last and first indexes of subarray => keep track of min and max element in every subarray
def min(x, y): return x if (x < y) else y def max(x, y): return x if (x > y) else y def findLength(arr): # Initialize result max_len = 1 for i in range(len(arr) - 1): # Initialize min and max for all subarrays starting with i mn = arr[i] mx = arr[i] # All subarrays starting w/i and ending w/j for j in range(i + 1, len(arr)): # Update min and max, if needed mn = min(mn, arr[j]) mx = max(mx, arr[j]) # If current subarray has all contiguous elements if ((mx - mn) == j - i): max_len = max(max_len, mx - mn + 1) return max_len arr = [1, 56, 58, 57, 90, 92, 94, 93, 91, 45] print("Length of longest contiguous subarray:", findLength(arr))
Length of longest contiguous subarray: 5
Smallest positive integer that cannot be represented as sum of any subset of a given array
Sorted non-decreasing array of positive numbers - time c. O(n).
Examples: {1, 3, 6, 10, 11, 15} => 2; {1, 1, 1, 1} => 5; {1, 1, 3, 4} => 10; {1, 2, 5, 10, 20, 40} => 4
Simple Solution: start from value 1 and check all values one by one if they can sum to values in the given array - reduces to subset sum problem (well known NP Complete Problem).
Better solution: initialize result as 1 (smallest possible results) and traverse => two possibilities for next element at index i:
- If arr[i] > res => found a gap, and the elements after arr[i] are also > res; res is the final solution
- Else, res is incremented by arr[i] - if elements from 0 to (i-1) can represent 1 to res-1, then elements from 0 to i can represent 1 to res + arr[i] – 1 by adding arr[i] to all subsets that represent 1 to res)
def findSmallest(arr, n): res = 1 # initialize result for i in range(n): # traverse and increment 'res' if arr[i] <= 'res' if arr[i] <= res: res = res + arr[i] else: break return res arr1 = [1, 3, 4, 5] n1 = len(arr1) print(findSmallest(arr1, n1)) arr2= [1, 2, 6, 10, 11, 15] n2 = len(arr2) print(findSmallest(arr2, n2)) arr3= [1, 1, 1, 1] n3 = len(arr3) print(findSmallest(arr3, n3)) arr4 = [1, 1, 3, 4] n4 = len(arr4) print(findSmallest(arr4, n4))
2 4 5 10
Maximum j – i such that arr[j] > arr[i]
Geeksforgeeks
Example: {34, 8, 10, 3, 2, 80, 30, 33, 1} => 6 (j = 7, i = 1)
Simple solution = two nested loops, O(n^2)
Better solution - O(n logn)
Best solution - time O(n), space O(n)
# Better solution - O(n logn) def max_diff(arr): #To store the index of an element. index = dict() n = len(arr) for i in range(n): if a[i] in index: #append to list (for duplicates) index[a[i]].append(i) else: #if first occurrence index[a[i]] = [i] #sort the input array a.sort() maxDiff = 0 # Temporary variable to keep track of minimum i temp = n for i in range(n): if temp > index[a[i]][0]: temp = index[a[i]][0] maxDiff = max(maxDiff, index[a[i]][-1]-temp) return maxDiff # Output: 6 (j = 7, i = 1) a = [34, 8, 10, 3, 2, 80, 30, 33, 1] print(max_diff(a)) #Output: 8 ( j = 8, i = 0) a = [9, 2, 3, 4, 5, 6, 7, 8, 18, 0] print(max_diff(a)) #Output: 5 (j = 5, i = 0) a = [1, 2, 3, 4, 5, 6] print(max_diff(a)) #Output: 0 a = [6, 5, 4, 3, 2, 1] print(max_diff(a))
6 8 5 0
# Best solution O(n) def max(a, b): return a if a > b else b def min(a,b): return a if a < b else b def max_diff_best(arr): n = len(arr) maxDiff = 0 LMin = [0] * n RMax = [0] * n # LMin[i] stores min(arr[0:i+1] = from 0 to i) LMin[0] = arr[0] for i in range(1, n): LMin[i] = min(arr[i], LMin[i - 1]) # RMax[j] stores max(arr[j:n] = from j to n-1 RMax[n - 1] = arr[n - 1] for j in range(n - 2, -1, -1): RMax[j] = max(arr[j], RMax[j + 1]); # Traverse both arrays left -> right, find optimum j - i. Similar to merge() of MergeSort i, j = 0, 0 maxDiff = -1 while (j < n and i < n): if (LMin[i] < RMax[j]): maxDiff = max(maxDiff, j - i) j = j + 1 else: i = i+1 return maxDiff # Output: 6 (j = 7, i = 1) a = [34, 8, 10, 3, 2, 80, 30, 33, 1] print(max_diff_best(a)) #Output: 8 ( j = 8, i = 0) a = [9, 2, 3, 4, 5, 6, 7, 8, 18, 0] print(max_diff_best(a)) #Output: 5 (j = 5, i = 0) a = [1, 2, 3, 4, 5, 6] print(max_diff_best(a)) #Output: -1 a = [6, 5, 4, 3, 2, 1] print(max_diff_best(a))
6 8 5 -1
Find kth Smallest Value Among m Sorted Arrays (FB)
You have m arrays of sorted integers. The sum of the array lengths is n. Find the kth smallest value of all the values.
For exmaple, if m = 3, n=8, and we have these lists:
list1 = [3,6,9] list2 = [8,15] list3 = [4, 7, 12]
if k = 1, then returned value should be 3 if k = 2, then returned value should be 4 if k = 3, then returned value should be 6
brute foce: combine all arrays and find kth smallest: O(n)
# time c. nlog(n), space c. O(n) def find_kth(arr, k): for idx, array in enumerate(arr): if not array: arr.remove(idx) if not arr: raise exception #return None array_lengths = [len(item) for item in arr] temp_array = [] for i in range(k): min_len = min(array_lengths) if k == min_len: idx = array_lengths.index(min_len) arr.remove(idx) temp_array.extend([item[i] for item in arr]) temp_array = sorted(temp_array) return temp_array[k-1] list1 = [3,6,9] list2 = [8,15] list3 = [4, 7, 12] arr = [ list1, list2, list3, ] k = 2 find_kth(arr, k)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Input In [24], in <cell line: 33>()
31 arr = [ list1, list2, list3, ]
32 k = 2
---> 33 find_kth(arr, k)
Input In [24], in find_kth(arr, k)
17 if k == min_len:
18 idx = array_lengths.index(min_len)
---> 19 arr.remove(idx)
20 temp_array.extend([item[i] for item in arr])
22 temp_array = sorted(temp_array)
ValueError: list.remove(x): x not in listCode testing
Types of tests
- Unit test - finding bugs in individual functions.
Should be considered together with integration test -
when a system is comprehensively unit tested,
it makes integration testing far easier - Integration test - test multiple components of application at once,
incl. interactions between the parts - identifies defects in the interfaces
between disparate parts of the codebase. - Functional test - whole system test. Sometimes it's aka
integration test, sometimes - user test
Basics of unittest library
What you need for a unit test:
* test fixture - preparations for test: creating temporary or proxy databases,
directories, or starting a server.
* test case - individual unit of testing, checks response to a particular set of inputs
(base class, TestCase, to create new test cases).
* test suite - collection of test cases, test suites, or both: to aggregate tests together.
* test runner - orchestrates the execution of tests and provides the outcome.
Additional
Main assertions: assertEqual(), assertTrue(), assertFalse(), assertRaises()
unittest.main() - command-line interface to run the test script
Test discovery via CLI finds all unittest files in the directory structure
Unittest can skip individual tests and whole classes of tests, marking a test as
an “expected failure,” (shouldn’t be counted as a failure on a TestResult)
Unittest can distinguish small differences in tests (e.g. in some parameters)
using the subTest() context manager. Doctest can test docstrings
#%tb import unittest class TestStringMethods(unittest.TestCase): def test_upper(self): self.assertEqual('foo'.upper(), 'FOO') def test_isupper(self): self.assertTrue('FOO'.isupper()) self.assertFalse('Foo'.isupper()) def test_split(self): s = 'hello world' self.assertEqual(s.split(), ['hello', 'world']) # check that s.split fails when the separator is not a string with self.assertRaises(TypeError): s.split(2) # entry point for running test if __name__ == '__main__' and '__file__' in globals(): unittest.main()
import unittest # Class containing unit tests class TestStringMethods(unittest.TestCase): def setUp(self): # Setup required resources self.string = "hello world" self.empty_string = "" self.whitespace_string = " " self.numeric_string = "12345" def tearDown(self): # Clean up resources pass def test_upper(self): self.assertEqual(self.string.upper(), "HELLO WORLD") def test_isupper(self): self.assertFalse(self.string.isupper()) self.assertTrue("HELLO".isupper()) def test_islower(self): self.assertTrue(self.string.islower()) self.assertFalse("Hello".islower()) def test_split(self): self.assertEqual(self.string.split(), ["hello", "world"]) with self.assertRaises(TypeError): s.split(2) # Entry point for running the tests if __name__ == '__main__': unittest.main()
def test_isspace(self):
self.assertTrue(self.whitespace_string.isspace())
self.assertFalse(self.string.isspace())
self.assertFalse(self.empty_string.isspace())
def test_isdigit(self):
self.assertTrue(self.numeric_string.isdigit())
self.assertFalse(self.string.isdigit())
def test_empty_string(self):
self.assertEqual(self.empty_string.upper(), "")
self.assertEqual(self.empty_string.split(), [])
self.assertTrue(self.empty_string.islower())
pytest
Third-party unittest framework with a lighter-weight syntax for writing tests.
In example below: we drop the TestCase, any use of classes, and the command-line entry point
import pytest # Fixture for setup and teardown @pytest.fixture def string_data(): data = { "string": "hello world", "empty_string": "", "whitespace_string": " ", "numeric_string": "12345" } return data # Test functions def test_upper(string_data): assert string_data["string"].upper() == "HELLO WORLD" def test_isupper(string_data): assert not string_data["string"].isupper() assert "HELLO".isupper() def test_islower(string_data): assert string_data["string"].islower() assert not "Hello".islower() def test_split(string_data): assert string_data["string"].split() == ["hello", "world"] assert string_data["string"].split('o') == ["hell", " w", "rld"] def test_isspace(string_data): assert string_data["whitespace_string"].isspace() assert not string_data["string"].isspace() assert not string_data["empty_string"].isspace() def test_isdigit(string_data): assert string_data["numeric_string"].isdigit() assert not string_data["string"].isdigit() def test_empty_string(string_data): assert string_data["empty_string"].upper() == "" assert string_data["empty_string"].split() == [] assert string_data["empty_string"].islower() # Run the tests if __name__ == '__main__': pytest.main()
Type Hints
from typing import Optional # var of specified type or None def get_user_email(user_id: int) -> Optional[str]: emails = {1: "alice@example.com", 4: "bob@example.com"} return emails.get(user_id, None) email = get_user_email(1) print(email) # Output: alice@example.com email = get_user_email(2) print(email) # Output: None
from typing import Any # any type. E.g. Dict[str, Any] def print_value(value: Any) -> None: print(f"Value: {value}") print_value(42) # Output: Value: 42 print_value("Hello") # Output: Value: Hello print_value([1, 2, 3]) # Output: Value: [1, 2, 3]
from typing import Union # var is one of several types def process_data(data: Union[int, str]) -> str: if isinstance(data, int): return f"Processed integer: {data}" elif isinstance(data, str): return f"Processed string: {data}"
from typing import Callable # var is func or method def execute_function( func: Callable[[int, int], int], a: int, b: int, ) -> int: return func(a, b) def add(x: int, y: int) -> int: return x + y result = execute_function(add, 5, 3) print(result) # Output: 8
REFERENCES
Google Interview Questions (Geekstogeeks)
General: https://www.geeksforgeeks.org/google-interview-preparation/
More coding challanges from Google: https://practice.geeksforgeeks.org/explore/?company%5B%5D=Google&page=1