问题一:数字序列中某一位的数字
问题描述
数字以0123456789101112131415…的格式序列化到一个字符序列中。
在这个序列中,第5位(从0开始计数)是5,第13位是1,第19位是4,等等。
请写一个函数求任意位对应的数字。
样例
1 | 输入:13 |
问题分析
这里需要通过三步来确定:
- 对于任给的一个数n,首先需要确定n这个数对应的是几位数,比如13对应的肯定是一个两位数,200对应的是一个三位数
- 一位数:共
9
个,每一个长度为1
,总长度是9*1 - 两位数:共
90
个,每一个长度是2
,总长度是90*2
- 三位数:共
900
个,每一个长度是3
,总长度是900*3
- 四位数:共
9000
,每一个长度是4
,总长度是9000*4
- 一位数:共
- 确定好n对应的次序的数是几位数之后,需要确定好是对应的次序的数的数值是多少
- 确定好数值之后就确定对应这个数值的从左到右第几位上
C++代码
1 | class Solution { |
问题二:把数组排成最小的数
问题描述
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。
例如输入数组[3, 32, 321],则打印出这3个数字能排成的最小数字321323。
样例
1 | 输入:[3, 32, 321] |
注意:输出数字的格式为字符串。
问题分析
重新定义一种排序的方法,排序后的数组只要从前往后加起来就可以得到要求的输出结果。
在这里我给出定义在这种运算的定义:$a<b \Leftrightarrow ab <ba$
解释:定义a<b,当且仅当ab<ba
由于ab和ba有可能超过int的表示范围,那么我们如果直接比较的话很可能会溢出,又因为ab和ba的长度是相同的,那么只需要把ab和ba表示成字符串,直接比较字符串就可以。
C++代码
1 | class Solution { |
总结
在上述思路中,由于我们定义了一种新的比较两个数字大小的规则,这种规则是不是有效的?另外,我们只是定义了比较两个数字的规则,却用它来排序一个含有多个数字的数组,最终拼接数组中的所有数字得到的是不是真的就是最小的数字?
我们首先需要证明之前定义的比较两个数字大小的规则是有效的。一个有效的比较规则需要3个条件:自反性、对称性和传递性。假设a,b是属于集合的元素,R是关系,则有:
- 自反性—-即对集合中的每一个元素
a
都有aRa
- 对称性—-即对集合中的任意元素
aRb
,aRb
成立当且仅当bRa
- 传递性—-即对集合中的任意元素
abc
若aRb
和bRc
成立则aRc
一定成立
如果a小于b,则ab<ba。假设a和b用十进制表示时分别有l位和m位,于是$ab=a10^l+b,ba=b10^m+a$
$ab<ba \rightarrow a10^m+b<b10^l+a \rightarrow a10^m-a<b10^l-b\rightarrow a(10^m-1)<b(10^l-1)\rightarrow a/(10^l-1)<b/(10^m-1) $
同理,如果b小于c,则bc<cb。假设c用十进制数表示时有n位,和前面的证明过程一样,可以得到$b/(10^m-1)<c/(10^n-1)$
$a/(10^l-1)<b/(10^m-1)$并且$b/(10^m-1)<c/(10^n-1) \rightarrow a/(10^l-1)<c/(10^n-1)\rightarrow a(10^n-1)<c(10^l-1)\rightarrow a10^n+c<c10^l+a\rightarrow ac<ca \rightarrowca \rightarrow a<b$
于是我们证明了这种比较规则满足自反性、对称性、传递性
问题三:把数字翻译成字符串
问题描述
给定一个数字,我们按照如下规则把它翻译为字符串:
0翻译成”a”,1翻译成”b”,……,11翻译成”l”,……,25翻译成”z”。
一个数字可能有多个翻译。例如12258有5种不同的翻译,它们分别是”bccfi”、”bwfi”、”bczi”、”mcfi”和”mzi”。
请编程实现一个函数用来计算一个数字有多少种不同的翻译方法。
样例
1 | 输入:"12258" |
问题分析
动态规划
f[i]表示第i位字符最多有多少种翻译方法
还记得经典的爬楼梯(斐波那契数列)吗?每次可以走1步或者2步,问n个台阶一共有多少种爬楼梯的方法?
dp[i]=dp[i-1]+dp[i-2]
这道题相当于加了一些限制条件。
C++代码
1 | class Solution { |
动态规划
动态规划就是说一个大问题可以划分为许多的小问题,将许多的小问题的解合并起来就可以得到大问题的解,比如前面的剪绳子那一题。
问题四:礼物的最大价值
问题描述
在一个m×n的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于0)。
你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格直到到达棋盘的右下角。
给定一个棋盘及其上面的礼物,请计算你最多能拿到多少价值的礼物?
注意:
- m,n>0m,n>0
样例:
1 | 输入: |
问题分析
动态规划
1 | class Solution { |
总结
动态规划三要素:
- 状态表示(初始状态要给出或者是默认为0)
- 转态转移
- 边界检查(一般会在行或者列多设一行或者一列)
问题五:最长不含重复字符的子字符串
问题描述
请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。
假设字符串中只包含从’a’到’z’的字符。
样例
1 | 输入:"abcabc" |
问题分析
双指针法
设置两个指针i
、j
,j
从前往后依次扫描,i
表示的是i
到j
最长的不重复子字符串。
在使用双指针的时候需要保证i
和j
都是单调的,也就是j
向后移动的话i
也要向后移动
C++代码
1 | class Solution { |
总结
在使用双指针的时候,一定要保证两个指针都是单调的增加,要不然就不能采用双指针的方法。
这个题目也可以采用动态规划的方法,用f(i)表示以i个字符结尾不包含重复子字符串的最长长度,从左向右扫描
- 若第i个字符在之前没出现过,则 f(i) = f(i-1) + 1;
- 若第i个字符在之前出现过,计算第i个字符距离上次出现之间的距离为d
- 若d <= f(i-1),则说明第i个字符上次出现在f(i-1)对应的不重复字符串之内,那么这时候更新 f(i) = d
- 若d > f(i-1),则无影响,f(i) = f(i-1) + 1
1 | class Solution { |
问题六:丑数
问题描述
我们把只包含因子2、3和5的数称作丑数(Ugly Number)。
例如6、8都是丑数,但14不是,因为它包含因子7。
求第n个丑数的值。
样例
1 | 输入:5 |
注意:习惯上我们把1当做第一个丑数。
问题分析
创建一个数组,里面的每一个数字是已经排序好的丑数,每个丑数是前面的丑数乘以2、3或者5得到的。
这种思路的关键就是怎么样确保里面的数组是排好序的。假设数组中已经有若干个排好序的丑数,并把最大的丑数记为M,接下来分析如何生成下面一个丑数。该丑数一定是前面一个丑数乘以2、3或者5的结果,所以我们首先考虑把已有的丑数每个乘以2.在乘以2的时候,能够得到若干个小于或者等于M的结果。由于是顺序生成的,小于或者等于M的肯定已经在数组中了,我们不需要再次考虑;还会得到若干大于M的结果,但是我们只需要第一个大于M的结果,因为我们希望得到的丑数是按照从小到大的顺序生成的,其它更大的结果以后再说。我们把得到的第一个乘以2后大于M的结果记为$M_2$。同样,我们把已有的每个丑数乘以3和5,能够得到第一个大于M的结果$M_3$和$M_5$。那么下一个丑数应该就是$M_2、M_3、M_5$这三个数中的最小数。
在前面的分析中,对于乘以2而言,一定存在一个丑数$T_2$,排在它前面的每个丑数乘以2得到的结果都会小于已有的最大的丑数,在它之后的每个丑数乘以2得到的结果都会大于已有的丑数,我们只需要记下这个丑数的位置,同时每次在生成新的数组的时候需要去更新$T_2$,同理也可以得到$T_3、T_5$.
C++代码
1 | class Solution { |
问题七:字符串中第一个只出现一次的字符
问题描述
在字符串中找出第一个只出现一次的字符。
如输入"abaccdeff"
,则输出b
。
如果字符串中不存在只出现一次的字符,返回#字符。
样例:
1 | 输入:"abaccdeff" |
问题分析
哈希表
只需要定义一个哈希表,记录每个字符出现的次数
C++代码
1 | class Solution { |
总结
- 定义一个函数,输入两个字符串,从第一个字符串中删除在第二个字符串中出现过的所有的字符。
- 这里我们可以用一个哈希表来存储第二个字符串,这样我们从头到尾扫描第一个字符串的每个字符,用$O(1)$时间就能够判断出该字符是不是在第二个字符中。
- 定义一个函数,删除字符串中所有重复出现的字符
- 创建一个哈希表,用于保存从头到尾扫描到的每一个字符串中的每个字符,如果扫描到该字符时发现哈希表对应的内容为true,那么表示这个字符在之前出现过,直接删除就可以
- 在英语中,如果两个单次出现的字母相同,并且每个字母出现的次数也相同,那么这两个单词就是
变位词
- 创建一个哈希表,用来统计字符串中每个字符出现的次数。当扫描到第一个字符串中每个字符时,为哈希表对应的项的值加1.接下来扫描第二个字符串,当扫描到每个字符的时候,为哈希表对应的项减去1.如果扫描完第二个字符串后,哈希表中所有的值都是0,那么这两个字符串就是变位词。
问题八:字符流中第一个只出现一次的字符
问题描述
请实现一个函数用来找出字符流中第一个只出现一次的字符。
例如,当从字符流中只读出前两个字符”go”时,第一个只出现一次的字符是’g’。
当从该字符流中读出前六个字符”google”时,第一个只出现一次的字符是’l’。
如果当前字符流没有存在出现一次的字符,返回#字符。
样例
1 | 输入:"google" |
问题分析
(哈希表,队列) $O(n)$
维护一个队列使得其中所有的元素都只存在一个,则队列头部则为第一个出现的数字
C++代码
1 | class Solution{ |
问题九:数组中的逆序对
问题描述
在数组中的两个数字如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。
输入一个数组,求出这个数组中的逆序对的总数。
样例
1 | 输入:[1,2,3,4,5,6,0] |
问题分析
归并排序$O(nlogn)$
先把数组分割成子数组,统计出子数组内部的逆序对的数目,然后统计出两个相邻子数组之间的逆序对的数目。在统计逆序对的过程中,还需要对数组进行排序。
C++代码
1 | class Solution { |
总结
设两段有序表$A[low…mid]A[mid+1…high]$存放在同一顺序表中相邻的位置上,先将它们复制到辅助数组B中。每次从对应$B$的两个段中取出一个记录进行关键字的比较,将较小者放入$A$中,当数组$B$中有一段的下标超出其对应表的表长时(即该段的所有元素已经完全复制到A中),将另一段剩余部分直接复制到A中。
1 | ElemType *B=(ElemType *)malloc((n+1)*sizeof(ElemType)); |
递归形式的2-路归并排序算法是基于分治的,其基本过程如下:
- 分解:将含有n个元素的待排序表分成各含n/2个元素的子表,采用2-路归并排序算法对两个子表递归地进行排序
- 合并两个已排好序的子表得到的结果
1 | void MergeSort(ElemType A[],int low,int high){ |
问题十: 两个链表的第一个公共结点
问题描述
输入两个链表,找出它们的第一个公共结点。
当不存在公共节点时,返回空节点。
样例
1 | 给出两个链表如下所示: |
问题分析
双指针法
由样例,设A中不重合的长度为a,B中不重合的长度为b,A和B重合的长度为c(c可能为0,当为0的时候表示没有公共节点)
指针q由a1出发,一直访问到c3,然后从b1出发
指针p由b1出发,一直访问到c3,然后从a1出发
在上面的两个过程中,如果由公共节点的话,那么a+c+b=b+c+a,那么指针q和指针p第一次遇见的点就是公共节点;如果没有公共节点的话,那么a+b=b+a,最终q和p都是为null
C++代码
1 | /** |
问题十一:数字在排序数组中出现的次数
问题描述
统计一个数字在排序数组中出现的次数。
例如输入排序数组[1, 2, 3, 3, 3, 3, 4, 5]和数字3,由于3在这个数组中出现了4次,因此输出4。
样例
1 | 输入:[1, 2, 3, 3, 3, 3, 4, 5] , 3 |
问题分析
二分查找
由于是已经排好序的数组,我们要查找的数是一段折线,中间的就是我们需要查找到的数。
我们只需要找到中间直线的左端点和右端点,那么我们就可以得到该数出现的次数。
那么我们在确定k的左端点时,只需要排除左边的数字,判别条件nums[mid]<k,令l=mid
在确定k的右端点的时候,只需要排除右边的数字
C++代码
1 | class Solution { |
总结
- 0~n-1中缺失的数字:一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内,在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数组
- 如果中间元素的值和下标相等,那么下一轮查找只需要查找右半边;如果中间元素的值和下标不相等,并且它前面一个元素和它的下标相等,这意味着中间的这个数字正好是第一个值和下标不相等的元素;如果中间元素的值和下标不相等,并且它前面一个元素和它的下标也不相等,这意味着中间的这个数字正好是第一个值和下标不相等的元素,这意味着下一轮查找只需要查找左半边。
- 数组中数值和下标相等的元素:单调递增的数组{-3,-1,1,3,5},数字3和它的下标相等
- 直接用二分查找