四十一、数据流中的中位数

题目:如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。

提示:数据是从一个数据流中读出来的,因此数据的数目随着时间的变化而增加,即如果用一个容器来保存从流中读出来的数据,则当有新的数据从流中读出来时,这些数据就插入数据容器。

分析:可以定义该数据容器的数据结构包括数组、链表、二叉搜索树、AVL树及最大堆和最小堆。

四十二、连续子数组的最大和

题目:输入一个整型数组,数组里有正数也有负数。数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。要求时间复杂度为O(n)。

提示:看到这道题,很多人都能想到最直观的方法,即枚举数组的所有子数组并求出它们的和。然而,这并非最优解。

分析:我们可以试着从头到尾逐个累加示例数组中的每个数字。初始化和为0。例如,输入的数组为{1, -2, 3, 10, -4, 7, 2, -5}。第一步加上第一个数字1,此时和为1;第二步加上数字-2,和就变成了-1,我们发现,由于-2是一个负数,因此累加-2之后得到的和比原来的和还要小,因此,我们要把之前得到的和1保存下来,因为它有可能是最大的子数组的和;第三步加上数字3,得到的和是2,比3本身还小,即从第一个数字开始的子数组的和会小于从第三个数字开始的子数组的和,因此我们不用考虑从第一个数字开始的子数组,之前累加的和也被抛弃,我们从第三个数字重新开始累加,此时得到的和是3;第四步加10,得到的和为13;第五步加上-4,和为9,我们发现,由于-4是一个负数,因此累加-4之后得到的和比原来的和还要小。因此,我们要把之前得到的和13保存下来,因为它有可能是最大的子数组的和;第六步加上数字7,得到的和为16,此时的和比之前最大的和13还要大,把最大的子数组的和由13更新为16;第七步加上2,累加得到的和为18,同时更新最大子数组的和;第八步加上最后一个数字-5,由于得到的和为13,小于此前最大的和18。因此,最终最大的子数组的和为18,对应的子数组是{3, 10, -4, 7, 2}。

解法:

int greatestSum_of_subArray(int *pData, int length)
{
if(pData == nullptr || length <= 0) return -1;
int curSum = 0; // 当前累加的子数组和
int greatestSum = 0x80000000; // 最大的子数组和
for(int i = 0; i < length; ++i) {
if(curSum <= 0)
curSum = pData[i];
else
curSum += pData[i];
if(curSum > greatestSum)
greatestSum = curSum;
}
return greatestSum;
}

小结:该方法需要应聘者仔细地分析累加子数组地和的过程,从而找到解题的规律。但如果应聘者熟练掌握了动态规划算法,那么它就能轻松地找到解题方案。

  

四十三、1~n整数中1出现的次数

题目:输入一个整数n,求1~n这n个整数的十进制表示中1出现的次数。例如,输入12,1~12这些整数中包含1的数字有1、10、11和12,1一共出现了5次。

效率不高的解法:

int numOfOne_betweenOneAndN(int n)
{
int num = 0;
for(int i = 1; i <= n; ++i)
num += num_of_one(i);
return num;
} int num_of_one(int n)
{
int num = 0;
while(n) {
if(n % 10 == 1) num++;
n = n / 10;
}
return num;
}

分析:此题考查应聘者做优化的激情和能力,上面这个解法大部分应聘者都能想到。当面试官提示还有更快的方法之后,应聘者千万不要轻易放弃尝试。虽然想到时间复杂度为O(logn)的方法不容易,但应聘者要展示自己追求更快算法的激情,多尝试不同的方法,不能轻易说自己想不出来并且放弃努力。

四十四、数字序列中某一位的数字

题目:数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从0开始计数)是5,第13位是1,第19位是4,等等。请写一个函数,求任意第n位对应的数字。

提示:和上题一样,本题有最直观的方法,但其不是最快的方法,所以需要想出更好的算法。

四十五、把数组排成最小的数

题目:输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的那个。例如,输入数组{3, 32, 321},则打印出这3个数字能排成的最小数字321323。

