哈希表和字符串

哈希表和字符串经典算法

算法准备

数据结构复习

HashTable
  1. 线程安全,操作函数使用synchronized关键字修饰,保证线程安全;
  2. 初始默认容量 11,拓展因子 0.75;最大值,为Integer.MAX_VALUE - 8;
  3. 扩容为oldsize<<1 +1;
  4. 根据key获取index的方式是:(hash&0x7FFFFFFF)%tab.length;
  5. put(key,value),value不允许为空,key也不允许(key.hashcode会抛出异常);
为什么key获取index的方法是(hash&0x7FFFFFFF)%tab.length?
因为这样可以使负数也变成正数,使其在数组的范围之内。
0x7FFFFFFF = 0111 ....1111 除了符号位都是1
与上一个负数,那么符号位肯定是0,就变成正数了。
HashMap
  1. 线程不安全
  2. 默认容量 16,容量一定是2的幂数(采用对原本容量减一,在与上其无符号右移1, 2, 4 ,8,16向与,在加一得策略);最大容量1<<30;默认拓展因子,0.75;
  3. 相对于HashTable,增加树化机制,防止链表过长。所以设计树节点,树节点是普通节点大小的两倍;链表变成树的阈值为8,树退化为链表的阈值为6;这里重点关注一个值MIN_TREEIFY_CAPACITY,它的意义是防止map扩容和map链表转树节点冲突。只有表中tab的length长度》=它时,才允许链表转树,否则全部采用扩容。
  4. 求key得hash(),采用key.hashCode() ^ key.hasCode() >>> 16, 使keyhash得高16与低16异或,更加均匀,不容易碰撞。
  5. 根据key获取tab index得方式是: tab.length -1 & hash(key),就是4中的计算方式,既保证了得到得值在tab数组得length范围内,又保证了hash均匀,使节点尽可能减少碰撞。
  6. put(key,value)都允许null值。

算法实战

LeetCode 409.最长回文串

思路:

  1. 首先理解最长回文的意思,就是左边和右边看一样的。
  2. 重要点是能够想象到如果一个字符是偶数个它一定是回文串,如果一个字符是奇数个那么它个数减一一定是回文,且最后的结果一定只包括一个奇数个数的字符。
  3. 那么思路就是,统计每个字符的个数,偶数字符直接计数,奇数字符减一计数。最后单独加上是否存在奇数或者偶数的flag;

代码

 public int longestPalindrome(String s) {
     //128位计数数组,包括了a-zA-Z
        int []countA = new int[128];
     //是否存在奇数字符
        int flag = 0;
     //结果
        int res = 0;
     //计数
        for(int i =0; i<s.length();i++){
            countA[s.charAt(i)]++;
        }
     //统计
        for(int i = 0; i<countA.length;i++){
            int val = countA[i];
            if(isO(val)){
                res+=val;
            }else{
                res+=val-1;
                flag =1;
            }
        }
        return res+flag;
    }

    public boolean isO(int val){
        return val%2==0?true:false;
    }

LeetCode 290. 单词规律

思路:

  1. 既然是匹配那么就是有一定的一一对应规律,pattern的字符对应s中的一个单词。
  2. 首先,如果s.split()之后数组的长度如果与pattern的长度不想等,那么直接返回true。
  3. 既然要记录一一对应,我们就可以利用hashMap来记录,这个关系。还需要一个数据结构,记录pattern字符是否出现,因为如果这个word在hashMap中没出现,但是在数据结构中已经出现,直接返回false;
  4. 如果s中的一个单词,不存在map中,并且对应的pattern未使用,则放入map。如果s在map中,比较map中的pattern 和对应的pattern是否相等,相等继续循环,不想等return false;

代码:

 public boolean wordPattern(String pattern, String s) {
        String[] sA = s.split(" ");
        int np = pattern.length();
        int ns = sA.length;
        if(np!=ns) return false;
        //记录pattern和s的关系
        Map<String,Character> valM = new HashMap<String,Character>();
        //记录patter是否用过
        int[] used = new int[128];
        for(int i = 0; i<np;i++){
            String word = sA[i];
            Character ps = pattern.charAt(i);
            if(valM.get(word)==null){
                if(used[ps]!=0){
                    return false;
                }else{
                    used[ps]++;
                }
                valM.put(word,ps);
            }else{
                if(!valM.get(word).equals(ps)) return false;
            }
        }
        return true;
    }

LeetCode 49 字母异位词分组

思路:

  1. 其实就是统计相同字母组合的单词,也就是将打乱顺序的字母组合排序,那么就可以得到相同的组合。
  2. 利用hashMap,key是单词排序后的string. value是排序之前的string的集合list.
  3. 遍历字符串数组,排序,判断是否在map中,在:取出list, 将原字符串添加进去,不在,创建list添加

代码:

 public List<List<String>> groupAnagrams(String[] strs) {
        Map<String,List<String>> val = new HashMap<String,List<String>>();
        List<List<String>> res = new ArrayList<List<String>>();
        for(String str:strs){
            char[] temp = str.toCharArray();
            Arrays.sort(temp);
            String sortS = new String(temp);
            if(val.containsKey(sortS)){
                List<String> list = val.get(sortS);
                list.add(str);
            }else{
                List<String> list2 = new ArrayList<String>();
                list2.add(str);
                val.put(sortS,list2);
            }
        }
        for(List list:val.values()){
            res.add(list);
        }
        return res;
    }

LettCode 3 无重复字符的最长子串

