哈希表算法面试题

来源:互联网 发布:itunes软件安装目录 编辑:程序博客网 时间:2024/06/16 17:06


分析

最常规的算法当然是先对数组进行排序,然后从两端开始逐渐调整下标使得两个元素的和为目标值。但是,题目要求返回数据的下标,因此我们只能在数组的拷贝上进行排序。空间复杂度O(n),时间复杂度O(n log )。

我们只需要找到特定的一组元素,而我们对整个数组进行了排序,是否有这个必要呢?

我们可以维护这样一个哈希表<元素,元素下标>,如果说target-a[i]也存在于哈希表中,那么我们就得到了这样的一对元素。

注:需要注意相同元素的情况。

public class TwoSum {     public int[] twoSum(int[] nums, int target) {    int[] res=new int[2];     Map<Integer,Integer> indexMap=new HashMap<Integer,Integer>();    for(int i=0;i<nums.length;i++){    if(target%2==0&&nums[i]==target/2&&indexMap.get(target/2)!=null){    int secondIndex=indexMap.get(target/2);    res[0]=Math.min(i, secondIndex);    res[1]=Math.max(i, secondIndex);    return res;    }    indexMap.put(nums[i], i);    }     for(int i=0;i<nums.length;i++){    if(indexMap.containsKey(target-nums[i])&&indexMap.get(target-nums[i])!=i){    int secondIndex=indexMap.get(target-nums[i]);    res[0]=Math.min(i, secondIndex);    res[1]=Math.max(i, secondIndex);    break;    }    }return res;     }}

分析

对于数独的验证,我们需要验证行,验证列,还需要验证9个3*3的格子。

因为每一行、列、3*3的格子只需要验证一次。并且是存在性判断,对于存在性判断(重复),我们必定会想到哈希表。

public class ValidSudoku {    public boolean isValidSudoku(char[][] board) {        int[][] rowsMap=new int[9][9];        int[][] colsMap=new int[9][9];        int[][] gridsMap=new int[9][9];        for(int row=0;row<9;row++){        for(int col=0;col<9;col++){        if(board[row][col]=='.') continue;        int value=board[row][col]-'1';//1-9变换成0~8        //验证行        if(rowsMap[row][value]==1){        return false;        }else{        rowsMap[row][value]=1;        }        //验证列        if(colsMap[col][value]==1){        return false;        }else{        colsMap[col][value]=1;        }        //验证单元格        int index=(row/3)*3+col/3;        if(gridsMap[index][value]==1){        return false;        }else{        gridsMap[index][value]=1;        }        }        }        return true;    }}

分析

从前往后遍历,我们用一个256位的数组(哈希表)记录每个字符上一次出现的位置,如果当前元素s[i]和t[i]相等,且上一次出现的位置也一样,则继续遍历,否则返回false。

public class Solution {    public boolean isIsomorphic(String s, String t) {     if(s.length()!=t.length()) return false;    int[] m1=new int[256];    int[] m2=new int[256];        int  n = s.length();        for(int i=0;i<256;i++){        m1[i]=m2[i]=-1;        }        for (int i = 0; i < n; ++i) {            if (m1[s.charAt(i)] != m2[t.charAt(i)]) return false;            m1[s.charAt(i)] = i ;            m2[t.charAt(i)] = i ;        }        return true;     }}

public class Solution {    public boolean containsDuplicate(int[] nums) {    HashMap<Integer,Integer> map=new HashMap<Integer,Integer>();    for(int i=0;i<nums.length;i++){    if(map.get(nums[i])!=null){    return true;    }else{    map.put(nums[i], 1);    }    }return false;     }}

分析:

在S的子串S[begin,end-1]中,我们确保其中的元素不重复。当我们向其中添加元素S[end],如果S[end]不在子串中,将其加入子串中,子串变成S[begin,end]。如果在子串中存在S[pre]与S[end]重复,那么更新子串为S[pre+1,end+1],即将之前重复的元素淘汰,新的元素加入。

对于元素的存在性问题哈希表再合适不过了,其查找为O(1),但是我们不仅仅是要存在性判断,我们还需要记录其上一次出现的下表,算法如下:

public class Solution {    public int lengthOfLongestSubstring(String s) {    if(s==null||s.length()==0)return 0;    int res=1,begin=0,end=1;    HashMap<Character,Integer> indexs=new HashMap<Character,Integer>();     indexs.put(s.charAt(0), 0);    while(end<s.length()){     Integer preIndex=indexs.get(s.charAt(end));     if(preIndex==null){    indexs.put(s.charAt(end), end);     res=Math.max(res, indexs.size());     }else{    while(begin<=preIndex){     indexs.remove(s.charAt(begin++));     }    indexs.put(s.charAt(end), end);    }    end++;    } return res;     } }

分析:

利用哈希表统计各字符的数量,数量相同就分到同一组。

public class Solution {    public List<List<String>> groupAnagrams(String[] strs) {    List<List<String>> res=new ArrayList<List<String>>();    HashMap<List<Integer>,List<String>> map=new HashMap<List<Integer>,List<String>>();    for(String str:strs){    ArrayList<Integer> counts=new ArrayList<Integer>(Collections.nCopies(26, new Integer(0)));    for(char c:str.toCharArray()){     counts.set(c-'a', counts.get(c-'a')+1);    }    if(map.get(counts)==null){    List<String> list=new ArrayList<String>();    list.add(str);    map.put(counts, list);    }else{    List<String> list=map.get(counts);    list.add(str);    }    }    for(List<String> value:map.values()){    res.add(value);    }return res;     }}

分析:

方案一:首先我们可以利用HashMap统计所有元素出现的次数,出现两次时就移除,最后剩下的元素就是我们要找的元素。

