Woodstock Blog

a tech blog for general algorithmic interview questions

[LeetCode Plus] Sliding Window Maximum

Question

link

A long array A[] is given to you. There is a sliding window of size w which is moving from the very left of the array to the very right. You can only see the w numbers in the window. Each time the sliding window moves rightwards by one position. Following is an example: The array is [1, 3, -1, -3, 5, 3, 6, 7], and w is 3.

Window position                Max
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

Input: A long array A[], and a window width w

Output: An array B[], B[i] is the maximum value of from A[i] to A[i+w-1]

Analysis

The naive approach is using a Heap. This time complexity is O(n*logn). However, there is a better way using a (double-ended) queue.

We do not need to keep all numbers. For example, suppose numbers in a window of size 4 are (1, 3, -1, 2). Obviously, no matter what next numbers are, 1 and -1 are never going to be a maximal as the window moving. The queue should look like (3, 2) in this case.

Solution

  1. When moves to a new number, iterate through back of the queue, removes all numbers that are not greater than the new one, and then insert the new one to the back.
  2. FindMax only need to take the first one of the queue.
  3. To remove a number outside the window, only compare whether the current index is greater than the front of queue. If so, remove it.

A natural way most people would think is to try to maintain the queue size the same as the window’s size. Try to break away from this thought and think out of the box.

Code

written by me

public int[] slidingWindowMax(int[] array, int w) {
    int[] ans = new int[array.length - w + 1];
    List<Integer> q = new LinkedList<Integer>();
    // Queue stores indices of array, and values are in decreasing order.
    // In this way, the top element in queue is the max in window
    for (int i = 0; i < array.length; i++) {
        // 1. remove element from head until first number within window
        if (!q.isEmpty() && q.get(0) + w <= i) {
            // it's OK to change 'while' to 'if' in the line above
            // cuz we actually remove 1 element at most
            q.remove(0);
        }
        // 2. before inserting i into queue, remove from the tail of the
        // queue indices with smaller value they array[i]
        while (!q.isEmpty() && array[q.get(q.size() - 1)] <= array[i]) {
            q.remove(q.size() - 1);
        }
        q.add(i);
        // 3. set the max value in the window (always the top number in
        // queue)
        if (i + 1 >= w) {
            ans[i + 1 - w] = array[q.get(0)];
        }
    }
    return ans;
}

[Question] Reconstruct Tree From Pre-Order Traversal

Question

link

A tree has a special property where leaves are represented with ‘2’ and non-leaf with ‘1’. Each node has either 0 or 2 children. If given preorder traversal of this tree, construct the tree.

Example: Given Pre Order string => 12122, output:

       1
      / \
     2   1
        / \
       2   2

Analysis

In normal scenario, it’s not possible to detect where left subtree ends and right subtree starts using only pre-order traversal. But here, we are given a special property. Since every node has either 2 children or no child, we can surely say that if a node exists then its sibling also exists.

Keep a public variable and build the tree recursively until the list finishes.

Code

ListNode list = null; // this is the input list public variable

public TreeNode main(ListNode input) {
    list = input;
    return constructTree();
}

private TreeNode constructTree() {
    if (list == null) {
        return null;
    }
    TreeNode root = new TreeNode(list.val);
    list = list.next;

    if (root.val == 1) {
        root.left = constructTree();
        root.right = constructTree();
    }
    return root;
}

[Question] Fit 1*2 Dominos in 2*N Strip

Question

link

In how many ways can one tile a 2 X N strip of square cells with 1 X 2 dominos?

Solution

X(n+1) = X(n) + X(n-1)

It’s a Fibonacci Series with X(1) = 1 and X(2) = 2.

[Question] Elephant and Bananas

Question

link

There’s a elephant, which can carry max 1000 bananas. The elephant eats a banana every 1 Km (both forward and back).

Now we want to transfer 3000 bananas to a place 1000 Km away. How many bananas can be left?

Also solved to generalized problem (write code for solution).

Analysis

If we subdivide distances for each kilometer. Notice if elephant wants to shift all the bananas 1 km, you will loose 5 bananas every km.

So we transferred 2995 (998+998+999) to one km distance. This process continues until after 200 km, we have only 2000 bananas left with remaining distance of 800 km.

Start from here, we only loose 3 bananas every km. This goes on for another 334 km, we will have 998 bananas left, and the rest of the bananas can be transfered in a single journey.

Solution

532 bananas.

[LeetCode Plus] Coins in a Line

Question

link

There are n coins in a line. Two players take turns to take a coin from one of the ends of the line until there are no more coins left. The player with the larger amount of money wins. Assume that you go first, describe an algorithm to compute the maximum amount of money you can win.

There might be even or odd number of coins.

Analysis

There’s a guaranteed ‘win strategy’, if the input array is even size. Find the sum of coins at even and odd positions respectively. Then, make sure you always take coin from even (or odd, whichever sum is bigger) position.

