[剑指Offer] 第5章课后题详解

[剑指Offer] 第5章课后题详解

目录

  • 剑指Offer 第5章课后题详解
    • 目录
    • 删除指定字符
      • 分析
      • 解法
      • 优化
    • 删除重复元素
      • 分析
      • 解法
    • 判断变位词
      • 分析
      • 解法
    • 求助

删除指定字符

本题为《剑指Offer》“面试题35:第一个只出现一次的字符”一节中的“相关题目”。

定义一个函数,输入两个字符串,从第一个字符串中删除在第二个字符串中出现过的所有字符。

分析

字符是一个长度为8的数据类型,共256种可能。创建一个长度为256的bool型数组,数组下标为字符对应的ASCII码值,分别记录字符是否在第二个字符串中出现过。由于直接调用erase函数删除元素效率较低,且在遍历时改变容器可能会造成严重错误,所以创建一个临时字符串将不会被删除的字符保存下来,再赋值给第一个字符串。

解法

auto DeleteAppearanceChar(string &s1, const string &s2) -> void{
    //如果s1或s2为空,则直接返回
    if(!s1.size() || !s2.size()){
        return;
    }

    //创建临时字符串
    string tempString;
    //创建bool数组
    const int tableSize = 256;
    bool hashTable[tableSize];

    //将所有bool值初始化为false
    for(unsigned int i = 0; i < tableSize; ++i){
        hashTable[i] = false;
    }

    //遍历s2,如果字符出现,则将数组对应位置标志为true
    auto begin = s2.begin();
    while(begin != s2.end()){
        hashTable[*(begin ++)] = true;
    }

    //遍历s1,如果字符没在s2中出现过就加入到tempString中
    begin = s1.begin();
    while(begin != s1.end()){
        if(!hashTable[*begin]){
            tempString.push_back(*begin);
        }
        begin++;
    }

    //用临时字符串的值替换s1原有的内容
    s1.assign(tempString);
}

优化

使用临时字符串会增加空间开销,可能使空间复杂度上升O(n),删除s1中字符的时候可以使用两个一前一后的迭代器,前面的迭代器寻找后面第一个没在s2中出现的字符,然后复制到前一个迭代器所指的位置,再将两个迭代器依次后移一步,如此循环直到后面的迭代器抵达s1末尾。最后再将两个迭代器之间的元素删除,这样删除操作就全在字符串尾部进行了,时间复杂度依旧为O(n)。删除s1中字符的代码可以用下面的代码替换。

    //使用一前一后两个迭代器
    begin = s1.begin();
    auto behind = s1.begin();
    //任意迭代器抵达字符串尾部后停止
    while(begin != s1.end() && behind != s1.end()){
        //如果字符没在s2中出现,则进行复制操作
        if(!hashTable[*begin]){
            *(behind++) = *(begin++);
            continue;
        }
        //迭代器begin向后直到寻找到下一个没在s2中出现的字符或者抵达字符串尾部为止
        while(begin++ != s1.end() && hashTable[*begin]);
        if(begin == s1.end()){
            break;
        }
        //找到之后进行复制操作,此行代码可省略
        *(behind++) = *(begin++);
    }
    //删除s1末尾的字符
    s1.erase(behind, begin);

删除重复元素

本题为《剑指Offer》“面试题35:第一个只出现一次的字符”一节中的“相关题目”。

定义一个函数,删除字符串中所有重复出现的字符。

分析

本题与上题大体类似,遍历一个字符串,如果遇到没出现过的字符便存入临时字符串中,并将对应的标志位设为true(表示出现过),遇到出现过的则直接跳过。

解法

auto DeleteRepeatChar(string &s) -> void{
    if(!s.size()){
        return;
    }

    string tempString;
    const int tableSize = 256;
    bool hashTable[tableSize];

    for(unsigned int i = 0; i < tableSize; ++i){
        hashTable[i] = false;
    }

    auto begin = s.begin();
    while(begin != s.end()){
        if(!hashTable[*begin]){
            tempString.push_back(*begin);
            //遇到没出现过的字符,将数组对应位置标志为true
            hashTable[*(begin)] = true;
        }
        begin++;
    }

    s.assign(tempString);
}