思路:

  1. 既然要找不重复的最长字串,这里面我们可以提取出什么信息呢?

    涉及到了重复,那么我们肯定需要一个记录字符是否出现的数据结构;最长字串那么一定是连续的,连续的就是需要顺序的遍历整个字符串,不能跳跃。

  2. 最容易想到的办法:肯定是找到所有子字符串,然后比较找到最长的。但是复杂度为n方,并且有很多多余的字串

  3. 其实这道题是非常经典的一道题,它的解决方案称之为滑动窗口。什么是滑动窗口?窗口就是代表一段视野,映射到java就是一段距离,两个点表示一段距离,也就是两个指针。我们根据不同的情况,来改变两个指针那么广义上看就是这个窗口一直在滑动。

代码:

public int lengthOfLongestSubstring(String s) {
    	//记录字符是否出现
        int used[] = new int[128];
        // 第一个指针
        int begin = 0;
    	//第二个指针
        int end = 0;
    	//最大长度
        int res = 0;
    	//遍历第二个指针,知道它到字符串末尾
        while(end < s.length()){
            //如果之前遍历没遇到该字符
            if(used[s.charAt(end)]==0){
                //记录该字符出现了
                used[s.charAt(end)]++;
                //第二个指针右移一位
                end++;
            }else
            //如果出现了重复字符
            {	//记录最长字符长度,因为只有这个时候才能达到最大长度
                res = Math.max(res,end -begin);
                //移动第一个指针,因为我们要找到是哪个字符重复了
              	//第一个指针,直到相同的字符停下
                while(s.charAt(begin)!=s.charAt(end) && begin < end){
                    //指针没忘前移动一个,说明我们窗口不包含这个字符了
                    //那就需要把这个用过的标记清除
                    used[s.charAt(begin)]--;
                    //第一个指针往右移动
                    begin++;
                }
                //注意这里的操作,这里这两个指针都指到了同一个字符
                //我们需要把这个窗口整体往后面移动一位,那么就保持了只含有唯一字符
                // abcbd---- abcbd-----》abcbd
                			  |	 |         | |
                begin++;
                end++;
            }
        }
        res = Math.max(res,end -begin);
        return res;
    }

LeetCode 187 重复的DNA序列

思路:

  1. 遍历目标对象,从头到尾记录长度为10的字串
  2. 通过hashMap判断字串是不是已经遍历过了:如果未遍历,放入map中。如果遍历了,value +1
  3. 遍历目标对象之后,遍历map,取出value >1 的key,加入结果集合
 public List<String> findRepeatedDnaSequences(String s) {
     //记录字串是否出现的次数
        Map<String,Integer> entries = new HashMap<String,Integer>();
     //结果集合
        List<String> res = new ArrayList<String>();
     //遍历目标字符串
        for(int i =0; i<s.length(); i++){
            //遍历到最后一个长度为10的字串为止
            if((10+i) > s.length()) break;
            //获取长度为10的字串
            String val = s.substring(i,10+i);
            //检查是否出现过
            if(entries.get(val)== null){
                entries.put(val,1);
            }else{
                //出现,记录出现的次数
                Integer time = entries.get(val);
                entries.put(val,time+1);
            }
        }
     //遍历map,生成结果集
        for(Map.Entry entry:entries.entrySet()){
            if((Integer)entry.getValue() > 1){
                res.add((String)entry.getKey());
            }
        }
        return res;
    }

LeetCode 76 最小覆盖子串

思路:

  1. 滑动窗口经典算法,实现方式:双指针。主要关注点,左右指针的边界、移动规则。
  2. 先移动右指针,边界(不能大于遍历字符串的总长度);当遍历的字串覆盖了目标字串时候,先记录这个时候字串的结果,因为这里肯定满足条件(比较现在的结果,和right-left+1的长度)。然后,这时候移动左指针,因为我们想让找到的字串最小,那么既然找到了一个字串,那么我们就应该缩小字串。左指针边界,小于right并且小于length,并且要保证遍历的字串长度小于目标字串(这个是保证我们移动右指针的触发条件)。
  3. 左指针向右移动只到窗口不包括目标字串,继续重复2.直到右边界越界

代码:

public String minWindow(String s, String t) {
        int[] tVal = new int[256];
        int[] sVal = new int[256];
        String res = "";
        //记录t中出现的字符
        for(int i = 0; i<t.length();i++){
            tVal[t.charAt(i)]++;
        }
        int left = findNextNum(0,s,tVal);
        if(left == s.length()) return "";
        int right= left;
        //记录是否已经遍历了一个字串
        int count = 0;
        while(right < s.length()){
            char temp = s.charAt(right);
            if(sVal[temp] < tVal[temp]){
                count ++;
            }
            sVal[temp]++;
            //找到了一个字串,记录长度,并且尝试移动左指针缩小串口
            while(left < s.length() && count == t.length()){
                if(res == "" || res.length() > right-left +1){
                    res = s.substring(left,right+1);
                }
                char LeftChar = s.charAt(left);
                //判断是不是count是否需要减少,如果s中的数目大于t中的数目,说明不需要,因为这时候即使少了一个字符,还是满足字串,如果是小于等于,说明我们要跳出left指针的移动,要遍历right去找新的字符来补充字串
                if(sVal[LeftChar] <= tVal[LeftChar]) count--;
                sVal[LeftChar]--;
                left = findNextNum(left+1,s,tVal);
            }
            right = findNextNum(right+1,s,tVal);
        }
        return res;
    }
//辅助函数,找到下一个目标字符
    public int findNextNum(int start, String s, int Vt[]){
        while(start < s.length()){
            char v = s.charAt(start);
            if(Vt[v] != 0) return start;
            start++;
        }
        return start;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值