def with_repeat(arr): # output is sorted lexicographically assert is_sorted(arr) n = len(arr) yield arr while True: left = next((i for i in rev_range(n-1) if arr[i] < arr[i+1]), None) # left: largest index s.t. a[left] < a[left+1] if left is None: # arr is reversely sorted return right = next(i for i in rev_range(left+1, n) if arr[left] < arr[i]) # largest index right > left, s.t. a[left] < a[right]. guaranteed to exist as a[left] < a[left+1] arr[left], arr[right] = arr[right], arr[left] # swap arr[left] and arr[right] arr[left+1:] = reversed(arr[left+1:]) # arr[left+1:].reverse() reverses on a copy yield arr
def control(hist): # O(n^2) max_rec, n = 0, len(hist) for i, x in enumerate(hist): left = next((j for j in rev_range(i) if hist[j] < x), -1) right = next((j for j in range(i+1, n) if hist[j] < x), n) max_rec = max(max_rec, x * (right - left - 1)) return max_rec
def search2(arr): """ Given the price of a stock over n days. Allow at most 2 transactions. Returns the maximum possible profit. Observation: max_profit = max(for each day i, first sell is <= day i, second buy-in is >= day i) = max(for each day i, first sell is on day i, second buy-in is >= day i) Note the possibility of selling & buying on day i, which merges two transactions. Solution is a 2-pass scan. Time complexity is O(n). Space complexity is O(n). :param arr: list[num] :return: num """ n = len(arr) if n <= 1: return 0 pre_min = arr[0] fst = [0] # fst[i]: maximum profit trading at most, considering arr[:i+1] for x in arr[1:]: pre_min = min(x, pre_min) fst.append(max(fst[-1], x - pre_min)) profit = 0 post_max = arr[-1] for i in rev_range(n): post_max = max(arr[i], post_max) profit = max(profit, post_max - arr[i] + fst[i]) return profit
def search2(arr): """ Returns a partition, represented as two subsequences a and b, s.t. |sum(a) - sum(b)| is minimised. Solution is classic 0-1 knapsack DP. Time complexity is O(n^2). Space complexity is O(s), where s = sum(arr). :param arr: list[int], non-negative :return: tuple[list[int],list[int]] """ n, s = len(arr), sum(arr) if n == 0: return [], [] dp = [None] * (s + 1) dp[0] = [0] * n # bit array for elements in arr for i, x in enumerate(arr): for j in list(filter_index(lambda x: x is not None, dp)): # indices must be obtained before modification if j + x < s + 1 and dp[j+x] is None: dp[j+x] = list(dp[j]) dp[j+x][i] = 1 left_ix = next(((i, dp[i]) for i in rev_range(ceil(s/2)) if dp[i] is not None), None) right_ix = next(((i, dp[i]) for i in range(ceil(s/2), s+1) if dp[i] is not None), None) if left_ix is None: selected = right_ix[1] elif right_ix is None: selected = left_ix[1] else: selected = min(left_ix, right_ix, key=lambda x: abs(x[0]-s/2))[1] left, right = [], [] for i in range(n): if selected[i]: left.append(arr[i]) else: right.append(arr[i]) return left, right
def insert(arr, start, finish): """ Given a non-overlapping sequence of intervals, denoted as (start, finish) pairs, and sorted by start time, inserts a new interval. Observation: since intervals are non-overlapping, end times are sorted too. Time complexity is O(n). Space complexity is O(n). :param arr: list[tuple[num, num]], sorted :param start: num, non-negative :param finish: num, positive. start < finish :return: list[tuple[num, num]] """ n = len(arr) left = next((i for i in range(n) if arr[i][1] >= start), None) # last interval that finishes no earlier than the # start of the new interval. all intervals in arr[:left] finish before the start of the new interval if left is None: # all intervals have finished when the new interval starts return arr + [(start, finish)] new_start = min( arr[left][0], start ) # arr[left] is either fully or partially contained in the new interval right = next((i for i in rev_range(n) if arr[i][0] <= finish), None) # first interval that starts no later than # the finish of the new interval. all intervals in arr[right+1:] start after the finish of the new interval if right is None: # no interval has started when the new interval ends return [(start, finish)] + arr new_end = max(arr[right][1], finish) return list(arr[:left]) + [(new_start, new_end)] + list(arr[right + 1:])
def control_subarray(arr): # O(n^3) def step(n): for win in sliding_window(arr, n): s = set(win) if len(s) == n and max(s) - min(s) == n - 1: return True return False return next(i for i in rev_range(len(arr) + 1) if step(i))
def int_to_arr(x): # big-endian assert x >= 0 n = floor(log10(x)) + 1 # 999 -> 3; 1000 -> 4 arr = [None] * n for i in rev_range(n): arr[i] = x % 10 x //= 10 assert x == 0 return arr
def spiral(mat): m = len(mat) if m == 0: return n = len(mat[0]) offset = 0 while True: if offset > (min(m, n) - 1) // 2: # 1 -> 0, 2 -> 0, 3 -> 1, 4 -> 1 return for i in range(offset, n - offset): yield mat[offset][i] for j in range(offset + 1, m - offset): yield mat[j][n-1-offset] if offset > min(m, n) / 2 - 1: # 1 -> -0.5, 2 -> 0, 3 -> 0.5, 4 -> 1 return for i in rev_range(offset, n - offset - 1): yield mat[m-1-offset][i] for j in rev_range(offset + 1, m - offset - 1): yield mat[j][offset] offset += 1
def spiral(mat: Sequence[Sequence[T]]) -> Generator[T, None, None]: M = len(mat) if M == 0: return N = len(mat[0]) offset = 0 while True: if offset > (min(M, N) - 1) // 2: # 1 -> 0, 2 -> 0, 3 -> 1, 4 -> 1 return for i in range(offset, N - offset): yield mat[offset][i] for j in range(offset + 1, M - offset): yield mat[j][N - 1 - offset] if offset > min(M, N) / 2 - 1: # 1 -> -0.5, 2 -> 0, 3 -> 0.5, 4 -> 1 return for i in rev_range(offset, N - offset - 1): yield mat[M - 1 - offset][i] for j in rev_range(offset + 1, M - offset - 1): yield mat[j][offset] offset += 1
def next_greater_arr(arr): n = len(arr) pivot = next((i for i in rev_range(n-1) if arr[i] < arr[i+1]), None) if pivot is None: return None min_idx, min_val = pivot+1, arr[pivot+1] # arr[pivot] < arr[pivot+1] by the definition of pivot for i in range(pivot+2, n): # smallest a[i] s.t. i > pivot and arr[i] > arr[pivot] if arr[i] > arr[pivot] and arr[i] < min_val: min_idx, min_val = i, arr[i] arr[pivot], arr[min_idx] = arr[min_idx], arr[pivot] arr[pivot+1:] = sorted(arr[pivot+1:]) return arr
def control(seq): # O(n^2) def is_balanced(sub): d = 0 for x in sub: d += 1 if x == '(' else -1 if d < 0: return False return d == 0 def step(n): for xs in sliding_window(seq, n): if is_balanced(xs): return True return False return next((i for i in rev_range(2, len(seq) + 1) if step(i)), 0)
def search2(mat): """ Returns the area of the maximum rectangle whose four corners are all 1s. Time complexity is O(n^3). Space complexity is O(1). :param mat: list[list[bool]] :return: int, non-negative """ if len(mat) == 0 or len(mat[0]) == 0: # numpy compatibility return 0 m, n = len(mat), len(mat[0]) max_area = 0 for top in range(m): for bottom in range(top, m): left = next((i for i in range(n) if mat[top][i] == mat[bottom][i] == 1), None) if left is None: continue right = next(i for i in rev_range(n) if mat[top][i] == mat[bottom][i] == 1) max_area = max(max_area, (right - left + 1) * (bottom - top + 1)) return max_area
def search2(mat): """ Returns the area of the maximum rectangle whose four corners are all 1s. Time complexity is O(n^3). Space complexity is O(1). :param mat: list[list[bool]] :return: int, non-negative """ if len(mat) == 0 or len(mat[0]) == 0: # numpy compatibility return 0 m, n = len(mat), len(mat[0]) max_area = 0 for top in range(m): for bottom in range(top, m): left = next( (i for i in range(n) if mat[top][i] == mat[bottom][i] == 1), None) if left is None: continue right = next(i for i in rev_range(n) if mat[top][i] == mat[bottom][i] == 1) max_area = max(max_area, (right - left + 1) * (bottom - top + 1)) return max_area
def search2(arr): """ Returns a partition, represented as two subsequences a and b, s.t. |sum(a) - sum(b)| is minimised. Solution is classic 0-1 knapsack DP. Time complexity is O(n^2). Space complexity is O(s), where s = sum(arr). :param arr: list[int], non-negative :return: tuple[list[int],list[int]] """ n, s = len(arr), sum(arr) if n == 0: return [], [] dp = [None] * (s + 1) dp[0] = [0] * n # bit array for elements in arr for i, x in enumerate(arr): for j in list(filter_index( lambda x: x is not None, dp)): # indices must be obtained before modification if j + x < s + 1 and dp[j + x] is None: dp[j + x] = list(dp[j]) dp[j + x][i] = 1 left_ix = next( ((i, dp[i]) for i in rev_range(ceil(s / 2)) if dp[i] is not None), None) right_ix = next( ((i, dp[i]) for i in range(ceil(s / 2), s + 1) if dp[i] is not None), None) if left_ix is None: selected = right_ix[1] elif right_ix is None: selected = left_ix[1] else: selected = min(left_ix, right_ix, key=lambda x: abs(x[0] - s / 2))[1] left, right = [], [] for i in range(n): if selected[i]: left.append(arr[i]) else: right.append(arr[i]) return left, right
def insert(arr, start, finish): """ Given a non-overlapping sequence of intervals, denoted as (start, finish) pairs, and sorted by start time, inserts a new interval. Observation: since intervals are non-overlapping, end times are sorted too. Time complexity is O(n). Space complexity is O(n). :param arr: list[tuple[num, num]], sorted :param start: num, non-negative :param finish: num, positive. start < finish :return: list[tuple[num, num]] """ n = len(arr) left = next((i for i in range(n) if arr[i][1] >= start), None) # last interval that finishes no earlier than the # start of the new interval. all intervals in arr[:left] finish before the start of the new interval if left is None: # all intervals have finished when the new interval starts return arr + [(start, finish)] new_start = min(arr[left][0], start) # arr[left] is either fully or partially contained in the new interval right = next((i for i in rev_range(n) if arr[i][0] <= finish), None) # first interval that starts no later than # the finish of the new interval. all intervals in arr[right+1:] start after the finish of the new interval if right is None: # no interval has started when the new interval ends return [(start, finish)] + arr new_end = max(arr[right][1], finish) return list(arr[:left]) + [(new_start, new_end)] + list(arr[right+1:])
def control(arr): # O(n^2) a, n = [], len(arr) for i, x in enumerate(arr): a.append(next(j for j in rev_range(i, n) if arr[j] >= x)) return max(x - i for i, x in enumerate(a))
lbg = [None] * n if n <= 1: # requires len(arr) >= 2 return lbg i, j = n - 1, n - 1 # arr[i+1:] is scanned, lbg[j+1:] is filled while i > 0: while i > 0 and arr[i] + gap >= arr[j]: # slide i leftwards i -= 1 while i <= j and arr[i] + gap < arr[j]: # slide j leftwards lbg[j] = i j -= 1 return lbg if __name__ == '__main__': std_test = { ((), 1): [], ((2, ), 1): [None], ((1, 2), 0): [None, 0], ((2, 5, 5, 9), 2): [None, 0, 0, 2] } for k, v in std_test.items(): assert search(*k) == v from random import randint for size in [x for x in range(50) for _ in range(x)]: a = sorted(randint(-size, size) for _ in range(size)) g = randint(0, size) assert search(a, g) == [ next((j for j in rev_range(i) if a[j] + g < a[i]), None) for i in range(size) ]
def heapify(self): for i in rev_range(len(self.a)): # order is not important, as long as heap property is maintained for all layers self.bubble_up(i) self.trickle_down(i)
def control_prev(arr): n = len(arr) r = [] * n for i in range(n): r.append(next((j for j in rev_range(i) if arr[j] > arr[i]), None)) return r
:param gap: num, non-negative :return: list[Optional[int]] """ assert gap >= 0 assert is_sorted(arr) n = len(arr) lbg = [None] * n if n <= 1: # requires len(arr) >= 2 return lbg i, j = n-1, n-1 # arr[i+1:] is scanned, lbg[j+1:] is filled while i > 0: while i > 0 and arr[i] + gap >= arr[j]: # slide i leftwards i -= 1 while i <= j and arr[i] + gap < arr[j]: # slide j leftwards lbg[j] = i j -= 1 return lbg if __name__ == '__main__': std_test = {((), 1): [], ((2,), 1): [None], ((1, 2), 0): [None, 0], ((2, 5, 5, 9), 2): [None, 0, 0, 2]} for k, v in std_test.items(): assert search(*k) == v from random import randint for size in [x for x in range(50) for _ in range(x)]: a = sorted(randint(-size, size) for _ in range(size)) g = randint(0, size) assert search(a, g) == [next((j for j in rev_range(i) if a[j] + g < a[i]), None) for i in range(size)]