Lontananza
less than 1 minute read
Memory images, once they are fixed in words, are erased.
嗨嗨嗨!!勋章来咯!!
01
Maximum Number of Achievable Transfer RequestsHard
DFS
You are given an integer array
cookies
, wherecookies[i]
denotes the number of cookies in theith
bag. You are also given an integerk
that denotes the number of children to distribute all the bags of cookies to. All the cookies in the same bag must go to the same child and cannot be split up.The unfairness of a distribution is defined as the maximum total cookies obtained by a single child in the distribution. Return the minimum unfairness of all distributions.
A counterexample where Greedy fails can be easily constructed. We observe the constraint that 2 <= cookies.length <= 8
, hence the clue of using DFS.
The idea is to iterate the cookies
. Each time we distribute this cookies
to any possible children, and go to the next layer of DFS.
class Solution:
def distributeCookies(self, cookies: List[int], k: int) -> int:
ans = sum(cookies)
def searchCandies(i, children, current_max):
nonlocal ans
# children records the current candies each child has
# current_max records the local answer
if i>=len(cookies):
ans = min(ans, current_max)
return
for child_id in range(len(children)):
# we assign the cookie to child i
if children[child_id]+cookies[i] >= ans: continue
children[child_id] += cookies[i]
searchCandies(i+1, children,
max(current_max, children[child_id]))
children[child_id] -= cookies[i]
searchCandies(0, [0]*k, 0)
return ans
if children[child_id]+cookies[i] >= ans: continue
The ans
is an existing local solution. So there is no point to do further DFS if the current distribution plan has a larger unfairness.
nonlocal
If you delete nonlocal
in code, an error emerges: UnboundLocalError: local variable 'ans' referenced before assignment
. This is because Python does not know the ans
variable in our searchCandies
function. It will consider it as a local variable, hence creating a new ans
inside the function - this explains the description of this error.
There are two ways to get around this. First is this nonlocal
argument. The second is to move ans
to the Solution
class. i.e. define ans
(and use it) in the form of self.ans
.
An idea is to have a better starting point. We use heap
to formulate a greedy solution: Each time we assign a cookie to the child with least number of cookies. This has constant time cost (because the number of cookies smaller than 8), but will give a very nice upper bound of solution, which cuts lots of leaves in the later DFS.
Simply replace the ans = sum(cookies)
with this:
cookies.sort(reverse = True)
heap = [0] * k
for i in cookies:
currentLeastChild = heapq.heappop(heap)
currentLeastChild += i
heapq.heappush(heap,currentLeastChild)
ans = max(heap)
sort
is again optional, but basically costs no time.02
Fair Distribution of CookiesHard
DFS
Divide and Conquer
bitMask
to search request casesclass Solution:
def maximumRequests(self, n: int, requests: List[List[int]]) -> int:
def bitmaskToBool(n):
bitsOn = []
while n:
bitsOn.append(n%2==1)
n = n//2
return(bitsOn)
ans = 0
for mask in range(1, (1<<len(requests))):
bitsOn = bitmaskToBool(mask)
pathLength = sum(bitsOn)
if pathLength<=ans: continue
indeg = [0 for _ in range(n)]
for i in range(len(bitsOn)):
if not bitsOn[i]: continue
req = requests[i]
indeg[req[0]]-=1
indeg[req[1]]+=1
if all([x==0 for x in indeg]):
ans = max(ans, pathLength)
return(ans)
03
Buddy StringsEasy
Silly
纯笨b摆烂天把所有情况想明白就完事了没什么好说的题目😅.
class Solution:
def buddyStrings(self, s: str, goal: str) -> bool:
if len(s)!=len(goal) or len(s)<=1: return(False)
if len(s)==2:
return (s[0]==goal[1] and s[1]==goal[0])
diff = -1
for i in range(len(s)):
if s[i]!=goal[i]:
if diff==-2: return(False) #two pairs differ
if diff==-1: # no pair differs
diff = i
else: # diff = i, one pair differs
if not(s[diff]==goal[i] and s[i]==goal[diff]):
return(False)
else:
diff=-2
if diff == -1: # all same
return not len(set(s))==len(s)
elif diff == -2:
return True
else:
return False
04
Single Number IIMedium
Array
Bit Manipulation
Use idea of XOR
. The logic is a bit confusing, better take some numbers and try for yourself 🙂.
class Solution:
def singleNumber(self, nums: List[int]) -> int:
a, b = 0, 0
for i in nums:
a = (a^i) & ~b
b = (b^i) & ~a
return a
05
Longest Subarray of 1’s After Deleting One ElementMedium
Array
Greedy
Given a binary array
nums
, you should delete one element from it.Return the size of the longest non-empty subarray containing only 1’s in the resulting array. Return 0 if there is no such subarray.
不是,您每天出这种题目,您图啥呢?Simply 贪心。
class Solution:
def longestSubarray(self, nums: List[int]) -> int:
if len(nums)==1: return(0)
if 0 not in nums: return(len(nums)-1)
current_max = now_len = prev_len = 0
for i in range(len(nums)):
if not nums[i]:
current_max = max(current_max, prev_len + now_len)
if i==len(nums)-1: return current_max
if nums[i+1]:
prev_len = now_len
now_len = 0
else:
prev_len = now_len = 0
else:
if now_len>current_max: current_max = now_len
now_len+=1
return(max(current_max, prev_len+now_len))
06
Minimum Size Subarray SumMedium
Pointer
Array
Given an array of positive integers
nums
and a positive integertarget
, return the minimal length of a subarray whose sum is greater than or equal to target. If there is no such subarray, return 0 instead.
普及组第一题都不敢出这么简单的级别泥🤧two pointers O(n)完事泥.
class Solution:
def minSubArrayLen(self, target: int, nums: List[int]) -> int:
left = now = 0
ans = len(nums)+1
for i in range(len(nums)):
now+=nums[i]
if now>=target:
while now-nums[left]>=target:
now-=nums[left]
left+=1
ans = min(ans, i-left+1)
return(0 if ans==len(nums)+1 else ans)
07
Maximize the Confusion of an ExamMedium
Pointer
Greedy
A teacher is writing a test with n true/false questions, with ‘T’ denoting true and ‘F’ denoting false. He wants to confuse the students by maximizing the number of consecutive questions with the same answer (multiple trues or multiple falses in a row).
You are given a string
answerKey
, whereanswerKey[i]
is the original answer to the ith question. In addition, you are given an integerk
, the maximum number of times you may perform the following operation:Change the answer key for any question to ‘T’ or ‘F’ (i.e., set
answerKey[i]
to ‘T’ or ‘F’). Return the maximum number of consecutive ‘T’s or ‘F’s in the answer key after performing the operation at most k times.
Simple greedy. No point not exploiting all k
s if possible. Simply use a pointer l
to indicate the current left position. If surpassed then adjust left to the closest.
class Solution:
def maxConsecutiveAnswers(self, answerKey: str, k: int) -> int:
ans = 0
for c in ['T','F']:
l = current = count = 0
for i in range(len(answerKey)):
if answerKey[i]==c: count+=1 # WT replace c
while count > k: #don't want the left
if answerKey[l]==c: count-=1
l+=1
current = max(current, i-l+1)
ans = max(ans,current)
return(ans)
今天的双周赛好水啊,就跟Leetcode的服务器一样😌。
08
Put Marbles in BagsHard
Greedy
You have k bags. You are given a 0-indexed integer array
weights
whereweights[i]
is the weight of the ith marble. You are also given the integerk
.Divide the marbles into the
k
bags according to the following rules:
- No bag is empty.
- If the ith marble and jth marble are in a bag, then all marbles with an index between the ith and jth indices should also be in that same bag.
- If a bag consists of all the marbles with an index from i to j inclusively, then the cost of the bag is
weights[i] + weights[j]
. The score after distributing the marbles is the sum of the costs of all the k bags.Return the difference between the maximum and minimum scores among marble distributions.
Constraints
It’s easy to think about taking a sum of neighbours and try to locate extreme situations among them. Two things to consider: 1
the boundary of arrays 2
the one-element groups. The first is easy to figure out, because in max and min cases the 0th and -1th element has to be included in the cost, so it cancel out.
For the second, take a neighbour sum (e.g. np.array(weights[1:]) + np.array(weights[:-1])
). You will find out that the partition is actually bijective to taking $k-1$ elements from neighbour-sum.
Didn’t use numpy
, as importing is slow, and wanna beat as many people as possible for QSD’s bizzard dignity.
class Solution:
def putMarbles(self, weights: List[int], k: int) -> int:
if k==1: return(0)
neighbourSumWeights = sorted([weights[i] + weights[i+1] for i in range(len(weights)-1)])
return(sum(neighbourSumWeights[-(k-1):])-sum(neighbourSumWeights[:(k-1)]))
09
Substring With Largest VarianceHard
DP
Finally an interesting question. Good for Leetcode 😌.
The variance of a string is defined as the largest difference between the number of occurrences of any 2 characters present in the string. Note the two characters may or may not be the same.
Given a string
s
consisting of lowercase English letters only, return the largest variance possible among all substrings ofs
.A substring is a contiguous sequence of characters within a string.
Constraints $1 \leq s.length \leq 10^4$
Classical DP question (as the data constraint suggests). The first idea is to 1. iterate the two letters among all letters. 2. count the occurrence among all possible substrings of s
.
This is clearly TLE. Then DP
naturally comes up to skip unncessary sub-problems during Step 2. The difference of occurrence between two letters is then linked to Kadane’s Algorithm (The classical $O(n)$ solution to Largest Sum Contiguous Subarray problem).
One problem remains is the situation of $abb$, which, as we reset the difference to 0 to find the optimal solution in Kadane, results in answer
0
. The convenient solution is to calculate again fors[::-1]
. (Further optimisation for this clearly exists as the overlapping of calculation, but only provides constant optimisation for time complexity)
time complexity
$26^2*\text{len}(s)$
class Solution:
def largestVariance(self, s: str) -> int:
letters = set(s)
if len(letters)==1: return(0)
ans = 0
for a in letters:
for b in letters:
if a==b: continue
for sSs in [s,s[::-1]]:
curMax = countA = countB = 0
for letter in sSs:
if letter not in [a,b]: continue
countA+= (letter==a)
countB+= (letter==b)
if countA<countB: countA = countB = 0
if countA*countB: curMax = max(curMax, countA-countB)
ans = max(ans, curMax)
return(ans)
10
Minium Depth of Binary TreeEasy
Tree
Given a binary tree, find its minimum depth.
The minimum depth is the number of nodes along the shortest path from the root node down to the nearest leaf node.
水,注意number of nodes可以是0。下一题。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def minDepth(self, root: Optional[TreeNode]) -> int:
if not root: return 0
ans = 0
queue = [root]
ne = root
while queue:
now = queue.pop(0)
if now==ne: ans+=1
if now.left==None and now.right==None: return(ans)
if now.left: queue.append(now.left)
if now.right: queue.append(now.right)
if now==ne:
if now.left:
ne=now.left
else:
ne=now.right
11
All Nodes Distance K in Binary TreeMedium
Tree
BFS
Given the root of a binary tree, the value of a target node
target
, and an integerk
, return an array of the values of all nodes that have a distancek
from the target node.
Classic BFS. Simple variation of yesterday’s question. Beat 8% in time, because just finished Mission Impossible and too exhuasted to optimise the algo.
class Solution:
def distanceK(self, root: TreeNode, target: TreeNode, k: int) -> List[int]:
queue = [root]
fathers = {root: None}
distance = [0 for _ in range(1000)]
while queue:
now = queue.pop(0)
if now.left:
queue.append(now.left)
fathers[now.left] = now
if now.right:
queue.append(now.right)
fathers[now.right] = now
ans = []
queue = [target]
hasVisited = []
distance[target.val] = 0
while queue:
now = queue.pop(0)
hasVisited.append(now.val)
if distance[now.val]==k: ans.append(now.val)
for x in [now.left,now.right,fathers[now]]:
if x==None: continue
if x.val in hasVisited: continue
queue.append(x)
distance[x.val] = distance[now.val] + 1
return(ans)
12
Find Eventual Safe StatesMedium
DFS
There is a directed graph of n nodes with each node labeled from 0 to n - 1. The graph is represented by a 0-indexed 2D integer array
graph
wheregraph[i]
is an integer array of nodes adjacent to node i, meaning there is an edge from node i to each node ingraph[i]
.A node is a terminal node if there are no outgoing edges. A node is a safe node if every possible path starting from that node leads to a terminal node (or another safe node).
Return an array containing all the safe nodes of the graph. The answer should be sorted in ascending order.
Classic DFS question. Nothing much to explain.
class Solution:
def eventualSafeNodes(self, graph: List[List[int]]) -> List[int]:
n = len(graph)
safe, ans = {}, []
def dfs(now: int):
if now in safe: return safe[now]
safe[now] = False
for adj in graph[now]:
if dfs(adj)==False: return(safe[now])
safe[now] = True
return safe[now]
for i in range(n):
if dfs(i): ans.append(i)
return(ans)
13
Course ScheduleMedium
Topological Sort
DFS
There are a total of
numCourses
courses you have to take, labeled from 0 tonumCourses-1
. You are given an arrayprerequisites
whereprerequisites[i] = [ai, bi]
indicates that you must take coursebi
first if you want to take courseai
.
- For example, the pair $[0, 1]$, indicates that to take course 0 you have to first take course 1.
Return
true
if you can finish all courses. Otherwise, returnfalse
.
class Solution:
def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
adj = [[] for _ in range(numCourses)]
for end, start in prerequisites:
# b first before a, so b->a
adj[start].append(end)
status = [0] * numCourses
#status: 0 not yet, -1 processing, 1 done
def isCycle(now):
# processing(-1): cycle; done(1): nope
if status[now]: return status[now]==-1
status[now] = -1
for v in adj[now]:
if isCycle(v): return True
status[now] = 1
return False
for v in range(numCourses):
if isCycle(v): return False
return True
14
Longest Arithmetic Subsequence of Given DifferenceMedium
Greedy
Given an integer array
arr
and an integerdifference
, return the length of the longest subsequence inarr
which is an arithmetic sequence such that the difference between adjacent elements in the subsequence equalsdifference
.A subsequence is a sequence that can be derived from arr by deleting some or no elements without changing the order of the remaining elements.
straightforward greedy. Use dict to collect nums.
class Solution:
def longestSubsequence(self, arr: List[int], difference: int) -> int:
prevLoc, ans = {}, 1
for i in arr:
if i-difference in prevLoc:
cur = prevLoc[i-difference] + 1
if i in prevLoc:
prevLoc[i] = max(prevLoc[i], cur)
else:
prevLoc[i] = cur
else:
if i not in prevLoc: prevLoc[i] = 1
if prevLoc[i]>ans: ans=prevLoc[i]
return ans
15
Maximum Number of Events That Can Be Attended IIHard
DP
You are given an array of events where
events[i]
=[startDayi, endDayi, valuei]
. The ith event starts atstartDayi
and ends atendDayi
, and if you attend this event, you will receive a value ofvaluei
. You are also given an integerk
which represents the maximum number of events you can attend.You can only attend one event at a time. If you choose to attend an event, you must attend the entire event. Note that the end day is inclusive: that is, you cannot attend two events where one of them starts and the other ends on the same day.
Return the maximum sum of values that you can receive by attending events.
Constraints $1\leq k * events.length \leq 10^6$
一眼DP。然后把数据量看成1≤k≤events.length≤10^6
,然后想了一年啊这怎么可能$O(n)$做啊啊啊啊,然后发现原来就是简单DP。 $dp[i][j]$前i件选j件的max。
class Solution:
def maxValue(self, events: List[List[int]], k: int) -> int:
events.sort(key=lambda x: x[1])
terminates = [x[1] for x in events]
@lru_cache(None)
def dp(i, j):
if i<0 or j==0: return 0
startat = bisect.bisect_left(terminates, events[i][0])-1
return max(dp(startat,j-1)+events[i][2], dp(i-1,j))
return dp(len(events)-1,k)
16
Smallest Sufficient TeamHard
Bitmask
DFS
In a project, you have a list of required skills
req_skills
, and a list of people. The ith personpeople[i]
contains a list of skills that the person has.Consider a sufficient team: a set of people such that for every required skill in
req_skills
, there is at least one person in the team who has that skill. We can represent these teams by the index of each person.Return any sufficient team of the smallest possible size, represented by the index of each person. You may return the answer in any order. It is guaranteed an answer exists.
Constraints
people[i]
are unique.people[i]
is a skill in req_skills
.A nice heavy-coding problem I would say. Most tricky in this month so far :).
The question’s model is set cover problem, which is NP complete, hence no exact polynomial-time solutions. Appropriate greedy algorithms can optimise the time complexity to approximately polynomial.
Indeed, we never no if we have achieved the minimal answer until searched all possible combinations. Therefore, the key is to constrain the possible combinations set:
skillsWho
skillsWho
according to the descending orders of number of skills they have.Note that 4️⃣ and 5️⃣ do not cut every corner. They speed up the convergence by finding a smaller ans
earlier. (essence of this greedy idea: we tend to desire people covering more skills)
class Solution:
def smallestSufficientTeam(self, req_skills: List[str], people: List[List[str]]) -> List[int]:
n = len(people)
n_skills = len(req_skills)
ans = [x for x in range(n)]
for i, p in enumerate(people):
people[i] = set(p)
# discard useless
for i, p1 in enumerate(people):
for j, p2 in enumerate(people):
if i==j: continue
if p1.issubset(p2):
people[i] = set()
# bitmask people skills
people_masked = []
for i in range(n):
skillset = [(x in people[i]) for x in req_skills]
sk = 0
for i in skillset:
sk = sk*2 + int(i)
people_masked.append(sk)
skillsWho = [[] for _ in range(n_skills)]
for p in range(n):
for i in people[p]:
skillsWho[req_skills.index(i)].append(p)
for i in range(n_skills):
skillsWho[i].sort(key=lambda x: -len(people[x]))
def complete(now) -> int:
#-1 complete; otherwise first '0' loc
skills = 0
for x in now:
skills = skills | people_masked[x]
if skills == 2**n_skills-1:
return -1
else:
bitmask = ""
while skills>0:
bitmask = str(skills%2) + bitmask
skills = skills // 2
bitmask = bitmask.zfill(n_skills)
return bitmask.find("0")
def dfs(now):
nonlocal ans
if len(now)>=len(ans): return
nex = complete(now)
print(now,nex)
if nex==-1:
ans = now
return
else:
for p in skillsWho[nex]:
if p in now: continue
dfs(sorted(now+[p]))
hardest = 0 # start search in the hardest skill
for i in range(n_skills):
if len(skillsWho[i])<len(skillsWho[hardest]): hardest = i
for p1 in skillsWho[hardest]:
dfs([p1])
return(ans)
17
Add Two Numbers IIMedium
Linked List
You are given two non-empty linked lists representing two non-negative integers. The most significant digit comes first and each of their nodes contains a single digit. Add the two numbers and return the sum as a linked list.
You may assume the two numbers do not contain any leading zero, except the number 0 itself.
Simple coding.
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
def toLinkedList(n: int) -> ListNode:
l = None
while n:
val = n % 10
n, l = n // 10, ListNode(val, l)
return l if l else ListNode()
def toInt(l: ListNode) -> int:
n = 0
while l:
n=n*10+l.val
l = l.next
return n
return toLinkedList(toInt(l1)+toInt(l2))
18
LRU CacheMedium
Data Structure
Design a data structure that follows the constraints of a Least Recently Used (LRU) cache.
Implement the
LRUCache
class:
LRUCache(int capacity)
Initialize the LRU cache with positive sizecapacity
.int get(int key)
Return the value of thekey
if the key exists, otherwise return -1.void put(int key, int value)
Update the value of thekey
if thekey
exists. Otherwise, add the > key-value pair to the cache. If the number of keys exceeds thecapacity
from this operation, evict the least recently used key.The functions
get
andput
must each run inO(1)
average time complexity.
Simple coding. Key to O(1)
time complexity is
dict
to store keysiter
and next
).class LRUCache:
def __init__(self, capacity: int):
self.capacity = capacity
self.elementDict = {}
def get(self, key: int) -> int:
if key in self.elementDict:
val = self.elementDict.pop(key)
self.elementDict[key] = val
return val
return -1
def put(self, key: int, value: int) -> None:
if key in self.elementDict:
self.elementDict.pop(key)
else:
if len(self.elementDict) == self.capacity:
ancient = next(iter(self.elementDict))
del self.elementDict[ancient]
self.elementDict[key] = value
19
Non-overlapping IntervalsMedium
Greedy
Given an array of intervals
intervals
whereintervals[i] = [starti, endi]
, return the minimum number of intervals you need to remove to make the rest of the intervals non-overlapping.
Greedy. We sort and start from the right, if the current fit in, then we take it, otherwise we drop it.
Fiddling with an example helps to get this idea quickly.
class Solution:
def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
intervals.sort(key=lambda x: x[0], reverse=True)
current, drop = intervals[0], 0
for s, e in intervals[1:]:
if current[0]>=e:
current = [s,e]
else:
drop+=1
return(drop)
20
Asteroid CollisionMedium
We are given an array
asteroids
of integers representing asteroids in a row.For each asteroid, the absolute value represents its size, and the sign represents its direction (positive meaning right, negative meaning left). Each asteroid moves at the same speed.
Find out the state of the asteroids after all collisions. If two asteroids meet, the smaller one will explode. If both are the same size, both will explode. Two asteroids moving in the same direction will never meet.
Simple simulation. We use cur
to record the current border. On the left of the border is the thing we have dealt with the collision.
Time Complexity should be below $O(n^2)$, because of potentially going to the end and going back.
class Solution:
def asteroidCollision(self, asteroids: List[int]) -> List[int]:
cur = 0
for i in range(len(asteroids)):
while cur and asteroids[cur-1]>0 and asteroids[i]<0 and abs(asteroids[cur-1])<abs(asteroids[i]):
cur-=1#crush
if asteroids[i]>0 or asteroids[cur-1]<0 or cur==0:
asteroids[cur] = asteroids[i]
cur+=1
elif asteroids[cur-1] == abs(asteroids[i]):
cur-=1
return asteroids[:cur]
21
Number of Longest Increasing SubsequenceMedium
DP
Given an integer array
nums
, return the number of longest increasing subsequences.Notice that the sequence has to be strictly increasing.
Classic DP. $f[i]$ records the max length of increasing subsequence ending at $i$. Update count
when finding previous length to be $f[i]-1$.
class Solution:
def findNumberOfLIS(self, nums: List[int]) -> int:
n = len(nums)
f = count = [1]*n
for i in range(1,n):
for j in range(i):
if nums[i]<=nums[j]: continue
if f[i] == f[j]+1:
count[i]+=count[j]
elif f[i] < f[j]+1:
f[i], count[i] = f[j]+1, count[j]
ans = max(f)
return sum(count[i] for i in range(n) if f[i]==ans)
22
Knight Probability in ChessboardMedium
On an n x n chessboard, a knight starts at the cell
(row, column)
and attempts to make exactlyk
moves. The rows and columns are 0-indexed, so the top-left cell is(0, 0)
, and the bottom-right cell is(n-1, n-1)
.A chess knight has eight possible moves it can make, as illustrated below. Each move is two cells in a cardinal direction, then one cell in an orthogonal direction.
Each time the knight is to move, it chooses one of eight possible moves uniformly at random (even if the piece would go off the chessboard) and moves there.
The knight continues moving until it has made exactly
k
moves or has moved off the chessboard.Return the probability that the knight remains on the board after it has stopped moving.
Simple coding. Just calculating probabilities step by step.
class Solution:
def knightProbability(self, n: int, k: int, row: int, column: int) -> float:
probabilitys = [[0]*n for _ in range(n)]
probabilitys[row][column] = 1
jump = [(-2,-1),(-2,1),(-1,-2),(-1,2),(1,-2),(1,2),(2,-1),(2,1)]
MoveOffProb = 0
for i in range(k):
# calculate probability from previous round
newProbabilitys = [[0]*n for _ in range(n)]
for r, c in itertools.product(range(n),range(n)):
for dr, dc in jump:
if 0<=r+dr<n and 0<=c+dc<n:
newProbabilitys[r+dr][c+dc] += probabilitys[r][c]/8
else:
MoveOffProb += probabilitys[r][c]/8
probabilitys = newProbabilitys
return(1-MoveOffProb)
23
All Possible Full Binary TreesMedium
Recursion
BinaryTree
Given an integer n, return a list of all possible full binary trees with
n
nodes. Each node of each tree in the answer must haveNode.val == 0
.Each element of the answer is the root node of one possible tree. You may return the final list of trees in any order.
A full binary tree is a binary tree where each node has exactly 0 or 2 children.
Nothing fancy, just list everything. We create a dictionary trees
to store all full binary trees with number of node i. Then bottom-up.
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def allPossibleFBT(self, n: int) -> List[Optional[TreeNode]]:
if not n%2: return []
trees = defaultdict(list)
trees[1].append(TreeNode(0))
t3 = TreeNode(0)
t3.left = TreeNode(0)
t3.right = TreeNode(0)
trees[3].append(t3)
for i in range(5,n+1,2):
l,r = 1, i-2
while r>=1:
for left_child in trees[l]:
for right_child in trees[r]:
tree = TreeNode(0)
tree.left, tree.right = left_child, right_child
trees[i].append(tree)
r-=2
l+=2
return trees[n]
24
Pow(x,n)Easy
DivideandConquer
Implement pow(x, n), which calculates
x
raised to the powern
(i.e., $x^n$).
Constraints
Simple coding, could top-down or bottom-up. Here we use cache()
to cut corners.
class Solution:
@lru_cache(None)
def myPow(self, x: float, n: int) -> float:
if not n: return 1
elif n==1: return x
elif n==-1: return 1/x
return self.myPow(x, n//2) * self.myPow(x, n//2) * self.myPow(x, n%2)
25
Peak Index in a Mountain ArrayMedium
BinarySearch
An array
arr
is a mountain if the following properties hold:
arr.length
>= 3- There exists some i with
0 < i < arr.length - 1
such that:
arr[0] < arr[1] < ... < arr[i - 1] < arr[i]
arr[i] > arr[i + 1] > ... > arr[arr.length - 1]
Given a mountain array
arr
, return the indexi
such thatarr[0] < arr[1] < ... < arr[i - 1] < arr[i] > arr[i + 1] > ... > arr[arr.length - 1].
You must solve it in
O(log(arr.length))
time complexity.
Nothing worth mentioning binary search.
class Solution:
def peakIndexInMountainArray(self, arr: List[int]) -> int:
l,r = 0, len(arr)-1
while True:
mid = (l+r)//2
if arr[mid]>arr[mid-1]:
if arr[mid]>arr[mid+1]:
return mid
else:
l=mid+1
else:
r = mid
26
Minium Speed to Arrive on TimeMedium
BinarySearch
You are given a floating-point number hour, representing the amount of time you have to reach the office. To commute to the office, you must take n trains in sequential order. You are also given an integer array
dist
of lengthn
, wheredist[i]
describes the distance (in kilometers) of the ith train ride.Each train can only depart at an integer hour, so you may need to wait in between each train ride.
- For example, if the 1st train ride takes 1.5 hours, you must wait for an additional 0.5 hours before you can depart on the 2nd train ride at the 2 hour mark.
Return the minimum positive integer speed (in kilometers per hour) that all the trains must travel at for you to reach the office on time, or -1 if it is impossible to be on time.
Tests are generated such that the answer will not exceed $10^7$ and
hour
will have at most two digits after the decimal point.
Constraints
Then we find that, given a speed, we can quickly verify whether it can fulfill the task. So we binary search the possible speeds.
class Solution:
def minSpeedOnTime(self, dist: List[int], hour: float) -> int:
if hour+1 <= len(dist): return -1
l, r = 1, max(dist)
if hour%1!=0:
r = max(r, dist[-1]//(hour-int(hour))+1)
while l<r:
mid = (l+r)//2
need = sum(i//mid + (i%mid!=0) for i in dist[:-1]) + dist[-1]/mid
if need<=hour:
r=mid
else:
l = mid+1
return int(l)
27
Maximum Running Time of N ComputersHard
BinarySearch
Greedy
You have n computers. You are given the integer
n
and a 0-indexed integer array batteries where the ith battery can run a computer forbatteries[i]
minutes. You are interested in running all n computers simultaneously using the given batteries.Initially, you can insert at most one battery into each computer. After that and at any integer time moment, you can remove a battery from a computer and insert another battery any number of times. The inserted battery can be a totally new battery or a battery from another computer. You may assume that the removing and inserting processes take no time.
Note that the batteries cannot be recharged.
Return the maximum number of minutes you can run all the
n
computers simultaneously.
Hard to proceed at first. Then by a greedy idea, we discover it’s easy to check whether a life
is viable (shown in the function check
). Then we just binary search the life
.
class Solution:
def maxRunTime(self, n: int, batteries: List[int]) -> int:
l = 1
r = sum(batteries) // n
def check(life: int) -> bool:
return sum([min(x, life) for x in batteries])>=n*life
while l<=r:
mid = (l+r)//2
if check(mid):
l=mid+1
else:
r=mid-1
return r
28
Predict the WinnerMedium
DP
Recursion
You are given an integer array nums. Two players are playing a game with this array: player 1 and player 2.
Player 1 and player 2 take turns, with player 1 starting first. Both players start the game with a score of 0. At each turn, the player takes one of the numbers from either end of the array (i.e.,
nums[0]
ornums[nums.length - 1]
) which reduces the size of the array by 1. The player adds the chosen number to their score. The game ends when there are no more elements in the array.Return true if Player 1 can win the game. If the scores of both players are equal, then player 1 is still the winner, and you should also return true. You may assume that both players are playing optimally.
总感觉以前哪里做过。。是HRT还是XTX的面试题吗😶
Simple DP. The idea is:
or
).and
). Then it’s clearly DP. Edge situation easy to handle. Again lru_cache
to cut corners.class Solution:
def PredictTheWinner(self, nums: List[int]) -> bool:
@lru_cache(None)
def search(l: int,r: int,score1: int,score2: int, now1: bool) -> bool:
nonlocal nums
if l>r: return score1>=score2
if now1:
return search(l+1, r, score1+nums[l], score2, False) or search(l,r-1,score1+nums[r],score2,False)
else:
return search(l+1, r, score1, score2+nums[l], True) and search(l,r-1,score1,score2+nums[r],True)
return search(0,len(nums)-1,0,0,True)
29
Soup ServingsMedium
DP
ti mu tai chang le, bu xiang ban yun.
just simple DP. Nothing to say.
class Solution:
def soupServings(self, n: int) -> float:
@lru_cache(None)
def dfs(a,b):
if a<=0 and b<=0:
return 0.5
elif a<=0:
return 1
elif b<=0:
return 0
return 0.25*(dfs(a-100,b)+dfs(a-75,b-25)+dfs(a-50,b-50)+dfs(a-25,b-75))
if n>=4451:
return 1
else:
return dfs(n,n)
30
Strange PrinterHard
DP
There is a strange printer with the following two special properties:
- The printer can only print a sequence of the same character each time.
- At each turn, the printer can print new characters starting from and ending at any place and will cover the original existing characters.
Given a string
s
, return the minimum number of turns the printer needed to print it.
DP is clear. How to proceed DP is hard. We consider **how many steps we take to change a string s
from the form aaaaaa where a
is s
’s end, to s
.
Key idea: there always exists an optimal strategy, where at each operation we use the printer to change the section of letters to its ending letter. i.e. change s[start:end+1]
to s[end] * (end-start+1)
. Just argue by contradiction.
Then with this idea we can divide the problem into subproblems, thus proceeding DP.
I think the solution explains the idea very clearly.
class Solution:
def strangePrinter(self, s: str) -> int:
nextDiffIndex, prevDiffIndex = [-1] * len(s), [-1] * len(s)
for i in range(len(s)-2, -1, -1):
if s[i]!=s[i+1]:
nextDiffIndex[i] = i+1
else:
nextDiffIndex[i] = nextDiffIndex[i+1]
for i in range(1, len(s)):
if s[i]!=s[i-1]:
prevDiffIndex[i] = i-1
else:
prevDiffIndex[i] = prevDiffIndex[i-1]
@lru_cache(None)
def dp(start, end) -> int:
#dp[l][r] = dp[j][i] + dp[i+1][r] + 1 for optimal i
# we assume we already have s[end]
if len(set(s[start:end+1])) == 1: return 0
next = nextDiffIndex[start] if s[start]==s[end] else start
if next == prevDiffIndex[end]: return 1
return 1 + min([dp(next, nextend)+dp(nextend+1, end) for nextend in range(next, end)])
return 1+dp(0, len(s)-1)
Finished this problem at a Stabucks in Bilbao, with a broken leg. Apparently the Spanish add sugars to cold brew.
31
Minimum ASCII Delete Sum for Two StringsMedium
DP
Given two strings
s1
ands2
, return the lowest ASCII sum of deleted characters to make two strings equal.
Constraints $1 \leq s1.length,\, s2.length \leq 1000$
A classic DP problem. Consider dp(i,j)
to be the answer of s1[i:]
and s2[j:]
.
class Solution:
def minimumDeleteSum(self, s1: str, s2: str) -> int:
@lru_cache(None)
def dp(i, j) -> int:
# ans since [i:] and [j:]
nonlocal s1,s2
if i==len(s1) and j==len(s2): return 0
if i==len(s1) and j<len(s2):
return sum([ord(x) for x in s2[j:]])
if i<len(s1) and j==len(s2):
return sum([ord(x) for x in s1[i:]])
if s1[i]==s2[j]: return dp(i+1,j+1)
return min(ord(s1[i])+dp(i+1,j), ord(s2[j])+dp(i,j+1))
return dp(0,0)
Jul LeetCoding Challenge COMPLETED