- [Tips](
#Tips) - Memoize function return value (cache) - Increase recursion depth - Classes tips - String - List - Dictionary - Golf - StdIn / StdOut - Sorting - Idioms - Some HackerRank solutions - Rotate left list - Count triplet - Solution: Reverse Shuffle Merge - Fraudulent Activity Notifications - Sherlock and Anagrams - String: Sub count - 1 My solution with too many if - 2 Can be replaced with regex !! - 3 Can mutualize some if - String: Longest Common substring problem - Tree: Swap Node - Array : max sum in subarray (Max Array Sum) - Pairs (of equal difference) - Triple sum (find unique) - Minimum Time Required - Balanced parenthesis (Stack of course) - Maze: Castle on the Grid - Array Largest rectangle - Recursion: Davis’ Staircase - Binary Tree: Height - Binary Tree: Lowest Common Ancestor - Trees: Is This a Binary Search Tree? - Tree: Huffman Decoding - Dynamic Programming: Abbreviation - Dynamic programming: candies - Find A Sub-Word (Regex list) - Regex: Alien Username
cur = curs.pop(0)
to remove first. Wanring not cur.pop()UNIQUE
<- grep this keyword, then pre-uniquify anything: can fasten a lot combinatory42.2 // 2
-> int(42.2/2)
or math.floor(42.2/2)
but the result is float
# Find last elt of list
li = ["a", "b", "a", "c", "x", "d", "a", "6"]
''.join(li).rindex('a')
len(li) - 1 - li[::-1].index('a')
# Find index
# # All of whatever condition
indexes = [i for i,x in enumerate(xs) if x == 'foo']
# # First (surround with try catch)
["foo", "bar", "baz"].index("bar")
# Contains only hi
all(elem == "hi" for elem in bag)
# Equality
import collections
collections.Counter(x) == collections.Counter(y)
# # or
sorted(x) == sorted(y)
# # or if unique
set(x) == set(y)
# Reverse
reversed([1, 2, 3])
# append multiple items
l = [1, 2, 3]
l.extends([4, 5, 6])
# " Better
l += [4, 5, 6]
# Enumerate, very usefull
# Set
# # intersection
z = x.intersection(y)
# Copy nested list (2 dimension)
y = [row[:] for row in x]
fptr = open(os.environ['OUTPUT_PATH'], 'w')
n = int(input())
s = input()
result = substrCount(n, s)
fptr.write(str(result) + '\n')
fptr.close()
Or more complex
# Eumerate function
l1 = ["eat","sleep","repeat"]
s1 = "geek"
print list(enumerate(l1))
# changing start index to 2 from 0
print list(enumerate(s1,2))
# Map lambda
new_list = map(lambda x: x%num, old_list)
new_list = [ x%num for x in old_list]
# Swap (with 'tuple-unpacking')
arr[i], arr[j] = arr[j], arr[i]
for(int i = 0; i < lengthOfArray; i++){
int newLocation = (i + (lengthOfArray - shiftAmount)) % lengthOfArray;
a[newLocation] = in.nextInt();
}
Count number of geometric progression in array [x, xk, xk*k]. K is given, read the rules !
Note : I did not manage in 3h. When tired, get really bad + no paper to note => Must concentrate
def reverseShuffleMerge(s):
# Total character counts in s
char_count = Counter(s)
# Character counts we need in our final string
string_chars = {
char: int(count / 2) for char, count in char_count.items()
}
# Character counts we need in the shuffled
# version of our original string
shuffled_chars = {
char: int(count / 2) for char, count in char_count.items()
}
string = []
for char in reversed(s):
if string_chars[char] > 0:
# See if this character should appear before any
# previous ones. Basically, if this char is smaller
# than the previous char, and the previous char
# still occurs in the chars of the shuffled string,
# we can the safely replace the previous char
# with this one. That's so because we should be
# able to still create a valid string which contains
# both characters although the order will differs.
while string and string[-1] > char and shuffled_chars[string[-1]] > 0:
removed = string.pop()
string_chars[removed] += 1
shuffled_chars[removed] -= 1
string.append(char)
string_chars[char] -= 1
else:
shuffled_chars[char] -= 1
return ''.join(string)
Explanation of @eduasinco:
you could remove v[de]
but it would take O(n) time to find de whereas bisect_left assume a sorted list and find the index in O(log(n)).
gives you an index (i) used to remove the last income
This is the slow version O(n): The end of the list must be copied. Here we are lucky, please explain to me !! This also work: l_sorted.remove(index)
from bisect import insort, bisect_left
def activityNotifications(expenditure, d):
# Check in
if len(expenditure) < d : return 0
# Usefull var
res = 0
half = int(d / 2)
is_impair = d % 2 == 0
# Prepare, sort
l_sorted = sorted(expenditure[:d])
for i, current in enumerate(expenditure[d:]):
# Add point
if is_impair: med2 = l_sorted[half] + l_sorted[half-1]
else: med2 = l_sorted[half] * 2
if current >= med2: res += 1
# Remove last
last = expenditure[i]
index = bisect_left(l_sorted, last)
del l_sorted[index]
# Add new
insort(l_sorted, current)
return res
# Complete the sherlockAndAnagrams function below.
def sherlockAndAnagrams(s):
res = 0; i_l = len(s)
# Loop letter number 1 .. i_l - 1
for i_letter in range(1, i_l):
# Loop i_c: 0 .. i_l - letter and sort i_c:i_c+letter
a_sort1 = sorted(s[:i_letter - 1])
for i_c in range(i_l - i_letter + 1):
insort(a_sort1, s[i_c+i_letter-1])
# Loop j_c: i_c+1: i_l - letter and sort j_c:j_c+letter
blacklist = []
a_sort2 = sorted(s[i_c+1 : i_c+1 + i_letter-1])
for j_c in range( i_c+1, i_l -i_letter + 1):
if j_c in blacklist: continue
insort(a_sort2, s[j_c+i_letter-1])
# Match
if a_sort1 == a_sort2:
res += 1
blacklist.append(j_c)
ix2 = bisect_left(a_sort2, s[j_c])
del a_sort2[ix2]
ix1 = bisect_left(a_sort1, s[i_c])
del a_sort1[ix1]
return res
def substrCount(n, s):
count = n
for i_start in range(n):
c_start = s[i_start]
i_middle = 0
for i_end in range(i_start+1, n):
if s[i_end] != c_start:
if not i_middle:
i_middle = i_end
continue
break
if not i_middle:
count += 1
continue
if i_end - i_middle == i_middle - i_start:
count += 1
break
return count
count = len(s)
# With middle
exp1 = r'(([a-z])\2*)(?!\1)(?=[a-z]\1)'
m = re.finditer(exp1,s)
count += sum([len(x.group(0)) for x in m])
# Just repeated seqs
exp2 = r'([a-z])\1+'
m = re.finditer(exp2,s)
count += sum([triangular_number(len(x.group(0))-1) for x in m])
return count
def triangular_number(n):
"""Count xxxx for any submatch xx xxx x .. so 4 + 3 + 2 + 1"""
return (pow(n,2)+n)//2
def substrCount(n, s):
tot = 0
count_sequence = 0
prev = ''
for i,v in enumerate(s):
# first increase counter for all seperate characters
count_sequence += 1
if i and (prev != v):
# if this is not the first char in the string
# and it is not same as previous char,
# we should check for sequence x.x, xx.xx, xxx.xxx etc
# and we know it cant be longer on the right side than
# the sequence we already found on the left side.
j = 1
while ((i-j) >= 0) and ((i+j) < len(s)) and j <= count_sequence:
# make sure the chars to the right and left are equal
# to the char in the previous found squence
if s[i-j] == prev == s[i+j]:
# if so increase total score and step one step further out
tot += 1
j += 1
else:
# no need to loop any further if this loop did
# not find an x.x pattern
break
#if the current char is different from previous, reset counter to 1
count_sequence = 1
tot += count_sequence
prev = v
return tot
My solution:
class Tree:
def __init__(self, i):
self.id = i
self.left = None
self.right = None
@staticmethod
def build(id, nexts):
"""Helper: create a tree and if not null append to next"""
if id == -1: return None
tree = Tree(id)
nexts.append(tree)
return Tree(id)
def __str__(self):
return f"Tree:{self.id}"
def traverse(tree):
if tree is None: return []
# Traverse the left subtree
res = traverse(tree.left)
# Visit root
res.append(tree.id)
# Traverse the right subtree
return res + traverse(tree.right)
def swapNodes(indexes, queries):
# Some test go far in recursion but just onces fct(fct(fct ....))
sys.setrecursionlimit(3000)
# Create tree
root = Tree(1)
# Memoize depth
depths = []
# Build
nexs = [root]; curs = []
for left, right in indexes:
if len(curs) == 0:
depths.append(nexs)
curs = nexs.copy()
nexs = []
cur = curs.pop(0)
cur.left = Tree.build(left, nexs)
cur.right = Tree.build(right, nexs)
# Swap
res = []
for query in queries:
for depth in range(query, len(depths), query):
for tree in depths[depth - 1]:
tree.left, tree.right = tree.right, tree.left
res.append(traverse(root))
return res
See Dynamic Programming: https://www.geeksforgeeks.org/dynamic-programming/
Max sum of non adjacent element in array
def maxSubsetSum(arr):
n = len(arr)
dp = [0]*n
dp[0] = arr[0]
dp[1] = max(arr[1], dp[0])
for i in range(2,n):
dp[i] = max(arr[i], dp[i-1], arr[i] + dp[i-2])
return dp[-1]
Intro: Since we know it’s DP, we can solve the problem by solving subproblems of smaller size.
Because of the condition. No two adjacent elements can be picked. Therefore we can either take one and then skip one, or skip one and run the subroutine.
we can solve this problem in linear time and constant space ;)
Idea: store solutions for problem of size i-2 and i-1, where i is the size of the subproblem. The solution for problem of size i is either solution for problem i-1, or solution for problem i-2, or solution of problem i-2 + arr[i], or ar[i]. Iterate for every i. Start with 0, 0 for problems of size - 2 and -1
def pairs(k, arr):
res = 0
ct = Counter(arr)
for val1 in ct:
# val1 - val2 = +/- k
res += ct[val1] * (ct[val1-k] + ct[val1+k])
return int(res / 2)
from bisect import bisect_left
def triplets(a, b, c):
res = 0
# Uniquely sort
a = sorted(set(a))
b = sorted(set(b))
c = sorted(set(c))
# Check if usefull to loop
if b[-1] < a[0] or b[-1] < c[0]:
return 0
# Find possible combinations
lowa = lowc = 0
# Fasten bisect search with low bound, known a priory
for i in b:
lowa = bisect_left(a, i+0.5, lo=lowa)
lowc = bisect_left(c, i+0.5, lo=lowc)
res += lowc * lowa
return res
My dichotomic bad solution
def in_day(n, ct):
# in n days they build ct[val1] * int(n / val) = items
item = 0
for val in ct:
if val > n : continue
item += ct[val] * int(n/val)
return item
# Complete the minTime function below.
def minTime(machines, goal):
ct = Counter(machines)
high = 999999999999999
day = 1000
low = 0
n1 = -1
while True:
n1 = in_day(day, ct)
print(day, high , low , n1)
# Lucky
if low >= high -1:
if in_day(low, ct) >= goal:
return low
else:
return high
# May increase
if n1 < goal:
low, day = day, day + int(abs(day - high) / 2)
continue
# Now find minimum
high, day = day, day - int(abs(day - low) / 2)
from bisect import bisect_left
class Yeild:
def __init__(self, machines):
self.machines_ = machines
def __getitem__(self, days):
production = 0
for d in machines:
production += int(days/d)
return production
def minTime(machines, goal):
machines.sort()
minDays = 0
maxDays = goal * machines[0]
days = bisect_left(Yeild(machines), goal, minDays, maxDays)
return days
import heapq
import statistics
def minTime(machines, goal):
minDays = math.ceil(goal*statistics.harmonic_mean(machines)/len(machines))
totalManufactured = sum([minDays//x for x in machines])
nextTimes = [(x*(minDays//x)+x,x) for x in machines]
heapq.heapify(nextTimes)
popped=None
while totalManufactured<goal:
popped= heapq.heappop(nextTimes)
heapq.heappush(nextTimes, (popped[0]+popped[1], popped[1]))
totalManufactured+=1
return minDays if not popped else popped[0]
def isBalanced(s):
if len(s) == 0: return "YES"
stack = []
for i in s:
if i in ('(', '[', '{'):
stack.append(i)
continue
if not len(stack): return "NO"
j = stack.pop()
if (j,i) not in (('(', ')'), ('[', ']'), ('{', '}')):
return "NO"
return "YES" if len(stack) == 0 else "NO"
# Complete the minimumMoves function below.
def minimumMoves(grid, startX, startY, goalX, goalY):
res = 1
pos = [ [startX, startY, (0, 0)] ]
# Create visited bool array to avoid looping
visited = []
for line in grid:
visited.append([False]* len(line))
next_pos = []
while True:
for x0, y0, last_move in pos:
# Check and mark as visited
if (x0, y0) == (goalX, goalY): return res
if visited[x0][y0]: continue
visited[x0][y0] = True
# Move any number one direction ...
for moves in ((-1, 0), (0, 1), (1, 0), (0, -1) ):
if moves in (last_move, (-last_move[0], -last_move[1])):
continue
x, y = x0, y0
# ... any number of step
while (0 <= x + moves[0] < len(grid)
and 0 <= y + moves[1] < len(grid[0])
and grid[x + moves[0]][y + moves[1]] != 'X'
):
x += moves[0]
y += moves[1]
# Return ?
if (x,y) == (goalX, goalY): return res
# Break: comming to late: my line and col is visited already
if visited[x][y]: break
# Add this point for a next walk
next_pos.append([x, y, moves])
print(next_pos)
pos = next_pos
next_pos = []
res += 1
from collections import deque
def minimumMoves(grid, startX, startY, goalX, goalY):
"""standard bfs
"""
start, goal = (startX, startY), (goalX, goalY)
# special case: dtart == goal
if start == goal: return 0
# set-up bfs
q = deque([(start, 0)])
parents = {}
moves = {'right':lambda n: (n[0], n[1]+1),
'down':lambda n: (n[0]+1, n[1]),
'left':lambda n: (n[0], n[1]-1),
'up':lambda n: (n[0]-1, n[1])
}
def is_valid(node):
x, y = node
return (x < len(grid) and y < len(grid)
and x >= 0 and y >= 0
and grid[x][y] == '.')
while q:
current_node, level = q.pop()
# match? we are done...
if current_node == goal: return level
# explore... bfs wise
for move in 'up', 'right', 'down', 'left':
next_node = moves[move](current_node.html)
while is_valid(next_node):
# no revisiting...
if next_node not in parents:
q.appendleft((next_node, level+1))
parents[next_node] = current_node
next_node = moves[move](next_node.html)
raise ValueError('unreachable')
def largestRectangle(h):
if(len(h)==0):return
if(len(h)==1):
if(h[0]>largestRectangle.max):
largestRectangle.max=n
return
v=min(h)
if(v*len(h)>largestRectangle.max):
largestRectangle.max=len(h)*v
largestRectangle(h[0:h.index(v)])
largestRectangle(h[h.index(v)+1:len(h)])
return largestRectangle.max
largestRectangle.max=0
def largestRectangle(h):
x=[]
for i in range(len(h)):
l=1
b=0
c=h[i]
while(i-b-1>=0 and h[i-b-1]>=c):
b+=1
while(i+l<len(h)and h[i+l]>c):
l+=1
l=l+b
s=l*c
x.append(s)
return max(x)
from math import factorial
def comb(k, n):
return factorial(n) // factorial(k) // factorial(n-k)
# Complete the stepPerms function below.
def stepPerms(n):
if n == 0 : return 0
res = 0
trees = (n // 3)
for tree in range(trees+1):
tree_rest = n - tree * 3
twos = tree_rest // 2
for two in range(twos+1):
one = tree_rest - two * 2
res += comb(tree, tree + two)
return res % 10000000007
from functools import lru_cache
@lru_cache(None)
def stepPerms(n):
if n == 0: return 1
# NA
if n == 1: return 1
# 1
if n == 2: return 2
# 2 / 1,1
# 3 / 2, .. / 1, ..
return stepPerms(n-3) + stepPerms(n-2) + stepPerms(n-1)
def stepPerms(n):
dp = []
dp.append(1)
# NA
dp.append(1)
# 1
dp.append(2)
# 2 / 1,1
for i in range(3, n+1):
dp.append(dp[i-3] + dp[i-2] + dp[i-1])
return dp[n]
My sol
sol = None
def who_in(root, v1, v2):
"""Reeturns list [], [v1, v2]"""
global sol
if root == None: return []
# print('Visiting', root.info)
res = []
if root.info in (v1, v2):
res.append(root.info)
res.extend(who_in(root.left, v1, v2))
res.extend(who_in(root.right, v1, v2))
# print(" res", res)
if sol is None and v1 in res and v2 in res:
sol = root
return res
def lca(root, v1, v2):
who_in(root, v1, v2)
return sol
Solution understanding that values are ordered (lower, highter), hence the binary search
def lca(root, v1, v2):
cur = root
while cur:
if v1<cur.info and v2<cur.info:
cur = cur.left
elif v1>cur.info and v2>cur.info:
cur = cur.right
elif max(v1, v2)>=cur.info and cur.info>=min(v1,v2):
return cur
Problem tester code
# Python 3 iterative solution
def lca(root , v1 , v2):
# make sure v2 > v1
if v1 > v2: v1, v2 = v2, v1
# traverse until terminal
while True:
if v1 < root.info and v2 < root.info:
root = root.left
elif v1 > root.info and v2 > root.info:
root = root.right
else:
return(root)
Tree class and parser (from problem itself
class Node:
def __init__(self, info):
self.info = info
self.left = None
self.right = None
self.level = None
def __str__(self):
return str(self.info)
class BinarySearchTree:
def __init__(self):
self.root = None
def create(self, val):
if self.root == None:
self.root = Node(val)
else:
current = self.root
while True:
if val < current.info:
if current.left:
current = current.left
else:
current.left = Node(val)
break
elif val > current.info:
if current.right:
current = current.right
else:
current.right = Node(val)
break
else:
break
# Enter your code here. Read input from STDIN. Print output to STDOUT
'''
class Node:
def __init__(self,info):
self.info = info
self.left = None
self.right = None
// this is a node of the tree , which contains info as data, left , right
'''
tree = BinarySearchTree()
t = int(input())
arr = list(map(int, input().split()))
for i in range(t):
tree.create(arr[i])
v = list(map(int, input().split()))
ans = lca(tree.root, v[0], v[1])
print (ans.info)
My solution, note that reading a String + Visiting a tree, the recursive solution could not be the shortest: because you have to pass many variables (at least 2) and one is often unchanged, or changed together so why bother: just reloop + I know when it is finished: when the string_in is completely consumed
def readChar(sub, s):
# Finish
if sub.data != '\0':
return (sub.data, 0)
# Go deeper, I know s is not null
sub = sub.left if s[0] == '0' else sub.right
s_new, i_more = readChar(sub, s[1:])
return s_new, i_more + 1
# Enter your code here. Read input from STDIN. Print output to STDOUT
def decodeHuff(root, s, p=True):
if root is None or len(s) == 0: return ''
# Get char and res
res, i_more = readChar(root, s)
res += decodeHuff(root, s[i_more:], p=False)
# Print is called by the test program
if p: print(res)
return res
Keep reading
void decode(String S, Node root)
{
StringBuilder sb = new StringBuilder();
Node c = root;
for (int i = 0; i < S.length(); i++) {
c = S.charAt(i) == '1' ? c.right : c.left;
if (c.left == null && c.right == null) {
sb.append(c.data);
c = root;
}
}
System.out.print(sb);
}
Or in python, a simple, visiting loop, restoring root when getting a char
def decodeHuff(root, s):
ans=''
curr=root
for i in range(0,len(s)):
if(s[i]=='0'):
curr=curr.left
else:
curr=curr.right
if(curr.left== None and curr.right== None):
ans=ans+curr.data
curr=root
print(ans)
Solution from Team
My timeout
sys.setrecursionlimit(5000)
@lru_cache(None)
def abbreviation_wrap(a, b):
if len(a) == 0: return len(b) == 0
if len(b) == 0: return a.islower()
if a[0] == b[0]:
return abbreviation_wrap(a[1:], b[1:])
if not a[0].islower():
return False
# Capitalize
res = a[0].capitalize() == b[0] and abbreviation_wrap(a[1:], b[1:])
res |= abbreviation_wrap(a[1:], b)
return res
This works
def abbreviation(a, b):
m, n = len(a), len(b)
dp = [[False]*(m+1) for _ in range(n+1)]
dp[0][0] = True
for i in range(n+1):
for j in range(1,m+1):
if a[j-1] == b[i-1]:
dp[i][j] = dp[i-1][j-1]
elif a[j-1].upper() == b[i-1]:
dp[i][j] = dp[i-1][j-1] or dp[i][j-1]
elif a[j-1].islower():
dp[i][j] = dp[i][j-1]
return "YES" if dp[n][m] else "NO"
def candies(n, arr):
if len(arr) < 2: return len(arr)
res = [1] * n
for i in range(len(arr)):
# If in valley
if ((i <= 0 or arr[i] <= arr[i-1])
and (i >= len(arr)-1 or arr[i] <= arr[i+1])):
print("In valley", i)
# up
j = i
while j < len(arr)-1 and arr[j+1] > arr[j]:
res[j+1] = max(res[j+1], res[j] + 1)
j += 1
# down
j = i
while j > 0 and arr[j-1] > arr[j]:
res[j-1] = max(res[j-1], res[j] + 1)
j -= 1
print(res)
return sum(res)
def candies(n, arr):
if len(arr) < 2: return len(arr)
# Minimum 1 candy for each
res = [1] * n
for i in range(len(arr)):
# Do Not work out of valley
in_valley = ((i <= 0 or arr[i] <= arr[i-1])
and (i >= len(arr)-1 or arr[i] <= arr[i+1]))
if not in_valley: continue
# Fire
for sens, bound in ((1, len(arr) - 1), (-1, 0)):
for j in range(i, bound, sens):
if arr[j+sens] <= arr[j]: break
res[j+sens] = max(res[j+sens], res[j] + 1)
# Return total number of cadies
return sum(res)
def candies(n, arr):
"""naive approach
"""
candies = [1] * len(arr)
# left -> right
for i in range(len(arr)-1):
if arr[i] < arr[i+1]:
candies[i+1] = max(candies[i+1], candies[i] + 1)
# right -> left
for i in reversed(range(1, len(arr))):
if arr[i] < arr[i-1]:
candies[i-1] = max(candies[i-1], candies[i] + 1)
return sum(candies)
Some definitions:
/ \ /\
/ RISE \ FALL / \ PEAK \ / VALLEY
/ \ \/
# Enter your code here. Read input from STDIN. Print output to STDOUT
import os
import re
def work(ss, sq):
res = []
for query in sq:
w = r'[0-9a-zA-Z_]+'
query = r'\b' + w + query + w + r'\b'
print(query, ss, re.findall(query, ss[0]))
# lambda x:
num = sum(list(map(lambda x: len((re.findall(query, x))), ss)))
res.append(num)
return res
fptr = open(os.environ['OUTPUT_PATH'], 'w')
s = int(input().strip())
strings = []
for _ in range(s):
strings.append(input())
q = int(input().strip())
queries = []
for _ in range(q):
queries.append(input())
ans = work(strings, queries)
fptr.write('\n'.join(map(str, ans)))
fptr.write('\n')