This strategy is clever and simple, but DOES NOT maximize your total sum, and coins must be even number.

Solution

The optimized solution is to use 2D DP. Now we have array A and C(i, j) which is the maximum sum possible when remaining coins are from i to j.

You can take either i or j. Since the opponent is as smart as you, he would have chosen the choice that yields the minimum amount to you.

So, the final equation is:

C(i, j) = MAX {

      Ai + MIN{C(i+2, j), C(i+1, j-1)}, 

      Aj + MIN{C(i+1, j-1), C(i, j-2)} 

}

If interested, read [Fundamental] Min-Max Algorithm for more.

Code

public int maxMoney(int[] coins) {
    int len = coins.length;
    int[][] dp = new int[len][len];
    for (int i = len - 1; i >= 0; i--) {
        for (int j = i; j < len; j++) {
            if (i == j) {
                dp[i][j] = coins[i];
            } else if (i + 1 == j) {
                dp[i][j] = Math.max(coins[i], coins[j]);
            } else {
                int chooseHead = coins[i]
                        + Math.min(dp[i + 2][j], dp[i + 1][j - 1]);
                int chooseTail = coins[j]
                        + Math.min(dp[i][j - 2], dp[i + 1][j - 1]);
                dp[i][j] = Math.max(chooseHead, chooseTail);
            }
        }
    }
    return dp[0][len - 1];
}

[Question] Truth Tell Brain Teaser

Question

link

There are 100 people in a room. A person always speaks either lie or truth.

When asked:

1st person says => all are liars
2nd person says => at most 1 speaks truth
3rd person says => at most 2 speak truth
4th person says => at most 3 speak truth
.
.
.
100th person says => at most 99 speak truth

“At most N” means “there’re N or less than N”.

How many people speak only truth?

Solution

  1. Assume 1st person speaks truth, then all including him should be liar. It means he doesn’t speak truth.

  2. Assume 2nd person speaks truth, then he is the only person who speaks truth. But if this statement is true then statements by all others are also true. I.e. if “at most 1 person speaks truth” is true then “at most N speak truth” is also true. So person 2 is also a liar.

  3. Assume 3rd person is speaking truth. But then the statements of person 4-100 are also true, which contradicts his own statement. It means that person 3 is also a liar.

  4. This process will continue since 50th person. So 1-50 people are liars.

  5. 51st person says “at most 50 speak truth”. Lets say he is speaking truth. “at most 50” means any number from 0-50. It means that statements like “at most 51 speak truth” and “at most 70 speak truth” are also true. It means that people from 51 to 100 are speaking truth.

Hence, 50 people speak truth.

[Testing] Test Number Divisibility

Question

link

Pay attention on Number 3, 7, 9 and 11.

The Divisibility Rules

Divisible by: If: Examples:
2 The last digit is even (0,2,4,6,8) 128 is
129 is not
3 The sum of the digits is divisible by 3

381 (3+8+1=12, and 12÷3 = 4) Yes

217 (2+1+7=10, and 10÷3 = 3 1/3) No

4 The last 2 digits are divisible by 4

1312 is (12÷4=3)
7019 is not

5 The last digit is 0 or 5 175 is
809 is not
6 The number is divisible by both 2 and 3 114 (it is even, and 1+1+4=6 and 6÷3 = 2) Yes

308 (it is even, but 3+0+8=11 and 11÷3 = 3 2/3) No
7 If you double the last digit and subtract it from the rest of the number and the answer is:
  • 0, or
  • divisible by 7
(Note: you can apply this rule to that answer again if you want)

672 (Double 2 is 4, 67-4=63, and 63÷7=9) Yes

905 (Double 5 is 10, 90-10=80, and 80÷7=11 3/7) No

8 The last three digits are divisible by 8

109816 (816÷8=102) Yes

216302 (302÷8=37 3/4) No

9 The sum of the digits is divisible by 9

(Note: you can apply this rule to that answer again if you want)

1629 (1+6+2+9=18, and again, 1+8=9) Yes

2013 (2+0+1+3=6) No

10 The number ends in 0

220 is
221 is not

11

If you sum every second digit and then subtract all other digits and the answer is:

  • 0, or
  • divisible by 11

1364 ((3+4) - (1+6) = 0) Yes

3729 ((7+9) - (3+2) = 11) Yes

25176 ((5+7) - (2+1+6) = 3) No

12 The number is divisible by both 3 and 4

648
(By 3? 6+4+8=18 and 18÷3=6 Yes.
By 4? 48÷4=12 Yes) Yes

