LeetCode刷题之哈希表

作者: 奔跑吧李博 | 来源:发表于2020-09-16 09:34 被阅读0次

    哈希表(Hash Table,也叫散列表),是根据键(Key)而直接访问在内存存储位置的数据结构。也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做哈希函数,存放记录的数组称做哈希表。

    哈希表是使用 O(1)O(1) 时间进行数据的插入删除和查找,但是哈希表不保证表中数据的有序性,这样在哈希表中查找最大数据或者最小数据的时间是 O(N)O(N) 实现。

    463. 岛屿的周长

    给定一个包含 0 和 1 的二维网格地图,其中 1 表示陆地 0 表示水域。

    网格中的格子水平和垂直方向相连(对角线方向不相连)。整个网格被水完全包围,但其中恰好有一个岛屿(或者说,一个或多个表示陆地的格子相连组成的岛屿)。

    岛屿中没有“湖”(“湖” 指水域在岛屿内部且不和岛屿周围的水相连)。格子是边长为 1 的正方形。网格为长方形,且宽度和高度均不超过 100 。计算这个岛屿的周长。

    示例 :
    输入:
    [[0,1,0,0],
    [1,1,1,0],
    [0,1,0,0],
    [1,1,0,0]]

    输出: 16

    题解:
    class Solution {
        public int islandPerimeter(int[][] grid) {
            //思路:每个岛屿,如果四周都没有岛,那么它对周长的贡献就是4,
            // 遍历二维数组,对每个岛查询上下左右相邻有没有岛屿,有岛屿对周长贡献-1,所有累加起来就是总周长
    
            int totalEdge = 0;
            for (int i = 0; i < grid.length; i++) {
                int[] row = grid[i];  //当前行数组
                //遍历当前行,值为1的为岛屿进行判断
                for (int j = 0; j < row.length; j++) {
                    if (row[j] == 1) {
                        //当前点为岛屿,位置为 grid[i][j]
    
                        //上
                        if (i != 0) {  //非第一行
                            if (grid[i - 1][j] != 1) {
                                //上一行对应位置没有岛屿,+1
                                totalEdge++;
                            }
    
                        } else {
                            totalEdge++;
                        }
                        //右
                        if (j != row.length-1) {  //非该行最后一个位置
                            //不是最后一个
                            if (row[j+1] != 1) {
                                totalEdge++;
                            }
                        } else {
                            totalEdge++;
                        }
                        //下
                        if (i != grid.length-1) {  //非最后一行
                            if (grid[i + 1][j] != 1) {
                                //下一行对应位置没有岛屿,+1
                                totalEdge++;
                            }
                        } else {
                            totalEdge++;
                        }
                        //左
                        if (j != 0) {  //非该行第一个位置
                            //不是第一个
                            if (row[j-1] != 1) {
                                totalEdge++;
                            }
                        } else {
                            totalEdge++;
                        }
                    }
                }
            }
    
            return totalEdge;
        }
    }
    

    500. 键盘行

    给定一个单词列表,只返回可以使用在键盘同一行的字母打印出来的单词。键盘如下图所示。

    示例:
    输入: ["Hello", "Alaska", "Dad", "Peace"]
    输出: ["Alaska", "Dad"]

    题解:
    class Solution {
        public String[] findWords(String[] words) {
            //思路:创建3个字符串,分别记录3行键盘所包含的字母字符
            //遍历words数组,遍历单词字符,记录每个字符出现的键盘行数,用set存储各个行数值,如果最终只有一个值,那么表明在键盘的同一行
    
            String[] enChar = new String[] {"qwertyuiopQWERTYUIOP", "asdfghjklASDFGHJKL", "zxcvbnmZXCVBNM"};
            ArrayList<String> result = new ArrayList<>();
            for (int i=0;i<words.length;i++) {
                //遍历words
                Set<Integer> record = new HashSet<>();  //用set存字符所在enchar位置,如果全部相等,最终set长度为1,否则大于1
                for (int j=0;j<words[i].length();j++) {
                    //遍历每个单词
                    for (int k=0;k<enChar.length;k++) {
                        //遍历键盘每一行字符串
                        String wordChar = String.valueOf(words[i].charAt(j));
                        if (enChar[k].contains(wordChar)) {
                            record.add(k);
                        }
                    }
                }
                if (record.size() == 1) {
                    result.add(words[i]); //记录该满足条件字符串
                }
    
            }
    
            return result.toArray(new String[result.size()]);
        }
    }
    

    771. 宝石与石头

    给定字符串J 代表石头中宝石的类型,和字符串 S代表你拥有的石头。 S 中每个字符代表了一种你拥有的石头的类型,你想知道你拥有的石头中有多少是宝石。

    J 中的字母不重复,J 和 S中的所有字符都是字母。字母区分大小写,因此"a"和"A"是不同类型的石头。

    示例 1:
    输入: J = "aA", S = "aAAbbbb"
    输出: 3

    示例 2:

    输入: J = "z", S = "ZZ"
    输出: 0

    题解:
    class Solution {
            public int numJewelsInStones(String J, String S) {
            int totalCount = 0;
            //遍历J中的每种宝石,将S中有宝石的各种数量累加起来
            for (int i=0;i<J.length();i++) {
                Character jeuwls = J.charAt(i);
                String restStr = S.replaceAll(String.valueOf(jeuwls), ""); //将珠宝字符替换掉
                int count = S.length() - restStr.length();  //长度变短数为该字符出现在字符串中的次数
                totalCount += count;  //累加每种宝石数量
            }
    
            return totalCount;
        }
    }
    

    349. 两个数组的交集

    给定两个数组,编写一个函数来计算它们的交集。

    示例 1:
    输入:nums1 = [1,2,2,1], nums2 = [2,2]
    输出:[2]

    示例 2:
    输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
    输出:[9,4]

    题解:
    class Solution {
        public int[] intersection(int[] nums1, int[] nums2) {
            HashSet<Integer> jiaoji = new HashSet<>();
            for (int i=0;i<nums1.length;i++) {
                for (int j=0;j<nums2.length;j++) {
                    if (nums1[i] == nums2[j]) {
                        jiaoji.add(nums1[i]);
                        break;
                    }
                }
            }
    
            int[] jiaojiArray = new int[jiaoji.size()];
            int pos = 0;
            Iterator iterator = jiaoji.iterator();
            while (iterator.hasNext()) {
                jiaojiArray[pos] = (int) iterator.next();
                pos++;
            }
    
            return jiaojiArray;
        }
    }
    

    1207. 独一无二的出现次数

    给你一个整数数组 arr,请你帮忙统计数组中每个数的出现次数。

    如果每个数的出现次数都是独一无二的,就返回 true;否则返回 false。

    示例 1:
    输入:arr = [1,2,2,1,1,3]
    输出:true
    解释:在该数组中,1 出现了 3 次,2 出现了 2 次,3 只出现了 1 次。没有两个数的出现次数相同。

    示例 2:
    输入:arr = [1,2]
    输出:false

    示例 3:
    输入:arr = [-3,0,1,-3,1,1,1,-3,10,0]
    输出:true

    题解:
    class Solution {
        public boolean uniqueOccurrences(int[] arr) {
            //思路:存储每一个数字出现的次数,判断这些次数是否都不相等
            Map<Integer, Integer> record = new HashMap<>();
            for (int num : arr) {
                if (!record.containsKey(num)) {
                    record.put(num, 1);
                } else {
                    record.put(num , record.get(num) + 1);
                }
            }
    
            //遍历map,将value值存入set去判断是否有相同值,如果有,则false,否则返回true
            Set<Map.Entry<Integer, Integer>> entries = record.entrySet();
            Set<Integer> set = new HashSet<>();
            for (Map.Entry<Integer, Integer> entry : entries) {
                if (!set.add(entry.getValue())) {
                    //添加失败,说明有相同值
                    return false;
                }
            }
            return true;
        }
    }
    

    1160. 拼写单词

    给你一份『词汇表』(字符串数组) words 和一张『字母表』(字符串) chars。
    假如你可以用 chars 中的『字母』(字符)拼写出 words 中的某个『单词』(字符串),那么我们就认为你掌握了这个单词。

    注意:每次拼写(指拼写词汇表中的一个单词)时,chars 中的每个字母都只能用一次。
    返回词汇表 words 中你掌握的所有单词的 长度之和。

    示例 :
    输入:words = ["cat","bt","hat","tree"], chars = "atach"
    输出:6
    解释:
    可以形成字符串 "cat" 和 "hat",所以答案是 3 + 3 = 6。

    题解:
    class Solution {
        public int countCharacters(String[] words, String chars) {
            int enLength = 26;
            StringBuilder stringBuilder = new StringBuilder();
            //解决方案:将每个字符串维护一张长度为26的int数组,存储a-z26个字母各自出现的次数
            int[] charsArray = new int[enLength];
            for (int i=0;i<chars.length();i++) {
                charsArray[chars.charAt(i) - 'a'] += 1; //chars.char(i)-'a' 表示当前字符在a-z数组的序号,如c-'a'=2为c在字母表中序号
            }
    
            //给words数组中每个字符串创建一张字母表
            a : for (String str : words) {
                int[] strArray = new int[enLength];
                for (int i=0;i<str.length();i++) {
                    strArray[str.charAt(i) - 'a'] += 1;
                }
    
                //核心判断:因为chars 中的每个字母都只能用一次,所有chars中每种字母次数不能比word中的小,不然就重复使用了
                for (int i=0;i<enLength;i++) {
                    if (strArray[i] > charsArray[i]) {
                        continue a;
                    }
                }
    
                //顺利执行到此,表明满足条件,添加单词记录
                stringBuilder.append(str);
            }
    
            return stringBuilder.length();
        }
    
    }
    

    1002. 查找常用字符

    给定仅有小写字母组成的字符串数组 A,返回列表中的每个字符串中都显示的全部字符(包括重复字符)组成的列表。例如,如果一个字符在每个字符串中出现 3 次,但不是 4 次,则需要在最终答案中包含该字符 3 次。

    你可以按任意顺序返回答案。

    示例 1:
    输入:["bella","label","roller"]
    输出:["e","l","l"]

    示例 2:
    输入:["cool","lock","cook"]

    输出:["c","o"]

    题解:
    class Solution {
        public List<String> commonChars(String[] A) {
            //思路:取出数组第一个字符串,遍历该字符串字符,遍历其他字符串,用indexof去找当前字符,如果没找到,则该字符为不常用
            //如果找到了,删除该字符串,找到一轮遍历完成,那么该字符为常用字符
    
            String compareStr = A[0];
            List<String> strArray = new ArrayList<>();
            a : for (int i=0;i<compareStr.length();i++) {
                char ch = compareStr.charAt(i);
                for (int j=1;j<A.length;j++) {
                    int pos = A[j].indexOf(ch);
                    if (pos == -1) {
                        //未找到
                        continue a;
                    } else {
                        //找到需要删去
                        A[j] = strRemoveChar(A[j], pos);
                    }
                }
                //ch字符在每个字符串都找到,属于常用字符串
                strArray.add(String.valueOf(ch));
            }
            return strArray;
        }
    
            /**
         * 删除字符串指定位置的字符
         * @param string
         * @param pos
         * @return
         */
        public String strRemoveChar(String string, int pos) {
            return string.substring(0, pos) + string.substring(pos+1);
        }
    }
    

    811. 子域名访问计数

    一个网站域名,如"discuss.leetcode.com",包含了多个子域名。作为顶级域名,常用的有"com",下一级则有"leetcode.com",最低的一级为"discuss.leetcode.com"。当我们访问域名"discuss.leetcode.com"时,也同时访问了其父域名"leetcode.com"以及顶级域名 "com"。

    给定一个带访问次数和域名的组合,要求分别计算每个域名被访问的次数。其格式为访问次数+空格+地址,例如:"9001 discuss.leetcode.com"。

    接下来会给出一组访问次数和域名组合的列表cpdomains 。要求解析出所有域名的访问次数,输出格式和输入格式相同,不限定先后顺序。

    示例 1:
    输入:
    ["9001 discuss.leetcode.com"]
    输出:
    ["9001 discuss.leetcode.com", "9001 leetcode.com", "9001 com"]
    说明:
    例子中仅包含一个网站域名:"discuss.leetcode.com"。按照前文假设,子域名"leetcode.com"和"com"都会被访问,所以它们都被访问了9001次。

    题解:
    class Solution {
        public List<String> subdomainVisits(String[] cpdomains) {
            //思路:分别计算各种域名访问的次数,从三级域名到二级到一级,分别计算二级和一级访问的总数
            //用Map来存各种域名访问的次数
    
            Map<String, Integer> record = new HashMap<>();
    
            for (String str : cpdomains) {
                record(record, str);
            }
    
            ArrayList<String> datas = new ArrayList<>();
            Set<Map.Entry<String, Integer>> entries = record.entrySet();
            for (Map.Entry<String, Integer> entry : entries) {
                datas.add(entry.getValue() + " " + entry.getKey());
            }
    
            return datas;
        }
    
        private void record(Map<String, Integer> record, String ip) {
            String[] data = ip.split(" ");
            int count = Integer.parseInt(data[0]);  //ip访问次数
            String fullIp = data[1];  //ip全称
    
            //从该ip获得所有个子ip地址
            putValue(record, fullIp, count);
    
            getSubIp(record, fullIp, count);
    
        }
    
        /**
         * 递归获取所有子域名
         * @param record
         * @param fullIp
         * @param count
         */
        private void getSubIp(Map<String, Integer> record, String fullIp, int count) {
            int pos = fullIp.indexOf(".");
            if (pos != -1) {
                //有子域名
                fullIp = fullIp.substring(pos+1);
    
                putValue(record, fullIp, count);
    
                getSubIp(record, fullIp, count);
            }
        }
    
        private void putValue(Map<String, Integer> record, String fullIp, int count) {
            if (!record.containsKey(fullIp)) {
                record.put(fullIp, count);
            } else {
                record.put(fullIp, record.get(fullIp)+count);
            }
        }
    }
    

    575. 分糖果

    给定一个偶数长度的数组,其中不同的数字代表着不同种类的糖果,每一个数字代表一个糖果。你需要把这些糖果平均分给一个弟弟和一个妹妹。返回妹妹可以获得的最大糖果的种类数。

    示例 1:
    输入: candies = [1,1,2,2,3,3]
    输出: 3
    解析: 一共有三种种类的糖果,每一种都有两个。
    最优分配方案:妹妹获得[1,2,3],弟弟也获得[1,2,3]。这样使妹妹获得糖果的种类数最多。

    题解:
    class Solution {
        public int distributeCandies(int[] candies) {
            //思路:遍历candies存入map中,判断map的长度,如果长度<=candies.length/2,则妹妹糖果种类为map长度,如果长度>candies.length/2,则为
            //candies.length/2
    
            Map<Integer, Integer> record = new HashMap<>();
            for (int candy : candies) {
                if (!record.containsKey(candy)) {
                    record.put(candy, 1);
                } else {
                    record.put(candy, record.get(candy) + 1);
                }
            }
    
            if (record.size() <= candies.length/2) {
                return record.size();
            } else {
                return candies.length/2;
            }
        }
    }
    

    884. 两句话中的不常见单词

    给定两个句子 A 和 B 。 (句子是一串由空格分隔的单词。每个单词仅由小写字母组成。)
    如果一个单词在其中一个句子中只出现一次,在另一个句子中却没有出现,那么这个单词就是不常见的。

    返回所有不常用单词的列表。
    您可以按任何顺序返回列表。

    示例 1:
    输入:A = "this apple is sweet", B = "this apple is sour"
    输出:["sweet","sour"]

    题解:
    class Solution {
        public String[] uncommonFromSentences(String A, String B) {
    
            String result = get(A, B) + get(B, A);
            System.out.println(result);
            if (result.length() == 0) {
                return new String[]{};
            } else {
                return result.split(" ");
            }
        }
    
        private String get(String A, String B) {
            StringBuilder stringBuilder = new StringBuilder();
            String[] arrayA = A.split(" ");
            String[] arrayB = B.split(" ");
    
            a : for (int i=0;i<arrayA.length;i++) {
                //如果B中包含字符串,则不是不常见单词
                for (String strB : arrayB) {
                    if (strB.equals(arrayA[i])) {
                        continue a;
                    }
                }
    
                //如果B中不包含,还需要判断arrayA中该字符串是否重复
    
                for (int j=0;j<arrayA.length;j++) {
                    if (i != j) {  //避免自己与自己再匹配
                        if (arrayA[j].equals(arrayA[i])) {
                            continue a;
                        }
                    }
                }
    
                stringBuilder.append(arrayA[i] + " ");
            }
    
            return stringBuilder.toString();
        }
    }
    

    389. 找不同

    给定两个字符串 s 和 t,它们只包含小写字母。
    字符串 t 由字符串 s 随机重排,然后在随机位置添加一个字母。
    请找出在 t 中被添加的字母。

    示例:
    输入:
    s = "abcd"
    t = "abcde"

    输出:
    e

    解释:
    'e' 是那个被添加的字母。

    题解:
    class Solution {
        public char findTheDifference(String s, String t) {
            //思路:将s、t字符串分别将字符存入map中,字符为key,存入次数为value,遍历t的map,如果存在t中key在s中不存在,则该key为被添加的字母
            //如果t中的value与s中对应keyvalue不相等,则该key也为被添加的字母
    
    
            Map<Character, Integer> mapS = record(s);
            Map<Character, Integer> mapT = record(t);
    
            Set<Map.Entry<Character, Integer>> entries = mapT.entrySet();
            for (Map.Entry<Character, Integer> entry : entries) {
                if (!mapS.containsKey(entry.getKey())) {
                    return entry.getKey();
                }
    
                if (entry.getValue() != mapS.get(entry.getKey())) {
                    return entry.getKey();
                }
            }
    
            throw new IllegalArgumentException("不存在该字母");
        }
    
        private Map<Character, Integer> record(String s) {
            Map<Character, Integer> mapS = new HashMap<>();
            for (int i=0;i<s.length();i++) {
                char ch = s.charAt(i);
                if (!mapS.containsKey(ch)) {
                    mapS.put(ch, 1);
                } else {
                    mapS.put(ch, mapS.get(ch)+1);
                }
            }
            return mapS;
        }
    }
    

    相关文章

      网友评论

        本文标题:LeetCode刷题之哈希表

        本文链接:https://www.haomeiwen.com/subject/wdzxyktx.html