Press "Enter" to skip to content

Huahua's Tech Road

花花酱 Amortized Analysis 均摊分析 SP7

Amortized Analysis

Amortized analysis can help us understand the actual cost of n operations on a data structure. Since some operations can be really fast e.g. O(1), but some operations can be really slow e.g. O(n). We could say the time complexity of that operation is O(n). However, it does not reflect the actual performance. And turns out, we can prove that certain operations have an amortized cost of O(1) while they can take O(n) in the worst case.

均摊分析可以帮助我们了解对一个数据结构进行n次操作的真实代价。对于相同的操作,有时候可以非常快,例如在O(1)时间内完成,而有时候则需要O(n)。我们当然可以说这个操作最坏情况下的时间复杂度是O(n),但是这并不能真实反映它的实际复杂度。通过均摊分析,我们可以证明,尽管有些操作在最坏情况下是O(n)的时间复杂度,但是均摊下来只需要O(1)。

 

Dynamic Array

dynamic array doubles its size when it’s full which could take O(i) time where i is the number of elements in the array. Otherwise just store the element which only cost O(1). We can use aggregate method to compute the amortized cost of dynamic array.

动态数组在容量满时将容量翻翻,这一步需要O(i)时间,i是当前数组中的元素个数。如果没有满,只需要将元素存储下来即可,只需要花费O(1)时间。我们可以使用聚合法来计算均摊成本。

((1 + 1) + (1 + 2) + (1) + (1 + 4) + (1) + (1) + (1) + (1+8) + … + (1+n)) / n, assuming n is 2^k.

= (1 * n + (1 + 2 + 4 + 8 + … + n)) / n = (n + 2n – 1) / n = 3. O(1)

C++

Output

 

Monotonic Stack

例子2: 单调栈

往单调栈push一个元素的时候,会删除上所有小于等于它的元素。这步操作在最优情况下是O(1)时间,如果它比栈顶元素要小。在最坏情况下是O(i)时间,栈上有i个元素,它比这i个元素都要大,所以一共要pop i次。

聚合法:

由于每个元素都会被push到栈上去1次,最多会被pop1次,所以总的操作数 <= 2n。 2n / n = 2 O(1)。

会计法:

每次push之前先存k块钱,k=2, 一块钱用于push自己,一块钱留着用于pop自己。

push的时候扣除1块钱,pop的时候再扣除1块钱。但不管怎样,我的账户上的钱永远>=0。这样我们可以说push的均摊成本是k=2。同样是O(1),尽管它的worst case是O(n)。

C++

Output

 

Related Problem

花花酱 LeetCode 10. Regular Expression Matching

Problem

Given an input string (s) and a pattern (p), implement regular expression matching with support for '.' and '*'.

'.' Matches any single character.
'*' Matches zero or more of the preceding element.

The matching should cover the entire input string (not partial).

Note:

  • s could be empty and contains only lowercase letters a-z.
  • p could be empty and contains only lowercase letters a-z, and characters like . or *.

Example 1:

Input:
s = "aa"
p = "a"
Output: false
Explanation: "a" does not match the entire string "aa".

Example 2:

Input:
s = "aa"
p = "a*"
Output: true
Explanation: '*' means zero or more of the precedeng element, 'a'. Therefore, by repeating 'a' once, it becomes "aa".

Example 3:

Input:
s = "ab"
p = ".*"
Output: true
Explanation: ".*" means "zero or more (*) of any character (.)".

Example 4:

Input:
s = "aab"
p = "c*a*b"
Output: true
Explanation: c can be repeated 0 times, a can be repeated 1 time. Therefore it matches "aab".

Example 5:

Input:
s = "mississippi"
p = "mis*is*p*."
Output: false

Solution 1: Recursion

Time complexity: O((|s| + |p|) * 2 ^ (|s| + |p|))

Space complexity: O(|s| + |p|)

C++

花花酱 LeetCode 907. Sum of Subarray Minimums

Problem

Given an array of integers A, find the sum of min(B), where B ranges over every (contiguous) subarray of A.

Since the answer may be large, return the answer modulo 10^9 + 7.

