LeetCode 查找表专题 3:set 和 map 不同底层实现的区别

LeetCode 查找表专题 3:set 和 map 不同底层实现的区别

set 和 map 不同的底层实现,影响了算法的实现效率。C++ 中的 Set 和 Map 的 默认实现就是平衡二叉树,即二分搜索树。

下面分别是针对前两节问题的解答。
(1)第4-1节的问题

(2)第4-2节的问题

unorder_map 和 unorder_set 的经典的底层实现是哈希表。
在 Java 中,默认就是 hash 的实现,hash 表能够实现高效地查找,但是hash 表有一个很严重的问题:缺失了数据的顺序性。
但是二分搜索树,就可以保持数据的顺序性。数据的顺序性能够帮助我们完成:1、数据集中元素的最大值和最小值;

2、某个元素的前驱和后继;
3、某个元素的 floor 和 ceil;
4、某个元素的排位(元素排在第几名);
5、选择某个排位的元素(select,第几名的元素是哪个?)

对于 hash 表来说,优点是查找, hash 表不能解决上面“数据的顺序性”部分说的 5 个问题。

对于上一节的问题:
时间复杂度是:O(nlogn),这里的时间复杂度是如何分析出来的?
空间复杂度是:O(logn)

经典的实现:哈希表,哈希表的一个缺点是失去了数据的顺序性。
例:

对于上一节的问题:
时间复杂度是:O(nlogn),这里的时间复杂度是如何分析出来的?
空间复杂度是:O(logn)

经典的实现:哈希表,哈希表的一个缺点是失去了数据的顺序性。

练习1:LeetCode 第 242 题:有效的字母异位词

传送门:英文网址:242. Valid Anagram ,中文网址:242. 有效的字母异位词

给定两个字符串 st ,编写一个函数来判断 t 是否是 s 的一个字母异位词。

示例 1:

输入: s = "anagram", t = "nagaram"
输出: true

示例 2:

输入: s = "rat", t = "car"
输出: false

说明:
你可以假设字符串只包含小写字母。

进阶:
如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况?

Java 代码1:把两个字符串都转换成字符数组以后,进行排序,然后逐位进行比较。

public class Solution {
    public boolean isAnagram(String s, String t) {
        boolean isAnagram = true;
        if (s.length() != t.length()) {
            isAnagram = false;
        } else {
            char[] sArray = s.toCharArray();
            Arrays.sort(sArray);
            char[] tArray = t.toCharArray();
            Arrays.sort(tArray);
            for (int i = 0; i < sArray.length; i++) {
                if (sArray[i] != tArray[i]) {
                    isAnagram = false;
                    break;
                }
            }
        }
        return isAnagram;
    }
}

Java 代码2:放入一个 Map 中,只要后面有一个元素不出现在 Map 中,就退出,最后应该使得这个 Map 里所有元素的 value 值都为 0。

public class Solution2 {
    public boolean isAnagram(String s, String t) {
        boolean isAnagram = true;
        if (s.length() != t.length()) {
            isAnagram = false;
        } else {
            char[] sArray = s.toCharArray();
            Map<Character, Integer> map1 = new HashMap<>();
            for (char c : sArray) {
                if (map1.containsKey(c)) {
                    map1.put(c, map1.get(c) + 1);
                } else {
                    map1.put(c, 1);
                }

            }

            char[] tArray = t.toCharArray();
            for (char c : tArray) {
                if (map1.containsKey(c) && map1.get(c) >= 1) {
                    map1.put(c, map1.get(c) - 1);
                } else {
                    isAnagram = false;
                    break;
                }
            }

        }
        return isAnagram;
    }
}

Python 代码:

from collections import Counter
class Solution:
    def isAnagram(self, s, t):
        """
        :type s: str
        :type t: str
        :rtype: bool
        """
        c1 = Counter(s)
        c2 = Counter(t)
        if (c1 == c2):
            return True
        return False

练习2:LeetCode 第 202 题:快乐数

传送门:202. 快乐数

编写一个算法来判断一个数是不是“快乐数”。

一个“快乐数”定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是无限循环但始终变不到 1。如果可以变为 1,那么这个数就是快乐数。

示例:

输入: 19
输出: true
解释: 
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1

思路:用散列表判断是否会遇到重复的数,遇到重复的数,就表示会死循环。

Python 代码:

class Solution:
    def isHappy(self, n):
        """
        :type n: int
        :rtype: bool
        """
        if n <= 0:
            return False
        # 得到一个数的每个数位
        s = set()
        while True:
            square_sum = 0
            while n != 0:
                mod = n % 10
                square_sum += mod ** 2
                n = n // 10
            if square_sum == 1:
                return True
            if square_sum in s:
                return False
            else:
                s.add(square_sum)
                n = square_sum


if __name__ == '__main__':
    n = 19
    s = Solution()

    result = s.isHappy(n)
    print(result)

练习3:LeetCode 第 290 题:单词模式

传送门:英文网址:290. Word Pattern ,中文网址:290. 单词模式

给定一种 pattern(模式) 和一个字符串 str ,判断 str 是否遵循相同的模式。

