AdSense

Showing posts with label Breadth-first search. Show all posts
Showing posts with label Breadth-first search. Show all posts

Monday, August 22, 2016

Find K Pairs with Smallest Sums

You are given two integer arrays nums1 and nums2 sorted in ascending order and an integer k.
Define a pair (u,v) which consists of one element from the first array and one element from the second array.
Find the k pairs (u1,v1),(u2,v2) ...(uk,vk) with the smallest sums.
Example 1:
Given nums1 = [1,7,11], nums2 = [2,4,6],  k = 3

Return: [1,2],[1,4],[1,6]

The first 3 pairs are returned from the sequence:
[1,2],[1,4],[1,6],[7,2],[7,4],[11,2],[7,6],[11,4],[11,6]
Example 2:
Given nums1 = [1,1,2], nums2 = [1,2,3],  k = 2

Return: [1,1],[1,1]

The first 2 pairs are returned from the sequence:
[1,1],[1,1],[1,2],[2,1],[1,2],[2,2],[1,3],[1,3],[2,3]
Example 3:
Given nums1 = [1,2], nums2 = [3],  k = 3 

Return: [1,3],[2,3]

All possible pairs are returned from the sequence:
[1,3],[2,3]
Very interesting problem. The approach is actually BFS. Let (x, y) be the last element position (the first position is (0, 0)) added to result, then the next element will either be (x + 1, y) or (x, y + 1). The rest of the part is just normal BFS.


    public List<Integer> kSmallestPairs(int[] nums1, int[] nums2, int k) {
        List<Integer> rst = new ArrayList<>();
        int len1 = nums1.length;
        int len2 = nums2.length;
        if (len1 == 0 || len2 == 0) {
            return rst;
        }
        PriorityQueue<int[]> queue = new PriorityQueue<>(k, new Comparator<int[]>() {
            @Override
            public int compare(int[] first, int[] second) {
                return nums1[first[0]] + nums2[first[1]] - (nums1[second[0]] + nums2[second[1]]);
            }
        });
        boolean[][] visited = new boolean[len1][len2];
        queue.add(new int[]{0, 0});
        visited[0][0] = true;
        int[][] neighbors = {{0, 1}, {1, 0}};
        while (!queue.isEmpty() && rst.size() < k) {
            int[] currPos = queue.poll();
            rst.add(new int[]{nums1[currPos[0]], nums2[currPos[1]]});
            for (int[] n : neighbors) {
                int x = currPos[0] + n[0], y = currPos[1] + n[1];
                if (x < len1 && y < len2 && !visited[x][y]) {
                    queue.add(new int[]{x, y});
                    visited[x][y] = true;
                }
            }
        }
        return rst;
    }

Thursday, January 22, 2015

The amazing maze II: searching the maze

This post I will talk about how to solve a maze. If you are interested in how to build a maze. Take a look at my last post.

There are couple ways to solve a maze, the easiest ones are using DFS and BFS. Here, I implemented DFS using recursion, DFS using a stack, and BFS.


package mazeDFS;
import java.util.*;
public class SolveMaze {
 
 List path;
 private Maze m;
 
 public SolveMaze(Maze m) {
  path = new ArrayList ();
  this.m = m;
 }
 /**
  * Using DFS to solve the maze
  */
 public void solveByDFS() {
  boolean[] visited = new boolean[m.grid.length];
  //Stack stack = new Stack ();
  dfs(path, visited, 0);
 }
 //using recursion
 private void dfs(List path, boolean[] visited, int curr) {
  if (curr == m.grid.length - 1) {
   visited[curr] = true; 
   path.add(curr);
   return;
  }
  path.add(curr);
  visited[curr] = true;
  int cell = m.grid[curr];
  if ((cell & Maze.LEFT) == 0 && (curr - 1) >= 0 && !visited[curr - 1] && !visited[m.grid.length - 1] && !visited[m.grid.length - 1])
   dfs(path, visited, curr - 1);
  if ((cell & Maze.RIGHT) == 0 && (curr + 1) < m.grid.length && !visited[curr + 1] && !visited[m.grid.length - 1])
   dfs(path, visited, curr + 1);
  if ((cell & Maze.UP) == 0 && (curr - m.columns) >= 0 && !visited[curr - m.columns] && !visited[m.grid.length - 1])
   dfs(path,visited, curr - m.columns);
  if ((cell & Maze.DOWN) == 0 && (curr + m.columns) < m.grid.length && !visited[curr + m.columns] && !visited[m.grid.length - 1])
   dfs(path, visited, curr + m.columns);
  if (visited[m.grid.length - 1])
   return;
  path.remove(path.size() - 1);
 }
 //using a stack
 public void solveByDFS2() {
  Stack stack = new Stack ();
  boolean[] visited = new boolean[m.grid.length];
  int[] distTo = new int[m.grid.length];
  int[] predecessor = new int[m.grid.length];
  Arrays.fill(distTo, Integer.MAX_VALUE);
  stack.push(0);
  visited[0] = true;
  distTo[0] = 0;
  predecessor[0] = -1;
  while (!stack.isEmpty() && !visited[m.grid.length - 1]) {
   int curr = stack.pop();
   int cell = m.grid[curr];
   if (curr == m.grid.length - 1) {
    break;
   }
   if ((cell & Maze.LEFT) == 0 && (curr - 1) >= 0 && !visited[curr - 1]) {
    stack.push(curr - 1);
    visited[curr - 1] = true;
    distTo[curr - 1] = distTo[curr] + 1;
    predecessor[curr - 1] = curr;
   }
   if ((cell & Maze.RIGHT) == 0 && (curr + 1) < m.grid.length && !visited[curr + 1]) {
    stack.push(curr + 1);
    visited[curr + 1] = true;
    distTo[curr + 1] = distTo[curr] + 1;
    predecessor[curr + 1] = curr;
   }
   if ((cell & Maze.UP) == 0 && (curr - m.columns) >= 0 && !visited[curr - m.columns]) {
    stack.push(curr - m.columns);
    visited[curr - m.columns] = true;
    distTo[curr - m.columns] = distTo[curr] + 1;
    predecessor[curr - m.columns] = curr;
   }
   if ((cell & Maze.DOWN) == 0 && (curr + m.columns) < m.grid.length && !visited[curr + m.columns]) {
    stack.push(curr + m.columns);
    visited[curr + m.columns] = true;
    distTo[curr + m.columns] = distTo[curr] + 1;
    predecessor[curr + m.columns] = curr;
   }
  }
  int x;
  for (x = m.grid.length - 1; distTo[x] != 0; x = predecessor[x]) {
   path.add(x);
  }
  path.add(0);
  Collections.reverse(path);
  
 }
 
 public void solveByBFS() {
  Queue q = new LinkedList ();
  boolean[] visited = new boolean[m.grid.length];
  int[] distTo = new int[m.grid.length];
  int[] predecessor = new int[m.grid.length];
  Arrays.fill(distTo, Integer.MAX_VALUE);
  q.offer(0);
  visited[0] = true;
  distTo[0] = 0;
  predecessor[0] = -1;
  while (!q.isEmpty() && !visited[m.grid.length - 1]) {
   int curr = q.poll();
   int cell = m.grid[curr];
   if (curr == m.grid.length - 1) {
    break;
   }
   if ((cell & Maze.LEFT) == 0 && (curr - 1) >= 0 && !visited[curr - 1]) {
    q.offer(curr - 1);
    visited[curr - 1] = true;
    distTo[curr - 1] = distTo[curr] + 1;
    predecessor[curr - 1] = curr;
   }
   if ((cell & Maze.RIGHT) == 0 && (curr + 1) < m.grid.length && !visited[curr + 1]) {
    q.offer(curr + 1);
    visited[curr + 1] = true;
    distTo[curr + 1] = distTo[curr] + 1;
    predecessor[curr + 1] = curr;
   }
   if ((cell & Maze.UP) == 0 && (curr - m.columns) >= 0 && !visited[curr - m.columns]) {
    q.offer(curr - m.columns);
    visited[curr - m.columns] = true;
    distTo[curr - m.columns] = distTo[curr] + 1;
    predecessor[curr - m.columns] = curr;
   }
   if ((cell & Maze.DOWN) == 0 && (curr + m.columns) < m.grid.length && !visited[curr + m.columns]) {
    q.offer(curr + m.columns);
    visited[curr + m.columns] = true;
    distTo[curr + m.columns] = distTo[curr] + 1;
    predecessor[curr + m.columns] = curr;
   }
  }
  int x;
  for (x = m.grid.length - 1; distTo[x] != 0; x = predecessor[x]) {
   path.add(x);
  }
  path.add(0);
  Collections.reverse(path); 
 }
}