分析:两个数字m和n能拼接成数字mn和nm。如果mn<nm,则定义m小于n;如果nm<mn,则定义n小于m;如果nm=mn,则定义n等于m。此外,虽然m和n都在int型能表达的范围内,但nm和mn用int型表示就有可能溢出了,所以这还是一个隐形的大数问题。

四十六、把数字翻译成字符串

题目:给定一个数字,我们按照如下规则把它翻译为字符串:0翻译成"a",1翻译成"b",……,11翻译成"l",……,25翻译成"z"。一个数字可能有多个翻译。例如,12258有5种不同的翻译,分别是"bccfi"、"bwfi"、"bczi"、"mcfi"和"mzi"。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。

提示:我们有两种不同的选择来翻译第一位数字1。第一种选择是数字1单独翻译成"b",后面剩下数字2258;第二种选择是1和紧挨着的2一起翻译成“m”,后面剩下数字258。即当最开始的一个或者两个数字被翻译成一个字符之后,我们接着翻译后面剩下的数字。显然,我们可以写一个递归函数来计算翻译的数目。

用递归的思路分析:定义函数f(i)表示从第 i 位数字开始的不同翻译的数目,那么f(i) = f(i+1) + g(i, i+1) × f(i+2)。当第 i 位和第 i+1 位两位数字拼接起来的数字在10~25的范围内时,函数g(i, i+1)的值为1;否则为0。

递归解法:

int get_translation_count(int number)
{
if(number < 0) return 0;
string numStr = to_string(number);
return get_translation(numStr);
} int get_translation(const string &numStr)
{
int length = numStr.length();
if(length == 0) return 0;
if(length == 1) return 1;
if(length == 2) {
if((numStr[0] == '1') || (numStr[0] == '2' && numStr[1] >= '0' && numStr[1] <= '5'))
return 2;
else
return 1;
}
if(length > 2) {
string str1 = numStr.substr(1);
string str2 = numStr.substr(2);
if((numStr[0] == '1') || (numStr[0] == '2' && numStr[1] >= '0' && numStr[1] <= '5'))
return get_translation(str1) + get_translation(str2);
else
return get_translation(str1);
}
}

分析:尽管可以这么写,但由于存在重复的子问题,故递归并不是解决这个问题的最佳方法。递归从最大的问题开始自上而下解决问题,我们也可以从最小的子问题开始自下而上解决问题,这样就可以消除重复的子问题。即我们从数字的末尾开始,然后从右到左翻译并计算不同翻译的数目。

循环解法:

int GetTranslationCount(int number)
{
if(number < 0)
return 0; string numberInString = to_string(number);
return GetTranslationCount(numberInString);
} int GetTranslationCount(const string& number)
{
int length = number.length();
int* counts = new int[length];
int count = 0; for(int i = length - 1; i >= 0; --i)
{
count = 0;
if(i < length - 1)
count = counts[i + 1];
else
count = 1; if(i < length - 1)
{
int digit1 = number[i] - '0';
int digit2 = number[i + 1] - '0';
int converted = digit1 * 10 + digit2;
if(converted >= 10 && converted <= 25)
{
if(i < length - 2)
count += counts[i + 2];
else
count += 1;
}
} counts[i] = count;
} count = counts[0];
delete[] counts; return count;
}

小结:如果应聘者只是把递归分析转换为递归代码,则应聘者不一定能够通过这道题的面试。面试官期待应聘者能够用基于循环的代码来避免不必要的重复计算。  

  

四十七、礼物的最大值

题目:在一个mxn的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格,直到到达棋盘的右下角。给定一个棋盘及其上面的礼物,请计算你最多能拿到多少价值的礼物?

提示:可以用动态规划解决此题。我们可以先用递归的思路来分析问题,而编写基于循环的代码。

用递归的思路分析:定义一个函数f(i, j)表示到达坐标为(i, j)的格子时能拿到的礼物总和的最大值。由题可知,我们有两种可能的路径到达坐标为(i, j)的格子:通过格子(i-1, j)或者(i, j-1)。所以f(i, j) = max(f(i-1, j), f(i, j-1)) + gift[i, j],gift[i, j]表示坐标为(i, j)的格子里礼物的价值。

四十八、最长不含重复字符的子字符串