Example 1:

Input: [3,1,2,4]
Output: 17
Explanation: Subarrays are [3], [1], [2], [4], [3,1], [1,2], [2,4], [3,1,2], [1,2,4], [3,1,2,4]. 
Minimums are 3, 1, 2, 4, 1, 1, 2, 1, 1, 1.  Sum is 17.

Note:

  1. 1 <= A.length <= 30000
  2. 1 <= A[i] <= 30000

Idea

  1. order matters, unlike 花花酱 LeetCode 898. Bitwise ORs of Subarrays we can not sort the numbers in this problem.
    1. e.g. sumSubarrayMins([3, 1, 2, 4]) !=sumSubarrayMins([1, 2, 3, 4]) since the first one will not have a subarray of [3,4].
  2. For A[i], assuming there are L numbers that are greater than A[i] in range A[0] ~ A[i – 1], and there are R numbers that are greater or equal than A[i] in the range of A[i+1] ~ A[n – 1]. Thus A[i] will be the min of (L + 1) * (R + 1) subsequences.
    1. e.g. A = [3,1,2,4], A[1] = 1, L = 1, R = 2, there are (1 + 1) * (2 + 1) = 6 subsequences are 1 is the min number. [3,1], [3,1,2], [3,1,2,4], [1], [1,2], [1,2,4]

Solution 1: Brute Force

Time complexity: O(n^2)

Space complexity: O(1)

C++

Java

Python3 (TLE)

Solution2 : Monotonic Stack

Time complexity: O(n)

Space complexity: O(n)

We can use a monotonic stack to compute left[i] and right[i] similar to 花花酱 LeetCode 901. Online Stock Span

C++

Java

Python3

Python3 V2

花花酱 LeetCode 905. Sort Array By Parity

Problem

Given an array A of non-negative integers, return an array consisting of all the even elements of A, followed by all the odd elements of A.

You may return any answer array that satisfies this condition.

Example 1:

Note:

  1. 1 <= A.length <= 5000
  2. 0 <= A[i] <= 5000

Solution 1: Split Odd/Even

Time complexity: O(n)

Space complexity: O(n)

C++

Solution 2: Stable sort by key % 2

Time complexity: O(nlogn)

Space complexity: O(1) in-place

C++

花花酱 LeetCode 904. Fruit Into Baskets

Problem

In a row of trees, the i-th tree produces fruit with type tree[i].

You start at any tree of your choice, then repeatedly perform the following steps:

  1. Add one piece of fruit from this tree to your baskets.  If you cannot, stop.
  2. Move to the next tree to the right of the current tree.  If there is no tree to the right, stop.

Note that you do not have any choice after the initial choice of starting tree: you must perform step 1, then step 2, then back to step 1, then step 2, and so on until you stop.

You have two baskets, and each basket can carry any quantity of fruit, but you want each basket to only carry one type of fruit each.

What is the total amount of fruit you can collect with this procedure?

Example 1:

Input: [1,2,1]
Output: 3
Explanation: We can collect [1,2,1].

Example 2:

Input: [0,1,2,2]
Output: 3
Explanation: We can collect [1,2,2].
If we started at the first tree, we would only collect [0, 1].

Example 3:

Input: [1,2,3,2,2]
Output: 4
Explanation: We can collect [2,3,2,2].
If we started at the first tree, we would only collect [1, 2].

Example 4:

Input: [3,3,3,1,2,1,1,2,3,3,4]
Output: 5
Explanation: We can collect [1,2,1,1,2].
If we started at the first tree or the eighth tree, we would only collect 4 fruits.

Note:

  1. 1 <= tree.length <= 40000
  2. 0 <= tree[i] < tree.length

Solution: Hashtable + Sliding window

Time complexity: O(n)

Space complexity: O(1)

Keep track of the last index of each element. If a third type of fruit comes in, the new window starts after the fruit with smaller last index. Otherwise extend the current window.

[1 3 1 3 1 1] 4 1 4 … <- org window, 3 has a smaller last index than 1.

1 3 1 3 [1 1 4] 1 4 … <- new window

C++