Performance

Average memory usage on a 50 by 50 maze

Average running time on a 50 by 50 maze

As we can see, DFS by recursion outperforms the other two methods in both time and memory. It is understandable that BFS is slower in this circumstance. If we search layer by layer, we probably need to traverse the whole maze to find the end point. Yet if we use DFS, any path leads to the end point can terminate the searching. 

Recursive solutions often result in smaller code, which means it's more likely that the code will fit into the CPU cache. A no-recursive solution that requires an explicitly managed stack can result in larger code, more cache misses, and slower performance than recursive solution. 

Wednesday, January 14, 2015

Graph traversal and a simple recommender system

I was practicing graph traversal when I found a video about FB's recommender system, so I thought, hey, why not try implementing one.

Apparently, I am not capable of implementing a real recommender system, but based on some concepts in the video, I did this small project.

There are three main parts in this project:
1. The network, which is implemented by a graph.(Graph, Node, Edge)
2. Find recommendations for a user(node) in the graph. (FindFriend)
3. Find connections from a node to another node.(FindFriend)

The Graph

Update: 2015 - 03 - 12

My first version of the graph looks, well, memory expensive, I optimized a little bit. Here is the new version.

/**
 * undirected graph
 * user can only retrieve the node by its label
 * all labels are unique
 * @author shirleyyoung
 *
 */
public class Graph {
 Map vertices;
 Set edges;
 Map> neighbors;
 
 
 public Graph (){
  vertices = new HashMap();
  edges = new HashSet();
  neighbors = new HashMap>();
 }
 
 /**
  * construct a graph from another graph
  * @param G
  */
 public Graph (Graph G) {
  this.vertices = new HashMap();
  this.edges = new HashSet();
  copyGraph(G);
 }
 /**
  * clone a graph from another one
  * ConcurrentModificationException: 
  * For example, it is not generally permissible 
  * for one thread to modify a Collection 
  * while another thread is iterating over it.
  */
 private void copyGraph(Graph G) {
  //clone nodes
  for (Node n : G.vertices.values()) {
   this.vertices.put(n.label, new Node(n));
  }
  //clone edges
  for(Edge e : G.edges){
   List nodes = (List) e.getNodes();
   Node n1 = nodes.get(0);
   Node n2 = nodes.get(1);
   this.edges.add(new Edge(vertices.get(n1.label), vertices.get(n2.label)));
   if(!neighbors.containsKey(n1))
    neighbors.put(n1, new ArrayList());
   if(!neighbors.containsKey(n2))
    neighbors.put(n2, new ArrayList());
   neighbors.get(n1).add(n2);
   neighbors.get(n2).add(n1);
  }
 }
 
 /**
  * Add vertex
  * @param label
  */
 public boolean addVertex(int label) {
  if(containsVertex(label))
   return false;
  vertices.put(label, new Node(label));
  return true;
 }
 
 /**
  * connect two nodes
  * @param l1
  * @param l2
  * @param distance
  * @return
  */
 public double addEdge(int l1, int l2, double distance) {
  if(!containsVertex(l1) || !containsVertex(l2))
   throw new NullPointerException("Graph doesn't contain such nodes");
  Edge e = getEdge(l1, l2);
  if(e != null)
   e.modifyWeight(distance);
  else{
   Node n1 = getNode(l1);
   Node n2 = getNode(l2);
   e = new Edge(n1, n2, distance);
   edges.add(e);
   if(!neighbors.containsKey(n1))
    neighbors.put(n1, new ArrayList());
   if(!neighbors.containsKey(n2))
    neighbors.put(n2, new ArrayList());
   neighbors.get(n1).add(n2);
   neighbors.get(n2).add(n1);
  }
  return e.weight;
 }
 public void chooseSource(int label) {
  Node source = getNode(label);
  source.dist = 0;
  
 }
 /**
  * if a node exists
  * @param label
  * @return
  */
 public boolean containsVertex(int label) {
  return vertices.containsKey(label);
 }
 
 /**
  * get the node 
  * @param label
  * @return
  */
 Node getNode(int label) {
  if (containsVertex(label))
   return vertices.get(label);
  return null;
 }
 /**
  * if there exists an edge between two node
  * @param l1
  * @param l2
  * @return
  */
 public boolean isConnected(int l1, int l2) {
  if(!containsVertex(l1) || !containsVertex(l2))
   throw new NullPointerException("Graph doesn't contain such node!");
  Edge tmp = new Edge(getNode(l1), getNode(l2));
  for(Edge e : edges){
   if(e.equals(tmp))
    return true;
  }
  return false;
  
 }
 /**
  * get an existing edge from the graph
  * @param l1
  * @param l2
  * @return
  */
 Edge getEdge(int l1, int l2){
  if(!containsVertex(l1) || !containsVertex(l2))
   throw new NullPointerException("Graph doesn't contain such node!");
  Edge tmp = new Edge(getNode(l1), getNode(l2));
  for(Edge e : edges){
   if(e.equals(tmp))
    return e;
  }
  return null;
 }
 /**
  * @return number of vertices in the graph
  */
 public int getSize() {
  return vertices.size();
 }
 /** 
  * @return number of edges in the graph
  */
 public int numEdges(){
  return edges.size();
 }
 
 
 public String toString() {
  StringBuilder s = new StringBuilder();
  String NEWLINE = System.getProperty("line.separator");
  s.append(getSize() + " vertices, " + numEdges() + " edges " + NEWLINE);
  for (Node v : vertices.values()) {
   s.append("Node: " + v.label + "\n");
   s.append("neighbors: \n");
   for (Node nb: neighbors.get(v)){
    s.append(nb.label + " ");
   }
   s.append(NEWLINE);
  }
  s.append(NEWLINE);
  for(Edge e : edges){
   List nodes = (List) e.getNodes();
   s.append(nodes.get(0).label).append(" - ").append(nodes.get(1).label + "\n");
  }
  s.append(NEWLINE);
  return s.toString();
 }
}
public class Node {
 int label;
 double dist;
 
 
 Node(int x) {
  label = x;
  dist = Integer.MAX_VALUE;
 }
 Node(Node n) {
  //only copy the label from another node, 
  //neighbors and edges need
  //to be redefined
  this.label = n.label;
  dist = Integer.MAX_VALUE;
 }
 
 
 public boolean equals(Node n) {
  return label == n.label;
 }
}
public class Edge implements Comparator {
 double weight;
 Node n1;
 Node n2;
 /**
  * default weight = 1.0
  * @param n1
  * @param n2
  */
 public Edge(Node n1, Node n2) {
  if (n1.equals (n2))
   throw new IllegalArgumentException("Cannot form an edge with itself!");
  this.n1 = n1;
  this.n2 = n2;
  weight = 1.0;
 }
 /**
  * constructor that allows user defined weight
  * @param n1
  * @param n2
  * @param weight
  */
 public Edge(Node n1, Node n2, double weight) {
  if (n1.equals (n2))
   throw new IllegalArgumentException("Dont't cheat, connecting with yourself doens't mean you have one more fan!");
  this.n1 = n1;
  this.n2 = n2;
  this.weight = weight;
 }
 /**
  * to modify the weight
  * @param w
  */
 public void modifyWeight(double w) {
  if (w <= 0.0)
   throw new IllegalArgumentException("Weight must be positive!");
  weight = w;
 }
 
