Woodstock Blog

a tech blog for general algorithmic interview questions

[Google] Array Distance A(i)+A(j)+(j-i)

Question

link

Given an int array A[], define:

distance = A[i] + A[j] + (j-i), j >= i.

Find max distance in A[]

Solution

Solution suggested on floor 8 of this post.

  1. distance = (A[i] - i) + (A[j] + j), so we do 2 iteration in the array and calculate (A[i] - i) and (A[j] + j) respectively.

  2. save the max value of (A[i] - i) from left to right

  3. save the max value of (A[j] + j) from right to left

  4. last iteration, calculate result.

Eg. input = {3, 3, 3, 5, 6, 4}

max value of (A[i] - i) from left to right: {3, 3, 3, 3, 3, 3}

max value of (A[j] + j) from right to left: {10, 10, 10, 10, 10, 9}

final result: 13

Code

written by me

public int distance(int[] A) {
    int len = A.length;
    int[] arrayI = new int[len];
    int[] arrayJ = new int[len];

    arrayI[0] = A[0] - 0;
    // arrayI stores max value of (A[i]-i) from left to right
    arrayJ[len - 1] = A[len - 1] + (len - 1);
    // arrayJ stores max value of (A[i]+i) from right to left

    for (int i = 1; i < len; i++) {
        arrayI[i] = Math.max(arrayI[i - 1], A[i] - i);
    }

    for (int i = len - 2; i >= 0; i--) {
        arrayJ[i] = Math.max(arrayJ[i + 1], A[i] + i);
    }

    Common.printArray(arrayI);
    Common.printArray(arrayJ);

    int max = Integer.MIN_VALUE;
    for (int i = 0; i < len; i++) {
        max = Math.max(max, arrayI[i] + arrayJ[i]);
    }
    return max;
}

[Design] Multithreading - Deadlock Prevention

Question

How to prevent deadlock? (question from MIT handouts 1)

Analysis

Preventing one of the 4 conditions will prevent deadlock:

  1. Removing the mutual exclusion condition, but not very possible.

  2. The hold and wait conditions may be removed by requiring processes to request all the resources they will need before starting up.

  3. The no preemption condition may also be difficult or impossible to avoid as a process has to be able to have a resource for a certain amount of time, or the processing outcome may be inconsistent or thrashing may occur.

  4. The final condition is the circular wait condition. Approaches that avoid circular waits include disabling interrupts during critical sections and using a hierarchy to determine a partial ordering of resources.

Answer

Assign an order to our locks (require that the locks always acquired in order).

This prevent 2 thread waiting to get the resource in each other’s hand.

[Facebook] Hamming Distance of Array

Question

link

Hamming distance between two strings of equal length is the number of positions at which the corresponding symbols are different.

Write a function that takes a list of binary numbers and returns the sum of the hamming distances for each pair.

Do it in O(n) time.

Solution

The naive solution is O(n2), so we simplify this question by using {0,0,0,1,1} as input. The output in this case would be 6. Why? Because 3 x 2 = 6.

So the equation for solution would be:

distance (for a bit) = number of ‘1’s * number of '0’s

The final answer would be the sum of distances for all bits. The solution is from this blog.

Code

Great solution, not written by me

int hammingDist(int[] nums) {

    int[] bits = new int[32];

    for (int i = 0; i < nums.length; i++) {
        int one = 1;
        int j = 0;

        while (nums[i] != 0) {
            if ((nums[i] & one) != 0)
                bits[j]++;
            j++;
            nums[i] = nums[i] >> 1;
        }
    }

    int ans = 0;
    for (int i = 0; i < 32; i++) {
        ans += bits[i] * (nums.length - bits[i]);
    }

    return ans;
}

[Google] Crosswod Solver

Question

link

Given a wordlist like this:

1. ccaa
1. baca
1. baaa
1. bbbb

and a Grid like this:

X X 
XXXX
X X 
XXXX

Now solve this crossword. One possible solution:

b c 
baca
b a 
baaa

Solution

