软考精选题代码.docx
- 文档编号:23325691
- 上传时间:2023-05-16
- 格式:DOCX
- 页数:69
- 大小:44.37KB
软考精选题代码.docx
《软考精选题代码.docx》由会员分享,可在线阅读,更多相关《软考精选题代码.docx(69页珍藏版)》请在冰豆网上搜索。
软考精选题代码
2015年软件水平考试程序员精选题
(1)
-在排序数组中查找和为给定值的两个数字
题目:
输入一个已经按升序排序过的数组和一个数字,在数组中查找两个数,使得它们的和正好是输入的那个数字。
要求时间复杂度是O(n)。
如果有多对数字的和等于输入的数字,输出任意一对即可。
例如输入数组1、2、4、7、11、15和数字15。
由于4+11=15,因此输出4和11。
分析:
如果我们不考虑时间复杂度,最简单想法的莫过去先在数组中固定一个数字,再依次判断数组中剩下的n-1个数字与它的和是不是等于输入的数字。
可惜这种思路需要的时间复杂度是O(n2)。
我们假设现在随便在数组中找到两个数。
如果它们的和等于输入的数字,那太好了,我们找到了要找的两个数字;如果小于输入的数字呢?
我们希望两个数字的和再大一点。
由于数组已经排好序了,我们是不是可以把较小的数字的往后面移动一个数字?
因为排在后面的数字要大一些,那么两个数字的和也要大一些,就有可能等于输入的数字了;同样,当两个数字的和大于输入的数字的时候,我们把较大的数字往前移动,因为排在数组前面的数字要小一些,它们的和就有可能等于输入的数字了。
我们把前面的思路整理一下:
最初我们找到数组的第一个数字和最后一个数字。
当两个数字的和大于输入的数字时,把较大的数字往前移动;当两个数字的和小于数字时,把较小的数字往后移动;当相等时,打完收工。
这样扫描的顺序是从数组的两端向数组的中间扫描。
问题是这样的思路是不是正确的呢?
这需要严格的数学证明。
感兴趣的读者可以自行证明一下。
参考代码:
///////////////////////////////////////////////////////////////////////
//Findtwonumberswithasuminasortedarray
//Output:
tureisfoundsuchtwonumbers,otherwisefalse
///////////////////////////////////////////////////////////////////////
boolFindTwoNumbersWithSum
(
intdata[],//asortedarray
unsignedintlength,//thelengthofthesortedarray
intsum,//thesum
int&num1,//thefirstnumber,output
int&num2//thesecondnumber,output
)
{
boolfound=false;
if(length<1)
returnfound;
intahead=length-1;
intbehind=0;
while(ahead>behind)
{
longlongcurSum=data[ahead]+data[behind];
//ifthesumoftwonumbersisequaltotheinput
//wehavefoundthem
if(curSum==sum)
{
num1=data[behind];
num2=data[ahead];
found=true;
break;
}
//ifthesumoftwonumbersisgreaterthantheinput
//decreasethegreaternumber
elseif(curSum>sum)
ahead--;
//ifthesumoftwonumbersislessthantheinput
//increasethelessnumber
else
behind++;
}
returnfound;
}
-查找链表中倒数第k个结点
题目:
输入一个单向链表,输出该链表中倒数第k个结点。
链表的倒数第0个结点为链表的尾指针。
链表结点定义如下:
structListNode
{
intm_nKey;
ListNode*m_pNext;
};
分析:
为了得到倒数第k个结点,很自然的想法是先走到链表的尾端,再从尾端回溯k步。
可是输入的是单向链表,只有从前往后的指针而没有从后往前的指针。
因此我们需要打开我们的思路。
既然不能从尾结点开始遍历这个链表,我们还是把思路回到头结点上来。
假设整个链表有n个结点,那么倒数第k个结点是从头结点开始的第n-k-1个结点(从0开始计数)。
如果我们能够得到链表中结点的个数n,那我们只要从头结点开始往后走n-k-1步就可以了。
如何得到结点数n?
这个不难,只需要从头开始遍历链表,每经过一个结点,计数器加一就行了。
这种思路的时间复杂度是O(n),但需要遍历链表两次。
第一次得到链表中结点个数n,第二次得到从头结点开始的第n-k-1个结点即倒数第k个结点。
如果链表的结点数不多,这是一种很好的方法。
但如果输入的链表的结点个数很多,有可能不能一次性把整个链表都从硬盘读入物理内存,那么遍历两遍意味着一个结点需要两次从硬盘读入到物理内存。
我们知道把数据从硬盘读入到内存是非常耗时间的操作。
我们能不能把链表遍历的次数减少到1?
如果可以,将能有效地提高代码执行的时间效率。
如果我们在遍历时维持两个指针,第一个指针从链表的头指针开始遍历,在第k-1步之前,第二个指针保持不动;在第k-1步开始,第二个指针也开始从链表的头指针开始遍历。
由于两个指针的距离保持在k-1,当第一个(走在前面的)指针到达链表的尾结点时,第二个指针(走在后面的)指针正好是倒数第k个结点。
这种思路只需要遍历链表一次。
对于很长的链表,只需要把每个结点从硬盘导入到内存一次。
因此这一方法的时间效率前面的方法要高。
思路一的参考代码:
///////////////////////////////////////////////////////////////////////
//Findthekthnodefromthetailofalist
//Input:
pListHead-theheadoflist
//k-thedistancetothetail
//Output:
thekthnodefromthetailofalist
///////////////////////////////////////////////////////////////////////
ListNode*FindKthToTail_Solution1(ListNode*pListHead,unsignedintk)
{
if(pListHead==NULL)
returnNULL;
//countthenodesnumberinthelist
ListNode*pCur=pListHead;
unsignedintnNum=0;
while(pCur->m_pNext!
=NULL)
{
pCur=pCur->m_pNext;
nNum++;
}
//ifthenumberofnodesinthelistislessthank
//donothing
if(nNum returnNULL; //thekthnodefromthetailofalist //isthe(n-k)thnodefromthehead pCur=pListHead; for(unsignedinti=0;i pCur=pCur->m_pNext; returnpCur; } 思路二的参考代码: /////////////////////////////////////////////////////////////////////// //Findthekthnodefromthetailofalist //Input: pListHead-theheadoflist //k-thedistancetothetail //Output: thekthnodefromthetailofalist /////////////////////////////////////////////////////////////////////// ListNode*FindKthToTail_Solution2(ListNode*pListHead,unsignedintk) { if(pListHead==NULL) returnNULL; ListNode*pAhead=pListHead; ListNode*pBehind=NULL; for(unsignedinti=0;i { if(pAhead->m_pNext! =NULL) pAhead=pAhead->m_pNext; else { //ifthenumberofnodesinthelistislessthank, //donothing returnNULL; } } pBehind=pListHead; //thedistancebetweenpAheadandpBehindisk //whenpAheadarrivesatthetail,p //Behindisatthekthnodefromthetail while(pAhead->m_pNext! =NULL) { pAhead=pAhead->m_pNext; pBehind=pBehind->m_pNext; } returnpBehind; } 讨论: 这道题的代码有大量的指针操作。 在软件开发中,错误的指针操作是大部分问题的根源。 因此每个公司都希望程序员在操作指针时有良好的习惯,比如使用指针之前判断是不是空指针。 这些都是编程的细节,但如果这些细节把握得不好,很有可能就会和心仪的公司失之交臂。 另外,这两种思路对应的代码都含有循环。 含有循环的代码经常出的问题是在循环结束条件的判断。 是该用小于还是小于等于? 是该用k还是该用k-1? 由于题目要求的是从0开始计数,而我们的习惯思维是从1开始计数,因此首先要想好这些边界条件再开始编写代码,再者要在编写完代码之后再用边界值、边界值减1、边界值加1都运行一次(在纸上写代码就只能在心里运行了)。 扩展: 和这道题类似的题目还有: 输入一个单向链表。 如果该链表的结点数为奇数,输出中间的结点;如果链表结点数为偶数,输出中间两个结点前面的一个。 2015年软件水平考试程序员精选题 (2) -求1+2+...+n 题目: 求1+2+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字以及条件判断语句(A? B: C)。 分析: 这道题没有多少实际意义,因为在软件开发中不会有这么变态的限制。 但这道题却能有效地考查发散思维能力,而发散思维能力能反映出对编程相关技术理解的深刻程度。 通常求1+2+…+n除了用公式n(n+1)/2之外,无外乎循环和递归两种思路。 由于已经明确限制for和while的使用,循环已经不能再用了。 同样,递归函数也需要用if语句或者条件判断语句来判断是继续递归下去还是终止递归,但现在题目已经不允许使用这两种语句了。 我们仍然围绕循环做文章。 循环只是让相同的代码执行n遍而已,我们完全可以不用for和while达到这个效果。 比如定义一个类,我们new一含有n个这种类型元素的数组,那么该类的构造函数将确定会被调用n次。 我们可以将需要执行的代码放到构造函数里。 如下代码正是基于这个思路: classTemp { public: Temp(){++N;Sum+=N;} staticvoidReset(){N=0;Sum=0;} staticintGetSum(){returnSum;} private: staticintN; staticintSum; }; intTemp: : N=0; intTemp: : Sum=0; intsolution1_Sum(intn) { Temp: : Reset(); Temp*a=newTemp[n]; delete[]a; a=0; returnTemp: : GetSum(); } 我们同样也可以围绕递归做文章。 既然不能判断是不是应该终止递归,我们不妨定义两个函数。 一个函数充当递归函数的角色,另一个函数处理终止递归的情况,我们需要做的就是在两个函数里二选一。 从二选一我们很自然的想到布尔变量,比如ture (1)的时候调用第一个函数,false(0)的时候调用第二个函数。 那现在的问题是如和把数值变量n转换成布尔值。 如果对n连续做两次反运算,即! ! n,那么非零的n转换为true,0转换为false。 有了上述分析,我们再来看下面的代码: classA; A*Array[2]; classA { public: virtualintSum(intn){return0;} }; classB: publicA { public: virtualintSum(intn){returnArray[! ! n]->Sum(n-1)+n;} }; intsolution2_Sum(intn) { Aa; Bb; Array[0]=&a; Array[1]=&b; intvalue=Array[1]->Sum(n); returnvalue; } 这种方法是用虚函数来实现函数的选择。 当n不为零时,执行函数B: : Sum;当n为0时,执行A: : Sum。 我们也可以直接用函数指针数组,这样可能还更直接一些: typedefint(*fun)(int); intsolution3_f1(inti) { return0; } intsolution3_f2(inti) { funf[2]={solution3_f1,solution3_f2}; returni+f[! ! i](i-1); } 另外我们还可以让编译器帮我们来完成类似于递归的运算,比如如下代码: templatestructsolution4_Sum { enumValue{N=solution4_Sum: : N+n}; }; template<>structsolution4_Sum<1> { enumValue{N=1}; }; solution4_Sum<100>: : N就是1+2+...+100的结果。 当编译器看到solution4_Sum<100>时,就是为模板类solution4_Sum以参数100生成该类型的代码。 但以100为参数的类型需要得到以99为参数的类型,因为solution4_Sum<100>: : N=solution4_Sum<99>: : N+100。 这个过程会递归一直到参数为1的类型,由于该类型已经显式定义,编译器无需生成,递归编译到此结束。 由于这个过程是在编译过程中完成的,因此要求输入n必须是在编译期间就能确定,不能动态输入。 这是该方法最大的缺点。 而且编译器对递归编译代码的递归深度是有限制的,也就是要求n不能太大。 2015年软件水平考试程序员精选题(3) -翻转句子中单词的顺序[折叠] 题目: 输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。 句子中单词以空格符隔开。 为简单起见,标点符号和普通字母一样处理。 例如输入“Iamastudent.”,则输出“student.aamI”。 分析: 由于编写字符串相关代码能够反映程序员的编程能力和编程习惯,与字符串相关的问题一直是程序员笔试、面试题的热门题目。 本题也曾多次受到包括微软在内的大量公司的青睐。 由于本题需要翻转句子,我们先颠倒句子中的所有字符。 这时,不但翻转了句子中单词的顺序,而且单词内字符也被翻转了。 我们再颠倒每个单词内的字符。 由于单词内的字符被翻转两次,因此顺序仍然和输入时的顺序保持一致。 还是以上面的输入为例子。 翻转“Iamastudent.”中所有字符得到“.tnedutsamaI”,再翻转每个单词中字符的顺序得到“students.aamI”,正是符合要求的输出。 参考代码: /////////////////////////////////////////////////////////////////////// //Reverseastringbetweentwopointers //Input: pBegin-thebeginpointerinastring //pEnd-theendpointerinastring /////////////////////////////////////////////////////////////////////// voidReverse(char*pBegin,char*pEnd) { if(pBegin==NULL||pEnd==NULL) return; while(pBegin { chartemp=*pBegin; *pBegin=*pEnd; *pEnd=temp; pBegin++,pEnd--; } } /////////////////////////////////////////////////////////////////////// //Reversethewordorderinasentence,butmaintainthecharacter //orderinsideaword //Input: pData-thesentencetobereversed /////////////////////////////////////////////////////////////////////// char*ReverseSentence(char*pData) { if(pData==NULL) returnNULL; char*pBegin=pData; char*pEnd=pData; while(*pEnd! ='\0') pEnd++; pEnd--; //Reversethewholesentence Reverse(pBegin,pEnd); //Reverseeverywordinthesentence pBegin=pEnd=pData; while(*pBegin! ='\0') { if(*pBegin=='') { pBegin++; pEnd++; continue; } //AwordisbetweenwithpBeginandpEnd,reverseit elseif(*pEnd==''||*pEnd=='\0') { Reverse(pBegin,--pEnd); pBegin=++pEnd; } else { pEnd++; } } returnpData; } 判断整数序列是不是二元查找树的后序遍历结果[折叠] 题目: 输入一个整数数组,判断该数组是不是某二元查找树的后序遍历的结果。 如果是返回true,否则返回false。 例如输入5、7、6、9、11、10、8,由于这一整数序列是如下树的后序遍历结果: 8 /\ 610 /\/\ 57911 因此返回true。 如果输入7、4、6、5,没有哪棵树的后序遍历的结果是这个序列,因此返回false。 分析: 这是一道trilogy的笔试题,主要考查对二元查找树的理解。 在后续遍历得到的序列中,最后一个元素为树的根结点。 从头开始扫描这个序列,比根结点小的元素都应该位于序列的左半部分;从第一个大于跟结点开始到跟结点前面的一个元素为止,所有元素都应该大于跟结点,因为这部分元素对应的是树的右子树。 根据这样的划分,把序列划分为左右两部分,我们递归地确认序列的左、右两部分是不是都是二元查找树。 参考代码: usingnamespacestd; /////////////////////////////////////////////////////////////////////// //Verifywhetherasquenceofintegersarethepostordertraversal //ofabinarysearchtree(BST) //Input: squence-thesquenceofintegers //length-thelengthofsquence //Return: returntureifthesquenceistraversalresultofaBST, //otherwise,returnfalse /////////////////////////////////////////////////////////////////////// boolve
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 精选 代码