 /**
  * get the nodes that are connected by this edge
  * @return
  */
 public Collection getNodes() {
  List nodes = new ArrayList ();
  nodes.add(n1);
  nodes.add(n2);
  return nodes;
 }
 /**
  * get the weight
  * @return
  */
 public double getWeight() {
  return weight;
 }
 public int compare(Edge e1, Edge e2){
  if(e1.weight > e2.weight)
   return 1;
  else if(e1.weight < e2.weight)
   return -1;
  return 0;
 }
 public boolean equals(Edge e){
  for(Node n : e.getNodes()){
   if(!this.getNodes().contains(n))
    return false;
  }
  return true;
 }
}


The graph is implemented by three classes: Node, Edge and Graph.

Node
Node class has three objects: a unique label, a list of neighbors and a map that stores edge and the neighbor node it connects. My initial idea is to include duplicate label conditions, because in reality it is possible that two people have the same name, sex, age, etc. Then I realize no matter how similar two nodes are, there must be something to distinguish them, so I finalized with unique label. If in future other data needs to be included in the calculation of distance between two nodes, a map<label, data> can be used to store extra data.

There are two methods in the Node class, getHash() and equals(), they have the same purpose: two check the equality of two nodes. I wrote the getHash() because initially I was thinking about duplicate labels, so I need to include neighbors to check the equality. However, if unique labels are used, this method is unnecessary.


import java.util.*;
public class Node {
 int label;
 List neighbors;
 //
 Map edges;
 
 Node(int x) {
  label = x;
  neighbors = new ArrayList ();
  edges = new HashMap ();
 }
 Node(Node n) {
  //only copy the label from another node, 
  //neighbors and edges need
  //to be redefined
  this.label = n.label;
  neighbors = new ArrayList();
  edges = new HashMap ();
 }
 //the hash function to determine the equality of two nodes
 //for graphs with unique nodes, this is not required
 private int getHash() {
  int hash_a = 378551;
  int hash_b = 63689;
  int hash = 0;
  for (Node neighbor : neighbors) {
   hash += hash * hash_a + neighbor.label;
   hash_a = hash_a * hash_b;
  }
  //hash += label;
  return hash;
 }
 public boolean equals(Node n) {
  return label == n.label && (getHash() == n.getHash());
 }
}


Edge
Initially I used a map in the node class to store the distance and connecting nodes, but I want the graph to look concrete, so I separated it and wrote this class. There are three objects in this class, two connecting nodes, and the weight. The weights are used to calculate the distance among nodes. There are two constructors, the first one set default weight to 1 and only require two nodes as inputs. The second one allows user to define weights. When Edge is initialized, both nodes will be added to the neighbor list of the other.

getNodes() method is used to get the two nodes that are connected by the edge and getWeight(), obviously, is used to get the weight.



import java.util.*;
public class Edge {
 int weight;
 Node n1;
 Node n2;
 //default weight = 1
 public Edge(Node n1, Node n2) {
  if (n1.equals (n2))
   throw new IllegalArgumentException("Cannot form an edge with itself!");
  if (n1.neighbors.contains(n2)) 
   throw new IllegalArgumentException("Edege exists!");
  this.n1 = n1;
  this.n2 = n2;
  n1.neighbors.add(n2);
  n2.neighbors.add(n1);
  weight = 1;
 }
 //constructor that allows user defined weight
 public Edge(Node n1, Node n2, int weight) {
  if (n1.equals (n2))
   throw new IllegalArgumentException("Dont't cheat, connecting with yourself doens't mean you have one more fan!");
  if (n1.edges.containsKey(n2) && n2.edges.containsKey(n1)) 
   throw new IllegalArgumentException("Edege exists!");
  this.n1 = n1;
  this.n2 = n2;
  n1.neighbors.add(n2);
  n2.neighbors.add(n1);
  this.weight = weight;
 }
 //to modify the weight
 public void modifyWeight(int w) {
  if (w <= 0)
   throw new IllegalArgumentException("Weight must be positive!");
  weight = w;
 }
 
 //get the nodes that are connected by this edge
 public List getNodes() {
  List nodes = new ArrayList ();
  nodes.add(n1);
  nodes.add(n2);
  return nodes;
 }
 //get the weight
 public int getWeight(Node n1, Node n2) {
  if (!(n1.equals(this.n1) && n2.equals(this.n2)) && !(n1.equals(this.n2) && n2.equals(this.n1)))
   throw new IllegalArgumentException("Wrong input node!");
  return weight;
 }
}


Graph
The Graph class is used to construct the "social network". It is an undirected graph (I don't see the reason if I connect with you but you cannot connect with me). I used a map to store <label, node> assuming users can only access the node by its label. A graph can be initialized as an empty graph or by clone another graph.

Some necessary methods include addVertex(), addEdge(), containsVertex(), isConnected() and toString(). Initially I mad getNode() method private, because I want the node can only be accessed within the class (accessed by label outside the class). Then my FindFriends class need to access the node, so I made it public. I am making a note here to make the modifications later. I commented out the Iterable version of getNeighbors() method because the user outside the class may only want to "see" the neighbors.


import java.util.*;


//undirected graph
//user can only retrieve the node by its label
//all labels are unique
//if other data needs to be stored in the node, a  map can be used
public class Graph {
 private Map vertices;
 //# of vertices
 private int size;
 //# of edges
 private int edges;
 
 
 public Graph (){
  size = 0;
  vertices = new HashMap();
  edges = 0;
 }
 
 //construct a graph from another graph
 public Graph (Graph G) {
  this.size = G.size;
  this.vertices = new HashMap();
  this.edges = G.edges;
  copyGraph(G);
 }
 //clone a graph from another one
 /*
  * ConcurrentModificationException: 
  * For example, it is not generally permissible 
  * for one thread to modify a Collection 
  * while another thread is iterating over it.
  */
 private void copyGraph(Graph G) {
  //clone nodes
  for (Node n : G.vertices.values()) {
   this.vertices.put(n.label, new Node(n));
  }
  //clone edges
  for (Node n : G.vertices.values()) {
   Node curr = getNode(n.label);
   for (Node w : n.neighbors) {
    Node neighbor = getNode(w.label);
    if (!isConnected(curr, neighbor)) {
     int weight = n.edges.get(w).getWeight(n, w);
     Edge e = new Edge(curr, neighbor, weight);
     curr.edges.put(neighbor, e);
     neighbor.edges.put(curr, e);
    }
    
   }
  }
 }
 
 //Add vertex
 public void addVertex(int label) {
  Node n = new Node(label);
  vertices.put(label, n);
  size++;
 }
 
 //Add edge
 //The edge class will take care of adding neighbors to the neighbor list of the node
 public void addEdge(int l1, int l2, int distance) {
  Node v = getNode(l1);
  Node w = getNode(l2);
  if (v.neighbors.contains(w) && w.neighbors.contains(v)) {
   System.out.println("Edge exists!");
   return;
  }
  Edge e = new Edge(v, w, distance);
  v.edges.put(w, e);
  w.edges.put(v, e);
  edges++;
 }
 
 //if a node exists
 public boolean containsVertex(int label) {
  return vertices.containsKey(label);
 }
 
 //if there exists an edge between two node
 public boolean isConnected(int l1, int l2) {
  return getNode(l1).edges.containsKey(getNode(l2)) 
    && getNode(l2).edges.containsKey(getNode(l1));
 }
 private boolean isConnected(Node n1, Node n2) {
  return n1.edges.containsKey(n2) && n2.edges.containsKey(n1);
 }
 
 public Node getNode(int label) {
  if (containsVertex(label))
   return vertices.get(label);
  return null;
 }
 
 /*public Iterable getNeighbors(int label) {
  if(!containsVertex(label))
   throw new IllegalArgumentException("Graph doesn't contain such node!");
  return getNode(label).neighbors;
 }*/
 
 //get neighbors of a node
 public String getNeighbors(int label) {
  String neighbor = "";
  for (Node n : getNode(label).neighbors) {
   neighbor += n.label + ", ";
  }
  return neighbor.substring(0, neighbor.length() - 2);
 }
 
 public int neighborSize(int label) {
  if (!containsVertex(label))
   throw new IllegalArgumentException("Graph doesn't contain such node!");
  return getNode(label).neighbors.size();
 }
 
 
 public String toString() {
  StringBuilder s = new StringBuilder();
  String NEWLINE = System.getProperty("line.separator");
  s.append(size + " vertices, " + edges + " edges " + NEWLINE);
  for (Node v : vertices.values()) {
   s.append("Node: " + v.label + "\n");
   for (Node w : v.neighbors) {
    s.append(w.label +  "; edge: " + v.edges.get(w).getWeight(v, w) + NEWLINE);
   }
   s.append(NEWLINE);
  }
  return s.toString();
 }
}



FindFriends
The purpose of this class is to, first, find desired number of recommendations for the user and, second, find connections between the user and a target.

This class is aimed to one user, so the constructor will require user to input a "user", kidding, a node. A list of potential friends will be generated upon initialization.

The potential friends list is generated from FOF, i.e., friends of friends. According to the FB video, a FB user on average has 100 friends, so FOF will be in the order of 10^4, if we consider third degree connection, it will be 10^6, so don't worry, you don't want to know that many people in your life.

Another map, distanceFOF is used to store the distance of FOF from the user. getDistance() is calculated by the following equation:


Of course it is from that interesting FB video. Make it simple, this equation calculate the priority of an FOF to the friend, to smaller the value, the high priority the FOF is, i.e., he or she will be highly recommended. Delta is the number of days that a person is connected with the other (user, friend i; friend i, FOF). I used the weight of the edge to represent delta. I didn't fully understand what "friends" in the divisor means, I guess it means the friends size(neighbor list) of the friend i. The equation then adds up all friends of the user that are friends with the FOF. 

In fact, I don't have to make things that complicated, I am not going to need this equation anyway, but , just for fun. :)

Update: 2015 - 03 - 30
So this class is finally updated too. :)