判断变位词

本题为《剑指Offer》“面试题35:第一个只出现一次的字符”一节中的“相关题目”。

在英语中,如果两个单词中出现的字母相同,并且每个字母出现的次数也相同,那么这两个单词互为变位词。

分析

与前面两题类似,用一个int型数组来统计其中一个单词中所有字符出现的次数,再遍历另一个单词,每遇到一个字符,便将数组对应位置的值减1,如果最后数组全部元素都未0,则说明是变位词,需要注意的是无效输入的处理。

解法

//定义一个全局变量监控输入错误
bool invaildInput = false;

auto IsAnagram(const string &s1, const string &s2) -> bool{

    //如果至少有一个单词为空,则报输入错误,并返回false。这里认定两个单词都为空也返回false(因为空字符串不是单词),这些边界条件可以跟面试官沟通如何判定,同时可以沟通的是完全相同的单词是否算做换位词
    if(!s1.size() || !s2.size()){
        invaildInput = true;
        return false;
    }

    //检查是否有非字母以外的非法输入,如果有则报输入错误,并返回false
    auto begin = s2.begin();
    while(begin != s2.end()){
        if (!((*begin >= 'a' && *begin <= 'z') || (*begin >= 'A' && *begin <= 'Z'))){
            invaildInput = true;
            return false;
        }
        begin++;
    }
    begin = s1.begin();
    while(begin != s1.end()){
        if (!((*begin >= 'a' && *begin <= 'z') || (*begin >= 'A' && *begin <= 'Z'))){
            invaildInput = true;
            return false;
        }
        begin++;
    }

    const int tableSize = 256;
    int hashTable[tableSize];

    for(unsigned int i = 0; i < tableSize; ++i){
        hashTable[i] = 0;
    }

    begin = s2.begin();
    while(begin != s2.end()){
        hashTable[*(begin ++)] ++;
    }

    begin = s1.begin();
    while(begin != s1.end()){
        //将遇到的每个字符在数组对应位置的值-1,如果出现负值则直接返回false
        if(--hashTable[*begin]< 0 ){
            return false;
        }
        begin++;
    }

    //遍历数字,检查是否有不为0的值
    for(unsigned int i = 0; i < tableSize; ++i){
        if(hashTable[i] != 0){
            return false;
        }
    }

    return true;
}

求助

《剑指Offer》“面试题35:第一个只出现一次的字符”一节中的“本题拓展”

在字符串中找出第一个只出现一次的字符,考虑汉字的情况。

关于本题,个人考虑了两个思路:
第一:照瓢画葫芦,与原题采用完全一样的解法,只是将ASCII码换为中文Unicode编码,由于汉字的Unicode编码位数较多,范围为0x3000到0x9FFF,虽然也可以将空间效率解释为O(1),但这个常量很大,至少需要用2的15次方级的空间,空间开销太大,感觉不太合理。
第二:使用哈希表,这个方法用java可行,但是C++标准库中没有哈希表,自定义哈希表的代码量较大,不适合在面试时书写。

所以恳请热心的高手能够解答我的疑惑,不胜感激!

时间: 2024-11-02 03:26:13

[剑指Offer] 第5章课后题详解的相关文章

[剑指Offer] 第4章课后题详解

[剑指Offer] 第4章课后题详解 目录 剑指Offer 第4章课后题详解 目录 二叉树的镜像 分析 解法 拓展 判断前序遍历 分析 解法 拓展 字符的组合 分析 解答 正方体的顶点 分析 解法 8个皇后 分析 解法 二叉树的镜像 本题为<剑指Offer>"面试题19:二叉树的镜像"一节中的"本题拓展". 请完成一个函数,输入一个二叉树,该函数输出它的镜像,要求使用循环的方法,不能用递归.二叉树节点定义如下: struct BinaryTreeNode

[剑指Offer] 第3章课后题详解