The corssword problem is NP-Complete, so your best shot is brute force: just try all possibilities, and stop when a possibility is a valid. Return failure when you exhausted all possible solutions.

Code

Pseudo code for brute force. (this just serve as a guide, not a complete/correct solution)

solve(words,grid):
   if words is empty:
       if grid.isValudSol():
          return grid
       else:
          return None
   for each word in words:
       possibleSol <- grid.fillFirst(word)
       ret <- solve(words\{word},possibleSol)
       if (ret != None):
          return ret
   return None

[Fundamental] Travelling Salesman Problem

TSP problem

Given a list of cities and the distances between each pair of cities, what is the shortest possible route that visits each city exactly once and returns to the origin city?

It is an NP-hard problem in combinatorial optimization. We will discuss 2 category of solutions:

  1. Exact algorithms
  2. Heuristic and approximation algorithms

Exact algorithms

  1. Brute force search, which the run time is O(n!), which is impossible for 20 cities.

  2. DP Held–Karp algorithm. O(n2 x 2n).

  3. Branch-and-bound algorithm. This can process 40-60 cities.

Heuristic and approximation algorithms

  1. Greedy, or Nearest Neighbour algorithm. (an improved version is called Nearest Fragment algorithm, which connect NN in groups)

  2. Minimum Spanning Tree (MST), build the MST using Kruskal’s algorithm and then do a Depth-first Tree Tour. link to video.

    1. Sort all edges from small to large, then add edges into MST as long as no cycle is created. In the end, a MST is achieved.

    2. Do Depth-first Tree Tour(DFTT)

    3. Length of DFTT is 2 x weight of MST.

    4. Skip some nodes that’s only visited once.

    5. We get an legitimate solution.

Iterative improvement

Now these are the solutions. However we can improve it by doing 2-opt Swap.

It means selecting 2 edges at random. If swapping results in an improvement, then keep it. Keep doing this. link to video.

[Fundamental] Min-Max Algorithm (Minmax)

Definition

For every two-person, zero-sum game with finitely many strategies, there exists a value V and a mixed strategy for each player, such that

  1. Given player 2’s strategy, the best payoff possible for player 1 is V, and
  2. Given player 1’s strategy, the best payoff possible for player 2 is −V.

Equivalently, Player 1’s strategy guarantees him a payoff of V regardless of Player 2’s strategy.

Put it in a simple way: MAX tries to max the utility, and MIN try to min it.

Steps

  1. Have a heuristic evaluation function, which gives a value to non-final game states.
  2. Generate the values down to terminal states.
  3. Min-max calculate the utility, like this:

An example

Othello game:

A player can place a new piece in a position if there exists at least one straight (horizontal, vertical, or diagonal) occupied line between the new piece and another piece of the same kind, with one or more contiguous pieces from the opponent player between them.

After placing the new piece, the pieces from the opponent player will be captured and become the pieces from the same player.

The player with the most pieces on the board wins.

First, the heuristic evaluation function:

Now, generate terminal level utility values:

Now, do min-max algorithm:

Pruning

The performance of the naïve minimax algorithm may be improved dramatically, without affecting the result, by the use of alpha-beta pruning.

Alpha–beta pruning is a search algorithm that seeks to decrease the number of nodes that are evaluated by the minimax algorithm in its search tree.

It works when you evaluate the tree left-to-rigth. Considering under the same parent, once you found any number that is largest then the smallest-found-so-far, in the MAX level, you can skip this node. Example:

The pruned values won’t influence the final result.

[Design] Cryptographic Hash, MD5 and Digital Signature

Overview

Cryptographic hash function is a hash function which is considered practically impossible to invert.

The input is arbitrary length, and output is fixed length.

The input is called ‘message’ and output (the hash value) is called ‘digest’.

Common examples are:

  1. MD5
    1. 128-bit (16-byte) hash value, typically expressed with 32 digit hex number
    2. have colllision attack risks
  2. SHA-1 (said to be better than MD5)
    1. 160-bit (20-byte) hash value, typically expressed with 40 digit hex number
    2. No known collision found so far