import java.util.*;
/**
 * This class is used for 
 * 1. find recommendations from friends of friends of the user
 * 2. find the closest connections between the user and a person
 * @author shirleyyoung
 *
 */
public class FindFriends {
 Node user;
 Graph g;
 Set potentialFriends;
 Map scoreFOF;
 int numRecommendations;
 
 /**
  * this constructor will find the potential friends list for the user 
  * and generate the distance between the user and a potential friend (fof)
  */
 public FindFriends(int label, int numRecommendations, Graph g){
  if (numRecommendations <= 0)
   throw new IllegalArgumentException("At least one recommendation, please...");
  this.g = g;
  if(!g.containsVertex(label))
   throw new IllegalArgumentException("No such user!");
  user = g.getNode(label);
  potentialFriends = new HashSet ();
  scoreFOF = new HashMap ();
  this.numRecommendations = numRecommendations;
  getPotentialFriends();
 }
 
 //find all fof from user's friends
 private void getPotentialFriends() {
  List neighbors = g.neighbors.get(user);
  for (Node w : neighbors) {
   for (Node fof : g.neighbors.get(w)) {
    if (!neighbors.contains(fof) && !fof.equals(user) 
      && potentialFriends.add(fof)) 
     getDistance(fof);
   }
  }
 }
 
 
 /**
  * distance is calculated by a formula from FB
  * the weight of the edge is actually the number of days 
  * that two people are connected
  * @param fof
  */
 private void getDistance(Node fof) {
  if(scoreFOF.containsKey(fof))
   return;
  double v = 0;
  for (Node ffof : g.neighbors.get(fof)) {
   if(g.isConnected(ffof.label, user.label)){
    //distance between user and friend
    double u_f = g.getEdge(user.label, ffof.label).weight;
    //distance between friend and fof
    double f_fof = g.getEdge(fof.label, ffof.label).weight;
    v += Math.pow(u_f * f_fof, -0.3) / Math.sqrt(g.neighbors.get(ffof).size());
   }
  }
  scoreFOF.put(fof, v);
 }
 
 /**
  * find recommendations
  * fofs with shortest distance will be recommended
  * Greedy approach
  * @return
  */
 private Collection getRecommendations() {
  NodeComparator nc = new NodeComparator() ;
  PriorityQueue recommendedFriend = new PriorityQueue(numRecommendations, nc);    
  
  for (Node fof : potentialFriends) {
   //System.out.println(fof.label);
   if (recommendedFriend.size() < numRecommendations) {
    recommendedFriend.add(fof);
   } else {
    if(nc.compare(fof, recommendedFriend.peek()) < 0) {
     recommendedFriend.poll();
     recommendedFriend.offer(fof);
    }
   }
  }
  return recommendedFriend;
 }
 public String recommendedFriend() {
  Collection pq = getRecommendations();
  StringBuilder sb = new StringBuilder();
  sb.append("People you may know: \n");
  for (Node n : pq) {
   sb.append(n.label).append(", ");
  }
  return sb.delete(sb.length() - 2, sb.length() - 1).append("\n*****").toString();
 }
 private class NodeComparator implements Comparator {
  public int compare(Node a, Node b) {
   double da = scoreFOF.get(a);
   double db = scoreFOF.get(b);
   if (da - db < 0)
    return 1;
   else if (da - db > 0)
    return -1;
   return 0;
   
  }
 
 }
 
 
 /**
  * find the shortest path from the user to a random (-.-)person
  * BFS is used
  * @param target
  * @param termination
  * @return
  */
 private List> findConnections(Node target, int termination) {
  if(termination < 3){
   termination = 3;
  }
  //
  Map> predecessor = new HashMap>();
  //
  Map distance = new HashMap(); 
  List> connections = new ArrayList> ();
  getConnections(target, predecessor, distance, termination);
  if (distance.size() == 1)
   return connections;
  getList(target, predecessor, distance, connections, new ArrayList());
  return connections;
 }
 
 private void getConnections(Node target, Map> predecessor, 
   Map distance, int termination) {
  Queue q = new LinkedList ();
  predecessor.put(user, new ArrayList());
  distance.put(user, 0);
  q.offer(user);
  boolean found = false;
  //terminate the search after certain degree or if the whole graph has been searched
  int degree = 0;
  while (distance.size() < g.getSize() && degree < termination && !found) {
   Node curr = q.poll();
   if (curr.equals(target)) {
    found = true;
    break;
   }
   for (Node neighbor : g.neighbors.get(curr)) {
    if (neighbor.equals(user))
     continue;
    if (!predecessor.containsKey(neighbor)) {
     predecessor.put(neighbor, new ArrayList());
     predecessor.get(neighbor).add(curr);
    }
    else {
     if (!predecessor.get(neighbor).contains(curr))
      predecessor.get(neighbor).add(curr);
    }
    if (!distance.containsKey(neighbor)) {
     distance.put(neighbor, distance.get(curr) + 1);
    }
    q.add(neighbor);
   }
  }
 }
 private void getList(Node curr, Map> predecessor, Map distance, 
   List> connections, List path) {
  path.add(curr);
  if (curr.equals(user)) {
   Collections.reverse(path);
   connections.add(new ArrayList (path));
   //Never ever forget this!
   Collections.reverse(path);
  }
  else {
   for (Node n : predecessor.get(curr)) {
    if (distance.containsKey(n) && distance.get(curr) == distance.get(n) + 1) {
     getList(n, predecessor, distance, connections, path);
    }
   }
  }
  path.remove(path.size() - 1);
 }
 public String connection(int label, int termination) {
  if(!g.containsVertex(label))
   throw new IllegalArgumentException("No such user!");
  Node target = g.getNode(label);
  List> connections = findConnections(target, termination);
  String NEWLINE = System.getProperty("line.separator");
  StringBuilder sb = new StringBuilder();
  sb.append("You can be connected with " + target.label + " through: " + NEWLINE);
  for (List path : connections) {
   for (Node n : path) {
    sb.append(n.label + " -> ");
   }
   sb.delete(sb.length() - 4, sb.length() - 1);
   sb.append(NEWLINE);
   
  }
  return sb.toString();
 }
 
}