[剑指Offer] 第3章课后题详解 目录 剑指Offer 第3章课后题详解 目录 大数加法 分析 解法 优化 链表的中间节点 分析 解法 环形链表 分析 解法 反转链表 分析 解法 大数加法 本题为<剑指Offer>"面试题12:打印1到最大的n位数"一节中的"相关题目". 定义一个函数,在该函数中可以实现任意两个整数的加法. 分析 由于没有限定输入两个数的大小范围,所以需要把它当做大数问题来处理.大数无法用int,long甚至long long类型来

[剑指Offer] 第2章课后题详解

[剑指Offer] 第2章课后题详解 目录 剑指Offer 第2章课后题详解 目录 有序数组的插入 分析 正常解法 非主流解法 两个队列实现栈 分析 解法 2的整数次方 分析 解法 不同位数 分析 解法 有序数组的插入 本题为<剑指Offer>"面试题4:替换空格"一节中的"相关题目". 有两个排序的数组A1和A2,内存在A1的末尾有足够多的空余空间容纳A2.请实现一个函数,把A2中的所有数字插入到A1中并且所有数字是排序的. 分析 其实这道题就是实现一

剑指offer:顺时针打印矩阵

剑指offer上的第20题,九度OJ上测试通过. 题目描述: 输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10. 输入: 输入可能包含多个测试样例,对于每个测试案例, 输入的第一行包括两个整数m和n(1<=m,n<=1000):表示矩阵的维数为m行n列. 返回栏目页:http://www

剑指offer:包含min函数的栈

剑指offer上的第21题,之前在Cracking the Coding interview上做过,思路参考这里,这次写了测试函数,在九度OJ上测试通过. 题目描述: 定义栈的数据结构,请在该类型中实现一个能够得到栈最小元素的min函数. 输入: 输入可能包含多个测试样例,输入以EOF结束. 对于每个测试案例,输入的第一行为一个整数n(1<=n<=1000000), n代表将要输入的操作的步骤数. 接下来有n行,每行开始有一个字母Ci. Ci='s'时,接下有一个数字k,代表将k压入栈. Ci

剑指offer:栈的压入弹出序列

剑指offer上的第22题,九度OJ上AC. 题目描述: 输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序.假设压入栈的所有数字均不相等.例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列. 输入: 每个测试案例包括3行: 第一行为1个整数n(1<=n<=100000),表示序列的长度. 第二行包含n个整数,表示栈的压入顺序. 第三行包含n个整数,表示栈的弹出顺序

剑指Offer详解之左旋转字符串

(1)暴力移位法 这种方法可能是最直观,最容易想出的方法.但这也是最坏的方法.时间复杂度挺高,用这种方法容易超时. 这种方法是一位一位的移动实现左旋转操作. [代码] #include <stdio.h> #include <malloc.h> #include <string.h> char *str; char* Reverse(char* str,int n){ if(str == NULL || n < 0){ return ""; }

剑指offer系列之二十六:输出字符串的全排列

题目描述 输入一个字符串,按字典序打印出该字符串中字符的所有排列.例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba. 结果请按字母顺序输出. 输入描述: 输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母. 此题与剑指offer原题存在一些初入,在原题中并没有规定字符串中字符是否有重复的出现.不过思路是一致的,只不过是添加额外的判断罢了.下面说说我的思路吧:先不考虑是否出现重读字符,要对一个字符进行全排列,可以

[剑指Offer]7.从尾到头打印链表

题目1511:从尾到头打印链表 时间限制:1 秒 内存限制:128 兆 特殊判题:否 提交:1082 解决:350 题目描述: 输入一个链表,从尾到头打印链表每个节点的值. 输入: 每个输入文件仅包含一组测试样例. 每一组测试案例包含多行,每行一个大于0的整数,代表一个链表的节点.第一行是链表第一个节点的值,依次类推.当输入到-1时代表链表输入完毕.-1本身不属于链表. 输出: 对应每个测试案例,以从尾到头的顺序输出链表每个节点的值,每个值占一行. 样例输入: 1 2 3 4 5 -1 样例输出