Labels

Showing posts with label HashTable. Show all posts
Showing posts with label HashTable. Show all posts

Sunday, June 21, 2015

Implement Trie (Prefix Tree)

Implement a trie with insert, search, and startsWith methods.
Note:
You may assume that all inputs are consist of lowercase letters a-z.

 class TrieNode {  
   // Initialize your data structure here.  
   public TrieNode() {  
       
   }  
 }  
   
 public class Trie {  
   private TrieNode root;  
   
   public Trie() {  
     root = new TrieNode();  
   }  
   
   // Inserts a word into the trie.  
   public void insert(String word) {  
       
   }  
   
   // Returns if the word is in the trie.  
   public boolean search(String word) {  
       
   }  
   
   // Returns if there is any word in the trie  
   // that starts with the given prefix.  
   public boolean startsWith(String prefix) {  
       
   }  
 }  
   
 // Your Trie object will be instantiated and called as such:  
 // Trie trie = new Trie();  
 // trie.insert("somestring");  
 // trie.search("key");  


Naive Thinking: At first I didn't know Trie. Here is the definition from Wikipedia.  https://en.wikipedia.org/?title=Trie

The structure was given. Only need to fill in the methods. The structure tell me that each node is described as TrieNode. And the whole trie is just referred by its root. Thus, a TrieNode should contain children TrieNode attributes in order to have access to every TrieNode in the tree via root TrieNode.

The search() method and startwith() method differs in that the last character must be an ending TrieNode in search() while startwith() doesn't have to be.

Below is my implementation. I use HashMap to maintain children TrieNode relationship.


 class TrieNode {  
   // Initialize your data structure here.  
   public String data;  
   public Map<Character, TrieNode> children;  
   public boolean isEnd;  
   public TrieNode() {  
     data = new String();  
     children = new HashMap<Character, TrieNode>();  
     isEnd = false;  
   }  
     
   public TrieNode(String s){  
     data = s;  
     children = new HashMap<Character, TrieNode>();  
     isEnd = false;  
   }  
 }  
   
 public class Trie {  
   private TrieNode root;  
   
   public Trie() {  
     root = new TrieNode();  
   }  
   
   // Inserts a word into the trie.  
   public void insert(String word) {  
     TrieNode cur = root;  
     for(int i = 0;i < word.length();i++){  
       if(cur.children.containsKey(word.charAt(i))){  
         cur = cur.children.get(word.charAt(i));  
       }else{  
         String s = root.data + word.charAt(i);  
         TrieNode child = new TrieNode(s);  
         cur.children.put(word.charAt(i), child);  
         cur = child;  
       }  
     }  
     cur.isEnd = true;  
   }  
   
   // Returns if the word is in the trie.  
   public boolean search(String word) {  
     TrieNode cur = root;  
     for(int i = 0;i < word.length();i++){  
       if(cur.children.containsKey(word.charAt(i)))  
         cur = cur.children.get(word.charAt(i));  
       else  
         return false;  
     }  
     return cur.isEnd;  
   }  
   
   // Returns if there is any word in the trie  
   // that starts with the given prefix.  
   public boolean startsWith(String prefix) {  
     TrieNode cur = root;  
     for(int i = 0;i < prefix.length();i++){  
       if(cur.children.containsKey(prefix.charAt(i)))  
         cur = cur.children.get(prefix.charAt(i));  
       else  
         return false;  
     }  
     return true;  
   }  
 }  
   
   


Sunday, May 24, 2015

Isomorphic Strings

Given two strings s and t, determine if they are isomorphic.
Two strings are isomorphic if the characters in s can be replaced to get t.
All occurrences of a character must be replaced with another character while preserving the order of characters. No two characters may map to the same character but a character may map to itself.
For example,
Given "egg", "add", return true.
Given "foo", "bar", return false.
Given "paper", "title", return true.

Naive Way: Use a HashMap to note down the mapping. Also use .containsKey and .containsValue function to check duplicates. The point being easily ignored is the value also needs to be uniquely mapped. No two characters can map to the same value.

 public class Solution {  
   public boolean isIsomorphic(String s, String t) {  
     Map<Character, Character> map = new HashMap<Character, Character>();  
     for(int i = 0;i < s.length();i++){  
       if(map.containsKey(s.charAt(i))){  
         if(t.charAt(i)!=map.get(s.charAt(i))) return false;  
       }else{  
         if(map.containsValue(t.charAt(i))) return false;  
         map.put(s.charAt(i), t.charAt(i));  
       }  
     }  
     return true;  
   }  
 }  