After initialization, all the FOFs and distances will be generated. 



public class FindFriends {
 Node user;
 List potentialFriends;
 //all friends of the user that connect with fof 
 Map> connectingFriends;
 Map distanceFOF;
 int numRecommendations;
 int GraphSize;
 
 /*
  * this constructor will find the potential friends list for the user 
  * and generate the distance between the user and a potential friend (fof)
  */
 public FindFriends(Node n, int numRecommendations, int GraphSize){
  if (numRecommendations <= 0)
   throw new IllegalArgumentException("At least one recommendation, please...");
  user = n;
  potentialFriends = new ArrayList ();
  connectingFriends = new HashMap>();
  distanceFOF = new HashMap ();
  this.numRecommendations = numRecommendations;
  this.GraphSize = GraphSize;
  getPotentialFriends(n);
  getDistance();
 }
 
 //find all fof from user's friends
 private void getPotentialFriends(Node n) {
  for (Node w : n.neighbors) {
   for (Node fof : w.neighbors) {
    if (!n.neighbors.contains(fof) && !fof.equals(user)) {
     if (!potentialFriends.contains(fof))
      potentialFriends.add(fof);
     if (!connectingFriends.containsKey(fof)) {
      connectingFriends.put(fof, new ArrayList());
      connectingFriends.get(fof).add(w);
     }
     else 
      connectingFriends.get(fof).add(w);
    }
   }
  }
 }
 /*
  * distance is calculated by a formula from FB
  * the weight of the edge is actually the number of days 
  * that two people are connected
  */
 private void getDistance() {
  for (Node fof : potentialFriends) {
   double v = 0;
   for (Node friend : connectingFriends.get(fof)) {
    //distance between user and friend
    double u_f = user.edges.get(friend).getWeight(user, friend);
    //distance between friend and fof
    double f_fof = friend.edges.get(fof).getWeight(friend, fof);
    v += Math.pow(u_f * f_fof, -0.3) / Math.sqrt(friend.neighbors.size());
   }
   distanceFOF.put(fof, v);
  }
 }




Find recommendations
Now all we need to do is to return the number of recommendations required. I used a priority queue with a fixed size, the number of recommendations. The queue will be sorted by the distance of the FOF with the user. If the queue reaches its initialized size, the queue will remove the last element, if it is larger than the current FOF.

/*
  * find recommendations
  * fofs with shortest distance will be recommended
  */
 private PriorityQueue getRecommendations() {
  NodeComparator nc = new NodeComparator() ;
  PriorityQueue recommendedFriend = new PriorityQueue(numRecommendations, nc);    
  Collections.sort(potentialFriends, nc);
  Node last = null;
  for (Node fof : potentialFriends) {
   //System.out.println(fof.label);
   if (recommendedFriend.size() < numRecommendations) {
    recommendedFriend.add(fof);
    last = fof;
   }
   else {
    if(nc.compare(fof, last) < 0) {
     System.out.println(last.label);
     recommendedFriend.remove(last);
     recommendedFriend.add(fof);
     last = fof;
    }
   }
  }
  return recommendedFriend;
 }
 public String recommendedFriend() {
  PriorityQueue pq = getRecommendations();
  StringBuilder sb = new StringBuilder();
  sb.append("People you may know: \n");
  for (Node n : pq) {
   sb.append(n.label).append(", ");
  }
  return sb.delete(sb.length() - 2, sb.length() - 1).append("\n*****").toString();
 }
 private class NodeComparator implements Comparator {
  public int compare(Node a, Node b) {
   double da = distanceFOF.get(a);
   double db = distanceFOF.get(b);
   if (da - db < 0)
    return -1;
   else if (da - db > 0)
    return 1;
   return 0;
   
  }
 
 }

Find connections
This is not my initial plan. After I finished recommendation part, I thought, eh, my purpose is to learn BFS and DFS, but there is no BFS and DFS in this project. I know there is this find connections product on Linkedin, so I decided to try it myself. When I googled "the shortest path" yesterday, all it showed were some advanced algorithms like A*. I was ready to take the pain and try one, but my friend reminded me that BFS actually works. And indeed, it worked. These three methods combine BFS and DFS, it will return all the shortest path from the user to the target, i.e., you can ask anyone you are more familiar with to introduce you. :)

The idea came from a LeetCode problem Word Ladder, take a look at my blog if you are interested. Probably that problem is the origin of all of these: I wasn't familiar with BFS and DFS.


/*
  * find the shortest path from the user to a random (-.-)person
  * BFS is used
  */
 private List> findConnections(Node target) {
  //
  Map> predecessor = new HashMap>();
  //
  Map distance = new HashMap(); 
  List> connections = new ArrayList> ();
  getConnections(target, predecessor, distance);
  if (distance.size() == 1)
   return connections;
  getList(target, predecessor, distance, connections, new ArrayList());
  return connections;
 }
 
 private void getConnections(Node target, Map> predecessor, 
   Map distance) {
  Queue q = new LinkedList ();
  predecessor.put(user, new ArrayList());
  distance.put(user, 0);
  q.offer(user);
  boolean found = false;
  //is it possible to terminate the recursion without knowing the graph size
  //if no path is found? 
  while (distance.size() < GraphSize && predecessor.size() < GraphSize && !found) {
   Node curr = q.poll();
   if (curr.equals(target)) {
    found = true;
    continue;
   }
   for (Node neighbor : curr.neighbors) {
    if (neighbor.equals(user))
     continue;
    if (!predecessor.containsKey(neighbor)) {
     predecessor.put(neighbor, new ArrayList());
     predecessor.get(neighbor).add(curr);
    }
    else {
     if (!predecessor.get(neighbor).contains(curr))
      predecessor.get(neighbor).add(curr);
    }
    if (!distance.containsKey(neighbor)) {
     distance.put(neighbor, distance.get(curr) + 1);
    }
    q.add(neighbor);
   }
  }
 }
 private void getList(Node curr, Map> predecessor, Map distance, 
   List> connections, List path) {
  path.add(curr);
  if (curr.equals(user)) {
   Collections.reverse(path);
   connections.add(new ArrayList (path));
   //Never ever forget this!
   Collections.reverse(path);
  }
  else {
   for (Node n : predecessor.get(curr)) {
    if (distance.containsKey(n) && distance.get(curr) == distance.get(n) + 1) {
     getList(n, predecessor, distance, connections, path);
    }
   }
  }
  path.remove(path.size() - 1);
 }
 public String connection(Node target) {
  List> connections = findConnections(target);
  String NEWLINE = System.getProperty("line.separator");
  StringBuilder sb = new StringBuilder();
  sb.append("You can be connected with " + target.label + " through: " + NEWLINE);
  for (List path : connections) {
   for (Node n : path) {
    sb.append(n.label + " -> ");
   }
   sb.delete(sb.length() - 4, sb.length() - 1);
   sb.append(NEWLINE);
   
  }
  return sb.toString();
 }


Test

I drew this graph randomly for tests. Honestly, it took me a while to figure out what I was drawing. 

Graph




Well, at least my code gives me the correct answer, worth the pain.

FindFriends

It took me a while to figure out that I forgot to reverse the reversed path list. -_-||| Eh, otherwise I should be sleeping right now.


Source code
All source code can be found on GitHub: https://github.com/shirleyyoung0812/Graph.git