    public int singleNumber(int[] nums) {    HashMap<Integer,Integer> countMap=new HashMap<Integer,Integer>();    for(int i:nums){    if(countMap.get(i)==null){    countMap.put(i, 1);    }else{    countMap.remove(i);    }    }    int res=0;    for(Integer key:countMap.keySet()){    res=key;    break;    }     return res;    }

方案二:我们可以将所有元素异或,最后的结果就是该元素。

public class Solution {    public int singleNumber(int[] nums) {    int res=0;    for(int i:nums){    res^=i;    }    return res;    }}

方案三:根据异或的原理,我们记录2^i次方出现的次数,0<=i<=31(int四字节,32位),最后我们将出现奇数次的位置1,出现偶数次的位置零,我们就得到的最终结果的二进制表示,最后根据二进制表示转换成整数即可。

public class Solution {    public int singleNumber(int[] nums) {    int[] count=new int[32];     for(int i:nums){    int location=0,t=1;    while(location<=31){     if((t&i)!=0){    count[location]=(count[location]+1)%2;    }    location++;    t<<=1;    }     }     int sign=1;    if(count[31]==1){//负数,将补码转换成原码        sign=-1;//记录结果的符号        //0和1转换        for(int i=31;i>=0;i--){        if(count[i]==0){        count[i]=1;        }else{        count[i]=0;        }        }        //+1        int add=1;        for(int i=0;i<=31;i++){         int sum=count[i]+add;        count[i]=sum%2;        add=sum/2;        if(add==0) break;        }    }    StringBuilder builder=new StringBuilder();     for(int i=31;i>=0;i--){//从高位到低位         builder.append(count[i]);    }    return Integer.valueOf(builder.toString(), 2)*sign;     }}
扩展:

变形1:除了一个数出现一次外,其余都出现3次呢?第二种方案肯定不能解决问题,但是方案三稍做改变就可以适用。

变形2:如果我们除了两个数(A和B)出现一次外,其余都出现两次呢?显然似乎只有方案一可行了,是这样吗?如果我们将所有的整数异,结果的二进制位表示中至少有一个位是1(因为A!=B),我们将所有整数按照二进制表示中该位是否为1的规则将整数划分为两个部分,然后分别进行异或运算。这样,我们就通过方案二得到了正确的结果。

public class Solution {    public List<String> findRepeatedDnaSequences(String s) {    List<String> res=new ArrayList<String>();    HashMap<String,Integer> map=new HashMap<String,Integer> ();    for(int i=0;i<=s.length()-10;i++){    String t=s.substring(i, i+10);    if(map.get(t)==null){    map.put(t, 1);    }else{    if(map.get(t)==1){    res.add(t);    }    map.put(t, map.get(t)+1);    }    }return res;     }}

分析:

与最长不重复子串思路类似(Leetcode 3),只不过我们这里的元素允许重复,我们记录元素出现的次数,并且需要记录元素出现的列表。