这里的遵循指完全匹配,例如, pattern 里的每个字母和字符串 str 中的每个非空单词之间存在着双向连接的对应模式。

示例1:

输入: pattern = "abba", str = "dog cat cat dog"
输出: true

示例 2:

输入:pattern = "abba", str = "dog cat cat fish"
输出: false

示例 3:

输入: pattern = "aaaa", str = "dog cat cat dog"
输出: false

示例 4:

输入: pattern = "abba", str = "dog dog dog dog"
输出: false

说明:
你可以假设 pattern 只包含小写字母, str 包含了由单个空格分隔的小写字母。

分析:建立双向单映射,即不同的字母不能映射到相同的单词。这里有一个小小的坑,就是当测试用例是: String pattern = "abba";String str = "dog dog dog dog"; 的时候,我们须要判断出结果是 false。

Java 代码:

public class Solution {
    public boolean wordPattern(String pattern, String str) {
        boolean wordPattern = false;
        int patternLength = pattern.length();
        String[] strArray = str.split(" ");
        if (patternLength == strArray.length) {

            Map<Character, String> map1 = new HashMap<>();
            Set<String> uniqueValue = new HashSet<>();
            char[] patternArray = pattern.toCharArray();
            for (int i = 0; i < patternLength; i++) {
                if (map1.containsKey(patternArray[i])) {
                    if (!map1.get(patternArray[i]).equals(strArray[i])) {
                        return wordPattern;
                    }
                } else {
                    if (uniqueValue.contains(strArray[i])) {
                        return wordPattern;
                    }
                    uniqueValue.add(strArray[i]);
                    map1.put(patternArray[i], strArray[i]);
                }
            }
            wordPattern = true;
        }
        return wordPattern;
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        String pattern = "abba";
        String str = "dog dog dog dog";
        boolean wordPattern = solution.wordPattern(pattern, str);
        System.out.println(wordPattern);
    }
}

练习4:LeetCode 第 205 题:同构字符串

传送门:英文网址:205. Isomorphic Strings ,中文网址:205. 同构字符串

给定两个字符串 st,判断它们是否是同构的。

如果 s 中的字符可以被替换得到 t ,那么这两个字符串是同构的。

所有出现的字符都必须用另一个字符替换,同时保留字符的顺序。两个字符不能映射到同一个字符上,但字符可以映射自己本身。

示例 1:

输入: s = "egg", t = "add"
输出: true

示例 2:

输入: s = "foo", t = "bar"
输出: false

示例 3:

输入: s = "paper", t = "title"
输出: true

说明:
你可以假设 st 具有相同的长度。

思路:建立映射关系的时候,要检查是不是两个 key 对应到同一个 value 上了。

  • 使用 Hash 表进行映射关系的建立,和检查 value 是否重复。
  • 对于字符映射的问题而言,Hash 表还可以使用字符数组得到同样的效果。

Java 代码:

public class Solution4 {

    public boolean isIsomorphic(String s, String t) {
        int slen = s.length();
        int tlen = t.length();
        if (slen != tlen) {
            return false;
        }
        Character[] map = new Character[256];
        boolean[] set = new boolean[256];
        for (int i = 0; i < slen; i++) {
            char key = s.charAt(i);
            char value = t.charAt(i);
            if (map[key] == null) {
                // 建立映射关系
                if (set[value]) {
                    return false;
                }
                map[key] = value;
                set[value] = true;
            } else {
                if (map[key] != value) {
                    return false;
                }
            }
        }
        return true;
    }
}

练习5:LeetCode 第 451 题:根据字符出现频率排序

传送门:451. 根据字符出现频率排序

给定一个字符串,请将字符串里的字符按照出现的频率降序排列。

示例 1:

输入:
"tree"

输出:
"eert"

解释:
'e'出现两次,'r'和't'都只出现一次。
因此'e'必须出现在'r'和't'之前。此外,"eetr"也是一个有效的答案。

示例 2:

输入:
"cccaaa"

输出:
"cccaaa"

解释:
'c'和'a'都出现三次。此外,"aaaccc"也是有效的答案。
注意"cacaca"是不正确的,因为相同的字母必须放在一起。

示例 3:

输入:
"Aabb"

输出:
"bbAa"

解释:
此外,"bbaA"也是一个有效的答案,但"Aabb"是不正确的。
注意'A'和'a'被认为是两种不同的字符。

分析:题目的要求是很简单的。其实就是要我们“将字符串里的字符按照出现的频率降序排列”,为此,我们要做一个计数器,然后给 sort 函数一个排序规则就可以了。这是我们最容易想到的。

思路1:指定排序规则

Python 代码:

class Solution:
    def frequencySort(self, s):
        """
        :type s: str
        :rtype: str
        """

        d = dict()
        # 词频统计
        for char in s:
            d[char] = d.setdefault(char, 0) + 1
        # 按照词频降序排序
        sorted_item = sorted(d.items(), key=lambda x: x[1], reverse=True)
        result = ''
        for key, count in sorted_item:
            result += key * count
        return result

思路2:我们又注意到,其实我们每次只关心频次最大的那个数,不妨借助最大堆,即使用优先队列,Python 中的优先队列可以传入 tuple。