import java.util.*;


//undirected graph
//user can only retrieve the node by its label
//all labels are unique
//if other data needs to be stored in the node, a  map can be used
public class Graph {
 private Map vertices;
 //# of vertices
 private int size;
 //# of edges
 private int edges;
 
 
 public Graph (){
  size = 0;
  vertices = new HashMap();
  edges = 0;
 }
 
 //construct a graph from another graph
 public Graph (Graph G) {
  this.size = G.size;
  this.vertices = new HashMap();
  this.edges = G.edges;
  copyGraph(G);
 }
 //clone a graph from another one
 /*
  * ConcurrentModificationException: 
  * For example, it is not generally permissible 
  * for one thread to modify a Collection 
  * while another thread is iterating over it.
  */
 private void copyGraph(Graph G) {
  //clone nodes
  for (Node n : G.vertices.values()) {
   this.vertices.put(n.label, new Node(n));
  }
  //clone edges
  for (Node n : G.vertices.values()) {
   Node curr = getNode(n.label);
   for (Node w : n.neighbors) {
    Node neighbor = getNode(w.label);
    if (!isConnected(curr, neighbor)) {
     int weight = n.edges.get(w).getWeight(n, w);
     Edge e = new Edge(curr, neighbor, weight);
     curr.edges.put(neighbor, e);
     neighbor.edges.put(curr, e);
    }
    
   }
  }
 }
 
 //Add vertex
 public void addVertex(int label) {
  Node n = new Node(label);
  vertices.put(label, n);
  size++;
 }
 
 //Add edge
 //The edge class will take care of adding neighbors to the neighbor list of the node
 public void addEdge(int l1, int l2, int distance) {
  Node v = getNode(l1);
  Node w = getNode(l2);
  if (v.neighbors.contains(w) && w.neighbors.contains(v)) {
   System.out.println("Edge exists!");
   return;
  }
  Edge e = new Edge(v, w, distance);
  v.edges.put(w, e);
  w.edges.put(v, e);
  edges++;
 }
 
 //if a node exists
 public boolean containsVertex(int label) {
  return vertices.containsKey(label);
 }
 
 //if there exists an edge between two node
 public boolean isConnected(int l1, int l2) {
  return getNode(l1).edges.containsKey(getNode(l2)) 
    && getNode(l2).edges.containsKey(getNode(l1));
 }
 private boolean isConnected(Node n1, Node n2) {
  return n1.edges.containsKey(n2) && n2.edges.containsKey(n1);
 }
 
 public Node getNode(int label) {
  if (containsVertex(label))
   return vertices.get(label);
  return null;
 }
 
 /*public Iterable getNeighbors(int label) {
  if(!containsVertex(label))
   throw new IllegalArgumentException("Graph doesn't contain such node!");
  return getNode(label).neighbors;
 }*/
 
 //get neighbors of a node
 public String getNeighbors(int label) {
  String neighbor = "";
  for (Node n : getNode(label).neighbors) {
   neighbor += n.label + ", ";
  }
  return neighbor.substring(0, neighbor.length() - 2);
 }
 
 public int neighborSize(int label) {
  if (!containsVertex(label))
   throw new IllegalArgumentException("Graph doesn't contain such node!");
  return getNode(label).neighbors.size();
 }
 
 
 public String toString() {
  StringBuilder s = new StringBuilder();
  String NEWLINE = System.getProperty("line.separator");
  s.append(size + " vertices, " + edges + " edges " + NEWLINE);
  for (Node v : vertices.values()) {
   s.append("Node: " + v.label + "\n");
   for (Node w : v.neighbors) {
    s.append(w.label +  "; edge: " + v.edges.get(w).getWeight(v, w) + NEWLINE);
   }
   s.append(NEWLINE);
  }
  return s.toString();
 }
}
import java.util.*;

public class Node {
 int label;
 List neighbors;
 //
 Map edges;
 
 Node(int x) {
  label = x;
  neighbors = new ArrayList ();
  edges = new HashMap ();
 }
 Node(Node n) {
  //only copy the label from another node, 
  //neighbors and edges need
  //to be redefined
  this.label = n.label;
  neighbors = new ArrayList();
  edges = new HashMap ();
 }
 //the hash function to determine the equality of two nodes
 //for graphs with unique nodes, this is not required
 private int getHash() {
  int hash_a = 378551;
  int hash_b = 63689;
  int hash = 0;
  for (Node neighbor : neighbors) {
   hash += hash * hash_a + neighbor.label;
   hash_a = hash_a * hash_b;
  }
  //hash += label;
  return hash;
 }
 public boolean equals(Node n) {
  return label == n.label && (getHash() == n.getHash());
 }
}
import java.util.*;
public class Edge {
 int weight;
 Node n1;
 Node n2;
 //default weight = 1
 public Edge(Node n1, Node n2) {
  if (n1.equals (n2))
   throw new IllegalArgumentException("Cannot form an edge with itself!");
  if (n1.neighbors.contains(n2)) 
   throw new IllegalArgumentException("Edege exists!");
  this.n1 = n1;
  this.n2 = n2;
  n1.neighbors.add(n2);
  n2.neighbors.add(n1);
  weight = 1;
 }
 //constructor that allows user defined weight
 public Edge(Node n1, Node n2, int weight) {
  if (n1.equals (n2))
   throw new IllegalArgumentException("Dont't cheat, connecting with yourself doens't mean you have one more fan!");
  if (n1.edges.containsKey(n2) && n2.edges.containsKey(n1)) 
   throw new IllegalArgumentException("Edege exists!");
  this.n1 = n1;
  this.n2 = n2;
  n1.neighbors.add(n2);
  n2.neighbors.add(n1);
  this.weight = weight;
 }
 //to modify the weight
 public void modifyWeight(int w) {
  if (w <= 0)
   throw new IllegalArgumentException("Weight must be positive!");
  weight = w;
 }
 
 //get the nodes that are connected by this edge
 public List getNodes() {
  List nodes = new ArrayList ();
  nodes.add(n1);
  nodes.add(n2);
  return nodes;
 }
 //get the weight
 public int getWeight(Node n1, Node n2) {
  if (!(n1.equals(this.n1) && n2.equals(this.n2)) && !(n1.equals(this.n2) && n2.equals(this.n1)))
   throw new IllegalArgumentException("Wrong input node!");
  return weight;
 }
}
import java.util.*;

/*
 * This class is used for 
 * 1. find recommendations from friends of friends of the user
 * 2. find the closest connections between the user and a person
 */
public class FindFriends {
 Node user;
 List potentialFriends;
 //all friends of the user that connect with fof 
 Map> connectingFriends;
 Map distanceFOF;
 int numRecommendations;
 int GraphSize;
 
 /*
  * this constructor will find the potential friends list for the user 
  * and generate the distance between the user and a potential friend (fof)
  */
 public FindFriends(Node n, int numRecommendations, int GraphSize){
  if (numRecommendations <= 0)
   throw new IllegalArgumentException("At least one recommendation, please...");
  user = n;
  potentialFriends = new ArrayList ();
  connectingFriends = new HashMap>();
  distanceFOF = new HashMap ();
  this.numRecommendations = numRecommendations;
  this.GraphSize = GraphSize;
  getPotentialFriends(n);
  getDistance();
 }
 