题目:请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。假设字符串中只包含'a'~'z'的字符。例如,在字符串"arabcacfr"中,最长的不含重复字符的子字符串是"acfr",长度为4。

提示:可以用动态规划解决此题。如果使用蛮力法(时间为O(n3)),就是找出字符串的所有子字符串,然后判断每个子字符串中是否包含重复的字符。

分析:定义函数f(i)表示以第 i 个字符为结尾的不包含重复字符的子字符串的最长长度。我们从左到右逐一扫描字符串中的每个字符。当我们计算以第 i 个字符为结尾的不包含重复字符的子字符串的最长长度f(i)时,我们已经知道f(i-1)了。

四十九、丑数

题目:我们把只包含因子2、3和5的数称作丑数。求按从小到大的顺序的第1500个丑数。例如,6、8都是丑数,但14不是,因为它包含因子7。习惯上我们把1当作第一个丑数。

提示:影响此题效率的是方法的选择,①计算所有的数;②只计算丑数。其中后一种方法需要创建数组保存已经找到的丑数,是一种用空间换时间的解法。

五十、第一个只出现一次的字符

题目一:字符串中第一个只出现一次的字符。在字符串中找出第一个只出现一次的字符。如输入"abaccdeff",则输出'b'。

分析:可以使用一个容器统计每个字符在该字符串中出现的次数,这个容器的作用是把一个字符映射成一个数字。这个容器可以是哈希表,即定义哈希表的键值(key)是字符,而值(value)是该字符出现的次数。

哈希表解法:

char firstNotRepeatChar(char *pStr)
{
if (pStr == nullptr) return '\0';
unsigned int hashTable[256];
memset(hashNum, 0, 256);
char *pCur = pStr;
while(*pCur != '\0') {
hashTable[*pCur]++;
pCur++;
}
pCur = pStr;
while(*pCur != '\0') {
if(hashTable[*pCur] == 1)
return *pCur;
pCur++;
}
return '\0';
}

小结:哈希表是一种比较复杂的数据结构,STL中的map和unordered_map实现了哈希表的功能,我们可以直接拿过来用。由于本题只需要一个非常简单的哈希表就能满足要求,所以我们可以实现一个简单的哈希表。  

题目二:字符流中第一个只出现一次的字符。请实现一个函数,用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是'g';当从该字符流中读出前6个字符"google"时,第一个只出现一次的字符是'l'。

提示:本题仍可用哈希表来完成。字符只能一个接着一个从字符流中读出来。

分析:定义一个哈希表来保存字符在字符流中的位置,哈希表的键值为字符的ASCII码,哈希表的值为字符对应的位置。

解法:

class CharStatistics
{
public:
CharStatistics() : index(0)
{
for(int i = 0; i < 256; ++i)
occurrence[i] = -1;
} void Insert(char ch)
{
if(occurrence[ch] == -1)
occurrence[ch] = index;
else if(occurrence[ch] >= 0)
occurrence[ch] = -2; index++;
} char FirstAppearingOnce()
{
char ch = '\0';
int minIndex = numeric_limits<int>::max();
for(int i = 0; i < 256; ++i)
{
if(occurrence[i] >= 0 && occurrence[i] < minIndex)
{
ch = (char) i;
minIndex = occurrence[i];
}
} return ch;
} private:
// occurrence[i]: A character with ASCII value i;
// occurrence[i] = -1: The character has not found;
// occurrence[i] = -2: The character has been found for mutlple times
// occurrence[i] >= 0: The character has been found only once
int occurrence[256];
int index;
};

  