Properties

  1. Computationally efficient
  2. Collision resistant
  3. Hide information
  4. Look random

Examples

I generated checksum values on an important file I backed up to ensure that everything went OK. source

To verify that the latest version of Firefox I was downloading is correct, I ran a cryptographic hash function, SHA-1 to be exact, on the download and compared that checksum with the one published on Mozilla’s site. source

Digital signature

Digital signature is a mathematical scheme for demonstrating the authenticity of a digital message. It uses public/private keys.

In practice, the signature is not used directly on the file, but rather on the digest of the file.

Video on digital signature.

[Fundamental] A-Star Search

Definition

A* is a computer algorithm that is widely used in pathfinding and graph traversal.

It uses a knowledge-plus-heuristic cost function like this:

f (n) = g(n) + h(n)

f (n): estimated total cost of path through n to goal

g(x) = past path-cost function

h(x) = future path-cost function, which is an admissible “heuristic estimate” of the distance from x to the goal

What sets A* apart from a greedy best-first search is that it also takes the distance already traveled into account.

Implementation

Maintains a priority queue (lower f(x) means higher priority). At each step, node with the lowest f(x) value is removed from the queue, and neighbors are added to the queue.

This continues until a goal node has a lower f value than any node in the queue. Goal found!

Relationship with Dijkstra

Dijkstra is a special case for A* (when the heuristics is 0).

Illustration (A-star):

Illustration (Dijkstra’s algorithm):

[Google] Boggle Solver (Search Words From Matrix)

Question

link

Boggle Game:

F X I E
A M L O
E W B X
A S T U

The goal of the game is to find as many words as you can that can be formed by chaining letters together. You are given a dictionary of words are reference.

Solution

The best solution is to use Trie, then do DFS search.

The idea is from this answer (however, this guy said his solution does not handle ‘visited’ nodes properly, meaning that same char might be visited again to produce a word).

We need to first define a class called Item:

class Item {
    public final int x, y;
    public final String prefix;

    public Item(int row, int column, String prefix) {
        this.x = row;
        this.y = column;
        this.prefix = prefix;
    }
}

So when we start doing DFS, we pass in an Item object which stores 2 information:

  1. The next position that we’re going to visit.
  2. The prefix string that we have validated so far (before visiting this position).

For example:

F X I E
A M L O
E W B X
A S T U

We’ll have Items objects like (0, 0, “”), (0, 1, “F”), (0, 2, “FA”) … We guarantee that the prefix must be a valid prefix by searching them in the Trie.

How to tell whether a string is a prefix of word, or it’s an actual word? We have a property in TrieNode called TrieNode.isWord() to help us.

The code I wrote below is based on this Java solution by zouzhile.

Code

BoggleSolver.java

public class BoggleSolver {

    private static BufferedReader in = null;
    private static final String INPUT_FILE = "dictionary.txt";

    public static void beginFileReader() {
        try {
            in = new BufferedReader(new FileReader(new File(BoggleSolver.class
                    .getResource(INPUT_FILE).toURI())));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }
    }

    private Trie buildTrie() {
        Trie trie = new Trie();
        beginFileReader();
        String line = null;
        try {
            while ((line = in.readLine()) != null) {
                String[] words = line.split(" ");
                for (String word : words) {
                    word = word.trim().toLowerCase();
                    trie.addWord(word);
                }
            }
            if (in != null) {
                in.close();
            }
        } catch (IOException e1) {
            e1.printStackTrace();
        }
        return trie;
    }