 //find all fof from user's friends
 private void getPotentialFriends(Node n) {
  for (Node w : n.neighbors) {
   for (Node fof : w.neighbors) {
    if (!n.neighbors.contains(fof) && !fof.equals(user)) {
     if (!potentialFriends.contains(fof))
      potentialFriends.add(fof);
     if (!connectingFriends.containsKey(fof)) {
      connectingFriends.put(fof, new ArrayList());
      connectingFriends.get(fof).add(w);
     }
     else 
      connectingFriends.get(fof).add(w);
    }
   }
  }
 }
 /*
  * distance is calculated by a formula from FB
  * the weight of the edge is actually the number of days 
  * that two people are connected
  */
 private void getDistance() {
  for (Node fof : potentialFriends) {
   double v = 0;
   for (Node friend : connectingFriends.get(fof)) {
    //distance between user and friend
    double u_f = user.edges.get(friend).getWeight(user, friend);
    //distance between friend and fof
    double f_fof = friend.edges.get(fof).getWeight(friend, fof);
    v += Math.pow(u_f * f_fof, -0.3) / Math.sqrt(friend.neighbors.size());
   }
   distanceFOF.put(fof, v);
  }
 }
 /*
  * find recommendations
  * fofs with shortest distance will be recommended
  */
 private PriorityQueue getRecommendations() {
  NodeComparator nc = new NodeComparator() ;
  PriorityQueue recommendedFriend = new PriorityQueue(numRecommendations, nc);    
  Collections.sort(potentialFriends, nc);
  Node last = null;
  for (Node fof : potentialFriends) {
   //System.out.println(fof.label);
   if (recommendedFriend.size() < numRecommendations) {
    recommendedFriend.add(fof);
    last = fof;
   }
   else {
    if(nc.compare(fof, last) < 0) {
     System.out.println(last.label);
     recommendedFriend.remove(last);
     recommendedFriend.add(fof);
     last = fof;
    }
   }
  }
  return recommendedFriend;
 }
 public String recommendedFriend() {
  PriorityQueue pq = getRecommendations();
  StringBuilder sb = new StringBuilder();
  sb.append("People you may know: \n");
  for (Node n : pq) {
   sb.append(n.label).append(", ");
  }
  return sb.delete(sb.length() - 2, sb.length() - 1).append("\n*****").toString();
 }
 private class NodeComparator implements Comparator {
  public int compare(Node a, Node b) {
   double da = distanceFOF.get(a);
   double db = distanceFOF.get(b);
   if (da - db < 0)
    return -1;
   else if (da - db > 0)
    return 1;
   return 0;
   
  }
 
 }
 
 /*
  * find the shortest path from the user to a random (-.-)person
  * BFS is used
  */
 private List> findConnections(Node target) {
  //
  Map> predecessor = new HashMap>();
  //
  Map distance = new HashMap(); 
  List> connections = new ArrayList> ();
  getConnections(target, predecessor, distance);
  if (distance.size() == 1)
   return connections;
  getList(target, predecessor, distance, connections, new ArrayList());
  return connections;
 }
 
 private void getConnections(Node target, Map> predecessor, 
   Map distance) {
  Queue q = new LinkedList ();
  predecessor.put(user, new ArrayList());
  distance.put(user, 0);
  q.offer(user);
  boolean found = false;
  //is it possible to terminate the recursion without knowing the graph size
  //if no path is found? 
  while (distance.size() < GraphSize && predecessor.size() < GraphSize && !found) {
   Node curr = q.poll();
   if (curr.equals(target)) {
    found = true;
    continue;
   }
   for (Node neighbor : curr.neighbors) {
    if (neighbor.equals(user))
     continue;
    if (!predecessor.containsKey(neighbor)) {
     predecessor.put(neighbor, new ArrayList());
     predecessor.get(neighbor).add(curr);
    }
    else {
     if (!predecessor.get(neighbor).contains(curr))
      predecessor.get(neighbor).add(curr);
    }
    if (!distance.containsKey(neighbor)) {
     distance.put(neighbor, distance.get(curr) + 1);
    }
    q.add(neighbor);
   }
  }
 }
 private void getList(Node curr, Map> predecessor, Map distance, 
   List> connections, List path) {
  path.add(curr);
  if (curr.equals(user)) {
   Collections.reverse(path);
   connections.add(new ArrayList (path));
   //Never ever forget this!
   Collections.reverse(path);
  }
  else {
   for (Node n : predecessor.get(curr)) {
    if (distance.containsKey(n) && distance.get(curr) == distance.get(n) + 1) {
     getList(n, predecessor, distance, connections, path);
    }
   }
  }
  path.remove(path.size() - 1);
 }
 public String connection(Node target) {
  List> connections = findConnections(target);
  String NEWLINE = System.getProperty("line.separator");
  StringBuilder sb = new StringBuilder();
  sb.append("You can be connected with " + target.label + " through: " + NEWLINE);
  for (List path : connections) {
   for (Node n : path) {
    sb.append(n.label + " -> ");
   }
   sb.delete(sb.length() - 4, sb.length() - 1);
   sb.append(NEWLINE);
   
  }
  return sb.toString();
 }
 
}

Wednesday, December 17, 2014

Surrounded Regions

Given a 2D board containing 'X' and 'O', capture all regions surrounded by 'X'.
A region is captured by flipping all 'O's into 'X's in that surrounded region.
For example,
X X X X
X O O X
X X O X
X O X X
After running your function, the board should be:
X X X X
X X X X
X X X X
X O X X
Another example,
X X X X
O O O X
X X O X
X X X X
After running your function, the board should be:
X X X X
O O O X
X X O X
X X X X

Update: 2016-09-27