Python 代码:

class Solution:
    def frequencySort(self, s):
        """
        :type s: str
        :rtype: str
        """

        l = len(s)
        if l <= 1:
            return s

        d = dict()
        for alpha in s:
            d[alpha] = d.setdefault(alpha, 0) + 1
        # print(d.items())

        import heapq
        h = []
        for alpha, counter in d.items():
            heapq.heappush(h, (-counter, alpha))
        res = ''

        dl = len(d.items())

        for _ in range(dl):
            counter, alpha = heapq.heappop(h)
            res += alpha * (-counter)
        return res

思路3:有很多重复键值的排序任务,可以使用三路快排。

其实,这道问题做到这里感觉已经差不多了。但注意到这道题的特殊性,待排序的字符串的字符是有限的:26 个小写英文字母 + 26 个大写英文字母,再加上一些特殊字符,在字符串很长的时候,就会有大量的重复元素。而有大量重复元素的排序任务恰恰好是三路快速排序擅长处理的问题。我们注意到这一点,就可以手动编写三路快排,当然我觉得如果在面试中,先写上面的写法是肯定没有问题的,也是应该最先想到的。三路快排的写法并没有改变这个问题求解的本质。

Java 代码:

class Solution {
    private Random random = new Random();

    public String frequencySort(String s) {
        char[] chars = s.toCharArray();
        // 记录每个字符出现的频次, 数字下标为字符ASCII码, LeetCode这题的测试用例128已经够用了
        int[] freq = new int[128];
        // 遍历字符数组, 计算每个字符出现的频次, 这一步时间复杂度O(n)
        for (int i = s.length() - 1; i >= 0; i--) {
            freq[chars[i]]++;
        }

        // 对chars数组进行三路快排, 设定频次越高的字符值越大,频次相等时再按照字符ASCII码进行比较
        // 排序这一步时间复杂度为O(nlogn), 所以整个算法的时间复杂度是O(nlogn)
        quicksort3Ways(chars, 0, s.length() - 1, freq);
        // 排序完成之后
        return  new String(chars);
    }

    /**
     * 三路快排, 递归算法, 对chars数组的[l...r]前闭后闭区间进行排序
     * @param chars : 要排序的字符数组
     * @param l : 数组左边界
     * @param r : 数组右边界
     * @param freq : 字符出现频次, 设定字符大小与频次高低一致
     */
    private void quicksort3Ways(char[] chars, int l, int r, int[] freq){
        // 递归终止条件,只有一个元素时默认数组是有序的
        if (r - l < 1) {
            return;
        }

        // 选取标定点, 随机选取, 避免数组本身是有序的导致快排性能下降
        int pivotIndex = random.nextInt(r - l + 1) + l;
        swap(chars, l, pivotIndex);
        char pivot = chars[l];
        // 设定chars[l+1...left] < pivot, chars[left + 1, i) == pivot, chars[right, r] < pivot
        int left = l, right = r + 1, i = l + 1;
        while(i < right){
            // 大于pivot的元素
            if (compare(chars[i], pivot, freq) > 0) {
                swap(chars, left + 1, i);
                left++;
                i++;
            // 小于pivot的元素
            } else if (compare(chars[i], pivot, freq) < 0) {
                swap(chars, right - 1, i);
                // right - 1的元素还没有遍历, 所以这里只需要right--, 并不用i++
                right--;
            } else {
                i++;
            }
            
        }

        // chars[l]==pivot, 所以最后需要将l与left交换位置
        swap(chars, l , left);
        // 递归对小于pivot的元素再进行排序
        quicksort3Ways(chars, l, left, freq);
        // 递归对大于pivot的元素再进行排序
        quicksort3Ways(chars, right, r, freq);
    }

    public int compare(char c1, char c2, int[] freq) {
        if (freq[c1] == freq[c2]){
            return c1 - c2;
        }
        return freq[c1] - freq[c2];
    }

    /**
     * 对数组chars数组中index1,index2位置的两个元素进行交换
     * @param chars
     * @param index1
     * @param index2
     */
    private void swap(char[] chars, int index1, int index2){
        char temp = chars[index1];
        chars[index1] = chars[index2];
        chars[index2] = temp;
    }

    public static void main(String[] args) {
        new Solution().frequencySort("cccaaa");
    }

}

(本节完)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 在C语言中,五种基本数据类型存储空间长度的排列顺序是: A)char B)char=int<=float C)ch...
    夏天再来阅读 3,320评论 0 2
  • 一、Python简介和环境搭建以及pip的安装 4课时实验课主要内容 【Python简介】: Python 是一个...
    _小老虎_阅读 5,718评论 0 10
  • 官网 中文版本 好的网站 Content-type: text/htmlBASH Section: User ...
    不排版阅读 4,362评论 0 5
  • 第5章 引用类型(返回首页) 本章内容 使用对象 创建并操作数组 理解基本的JavaScript类型 使用基本类型...
    大学一百阅读 3,204评论 0 4
  • (原创首发,不得转载) 漫武走书天地间, ...
    财道阅读 492评论 22 37