Wednesday, May 20, 2015

Happy Number

Write an algorithm to determine if a number is "happy".
A happy number is a number defined by the following process: Starting with any positive integer, replace the number by the sum of the squares of its digits, and repeat the process until the number equals 1 (where it will stay), or it loops endlessly in a cycle which does not include 1. Those numbers for which this process ends in 1 are happy numbers.
Example: 19 is a happy number
  • 12 + 92 = 82
  • 82 + 22 = 68
  • 62 + 82 = 100
  • 12 + 02 + 02 = 1

Naive Way: The statement is describing a recursive process. So it is obvious that the question want me to write a recursive method. The logic has been stated and clear. Base case (ending condition) is either n=1 or n has been visited. And since I need to mark whether a number has been visited, a hashset is required.

 public class Solution {  
   Set<Integer> visited = new HashSet<Integer>();  
   public boolean isHappy(int n) {  
     // base case  
     if(n==1) return true;  
     // visited  
     if(visited.contains(n)) return false;  
     // main processing  
     int sum = 0;  
     visited.add(n);  
     while(n!=0){  
       int lastDigit = n % 10;  
       sum += lastDigit * lastDigit;  
       n /= 10;  
     }  
     return isHappy(sum);  
   }  
 }  

Saturday, May 16, 2015

Number of Islands

Given a 2d grid map of '1's (land) and '0's (water), count the number of islands. An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are all surrounded by water.
Example 1:
11110
11010
11000
00000
Answer: 1
Example 2:
11000
11000
00100
00011
Answer: 3

Naive Way: Use DFS to go though all grid cells. Use a hashset to keep visited grid cells. Run time is O(n^2). And space is O(n^2).

 public class Solution {  
   public int numIslands(char[][] grid) {  
     Set<List<Integer>> visited = new HashSet<List<Integer>>();  
     int num = 0;  
     for(int i = 0;i < grid.length;i++)  
       for(int j = 0;j < grid[0].length;j++)  
         num+=dfs(grid,i,j,visited);  
     return num;  
   }  
   private int dfs(char[][] grid, int x, int y, Set<List<Integer>> visited){  
     // edge case  
     if(x < 0 || x >= grid.length || y < 0 || y >= grid[0].length) return 0;  
     // island  
     if(grid[x][y] == '1'){  
       List<Integer> list = new ArrayList<Integer>();  
       list.add(x);  
       list.add(y);  
       if(!visited.contains(list)){  
         visited.add(list);  
         dfs(grid, x-1, y, visited);  
         dfs(grid, x+1, y, visited);  
         dfs(grid, x, y-1, visited);  
         dfs(grid, x, y+1, visited);  
         return 1;  
       }  
     }  
     return 0;  
   }  
 }  

I saw a algorithm in Discuss use the original grid to store visited grid cells. Thus reduce space used to O(1).

Sunday, March 29, 2015

Minimum Window Substring

Given a string S and a string T, find the minimum window in S which will contain all the characters in T in complexity O(n).
For example,
S = "ADOBECODEBANC"
T = "ABC"

Minimum window is "BANC".
Note:
If there is no such window in S that covers all characters in T, return the emtpy string "".
If there are multiple such windows, you are guaranteed that there will always be only one unique minimum window in S.

Naive Way: In order to find a minimum window, there must be a way to represent the pattern (which is T) here and current accumulate characters set. A map is suitable to deal with that. Map each character to # of its appearance. And then, need to find a way to keep track of every possible position when a match was found and you need to delete the first character from current accumulate char set. A queue is suitable to do that.