In fact both DFS and BFS is acceptable. However, DFS is much faster. The idea is similar. Adding all "free nodes" (nodes that don't need to be flipped) on the edge to the queue. Using DFS to find all neighbors of "free nodes" and adding them to queue too, since these nodes should not be flipped either. Then just flip all other Os to Xs.



public void solve(char[][] board) {
        if(board == null || board.length == 0)
            return;
        int cols = board[0].length;
        int rows = board.length;
        Queue freeNodes = new LinkedList();
        for(int j = 0; j < cols; j++){
            enqueue(0, j, board, freeNodes);
            enqueue(rows - 1, j, board, freeNodes);
        }
        for(int i = 0; i < rows; i++){
            enqueue(i, 0, board, freeNodes);
            enqueue(i, cols - 1, board, freeNodes);
        }
        while(!freeNodes.isEmpty()){
            int n = freeNodes.poll();
            int x = n / cols;
            int y = n % cols;
            enqueue(x + 1, y, board, freeNodes);
            enqueue(x - 1, y, board, freeNodes);
            enqueue(x, y + 1, board, freeNodes);
            enqueue(x, y - 1, board, freeNodes);
        }
        for (int i = 0; i < rows; i++){
            for (int j = 0; j < cols; j++){
                if(board[i][j] == 'O')
                    board[i][j] = 'X';
                else if(board[i][j] == 'D')
                    board[i][j] = 'O';
            }
        }
    }
    private void enqueue(int x, int y, char[][] board, Queue freeNodes){
        if(x >= 0 && x < board.length && y >= 0 && y < board[0].length && board[x][y] == 'O'){
            board[x][y] = 'D';
            freeNodes.add(x * board[0].length + y);
        }
    }



I had some problem understanding what the "surrounded regions" means. At first glance, I thought it means the 'O' is surrounded by all 'X's, which doesn't make sense for two consecutive 'O's. Then I thought may be it means after I flip the first 'O'? However, in the example, all 'O's are surrounded by at least another 'O'. The only possibility is, all the 'O' s are surrounded by 'X's, which means only the 'O's at boarders and those surrounded by these 'O's will not be flipped.

The approach is: BFS.
We search the boarders first, mark the 'O', and if the boarder 'O' has neighbor 'O's (upper, left, right, down), mark them too.
After that, we simply search the rest board and flip 'O's to 'X's.





public class FlipO {
    static final int[] X_axis = {1, -1, 0, 0};
    static final int[] Y_axis = {0, 0, 1, -1};
    static final char FREE_NODE = 'F';
    static final char TRAVELED_NODE = 'T';
    //I declared a class for coordinates of characters on the board
    static class Node
    {
        int x;
        int y;
        public Node(int x, int y)
        {
            this.x = x;
            this.y = y;
        }
    }
    public void solve(char[][] board) {
        if (board == null || board.length <= 1 || board[0].length <= 1)
            return;
        int rows = board.length;
        int cols = board[0].length;
        for (int i = 0; i < rows; i++)
        {
            findFreeNode(board, i, 0);
            findFreeNode(board, i, cols - 1);
        }
        for (int j = 1; j < cols - 1; j++)
        {
            findFreeNode(board, 0, j);
            findFreeNode(board, rows - 1, j);
        }
        for (int i = 0; i < rows; i++)
        {
            for (int j = 0; j < cols; j++)
            {
                switch(board[i][j])
                {
                    case 'O':
                        board[i][j] = 'X';
                        break;
                    case 'F':
                        board[i][j] = 'O';
                }
            }
        }
        
    }
    //BFS
    private void findFreeNode(char[][] board, int x, int y)
    {
        //May equal 'T', 'F'
        if (board[x][y] != 'O')
            return;
        Queue queue = new LinkedList();
        queue.offer(new Node(x, y));
        while (!queue.isEmpty())
        {
            Node curr = queue.poll();
            board[curr.x][curr.y] = FREE_NODE;
            for (Node neighbor : neighborList(board, curr))
                queue.offer(neighbor);
        }
    }
    private ArrayList neighborList(char[][] board, Node curr)
    {
        ArrayList list = new ArrayList();
        for (int i = 0; i < X_axis.length; i++)
        {
            int x = curr.x + X_axis[i];
            int y = curr.y + Y_axis[i];
            if (x >= 0 && x < board.length && 
            y >= 0 && y < board[0].length && board[x][y] == 'O')
            {
                list.add(new Node(x, y));
                board[x][y] = TRAVELED_NODE;
            }
        }
        return list;
    }
}

Word Ladder I & II

The first one:
Given two words (start and end), and a dictionary, find the length of shortest transformation sequence from start to end, such that:
  1. Only one letter can be changed at a time
  2. Each intermediate word must exist in the dictionary
For example,
Given:
start = "hit"
end = "cog"
dict = ["hot","dot","dog","lot","log"]
As one shortest transformation is "hit" -> "hot" -> "dot" -> "dog" -> "cog",
return its length 5.
Note:
  • Return 0 if there is no such transformation sequence.
  • All words have the same length.
  • All words contain only lowercase alphabetic characters.

A typical BFS problem. Start from "start",  we go through all possible replacements and see if we can find some transformations in the dict. If we can, we put the intermediate word in the queue. The queue always stores transformations of words in the current level. (e.g, "dot" and "lot" will both be placed in the queue after "hot" is polled). Whenever "end" is reached, we return the length + 1, where "+ 1" indicates the "end" string.

public class Solution {
    public int ladderLength(String start, String end, Set dict) {
        if (dict == null || dict.size() == 0)
            return 0;
        if (start.equals(end))
            return 1;
        int length = 1;
        Queue queue = new LinkedList ();
        queue.offer(start);
        dict.remove(start);
        while (!queue.isEmpty())
        {
            //for multiple transformations, we only want to increment length once, 
            // thus only increment length after current stage of transformation is done
            int size = queue.size();
            for (int j = 0; j < size; j++)
            {
                String current = queue.poll();
                for (char c = 'a'; c <= 'z'; c++)
                {
                    for (int i = 0; i < current.length(); i++)
                    {
                        if (current.charAt(i) == c)
                            continue;
                        String tmp = replace(current, i, c);
                        if (tmp.equals(end))
                                return length + 1;
                        if (dict.contains(tmp))
                        {
                            //it is possible that another replacement of the character will lead to a shorter length, 
                            //thus length cannot be incremented here
                            //e.g., "a", "b", "c", if length is incremented at b, we will have one extra count
                            queue.offer(tmp);
                            dict.remove(tmp);
                        }
                    }
                }
            }
            //if no transformation is found, queue is empty, and we will exit the loop and return 0
            // otherwise we assume one transformation is found
            length++;
        }
        return 0;
    }

    private String replace(String s, int pos, char character)
    {
        char[] current = s.toCharArray();
        current[pos] = character;
        return new String(current);
    }
}


The second one:
Given two words (start and end), and a dictionary, find all shortest transformation sequence(s) from start to end, such that:
  1. Only one letter can be changed at a time
  2. Each intermediate word must exist in the dictionary
For example,
Given:
start = "hit"
end = "cog"
dict = ["hot","dot","dog","lot","log"]
Return
  [
    ["hit","hot","dot","dog","cog"],
    ["hit","hot","lot","log","cog"]
  ]
Note:
  • All words have the same length.
  • All words contain only lowercase alphabetic characters.
This problem is hard one because of the time and memory complexity.
Word Ladder I is a typical BFS problem, so for this one, intuitively we would think of using BFS too.
Not yet done...
Because we need to return every possible shortest path, we cannot simply remove the used words ("hot" is used in both paths). A possible approach can be as follows:

1. Do BFS first.
    Use a map<String, ArrayList<String>> to track all possible transformations to the key String. e.g., "cog", {"dog", "log"};
    Use another map<String, Integer> to track the level of the string. e.g., "hot", 1; "cog", 5;
2. The transformation is achieved by the same method as we used in the first problem.
3. Do DFS.
    From the "end" string, search the map and get the previous strings, until the start string is reached.
    Reverse the path (because we search from the end to the start) and return the path.

Note: Difference between Backtracking and DFS. 
Backtracking is a more general purpose algorithm. It can be used on any type of structure where portions of the domain can be eliminated.
DFS is a specific form of backtracking related to searching tree structures, and is limited to a tree structure.


public class Solution {
    public ArrayList> findLadders(String start, String end, Set dict) {
        ArrayList> ladders = new ArrayList> ();
        if (start.equals(end))
            return ladders;
        HashMap> prevWord = new HashMap>();
        HashMap level = new HashMap ();
        
        dict.add(start);
        dict.add(end);
        
        getTransformation(prevWord, level, start, dict);
        
        ArrayList path = new ArrayList();
        
        getPath(ladders, path, prevWord, level, end, start);
        
        return ladders;
        
    }
    //apply DFS
    private void getPath(ArrayList> ladders, ArrayList path, 
    HashMap> prevWord, HashMap level, String curr, String start)
    {
        path.add(curr);
        if (curr.equals(start))
        {
            Collections.reverse(path);
            ladders.add(new ArrayList(path));
            Collections.reverse(path);
        }
        else
        {
            for (String word : prevWord.get(curr))
            {
                if (level.containsKey(word) && level.get(curr) == level.get(word) + 1)
                    getPath(ladders, path, prevWord, level, word, start);
            }
        }
        // should be outside the loop since we add the word at the beginning of the method
        path.remove(path.size() - 1);
    }
    //applying BFS
    private void getTransformation(HashMap> prevWord, HashMap level,
    String start, Set dict)
    {
        Queue queue = new LinkedList();
        queue.offer(start);
        for (String s : dict)
            prevWord.put(s, new ArrayList());
        level.put(start, 0);
        while (!queue.isEmpty())
        {
            String curr = queue.poll();
            ArrayList list = transformWord(curr, dict);
            for (String word : list)
            {
                //prevWord tracks the word before 
             //the key is the word transformed from the value
             //curr is the word before the next
                prevWord.get(word).add(curr);
                //if level already contains the word, it means the word is at the upper level 
                //we do not want to change it to a lower level
                if (!level.containsKey(word))
                {
                    level.put(word, level.get(curr) + 1);
                    queue.offer(word);
                }
            }
        }
    }
    private ArrayList transformWord(String s, Set dict)
    {
        ArrayList transformations = new ArrayList();
        for (char c = 'a'; c <= 'z'; c++)
        {
            for (int i = 0; i < s.length(); i++)
            {
                if(s.charAt(i) == c)
                    continue;
                String tmp = s.substring(0,i) + c + s.substring(i + 1);
                if (dict.contains(tmp))
                    transformations.add(tmp);
            }
        }
        return transformations;
    }
    
}