524
(By 3? 5+2+4=11, 11÷3= 3 2/3 No.
Don't need to check by 4.) No

[Question] Random Number Generate Question

Question

link

Given a RNG r(5) which generates number between 1-5 uniformly, make r(7) which generates random number between 1-7.

Solution

int rand7() {
    int r = 0;
    do {
        int a = rand(5) - 1;    //uniformly at random from 0 to 4
        int b = rand(5) - 1;
        r = 5 * b + a;            //uniformly at random from 0 to 24
    }
    while (r >= 21);            // in this event, we have to roll again
    return r % 7 + 1; 
}

[Design] Multithreading Q&A

General Q & A

source

Can a thread acquire more than one lock (Mutex)?

Yes, if they need more resource.

Can a mutex be locked more than once?

Unless it’s a recursive mutex, no.

A mutex is a lock.

What happens if a non-recursive mutex is locked more than once?

Deadlock.

Trying to lock the mutex again, it will enter into the waiting queue. But no other thread can unlock that mutex.

Are binary semaphore and mutex same?

No. One is signalling, another is locking mechanism.

A semaphore is more general concept than mutex. A mutex is (almost) a special case of a semaphore.

Why use mutex and critical section?

Critical section is group of instructions that need to be executed atomically.

The objective of mutex is atomic access of critical section.

Can we acquire mutex/semaphore in an Interrupt Service Routine?

Yes, but very bad practise.

The ISR are meant be short, the call to mutex/semaphore may block the current running thread. However, an ISR can signal a semaphore or unlock a mutex.

What is thread blocking on mutex/semaphore?

When the resource is not available, the requesting thread will be moved from the running list of processor to the waiting list of the synchronization primitive.

When the resource is available, the higher priority thread on the waiting list will get resource (more precisely, it depends on the scheduling policies).

Is it necessary that a thread must block when resource is not available?

No.

If the design is sure ‘what has to be done when resource is not available‘, the thread can take up that work (a different code branch). To support, application requirements the OS provides non-blocking API.

Google interview questions

source

What is the difference between a mutex and a semaphore? Which one would you use to protect access to an increment operation?

A mutex is used when only one thread or process is allowed to access a resource and a semaphore is used when only a certain set limit of threads or processes can access the shared resource.

It looks like a mutex is a binary semaphore. But the expected answer is mutex.

A big differences is that mutexes have the concept of “ownership”. Only the thread that owns the mutex (i.e. was the thread that originally claimed the mutex) can give it up. If another thread tries to free the mutex, this will cause an error. With semaphores, basically any thread is allowed to manipulate them.

What is multithreaded programming? What is a deadlock?

Multithreading as a widespread programming and execution model allows multiple threads to exist within the context of a single process.

These threads share the process' resources but are able to execute independently.

Deadlock refers to a specific condition when two or more processes are each waiting for the other to release a resource, or more than two processes are waiting for resources in a circular chain

In an operating system, a deadlock is a situation which occurs when a process or thread enters a waiting state because a resource requested is being held by another waiting process, which in turn is waiting for another resource.

[Design] Big Data - Top K Frequency

Question

link

搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为1-255字节。

假设目前有一千万个记录(这些查询串的重复度比较高,虽然总数是1千万,但如果除去重复后,不超过3百万个。一个查询串的重复度越高,说明查询它的用户越多,也就是越热门。),请你统计最热门的10个查询串,要求使用的内存不能超过1G。

Analysis

  1. divide and conquer (for large input)

  2. Query统计 (hash/trie)

  3. 根据统计结果,找Top 10 (minheap / quickselect)

Be careful: 内存不能超过1G,10 million 条记录,每条记录是255Byte,很显然要占据2.375G内存.

Query统计

Option 1: HashMap

虽然有一千万个Query,但是由于重复度比较高,因此事实上只有300万的Query,每个Query 255Byte,因此我们可以考虑把他们都放进内存中去。

Hash Table绝对是我们优先的选择,因为Hash Table的查询速度非常的快,几乎是O(1)的时间复杂度。我们在O(N)的时间复杂度内完成了对该海量数据的处理。

Option 2: trie

这题是考虑时间效率。用trie树统计每个词出现的次数,时间复杂度是O(n x le)(le表示单词的平准长度)。然后是找出出现最频繁的前10个词,可以用堆来实现,前面的题中已经讲到了,时间复杂度是O(n x lg10)。所以总的时间复杂度,是O(n x le)与O(n x lg10)中较大的哪一个。

How to use Trie to calculate word frequency?

在Trie的node节点中添加count域后,可以统计单词出现的次数。统计的方法就是在插入单词的时候,令相应的count域加1(初始化为0).

找Top 10

Heap.

借助堆结构,我们可以在log量级的时间内查找和调整/移动。因此到这里,我们的算法可以改进为这样,维护一个K(该题目中是10)大小的小根堆,然后遍历300万的Query,分别和根元素进行对比。

查找目标元素的时间复杂度为 O(logK)。

Quickselect

refer to another pose [Fundamental] Quickselect.

Conclusion

至此,算法就完全结束了。(这道题,我们不需要分治)。经过上述第一步、先用Hash表统计每个Query出现的次数,O(N);然后第二步、采用堆数据结构找出Top 10,O(NlogK)。所以,我们最终的时间复杂度是:O(N) + O(N'logK)。