    public Set<String> findWords(char[][] map, Trie dict) {
        Set<String> ans = new TreeSet<String>();
        int m = map.length;
        int n = map[0].length;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                boolean[][] visited = new boolean[m][n];
                findWordsDfs(ans, dict, map, visited, new Item(i, j, ""));
                // item have 3 parameters:
                // location x,y and the prefix string before reaching (i.j)
            }
        }
        return ans;
    }

    public void findWordsDfs(Set<String> ans, Trie dict, char[][] map,
            boolean[][] visited, Item item) {
        // item: the location that we're going to test
        // item.prefix is the word prefix before reaching (x, y)

        int m = map.length;
        int n = map[0].length;
        int x = item.x;
        int y = item.y;

        // check whether cur.(x,y) is a valid position
        if (x < 0 || x >= m || y < 0 || y >= n) {
            return;
        } else if (visited[x][y]) {
            return;
        }
        String newWord = item.prefix + map[x][y];
        // check whether cur.prefix is a valid prefix
        TrieNode findWord = dict.match(newWord);
        if (findWord == null) {
            // up to this position (x, y), the word dont' exists
            return;
        }
        // now cur is in a valid position, with a valid prefix
        if (findWord.isWord()) {
            ans.add(newWord);
        }
        // visit this position, and continue in 4 different directions
        visited[x][y] = true;
        findWordsDfs(ans, dict, map, visited, new Item(x, y - 1, newWord));
        findWordsDfs(ans, dict, map, visited, new Item(x, y + 1, newWord));
        findWordsDfs(ans, dict, map, visited, new Item(x - 1, y, newWord));
        findWordsDfs(ans, dict, map, visited, new Item(x + 1, y, newWord));
        visited[x][y] = false;
    }

    public static void main(String[] args) {
        String[] rows = "eela,elps,weut,korn".split(",");
        char[][] input = new char[4][4];
        for (int i = 0; i < 4; i++) {
            input[i] = rows[i].toCharArray();
        }

        // prepare test data
        BoggleSolver solver = new BoggleSolver();
        Trie dictionary = solver.buildTrie();
        // start finding words
        Set<String> set = solver.findWords(input, dictionary);
        // present the result
        System.out.println(set.size() + " words are found, they are: ");
        for (String str : set) {
            System.out.println(str);
        }
    }

    class Item {
        public final int x, y;
        public final String prefix;

        public Item(int row, int column, String prefix) {
            this.x = row;
            this.y = column;
            this.prefix = prefix;
        }
    }
}

Trie.java

public class Trie {
    private TrieNode root;

    public Trie() {
        this.root = new TrieNode();
    }

    public void addWord(String word) {
        TrieNode node = this.root;
        for (char c : word.toCharArray()) {
            node = node.addChild(c);
            if (node == null)
                return;
        }
        node.setWord(true);
    }

    public TrieNode match(String s) {
        TrieNode node = this.root;
        for (char c : s.toCharArray()) {
            node = node.get(c);
            if (node == null)
                return null;
        }
        return node;
    }
}

TrieNode.java

public class TrieNode {
    private TrieNode[] children;
    private boolean isWord = false;

    public TrieNode() {
        this.children = new TrieNode[26];
    }

    public TrieNode addChild(char child) {
        if (child < 'a' || child > 'z')
            return null;

        int offset = child - 'a';
        if (this.children[offset] == null) {
            this.children[offset] = new TrieNode();
        }
        return this.children[offset];
    }

    public boolean isWord() {
        return isWord;
    }

    public void setWord(boolean isWord) {
        this.isWord = isWord;
    }

    public TrieNode get(char c) {
        int offset = c - 'a';
        return this.children[offset];
    }
}

[CC150v4] 10.7 Ugly Numbers (Hamming Numbers)

Question

Design an algorithm to find the kth number such that the only prime factors are 3, 5, and 7.

Solution

This is very difficult question. All I can say is, just memorize this solution.

It works like this:

  1. Init 3 queues, with initial value of 3, 5 and 7 respectively.
  2. Fetch the smallest element x at the head of 3 queues.
  3. Add number x to the result list, then:
    1. if number x comes from queue3, add 3x, 5x and 7x into 3 queues
    2. if number x comes from queue5, add 5x and 7x into queue5 and queue7
    3. if number x comes from queue7, add 7x into queue7
  4. Fetch next smallest element and do this recursively.

A dp solution is available. It’s using the same method actually, but less intuitive.

Code

not written