剑指offer做题笔记
1.数据结构与算法
- 对数组名调用sizeof返回数组整体内存长度,对指针调用sizeof返回4(32位系统中)
- 多维数组a[][3]的数组名是多维的,而a[0]则指其第一个元素数组,是单位数组,在作为实参传递时相当于传递数组名,会自动转化为指向首元素的指针array,通过该指针不止可以访问单维数组,通过array[row*cols+col]可以访问多维数组
- 当几个指针赋值给相同的常量字符串时,它们实际上会指向相同的内存地址,char *a="hello world",而通过常量字符串初始化数组char a[]="hello world"时则会另外分配地址,因此相同常量字符串初始化的多个数组名代表的指针值不同。
- 编写向链表添加元素的函数时,要传入指向头元素的指针的指针,因为链表为空情况下,若传入指向头元素的指针null,则无法修改函数外的指针值,当链表头结点指针值有可能在函数执行完后改变的,都要将形参定义为头结点指针的指针,以将头结点的变化传递到函数外
- 回溯法:树状结构解决方案,回溯。从解决问题每一步的所有可能选项里系统地选择出一个可行的解决方案,通常采用递归函数实现,在递归函数中负责判断本步骤是否满足要求条件,如果满足就继续尝试下一步的所有选项,若下一步也满足条件则返回正确答案,否则要将变量还原到本步骤选择之前。
- 回溯法适用于解决迷宫及类似问题,通过递归遍历并在碰壁时回溯
- 如果问题是求一个问题的最优解,那么可以尝试使用动态规划,从上往下分析问题,从下往上求解问题,假如在使用动态规划分析问题时发现每一步都存在一个能得到最优解的选择,那么可以尝试使用贪婪算法
- 动态规划详解:http://blog.csdn.net/baidu_28312631/article/details/47418773
- pow函数接受底数和指数计算幂,返回值有double ,float,long double三种,需要手动转化为int
- 位运算时,右移时若原数为正则加0,为负则加1,负数以补码形式表示(取反再加1,符号位不变),n&(n-1)可将其最右一位1置零,其余位不变
- 最小的负数的二进制表示为0x80000000,补码与源码是一样的
double getMachineEpsilon() //超机智方法得到某个类型的精度 { double epsilon = 1.0; while (1 + epsilon>1) epsilon /= 2.0; return 2 * epsilon; }
2.高质量的代码
- 28:判断二叉树是否对称
可以定义一种对称前序遍历,分别用前序遍历和对称前序遍历二叉树,若遍历结果一致,则二叉树对称。
遍历过程要将Null也考虑进遍历序列中,采用递归实现,每前序遍历一个节点,相应地对称前序遍历一个节点,边遍历边对比节点值(nullptr也要对比)。
递归形式较为简洁,但也可以采用循环模式实现:
bool isSymmetric(TreeNode *root) { if(root == NULL) return true; TreeNode *left, *right; left = root->left; right = root->right; queue<TreeNode*> qu1, qu2; qu1.push(left); qu2.push(right); while(!qu1.empty() && !qu2.empty()){ TreeNode *node1 = qu1.front(); qu1.pop(); TreeNode *node2 = qu2.front(); qu2.pop(); if(!node1 && !node2) continue; if(!node1 || !node2) return false; if(node1->val != node2->val) return false; qu1.push(node1->left); qu1.push(node1->right); qu2.push(node2->right); qu2.push(node2->left); } return true; }
- 29:顺时针打印矩阵
将一圈打印分为四个步骤,第三步与第四步要做严格的条件检查,通过画图来举例更加清晰
- 30:定义一个包含Min函数的栈模板
在模板中除了数据栈外再多保存一个最小元素栈,其元素个数保持与数据栈一致,可以保证每次头元素都是当前数据栈的最小元素值
定义模板使用template<typename T> class Blob{},类中不要忘了将方法放在public:块中
- 31:判断序列是否为栈的弹出序列
使用一个辅助栈来模拟栈的压入与弹出,当无法得到当前需要弹出的元素时,即可判断该序列不是弹出序列
- 32:从上到下打印二叉树
举例可以发现打印序列是按照先进先出的遍历打印顺序,每打印一个元素之前要先将其左右元素加入先进先出的队列中,队列中保存树节点的指针值,当队列为空,即可结束打印。
若要求每层分一行打印,则另外定义两个变量用于保存当前打印层的剩余打印元素个数和下层的打印元素个数
若要求按之字形分层打印,则需要定义两个栈来保存当前层与下层的节点指针,奇数层从左到右保存,偶数层从右到左保存,打印完一行输出换行符
之字形打印:也可以使用deque来实现,输出模式总是先进后出的,奇数行与偶数行的区别在于先保存左子树还是先保存右子树。
- 33:判断某数组是否为二叉搜索树的后序遍历结果
根据后序遍历的特点,最后一个元素必然是整个数的根节点,因此可以根据最后一个元素值,将数据分为前半部分与后半部分,对应左子树和右子树,左子树的所有元素要小于根元素,右子树的所有元素要大于根元素,通过递归实现子树的判断,要注意若无左子树或无右子树,则要进行特殊处理,否则前后指针会顺序颠倒,使指针越界。
如果面试题要求处理一棵二叉树的遍历序列,一般可以先找到二叉树的根节点,再基于根节点把整棵树的遍历序列拆分成左右子树对应的子序列,接下来再递归地处理这两个子序列。
- 34:寻找二叉树中和为某一值的路径
本质为深度优先遍历(前序遍历)二叉树,需要辅助栈来保存历史路径,当路径满足要求条件时,即可访问栈得到当前路径信息。
访问一个节点时,将其压入栈,然后判断当前路径是否满足条件,随后递归访问左子节点和右子节点,递归返回后,将当前节点出栈,路径恢复到未访问该节点的状态。
由于栈不方便得到入栈顺序,可用vector实现栈的功能。
- 35:复杂链表的复制
复杂链表除了保存下一节点的指针外,还保存着随机链接节点指针,顺序复制时只能保存其下一节点的指针,其随机链接节点只能等到所有节点都创建后才能进行保存。由于其链接是随机的,因此若要一一赋值,每次都需要从头遍历寻找链接节点的拷贝节点。采用书上的方法,复制一个节点时将其插入原节点后面,因此可以很方便找到随机链接节点的拷贝节点,最后需要将奇数节点和偶数节点分开,形成两个链表即原链表和拷贝链表。分步是关键。
- 36:二叉搜索树转化为双向链表
中序遍历二叉搜索树刚好可以得到排序的一个序列,中序遍历需要采用递归形式实现,访问一个节点的函数中,先判断其是否存在左子节点,有则递归先访问其左子节点,函数返回后访问当前节点,然后访问右子节点。将二叉搜索树转化为双向链表,要在中序遍历的基础上对树节点的结构进行修改,需要在遍历过程中保存当前序列的尾节点指针值,可以将遍历与修改分为三个步骤:一、访问左子节点 二、若序列的尾节点非空,则将当前节点与尾节点链接 三、尾节点递增为当前节点,访问当前节点的右子节点。
- 37:序列化与反序列化二叉树
使用前序遍历二叉树形成序列,为使序列对应二叉树唯一,要将空指针也对应一个字符输出到序列中,另外由于每个节点值的位数不定,所以要在不同节点间用","隔开,使用string.c_str将string转化为char*(以'\0'结尾),使用strcpy进行字符串的拷贝('\0'也会拷贝过来),使用strcat进行字符串的连接(保持最后的'\0')。前序遍历依旧需要使用递归实现。
- 38:打印字符串的所有可能排列
全排列问题可以分解为头元素的选取和后续元素的排列,使用递归函数实现,在递归函数中,循环地将当前头元素与其它可排元素交换(包括它自己),然后递增头元素,进行下一部分元素的排列,当头元素为序列的最后一个元素,即为可能的一个排列。注意字符串可能有重复元素,此时不应将重复元素进行交换(头元素与头元素的交换仍要进行)。为加快速度,参数使用引用或指针类型,因此在每次交换元素后,需要再交换一次恢复原样。
八皇后问题:
所有皇后不能同行同列或同对角线,因此可以定义一个数组cols[8],初始化为0~7,此时就满足条件不同行不同列了,然后对数组进行全排列,判断全排列中每个排列是否满足不同对角线的条件(i-j!=cols[i]-cols[j]&&j-i!=cols[i]-cols[j])。在全排列过程中,为加快速度,采用回溯思想:
回溯法:从解决问题每一步的所有可能选项里系统地选择出一个可行的解决方案,通常采用递归函数实现,在递归函数中负责判断本步骤是否满足要求条件,如果满足就继续尝试下一步的所有选项,若下一步也满足条件则返回正确答案,否则要将变量还原到本步骤选择之前。
检查对角线条件时,前部分元素都是已经检查过的,只需要检查最后一个元素是否满足条件。
- 39:寻找数组中出现次数超过一半的数字
1.第一种做法是将数组先进行排序,然后判断是否存在长度超过总长一半的同元素序列,使用sort函数可以对begin到end之间的元素进行升序排序。时间复杂度为O(nlogn)。
sort函数:其实sort()并不能叫做快速排序,而应该说成智能排序;它正常情况下,会使用快排,但是发现快排恶化的话,会自动调整成其他排序来辅助。是最高效的排序。在C++的algorithm库中。可以定义一个判断函数作为sort函数的第三个输入参数(函数指针),以实现降序排序或自定义的排序。
2.第二种做法,若存在一个元素出现次数大于数组长度的一半,则该序列的中位数必然是该数字,采用快速排序的partition函数可以划分数组并得到当前元素的排序序号,若序号小于中位,则在后面序列中继续递归查找,如果大于中位,则在前面序列中继续查找,如果刚好是中位,还需要判断该中位数是否真的出现次数大于length/2。时间复杂度为O(n)。
3.第三种做法,如果数组中有一元素出现次数大于length/2,即其出现次数比所有其他元素出现次数的和还多。因此可以在遍历过程中保存两个变量,一个表示元素值,一个表示累计次数。当遍历到一个与value值相同的元素,times++,否则tims--,当times==0,重新设置value。时间复杂度为O(n)。
- 40:寻找数组中最小的k个数
1.第一种做法,利用快速排序的partition函数找到排序序号为k-1的数,并在寻找过程中对数组进行划分和排序,可以在O(n)时间内找到最小的k个数。
2.第二中做法,//当不允许修改数组,采用书上的第二种方法
使用辅助容器存储遍历过程中最小的k个数,当容器未满,直接插入遍历元素,当遍历元素满了,需要比较容器内最大值与遍历元素,插入较小的元素,跳过较大的元素。
书上采用红黑树实现最大堆(最大元素总在顶部),set和multiset都是基于红黑树实现的。当需要处理海量数据时,采用此方法更适用。时间复杂度O(nlogk)。
使用普通数组作为辅助容器的话,时间复杂度将式O(nk)。
- C++中堆的应用:make_heap, pop_heap, push_heap, sort_heap
函数说明:
std::make_heap将[start, end)范围进行堆排序,默认使用less, 即最大元素放在第一个。
std::pop_heap将front(即第一个最大元素)移动到end的前部,同时将剩下的元素重新构造成(堆排序)一个新的heap。
std::push_heap对刚插入的(尾部)元素做堆排序。
std::sort_heap将一个堆做排序,最终成为一个有序的系列,可以看到sort_heap时,必须先是一个堆(两个特性:1、最大元素在第一个 2、添加或者删除元素以对数时间),因此必须先做一次make_heap.
make_heap, pop_heap, push_heap, sort_heap都是标准算法库里的模板函数,用于将存储在vector/deque 中的元素进行堆操作,对不愿自己写数据结构堆的C++选手来说,这几个算法函数很有用,下面是这几个函数操作vector中元素的例子。详细解释可以参见:
上述函数需要#include <algorithm>
- 41:寻找数据流中的中位数
1.第一种做法,插入时排序,插入的时间复杂度为O(n),寻找中位数的时间复杂度为O(1);或者直接插入到数组的尾部,寻找中位数时采用partition函数,则插入的时间复杂度为O(1),寻找中位数的时间复杂度为O(n)。
2.第二种做法,采用最大堆和最小堆来分别保存中位数左边序列和右边序列,由于最大堆与最小堆的特性,插入元素的时间复杂度为O(logn),而寻找中位数即为最大堆与最小堆的首元素,因此时间复杂度为O(1),效率最高。
具体实现:当总个数为偶数时,插入元素到最小堆中,插入前要检查待插入元素是否比最大堆的最大元素(首元素)小,若小于,则需要先插入到最大堆中,然后置换出其新排序后的首元素出来,再插入到最小堆中。当总个数为奇数时,插入元素到最大堆中,步骤类似。寻找中位数时,若总个数为奇数,则直接返回最小堆的首元素,否则返回最小堆与最大堆的首元素的均值。
注意:按位与或等操作(&|)的优先级低于==、!=
最小堆与最大堆实现细节:
使用vector和algorithm中的函数make_heap, pop_heap, push_heap, sort_heap就可实现最小堆与最大堆。例如:
max.push_back(num); push_heap(max.begin(), max.end(),less<int>()); num = max[0]; pop_heap(max.begin(), max.end(), less<int>()); max.pop_back();
注意:less与greater是模板函数。
- 42:寻找连续子数组的最大和
//设置两个辅助变量用于存储历史最高值和当前累计值
//当遇到负数后的第一个正数,判断重新累计的和大还是继续累计的和大
//若重新累计的和大,则重新累计
//若当前累计值高于历史最高值,则更新历史最高值
//我的代码,无法处理都是负数的情况
书上的方法更为简洁正确,可以处理所有情况:
不管当前元素是正是负,而是去判断当前累计值是否小于0,若小于零则重新累计,否则继续累计,并且在遍历过程中要判断历史累计值和当前累计值的大小,以保存实际的最大和。将历史累计值初始化为最小的int 0x80000000。int最大值为0x7fffffff。注意直接写0x80000000将得到一个正数,只有将0x80000000赋给int类型(或其它有符号32位类型),才能得到最小的32位负值。
- 43:统计1~n整数中1出现的次数
总结规律:
//使用n%base得到former,个位时n%1=0,因此可以不分别对待
- 44:数字序列中某一位的数字
通过举例寻找规律,0~9有10个数字(作为例外),10~99有90*2个数字,100~999有900*3个数字,以此类推,可以定义一个量表示当前单个整数长度,一个量用于统计当前长度的整数共有多少个数字,当累计数字量大于index,则在当前长度的整数中查找。注意当找到确切的那个整数时,要判断该位数字是否是该整数的最后一位,如果是要特殊处理,不是的话通过除10取余可得到该位数字。
- 45:把数组排成最小的数
需要定义一个函数判断两个整数a、b中哪一个需要排在前面,设此函数表示小于的含义,用此函数对整个数组进行排序,则可以得到组合的最小数。比较两个整数时,要转化为string后进行对比较为方便,且可以解决大数问题。
- 46:把数字翻译成字符串
可以通过递归解题,设函数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。
递归一般从最大的问题自上而下解决问题,但本题中发现子问题中有大量的重复子问题,因此考虑采用循环实现递归,从右向左计算f(i)函数值。注意i==length-1和i==length-2两个时刻要特殊处理。
- 47:寻找捡到礼物最大值的路径
典型的动态规划问题,将问题分解为一个个的子问题进行解决,定义数组f[i][j]表示在第i行第j列能达到的最大值,该最大值等于max(up,left)+values[i][j],由左上角开始循环计算出每个格子的f[i][j]值,最终能得到在右下角能达到的最大值。
由于计算一个格子的f只与上一行元素和本行元素相关,因此使用二维数组来实现maxValues实际上有冗余,可以采用一维数组(长度为cols),在计算f[i][j]时,maxValues[0~j-1]为同一行的左边格子数据,maxValues[j~cols-1]为上一行的右边格子数据。
- 48:求最长的不含重复字符的子字符串长度
//用数组f[i]保存以第i个字符为结尾的子字符串长度
//当f[i-1]对应的子字符串不包含第i个字符,f[i]=f[i-1]+1
//当f[i-1]对应的子字符串中包含有第i个字符,且其出现位置与i的距离为d,则f[i]=d
注意第一个元素要在循环外进行初始化。
使用数组charLoc[26]用来保存每个字符最后出现的位置,开始时全初始化为-1。
- 49:求第index个丑数
丑数是只包含因子2,3,5的数
每个丑数都是通过前面的某个丑数乘2或3或5得到的
1.先把前面的丑数都乘2,得到大于当前最右值的乘值,再乘3,乘5,取三个乘值的最小值作为下一个丑数。
2.通过保存mutiply2、mutiply3、mutiply5来减少遍历前面所有丑数,而是只从大于当前最大丑数的mutiply*n开始遍历
- 50:求字符串中第一个只出现一次的字符
本质是通过哈希表解决问题,键值为字符,值为字符出现的次数,STL中的map和unordered_map实现了哈希表的功能,但在此问题中,字符一共只有256个,所以可以简化使用一个int[256]数组实现哈希表,保存每个字符出现的次数,然后再遍历一次字符串,就可以得到该字符的位置。
如果需要判断多个字符是不是在某个字符串里出现过或者统计多个字符在某个字符串中出现的次数,那么我们可以考虑基于数组创建一个简单的哈希表,这样可以用很小的空间消耗来换时间效率的提升。
- 51:统计数组中的逆序对
若对每个元素进行逆序对的统计,则需要O(n^2)的时间复杂度,计算效率不高,而采用归并排序过程中进行逆序对统计,只需要O(nlogn)的时间复杂度,较为高效,用O(n)的空间换取了时间上的提升。
需要对归并排序做一定修改,在合并两个排序好的子数组时,从每个子数组的最后一个元素开始对比,假设子数组1的起始为left,结束为mid,遍历变量为i,子数组2的起始为mid+1,结束为right,遍历变量为j,若a[i]>a[j],则有逆序对j-mid个。
- 52:寻找两个链表的第一个公共节点
先求出两个链表的长度,然后长的链表先走n1-n2步,随后两链表共同前进直到找到第一个重合点。
第6章 面试中的各项能力
- 求树中两个节点的最低公共祖先:要先问清楚是否为二叉树,是否是排序的二叉树,是的话,可以从头节点开始遍历,直到找到一个节点的值夹在两个输入节点值之间。如果不是排序二叉树,而是有父节点指针的普通树,那么可以看作是链表,如52题:寻找两个链表的第一个公共节点。
- 53:统计数字在排序数组中出现的次数
采用二分法查找最左边的k和最右边的k的位置,注意二分法查找的最外判断条件都是left<=right,必须带等号。根据特殊条件判断内部条件符号位>=或<=。
- 54:寻找二叉搜索树中第k大的节点
采用中序遍历二叉搜索树,可以得到各节点的递增排序,在中序遍历过程中,传入引用类型的参数k,先对非空的左子树进行遍历,如果返回后k为0,则返回pTemp,否则遍历当前节点,并--k,若k为0,则返回当前节点,否则对非空的右子树进行遍历,如果返回后k为0,则返回pTemp,否则返回nullptr。
- 55:探寻二叉树的最大深度
采用遍历方式代码会很冗长,先对问题进行观察研究,发现一个节点的深度等于其左节点与右节点深度最大值+1,于是可以很方便写出递归代码,return (nLeft > nRight) ? (nLeft + 1) : (nRight + 1);
拓展:若探寻最小深度,则要改变判断策略,要是叶子节点
if(!root) return 0; int l = run(root->left); int r = run(root->right); if(l==0 || r==0) return 1+l+r; return 1+min(l,r);
拓展:判断一颗树是否为平衡二叉树(任意节点的左右子树的深度差不超过1)要采用后序遍历
采用探寻二叉树的最大深度来 ,遍历每个节点的实现方式,会有大量冗余的遍历过程,因此采用后序遍历的方式在遍历过程中比较左右节点的深度。
以函数IsBalanced_SolutionCore为遍历函数,返回判断该节点的左右子树是否为平衡节点的bool变量,且在参数中包含一个引用型参数,用于保存遍历节点的深度值。首先判断节点是否为空,若为空则将depth置零,并返回true;然后依次遍历当前节点的左子树与右子树,并保存其深度left、right,如果左右子树都是平衡的,那么将left与right中的较大值加1后赋给depth,并返回true;否则返回false。
- 56:寻找数组中只出现一次的元素
一个数组中若有一个元素只出现一次,其它元素都出现两次,那么可以依次异或所有元素,由于相同的元素异或后为零,所以最后的异或结果就是那个只出现一次的元素。
若存在两个元素只出现一次,其它元素都出现两次,那么应该先把数组分为两个子数组,然后才能使用如上方法来找到只出现一次的数。先把所有元素进行异或,由于存在两个只出现一次的元素,因此最后结果中肯定有非零位,因此两个子数组的划分依据就是这个非零位,最后采用上述方法处理每个子数组内的问题。
若一个数组中有一个元素只出现一次,其它元素都出现三次,则可以定义一个32维的数组代表int的32位,把每个元素的对应位相加,最后的结果每个位都%3,组合数组各个位就可以得到只出现一次的元素。
- 57:寻找排序数组中和为s的两个数
要充分利用数组的递增排序特性,定义left=0,right=length-1,若a[left]+a[right]<sum,则left++,否则right--,要注意边界条件和最后返回前的==sum判断。
求和为sum的所有连续正数序列:
根据上一题的思路,可以设置left和right变量,当序列和小于sum,则right++,当序列和大于sum,则left++,等于时输出序列并left++。
- 58:翻转句子中的单词顺序
可以先把整个字符串进行翻转,随后逐个单词再翻转一次就得到逆序句子。
也可以从后向前遍历字符串,使用栈来将保存单词,出栈时就可以恢复正常拼写顺序。
翻转思想的迁移运用
左旋转字符串:
先分别翻转要旋转的部分和不旋转的部分,然后把整个字符串进行翻转,最后得到左旋转字符串,注意n<0或空字符串或n>=length的情况,n>=length时,要n%=length。
- 59:求滑动窗口的最大值
常规做法是每滑动一次,计算一次最大值,因此时间复杂度是O(nk)。
而经过统计可以发现一定规律,采用双端队列来保存元素,始终保持队列首元素为最大值,滑动窗口时,将下一元素从队列尾部插入,若下一个元素大于队列中原有元素,则需要先把原有元素从尾部取出,然后再进行插入操作。队列保存的是元素的下标值,因此在每次滑动窗口时,要从队列头部开始检查元素下标是否和当前窗口值匹配,若不匹配则从队列头部取出。滑动窗口操作结束后,队列首元素对应的数组元素就是当前窗口的最大值。采用此方法时间复杂度近似于O(n)。
拓展:定义一个队列使得可以在O(1)时间内得到队列的当前最大值
根据上题思路,使用一个辅助的双向队列来实现O(1)时间内获得队列最大值的功能。
由于要根据元素判断其是否还在队列中,因此数据队列和辅助队列都要保存自定义的结构体,结构体内有两个元素,分别为数据值和插入队列的序号。插入元素时,将辅助队列内小于插入元素的尾端元素pop出队列,然后构建结构体并分别插入数据队列和辅助队列。取出元素时,要判断队列是否为空,为空情况下要抛出异常,不为空时要判断出队元素的序号和辅助队列的首元素序号是否一致,一致的话则一起出队,否则只对数据队列进行出队操作。获得最大元素时,同样要先判断队列是否为空,为空情况下要抛出异常,否则返回辅助队列的首元素数据值即可。
- 60:统计n个骰子出现点数的概率
有两种做法:
第一种做法是定义一个一维数组用来存放点数出现的次数,并对每个骰子的点数进行遍历,当遍历到最后一个骰子,把相应的点数和对应的数组元素+1。总的次数是n的6次方,将数组每个元素除以总次数,即可得到其出现概率。注意骰子的点数6要定义全局变量表示,以便在出现新型骰子时程序可以沿用,计算概率时要注意int到double的类型转换,pow函数返回double类型。
第二种做法是定义两个一维数组用来存放点数出现的次数,将骰子一个一个进行相加,n个骰子的点数出现次数存放在第一个数组中,n+1个骰子的点数出现次数存放在第二个数组中,n+1情况可以根据n对应的数组值进行计算,点数为s的次数等于sum(s-1,s-2,s-3,s-4,s-5,s-6),进行双重循环计算即可得到点数出现次数,相比递归计算节省了计算量。
- 61:判断一个数组对应的扑克牌是不是顺子
抽象建模,将大小王当做0,判断一个数组是不是顺子。
第一种方法是计算数组中除0元素外的最大值与最小值差,若差大于4,则不是顺子,否则可能是顺子(还要去除存在对子的情况)。
第二种方法是将数组先进行排序(使用sort函数即可,代码更简洁),然后统计numOfZeros和numOfGaps,若numOfGaps>numOfZeros,则不是顺子,否则可能是顺子(还要去除存在对子的情况)。
- 62:约瑟夫环:寻找圆圈中最后剩下的数字
//第一种方法,循环进行标记,最后剩下的数字就是寻找的数字
//第二种方法,通过推导可以获知f(n,m)=(f(n-1,m)+m)%n的递推公式,根据此公式进行计算,更为高效。在递推时,已知的是f(n-1,m)如f(0,m)=0,要根据递推公式循环向上计算f(n,m)。
- 63:求股票的最大利润
想象你是买了股票的人,每看到一次股票的新行情时,就会感叹要是当时在最低价买了现在卖了能赚多少钱。因此只需要遍历一次数组即可,遍历过程中记录当前的行情最小值和最大差价。
- 64:不用乘除和循环判断语句求解1+2+。。。+n
第一种做法是利用类的构造函数来进行循环,构造n个类,则会调用n次构造函数,在类中再定义静态成员以分别保存n和sum。
第二种做法是利用函数指针来进行判断,调用func[!n]来判断是否终止计算,使用递归来代替循环加法计算。
- 65:不用加减乘除做加法
代码:
int result, over; result = num1^num2;//和项 over = (num1&num2)<<1;//进位项 while (over != 0)//相加直到不再有进位 { num1 = result; num2 = over; result = num1^num2; over = (num1&num2)<<1; } return result;
- 66:构建乘积数组
给定一个数组A[0,1,...,n-1],请构建一个数组B[0,1,...,n-1],其中B中的元素B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1]。不能使用除法。
如果能用除法则可以先计算一次总的乘积,计算每个元素只要做一次除法就可以,时间复杂度为O(n),另外还要注意元素0不能作为除数。但题目说明不能用除法,那么如果对每个元素都进行循环计算的话,每个元素需要进行n-1次乘法,总的时间复杂度就为O(n2)。
观察后会发现,对每个元素进行乘法运算时有很多重复计算,如果用辅助数组来保存过程中的计算结果可以节省大量的计算时间。
将每个元素的乘积展开后排列为矩阵可以发现,矩阵可以分为左下角矩阵和右上角矩阵,对角线都为元素1,那么可以先将左下角矩阵和右上角矩阵计算出来,B数组中的第i个元素就等于left[i]*right[i]。而计算左下角矩阵和右上角矩阵时,可以通过上一步计算结果进行一次乘法就得到下一个元素,left[i] = left[i - 1] * A[i - 1];,right[i] = right[i + 1] * A[i + 1]。总的来看,只要进行3次n循环就可以得到数组B,时间复杂度为O(n)。
- 67:字符串转为整数
先判断数字前是否有正负号,然后将字符串一位一位地加入数字中,注意数字要定义为long long类型,这样当字符串对应数字超过int范围时(0x80000000,0x7fffffff),可以直接判断出来并提前结束转换,比较0x80000000时要用-result。最后,如果是负数则将负号加上。

浙公网安备 33010602011771号