《剑指Offer》题四十一~题五十的更多相关文章

  1. 《剑指offer》第二十一题(调整数组顺序使奇数位于偶数前面)

    // 面试题21:调整数组顺序使奇数位于偶数前面 // 题目:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有 // 奇数位于数组的前半部分,所有偶数位于数组的后半部分. #inclu ...

  2. 《剑指offer》第十一题(旋转数组的最小数字)

    // 面试题:旋转数组的最小数字 // 题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转. // 输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素.例如数组 // {3, ...

  3. 剑指offer第四章

    剑指offer第四章 1.二叉树的镜像 二叉树的镜像:输入一个二叉树,输出它的镜像 分析:求树的镜像过程其实就是在遍历树的同时,交换非叶结点的左右子结点. 求镜像的过程:先前序遍历这棵树的每个结点,如 ...

  4. 剑指Offer(二十一):栈的压入、弹出序列

    剑指Offer(二十一):栈的压入.弹出序列 搜索微信公众号:'AI-ming3526'或者'计算机视觉这件小事' 获取更多算法.机器学习干货 csdn:https://blog.csdn.net/b ...

  5. 剑指Offer(三十一):整数中1出现的次数(从1到n整数中1出现的次数)

    剑指Offer(三十一):整数中1出现的次数(从1到n整数中1出现的次数) 搜索微信公众号:'AI-ming3526'或者'计算机视觉这件小事' 获取更多算法.机器学习干货 csdn:https:// ...

  6. 【剑指Offer】简单部分每日五题 - Day 1

    今天开始更新leetcode上<剑指Offer>的题解,先从简单难度开始.预计按下列顺序更新: 简单难度:每日5题 中等难度:每日3题 困难难度:每日1题 17 - 打印从1到最大的n位数 ...

  7. [持久更新] 剑指offer题目Python做题记录

    第一题 题目:在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数. 思路:先快速定位到 ...

  8. JS 剑指Offer(四) 从尾到头打印链表

    题目:输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回). 首先定义一下链表中的节点,关于链表这个数据结构在另外一篇文章中会详细讲 function ListNode(val) { t ...

  9. 【剑指Offer】俯视50题之21 - 30题

    面试题21包括min函数的栈  面试题22栈的压入.弹出序列  面试题23从上往下打印二叉树  面试题24二叉搜索树的后序遍历序列  面试题25二叉树中和为某一值的路径  面试题26复杂链表的复制  ...

随机推荐

  1. 『ACM C++』 PTA 天梯赛练习集L1 | 034-035

    在一个团队里,一群人一起为一件事情努力奋斗的过程,真的很值得享受,真希望我能拥有很多这样的团队. ------------------------------------------------L1- ...

  2. centos7中nginx的搭建

    ./nginx 启动服务./nginx -s stop 关闭服务./nginx -s reload 重新加载配置文件 首先我们应当安装一个依赖的软件包: yum install gcc-c++yum ...

  3. 使用EF Core的CodeFirt 出现的问题The specified framework version '2.1' could not be parsed

    今天使用了一下EF Core的Code First,进行数据库迁移的的时候报错了: The specified framework version '2.1' could not be parsed ...

  4. mongodb rebo 3T 执行出错 failed to execute script 但是执行成功 171条

    我现在也不清楚到底是什么原因 解决方法: 把你要执行的脚本保存到文件 在最上面添加下面两行代码:根据你的数据库 信息填写 conn = new Mongo('host:port'); db = con ...

  5. 泛型List集合转化为DateTable

    using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; name ...

  6. Java实例 Part1:Java基础输出语句

    ** Part1:Java基础输出语句 ** 第一部分最基础,就是标准的输出语句. ps:(目前还没熟悉这个编辑器,先尝试一下) Example01 : 输出"hello world&quo ...

  7. Altium Designer常用快捷键

    一:Altium原理图快捷键:    Shift+左键选择    :实现多个目标选择    Ctrl+左键拖动    :保持连线拖动目标        Shift+c       :清除当前过滤(?? ...

  8. python学习笔记:第4天 列表和元组

    目录 基本数据类型:列表 基本数据类型:元组 补充知识 基本数据类型:列表 1. 列表的介绍 列表也是python的基础的数据类型之一,类似于Java中的数组一样,可以存放很多元素.列表是用括号括起来 ...

  9. Django学习之mysql增删改查

    上节介绍了如何使用命令行操作mysql增删改查,现在介绍如何使用python管理mysql 使用pip 下载完mysql后,mysql会以pymysql模块的形式存储在pycharm的包文件里.我们通 ...

  10. C语言变量的初始化

    关于C语言变量是否需要初始化的问题.以前西北工业大学的C语言老师说的是,需要初始化,如果不初始化就使用的话,变量的值是以前遗留在内存中的,是不确定的(这只是针对局部变量的).C语言全局变量如果没有初始 ...