   public List<Integer> findSubstring(String s, String[] words) {    List<Integer> res=new ArrayList<Integer>();    HashMap<String,Integer> countMap=new HashMap<String,Integer> ();    for(String word:words){//统计每个单词出现的次数    if(countMap.get(word)==null){    countMap.put(word, 1);    }else{    countMap.put(word, 1+countMap.get(word));    }     }    //匹配到的单词的下标    HashMap<String,List<Integer>> findMap=new HashMap<String,List<Integer>> ();//匹配元素    int wl=words[0].length(),//单词长度    wc=words.length;//单词个数    for(int i=0;i<wl;i++){        findMap.clear();    int begin=i,//记录起始点    now=i;//当前匹配点    while(now+wl<=s.length()){    String t=s.substring(now, now+wl);    if(!countMap.containsKey(t)){//单词不匹配,重新匹配    findMap.clear();    begin=now+wl;//更新起点    }else{    if(!findMap.containsKey(t)){//之前没找到过    List<Integer> list=new LinkedList<Integer>();    list.add(now);    findMap.put(t, list);    if((now-begin)==(wl*wc-wl)){//符合要求    res.add(begin);    }    }else{    if(findMap.get(t).size()<countMap.get(t)){//还可以匹配t    findMap.get(t).add(now);        if((now-begin)==(wl*wc-wl)){//符合要求        res.add(begin);        }    }else{//多余字符t    int first=findMap.get(t).get(0);//t第一次出现的位置    while(begin<=first){    findMap.get(s.substring(begin, begin+wl)).remove(0);    begin+=wl;    }    findMap.get(t).add(now);        if((now-begin)==(wl*wc-wl)){//符合要求        res.add(begin);        }    }    }    }    now+=wl;    }    }return res;     }


分析

对于数独的解法,我们都知道是通过回溯,每个待填写的格子我们都需要尝试各种可能性,但是我们在尝试时必须判定合法性(行、列或3*3格子中是否有重复),而不是填入后再验证合法性,因此我们可以利用哈希表来进行存在性验证,简化合法性验证过程。

public class Solution {boolean[][] rowMaps=new boolean[9][9];boolean[][] colMaps=new boolean[9][9];boolean[][] cellMaps=new boolean[9][9]; private void mark(int row,int col,char c){int index=c-'1';rowMaps[row][index]=true;colMaps[col][index]=true;cellMaps[row/3*3+col/3][index]=true;}private void unMark(int row,int col,char c){int index=c-'1';rowMaps[row][index]=false;colMaps[col][index]=false;cellMaps[row/3*3+col/3][index]=false;}private boolean isValid(int row ,int col,char c){int index=c-'1';return rowMaps[row][index]==false&&colMaps[col][index]==false&&cellMaps[row/3*3+col/3][index]==false;}    public void solveSudoku(char[][] board) {        //初始化    ArrayList<Integer> locations=new ArrayList<Integer>();    for(int row=0;row<9;row++){    for(int col=0;col<9;col++){    char c=board[row][col];    if(c=='.'){        locations.add(row*9+col);    continue;    }else{    mark(row,col,c);        }    }    }    int now=0;    while(now<locations.size()&&now>=0){    int location=locations.get(now);    int row=location/9,col=location%9;    char next='1',c=board[row][col];    if(c!='.'){    next=(char)(c+1);    unMark(row,col,c);    }    while(next<='9'){ //找到下一个合法的值    if(isValid(row,col,next)){    break;    }     next++;    }    if(next=='9'+1){//尝试完了都    now--;    board[row][col]='.';    }else{    board[row][col]=next;    mark(row,col,next);    now++;    }    }      }}

public class Solution {private boolean isValid(HashMap<Character,Integer> countMap,HashMap<Character,Integer> findMap){for(Character c:countMap.keySet()){if(findMap.get(c)==null||findMap.get(c)<countMap.get(c)){return false;}}return true;}    public String minWindow(String s, String t) {     int minWindow=Integer.MAX_VALUE,start=-1;    LinkedList<Integer> indexs=new LinkedList<Integer>();    HashMap<Character,Integer> countMap=new HashMap<Character,Integer>();    HashMap<Character,Integer> findMap=new HashMap<Character,Integer>();    //初始化    for(char c:t.toCharArray()){     countMap.put(c, 0);    findMap.put(c, 0);    }    for(char c:t.toCharArray()){    countMap.put(c, countMap.get(c)+1);    }    int now=0;    while(now<s.length()){    char c=s.charAt(now);    if(!countMap.containsKey(c)){    now++;    continue;    }else{//存在于t中    indexs.add(now);    if(findMap.get(c)<countMap.get(c)){//还可以匹配该字符    findMap.put(c,findMap.get(c)+1);    while(isValid(countMap,findMap)){//匹配    if(indexs.getLast()-indexs.getFirst()+1<minWindow){//更新最小窗体大小    minWindow=indexs.getLast()-indexs.getFirst()+1;    start=indexs.getFirst();    }    //使得findMap达到不匹配状态    char first=s.charAt(indexs.removeFirst());    findMap.put(first, findMap.get(first)-1);    }    }else{//超出特定数量,肯定不会匹配    findMap.put(c, findMap.get(c)+1);    }     }    now++;    }    if(start==-1){    return "";    }else{    return s.substring(start,start+minWindow);    }     }}

优化:

我们可以用数组来实现哈希表而不是用HashMap,效率更高。此外,我们用一个counter记录已经匹配个数,而不是每次进行检查是否完成匹配。

public class Solution {    public String minWindow(String s, String t) {    int[] countMap=new int[128];    int[] findMap=new int[128];    for(char c:t.toCharArray()){    countMap[c]++;     }    int counter=0,begin=0,end=0,head=0,minWindow=Integer.MAX_VALUE;    while(end<s.length()){    char c=s.charAt(end);    if(countMap[c]==0){//不在t中的字符,忽略掉    end++;    continue;    }else{    findMap[c]++;    if(findMap[c]<=countMap[c])counter++;//匹配字符    end++;    while(counter==t.length()){//匹配     if(end-begin<minWindow){    head=begin;    minWindow=end-begin;    }    if(countMap[s.charAt(begin)]==0){//不存在于t    begin++;    }else{    findMap[s.charAt(begin)]--;    if(findMap[s.charAt(begin)]<countMap[s.charAt(begin)]) counter--;//消除匹配,保证处于不匹配的状态    begin++;     }    }     }    }    return minWindow==Integer.MAX_VALUE?"":s.substring(head, head+minWindow);          }}


方法论:

I will first give the solution then show you the magic template.

The code of solving this problem is below. It might be the shortest among all solutions provided in Discuss.

string minWindow(string s, string t) {        vector<int> map(128,0);        for(auto c: t) map[c]++;        int counter=t.size(), begin=0, end=0, d=INT_MAX, head=0;        while(end<s.size()){            if(map[s[end++]]-->0) counter--; //in t            while(counter==0){ //valid                if(end-begin<d)  d=end-(head=begin);                if(map[s[begin++]]++==0) counter++;  //make it invalid            }          }        return d==INT_MAX? "":s.substr(head, d);    }

Here comes the template.

For most substring problem, we are given a string and need to find a substring of it which satisfy some restrictions. A general way is to use a hashmap assisted with two pointers. The template is given below.

int findSubstring(string s){        vector<int> map(128,0);        int counter; // check whether the substring is valid        int begin=0, end=0; //two pointers, one point to tail and one  head        int d; //the length of substring        for() { /* initialize the hash map here */ }        while(end<s.size()){            if(map[s[end++]]-- ?){  /* modify counter here */ }            while(/* counter condition */){                  /* update d here if finding minimum*/                //increase begin to make it invalid/valid again                if(map[s[begin++]]++ ?){ /*modify counter here*/ }            }              /* update d here if finding maximum*/        }        return d;  }

One thing needs to be mentioned is that when asked to find maximum substring, we should update maximum after the inner while loop to guarantee that the substring is valid. On the other hand, when asked to find minimum substring, we should update minimum inside the inner while loop.

The code of solving Longest Substring with At Most Two Distinct Characters is below:

int lengthOfLongestSubstringTwoDistinct(string s) {        vector<int> map(128, 0);        int counter=0, begin=0, end=0, d=0;         while(end<s.size()){            if(map[s[end++]]++==0) counter++;            while(counter>2) if(map[s[begin++]]--==1) counter--;            d=max(d, end-begin);        }        return d;    }

The code of solving Longest Substring Without Repeating Characters is below:

int lengthOfLongestSubstring(string s) {        vector<int> map(128,0);        int counter=0, begin=0, end=0, d=0;         while(end<s.size()){            if(map[s[end++]]++>0) counter++;             while(counter>0) if(map[s[begin++]]-->1) counter--;            d=max(d, end-begin); //while valid, update d        }        return d;    }

分析:

我们可以先复制链表的主体部分,我们只需要使用尾插法即可。第二趟我们来复制随机指针,因为原来链表中的的随机指针指向原链表的节点这里不能直接复制,因此需要将指向原链表节点的指针映射到新链表的节点,因此需要利用哈希表建立这种映射关系。

public class Solution {    public RandomListNode copyRandomList(RandomListNode head) {    RandomListNode myHead=new RandomListNode(-1),last=myHead,p=head;    HashMap<RandomListNode,RandomListNode> map=new HashMap<RandomListNode,RandomListNode>();    while(p!=null){//尾插入法    RandomListNode t=new RandomListNode(p.label);     map.put(p, t);//建立映射关系,便于随机指针的复制    last.next=t;    last=t;    p=p.next;    }    p=head;    while(p!=null){//复制随机指针    RandomListNode t=map.get(p);    if(p.random==null){    t.random=null;    }else{    t.random=map.get(p.random);    }    p=p.next;    } return myHead.next;     }}




1 0
原创粉丝点击