A case when S = "bba" and T="ab" leads to  this line.
  while(isMatch(pattern, cur)){ /* Important Code! */  

One shortcoming is isMatch() funciton takes O(T) time.

 public class Solution {  
   public String minWindow(String S, String T) {  
     Map<Character, Integer> pattern = new HashMap<Character, Integer>();  
     Map<Character, Integer> cur = new HashMap<Character, Integer>();  
     Queue<Integer> queue = new LinkedList<Integer>();  
     int min = Integer.MAX_VALUE;  
     int begin = 0, end = 0;  
     // fill in pattern by T  
     for(int i = 0;i < T.length();i++) addToMap(pattern, T.charAt(i));  
     // initialize current set  
     for(int i = 0;i < T.length();i++) cur.put(T.charAt(i), 0);  
     // go through S to match the pattern by minimum length  
     for(int i = 0;i < S.length();i++){  
       if(pattern.containsKey(S.charAt(i))){  
         queue.add(i);  
         addToMap(cur, S.charAt(i));  
         // check if pattern is matched  
         while(isMatch(pattern, cur)){ /* Important Code! */  
           if(i - queue.peek() < min){  
             min = i - queue.peek();  
             begin = queue.peek();  
             end = i+1;  
           }  
           cur.put(S.charAt(queue.peek()), cur.get(S.charAt(queue.peek()))-1);  
           queue.poll();  
         }  
       }  
     }  
     return end > begin?S.substring(begin, end):"";  
   }  
   private void addToMap(Map<Character, Integer> map, Character c){  
     if(map.containsKey(c))  
       map.put(c, map.get(c)+1);  
     else  
       map.put(c,1);  
   }  
   private boolean isMatch(Map<Character, Integer> p, Map<Character, Integer> cur){  
     for(Map.Entry<Character, Integer> entry: p.entrySet())  
       if(cur.get((char)entry.getKey()) < (int)entry.getValue()) return false;  
     return true;  
   }  
 }  

Sunday, March 8, 2015

Two Sum III - Data structure design

Design and implement a TwoSum class. It should support the following operations: add and find.
add - Add the number to an internal data structure.
find - Find if there exists any pair of numbers which sum is equal to the value.
For example,
add(1); add(3); add(5);
find(4) -> true
find(7) -> false


Naive Way: Use a HashTable to store added numbers. If I want to make add operation O(1), then find operation requires O(n) to go through the HashTable. If I want to make find operation O(1), then the add operation requires O(n) to generate all possible two sums.

add O(1), find O(n), space O(n^2)

 // add O(n), find O(1), space O(n^2)  
      Set<Integer> twoSums = new HashSet<Integer>();  
      Set<Integer> ints = new HashSet<Integer>();  
       public void add(int n){  
            Iterator<Integer> iter = ints.iterator();  
            while(iter.hasNext()){  
                 twoSums.add(iter.next()+n);  
            }  
            ints.add(n);  
       }  
       public boolean find(int n){  
            return twoSums.contains(n);  
       }  

add O(n), find O(1), space O(n)

 // add O(1), find O(n), space O(n)  
      Map<Integer, Integer> map = new HashMap<Integer, Integer>();  
      public void add(int n){  
           if(map.containsKey(n))  
                map.put(n,map.get(n)+1);  
           else  
                map.put(n,1);  
      }  
      public boolean find(int n){  
           Iterator iter = map.entrySet().iterator();  
           while(iter.hasNext()){  
                Entry cur = (Entry)iter.next();  
                if(map.containsKey(n-(int)cur.getKey())){  
                     if(n == 2*(int)cur.getKey())  
                          return (int)cur.getValue()>=2;  
                     else  
                          return true;  
                }  
           }  
           return false;  
      }  

Tuesday, March 3, 2015

Anagrams

Given an array of strings, return all groups of strings that are anagrams.
Note: All inputs will be in lower-case.

Naive Way: My first thought was use int[26] array to store each word's information, and then use a Map to record that information, once an anagram was found, the int[26] array info will be the same with one of the map. And later I found out that two int[] with same value aren't equal in Java from this post http://stackoverflow.com/questions/2627889/java-hashmap-with-int-array. So I use an ArrayList<Integer> instead.

This algorithm takes O(n) run time and O(n) space. And one corner case I met is when strs[] = {"",""}, the result is ["",""] instead of [""]. It means the second time a particular character appearance list was met, the string must be added into the result.

 public class Solution {  
   public List<String> anagrams(String[] strs) {  
     List<String> rslt = new ArrayList<String>();  
     Set<String> set = new HashSet<String>();  
     Map<List<Integer>, String> map = new HashMap<List<Integer>, String>();  
     for(int i = 0;i < strs.length;i++){  
       // record char appearance into a List<Integer>  
       int[] appear = new int[26];  
       List<Integer> list = new ArrayList<Integer>();  
       for(int j = 0;j < strs[i].length();j++)  
         appear[(int)(strs[i].charAt(j)-'a')]++;  
       for(int j = 0;j < appear.length;j++)  
         list.add(appear[j]);  
       // map list to string  
       if(!map.containsKey(list)){  
         map.put(list, strs[i]);   
       }else{  
         if(!set.contains(map.get(list)))  
           rslt.add(map.get(list));  
         rslt.add(strs[i]);  
         set.add(map.get(list));  
         set.add(strs[i]);  
       }  
     }  
     return rslt;  
   }  
 }  

Friday, February 20, 2015

Longest Consecutive Sequence


Longest Consecutive Sequence



 


Given an unsorted array of integers, find the length of the longest consecutive elements sequence.
For example,
Given [100, 4, 200, 1, 3, 2],
The longest consecutive elements sequence is [1, 2, 3, 4]. Return its length: 4.
Your algorithm should run in O(n) complexity.

Naive Way:O(n)应有两种方案,一种是用hashmap,一种是用bucket sort的思想。想了一想bucket sort,可以将数字全部分割成小组,然后再每个小组内再分割,直到某个小组容量正好等于数的数量就是连续的,但是还要追溯相邻的小组,反正不太像O(n)的。如果用hashmap,好像好做一点,可以map相邻的两个数,记下起点,最后不停追溯map,但是每次记录起点又需要O(n)的追溯了。还可以map连续出现的个数,比如一开始100->1, 4->1, 200->1,然后3出现,就要使得3->2, 4->2,这样有个麻烦就是如果已匹配好的连续数段很长,还要每一个更新一遍映射。后来仔细想了想,不用所有的都更新,只需要更新一头一尾。

算法复杂度O(n), space O(n)。



 

 public class Solution {  
   public int longestConsecutive(int[] num) {  
     int longest = 0;  
     Map<Integer, Integer> map = new HashMap<Integer, Integer>();  
     for(int i = 0;i < num.length;i++){  
       // if there is no duplicates, these two lines can be commented  
       if(map.containsKey(num[i])) continue;  
       map.put(num[i],1);  
       int end = num[i];  
       int begin = num[i];  
       if(map.containsKey(num[i]+1))  
         end = num[i] + map.get(num[i]+1);  
       if(map.containsKey(num[i]-1))  
         begin = num[i] - map.get(num[i]-1);  
       longest = Math.max(longest, end-begin+1);  
       map.put(end, end-begin+1);  
       map.put(begin, end-begin+1);  
     }  
     return longest;  
   }  
 }  

Thursday, February 19, 2015

Two Sum


Two Sum



 


Given an array of integers, find two numbers such that they add up to a specific target number.
The function twoSum should return indices of the two numbers such that they add up to the target, where index1 must be less than index2. Please note that your returned answers (both index1 and index2) are not zero-based.
You may assume that each input would have exactly one solution.
Input: numbers={2, 7, 11, 15}, target=9
Output: index1=1, index2=2 


 

Naive Way:如果序列是排好序的,可不可以Binary Search。联想3sum的做法,好像可以。但是如果不是排好序的就不行,因为要返回Index而非boolean值,排序会打乱index。可不可以在O(n)的时间内做出来。可以,用一个HashTable去存已经遍历过的。




这是O(n) run time, O(n) space的做法。



 

 public class Solution {  
   public int[] twoSum(int[] numbers, int target) {  
     int rslt[] = {-1,-1};  
     Map<Integer, Integer> map = new HashMap<Integer, Integer>();  
     for(int i = 0;i < numbers.length;i++){  
       int t = target - numbers[i];  
       if(map.containsKey(t)){  
         rslt[0] = map.get(t);  
         rslt[1] = i+1;  
         return rslt;  
       }else{  
         map.put(numbers[i],i+1);  
       }  
     }  
     return rslt;  
   }  
 }  

Wednesday, February 18, 2015

Substring with Concatenation of All Words

You are given a string, S, and a list of words, L, that are all of the same length. Find all starting indices of substring(s) in S that is a concatenation of each word in L exactly once and without any intervening characters.
For example, given:
S: "barfoothefoobarman"
L: ["foo", "bar"]
You should return the indices: [0,9].
(order does not matter).


Naive Way: brute force 的方法将是每一个字符的遍历,看它所引领的字符串是否为字典所组成。假设|S| = n, |L| = m, |L[0]| = k,这样的算法复杂度是O(nm)。

而我的基本的想法是隔着一个单词长度的遍历,用一个queue控制进出,然后用一个map装入L的信息,一旦queue.size() == L.length 说明找到一个match。

 如果出现这种情况 L:["foo","bar","arf"...],说明一个substring即使在字典中找到,也不可以一个单词一个单词的向后递增。

假设每个单词长度是k,任然可以用上述方法,需要为每一个间隔都设置一个queue和一个map。然后每个间隔单独执行以上算法,这样就需要k个queue和k各map。算法复杂度是O(n)。
我觉得这个算法复杂度真心足够了。

有一个容易出错的地方是当map不包含当前word时,可能queue出来的那个正好等于当前word,此时,要做的就是不改变map,把当前word推进queue,然后queue再排出最前面的word。

算法复杂度是O(n), space O(km)

 public class Solution {  
   public List<Integer> findSubstring(String S, String[] L) {  
     List<Integer> rslt = new ArrayList<Integer>();  
     // edge case  
     if(L.length==0) return rslt;  
     // initialize k queues and k maps  
     int k = L[0].length();  
     List<Queue<String>> queues = new ArrayList<Queue<String>>();  
     List<Map<String, Integer>> maps = new ArrayList<Map<String, Integer>>();  
     Map<String, Integer> map_temp = new HashMap<String, Integer>();  
     for(int i = 0;i < L.length;i++) setMap(map_temp, L[i]);  
     for(int i = 0;i < k;i++) queues.add(new LinkedList<String>());  
     for(int i = 0;i < k;i++) maps.add(new HashMap<String, Integer>(map_temp));  
     // go though S  
     for(int i = 0;i <= S.length()-k;i++){  
       Queue<String> queue = queues.get(i%k);  
       Map<String, Integer> map = maps.get(i%k);   
       String s = S.substring(i,i+k);  
       if(map.containsKey(s)){  
         queue.add(s);  
         map.put(s,map.get(s)-1);  
         if(map.get(s)==0) map.remove(s);  
       }else{  
         if(!queue.isEmpty()){  
           if(s.equals(queue.peek())) // when the word to be poll == the word to be add  
             queue.add(queue.poll()); // don't need to modify map  
           else  
             while(!queue.isEmpty()) setMap(map, queue.poll());  
         }  
       }  
       // find a match  
       if(queue.size()==L.length){  
         rslt.add(i-(L.length-1)*k);  
         setMap(map,queue.poll());  
       }  
     }  
     return rslt;  
   }  
   private void setMap(Map<String, Integer> map, String s){  
     if(!map.containsKey(s))  
       map.put(s,1);  
     else  
       map.put(s,map.get(s)+1);  
     return;  
   }  
 }  



Improved Way: 我看了https://oj.leetcode.com/discuss/20151/an-o-n-solution-with-detailed-explanation这个算法后发现queue完全可以用一个变量来代替,记录长度即可。

 public class Solution {  
   public List<Integer> findSubstring(String S, String[] L) {  
     List<Integer> rslt = new ArrayList<Integer>();  
     // edge case  
     if(L.length==0) return rslt;  
     // initialize k queues and k maps  
     int k = L[0].length();  
     int queues[] = new int[k];  
     List<Map<String, Integer>> maps = new ArrayList<Map<String, Integer>>();  
     Map<String, Integer> map_temp = new HashMap<String, Integer>();  
     for(int i = 0;i < L.length;i++) setMap(map_temp, L[i]);  
     for(int i = 0;i < k;i++) queues[i] = 0;  
     for(int i = 0;i < k;i++) maps.add(new HashMap<String, Integer>(map_temp));  
     // go though S  
     for(int i = 0;i <= S.length()-k;i++){  
       int queue = queues[i%k];  
       Map<String, Integer> map = maps.get(i%k);   
       String s = S.substring(i,i+k);  
       if(map.containsKey(s)){  
         queue++;  
         map.put(s,map.get(s)-1);  
         if(map.get(s)==0) map.remove(s);  
         // find a match  
         if(queue==L.length){  
           rslt.add(i-(queue-1)*k);  
           setMap(map, S.substring(i-k*(queue-1),i-k*(queue-1)+k));  
           queue--;  
         }  
       }else{  
         if(queue > 0){  
           if(!s.equals(S.substring(i-k*queue,i-k*queue+k)))  
             while(queue > 0){  
               setMap(map, S.substring(i-k*queue,i-k*queue+k));  
               queue--;  
             }  
         }  
       }  
       queues[i%k] = queue;  
     }  
     return rslt;  
   }  
   private void setMap(Map<String, Integer> map, String s){  
     if(!map.containsKey(s))  
       map.put(s,1);  
     else  
       map.put(s,map.get(s)+1);  
     return;  
   }  
 }  


Tuesday, February 10, 2015

Repeated DNA Sequences


Repeated DNA Sequences



 


All DNA is composed of a series of nucleotides abbreviated as A, C, G, and T, for example: "ACGAATTCCG". When studying DNA, it is sometimes useful to identify repeated sequences within the DNA.
Write a function to find all the 10-letter-long sequences (substrings) that occur more than once in a DNA molecule.
For example,
Given s = "AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT",

Return:
["AAAAACCCCC", "CCCCCAAAAA"]. 
 
 
Naive Way: 一开始试了两种方法都不行。一种是用一个set去存
已经遍历过的substring,然后每取得一个新的就看set是否contain。 
这样的做法的算法复杂度是O(n),但是需要O(n)的space,
会出现内存不够。第二种方法是不用set,每次取得一个substring
都和之前开头到当前位置的每一个substring比较,
这样的算法复杂度是O(n^2),space O(1),会出现超时。
 
 


然后我想到了这个在time complexity 和 
memory之间折中的办法。Map存的是对应前m个字符一致时,
各个(m字符串)出现的位置,1<m<=10,这样m越大,space 
需求越大,time complexity越小。 但是还是不行,
在m=3的时候会报超时,m=4的时候会报空间不足。


public class Solution {
    public List<String> findRepeatedDnaSequences(String s) {
        List<String> rlst = new ArrayList<String>();
        Map<String, List<Integer>> map = new HashMap<String, List<Integer>>();
        int k = 10;
        int offset = 4;
        for(int i = 0;i < s.length()-k+1;i++){
            String ss = s.substring(i,i+offset);
            if(!map.containsKey(ss)){
                List<Integer> list = new ArrayList<Integer>();
                list.add(i);
                map.put(ss, list);
            }else{
                List<Integer> list = map.get(ss);
                boolean found = false;
                for(int j = 0;j < list.size();j++){
                    int t = offset;
                    for(;t < k;t++){
                        if(s.charAt(i+t)!=s.charAt(list.get(j)+t))
                            break;
                    }
                    if(t==k){
                        rlst.add(s.substring(i,i+k));
                        found = true;
                        break;
                    }
                }
                if(!found)
                    list.add(i);
            }
        }
        return rlst;
    }
}


然后还试了一种树形的存贮方式,也是memory limit exceed。看了tag标记为bit manipulate,似乎有点头绪了。四种字符可以用两位二进制完全表示。10个连续字符是20位二进制,一个int型是32位。是否可以用一个Set<Integer>来存遍历过的字符串,这样每一次存贮可以节省16*10-32=128bit 空间。这种方法还是会内存不足。










 最后的最后!



  Improved Way:



终于让这道题Accepted了,已经是第五天了,这回我也算是竭尽所能的减少空间使用率。首先使用了一个byte array来存10个连续单位中A,C,G,T出现次数 ,又因为正好4个byte是一个int,就可以用一个int来表示出现次数,使用一个Map<Integer, Set<String>>的map来将出现次数和同一种出现次数下的对应String Set做映射。然后由于10个单位的两比特数是20比特,又可以用一个int表示,就将所有String换成int来存,这样最后的map就变成了Map<Intger, Set<Integer>>。每次只要看该String对应的Integer是否在Set中就可以判断是否出现过了。







public class Solution {
    public List<String> findRepeatedDnaSequences(String s) {
        List<String> list = new ArrayList<String>();
        Map<Integer, Set<Integer>> map = new HashMap<Integer, Set<Integer>>();
        Set<String> visited = new HashSet<String>();
        int k = 10;
        byte[] appear = new byte[4];
        Arrays.fill(appear, (byte)0);
        for(int i = 0;i < s.length();i++){
            appear[dna2int(s.charAt(i))]++;
            if(i >= k-1){
                String sample = s.substring(i-k+1,i+1);
                int key = byte2int(appear);
                int intSample = seq2int(sample);
                if(!map.containsKey(key)){
                    Set<Integer> set = new HashSet<Integer>();
                    set.add(intSample);
                    map.put(key,set);
                }else{
                    Set<Integer> set = map.get(key);
                    if(set.contains(intSample)){
                        if(!visited.contains(sample))
                            visited.add(sample);
                    }else{
                        set.add(intSample);
                        //map.put(key,set);
                    }
                }
                appear[dna2int(s.charAt(i-k+1))]--;
            }
        }
        list.addAll(visited);
        return list;
    }
    
    private int dna2int(char a){
        switch(a){
            case 'A':return 0;
            case 'C':return 1;
            case 'G':return 2;
            case 'T':return 3;
            default: return 0;
        }
    }
    
    private int byte2int(byte[] b){
        int x = b[0] << 24 | b[1] << 16 | b[2] << 8 | b[3];
        return x;
    }
    
    private int seq2int(String s){
        int x = 0;
        for(int i = 0;i < s.length();i++){
            x <<= 2;
            switch(s.charAt(i)){
                case 'A':
                    x |= 0;
                    break;
                case 'C':
                    x |= 1;
                    break;
                case 'G':
                    x |= 2;
                    break;
                case 'T':
                    x |= 3;
                    break;
                default:
                    break;
            }
        }
        return x;
    }
    
}



 



 



最后在discuss里看到了一个最厉害的方法,叫做Rabin-Karp,可用来解strStr()。它采用Rolling Hash的方法,每次固定大小的窗移动一位时,只需减去对应的hash量和加上新的hash量就可以形成新的hash码,它的理论基础大概是这样的。



 



对于这道题,一共有ACGT四个变量,为他们赋值0123,那么以4为底的话,就能让连续k个组合的序列具有唯一的hash码。比如k=3时,




ACG = 0 * 4^2 + 1 * 4^1 + 2 * 4^0;



TTC  = 3 * 4^2 + 3 * 4^1 + 1 * 4^0;



就像10进制,只要每一位上的数不是完全相同,两个数就不相同。



使用这个的前提是4^(k+1) <= Integer.MAX_VALUE。



k=10的时候 4^11 = 2^22 < 2^31。






维基百科中说,即使变量的个数很大时,也可以用小于变量个数的大质数做底,因为质数幂次方后很少出现两个不同数的hash码一致。









public class Solution {
    private static final int base = 4;
    public List<String> findRepeatedDnaSequences(String s) {
        // Rabin-Karp rolling hash
        List<String> list = new ArrayList<String>();
        Set<Integer> set = new HashSet<Integer>();
        Set<String> rslt = new HashSet<String>();
        int k = 10;
        int head_factor = (int)Math.pow(base,k-1);
        int hash = 0;
        for(int i = 0;i < s.length();i++){
            hash -= i>=k?head_factor*dna2int(s.charAt(i-k)):0;
            hash = hash*base+dna2int(s.charAt(i));
            if(i >= k-1 && !set.add(hash))
                rslt.add(s.substring(i-k+1,i+1));
        }
        list.addAll(rslt);
        return list;
    }
    
    private int dna2int(Character c){
        switch(c){
            case 'A':return 0;
            case 'C':return 1;
            case 'G':return 2;
            case 'T':return 3;
            default:return 0;
        }
    }
}





Monday, January 26, 2015

Max Points on a Line


Max Points on a Line


Given n points on a 2D plane, find the maximum number of points that lie on the same straight line.



Naive Way: 每两个点确定一条直线 ,每确定一条直线遍历所有点,每次用经过的点个数和最大值作比较。这样的话就需要O(n^3) run time。其中,有直线平行于x-axis或y-axis,两个点重叠的edge case可能会造成遗漏。并且考虑到k和b是小数可能带来误差,比较时采用取差值在一定小的范围内的方法。


/**
 * Definition for a point.
 * class Point {
 *     int x;
 *     int y;
 *     Point() { x = 0; y = 0; }
 *     Point(int a, int b) { x = a; y = b; }
 * }
 */

public class Solution {
    static final double zero = 0.0001;
    public int maxPoints(Point[] points) {
    // O(n^3)
        int max = 1;
        for(int i = 0;i < points.length-1;i++){
            for(int j = i+1;j < points.length;j++){
                int count = 0;
                // case 1: no y parallel
                if(points[i].x!=points[j].x){
                    double k = (points[i].y - points[j].y)/(double)(points[i].x-points[j].x);
                    double b = (double)points[i].y - k*points[i].x;
                    for(int t = 0;t < points.length;t++){
                        if(Math.abs(k*points[t].x+b-points[t].y) < zero)
                            count++;
                    }
                    max = Math.max(max, count);
                }else{ // case 2: parallel to y axis
                    for(int t = 0;t < points.length;t++){
                        if(points[t].x == points[i].x)
                            count++;
                    }
                    max = Math.max(max, count);
                }
            }
        }
        return points.length==0?0:max;
    }
}

Improved Way: 由于最坏的情况是没有三个点在同一条直线上,那么至少需要运行O(n^2)次来遍历所有可能的直线,所以最低的run time应该也要O(n^2)。 一个比较直观的感觉就是如果可以记录每一条直线,存入一个Map中,每次得到新的直线都先看Map中是否已有,有就增加Map的value,没有就加入新的直线,该法基于Map可以O(1)的读存, 使run time 提升到O(n^2),但同时也带来了O(n^2)的extra space.

但其实这种方法有难度,难在如何记录一条直线上。我自己想到的方法是构建一个新类表示一条线。采用两个参数 k 和 b, 分别表示 y = kx+b中中的两个参数。这样会带来一个问题,表示x = c时会无法表示。可以多设立一个c参数和一个boolean值来区分是否是平行于x-axis。

这里写的时候才发现了另一个问题,同样参数的两个新类,Map不会设别成同一个Key,必须得自己重写equals函数和hashCode函数,具体我是看了一个教程教你如何用Java写Equal函数 How to Write a Equality Method in Java,很有用, 具体自己的hashCode函数是rounding的,肯定很有误差,但是估计leetcode的样本很少,还是能通过。




public class Solution {
        static final double zero = 0.0001;
        public int maxPoints(Point[] points) { 
            // O(n^2)
            int max = 1;
        Map<Line, Set<Integer>> map = new HashMap<Line, Set<Integer>>();
        for(int i = 0; i < points.length-1;i++){
            for(int j = i+1; j < points.length;j++){
                if(points[i].x==points[j].x){
                    Line line = new Line(points[i].x);
                    if(!map.containsKey(line)){
                        Set<Integer> set = new HashSet<Integer>();
                        set.add(i);
                        set.add(j);
                        map.put(line, set);
                    }else{
                        Set<Integer> set = map.get(line);
                        set.add(i);
                        set.add(j);
                        map.put(line, set);
                    }
                }else{
                    double k = (points[i].y - points[j].y)/(double)(points[i].x-points[j].x);
                    double b = (double)points[i].y - k*points[i].x;
                    Line line = new Line(k,b);
                    if(!map.containsKey(line)){
                        Set<Integer> set = new HashSet<Integer>();
                        set.add(i);
                        set.add(j);
                        map.put(line, set);
                    }else{
                        Set<Integer> set = map.get(line);
                        set.add(i);
                        set.add(j);
                        map.put(line, set);
                    }
                }
            }
        }
        for(Map.Entry<Line, Set<Integer>> entry: map.entrySet())
            max = Math.max(max, entry.getValue().size());
        return points.length==0?0:max;
        }
       
        class Line{
            double k;
            double b;
            boolean xp;
            int xValue;
            Line(double m, double n){
                k = m;
                b = n;
                xp = false;
                xValue = 0;
            }
            Line(int v){
                k = Integer.MAX_VALUE;
                b = 0;
                xp = true;
                xValue = v;
            }
            @Override
            public int hashCode(){
                return (int)Math.round(k) << 16|(int)Math.round(b) + xValue;
            }
            @Override
            public boolean equals(Object o){
                if(o == this)
                    return true;
                if(!(o instanceof Line))
                    return false;
                Line l = (Line)o;
                if(xp && l.xp)
                    return xValue==l.xValue;
                else
                    return Math.abs(k-l.k) < zero && Math.abs(b-l.b) < zero;
            }
        }
    }



Other Ways: 在网上看到一个很好的方法,是采用两个Map和找最大公约数。对于一条直线y=kx+b, 每次得到k和b通过求他们最大公约数可以把他们化成最小量值,这样再次出现一样斜率的直线,就可以找相同的k,那么第一个map就是 k->b 的map。每一个斜率对应不同偏量。 第二个map就是 b->#points 的map, 每个确定的直线对应点的个数,最后就是一个Map<Integer, Map<Integer, Integer>>的形式,在这里,大家可以去看一看具体的code,写得好呀。