《剑指Offer》读书笔记(3)——第三章 高质量的代码

前言

在面试过程中,面试官对代码的检查通常是会去检查1. 是否完成了基本功能。2. 输入边界值是否能得到正确的输出。3. 是否对各种不合规范的非法输入做出了合理的错误处理。

正文

经验

  1. 写代码的过程中,应跳出惯行思维,考虑全面。
  2. 三种测试方法:功能测试,边界测试和负面测试(考察各种错误输入)。
  3. 在写函数时,如果要检测调用接口是否成功,最好是有一个整数返回值,这样才能确定,调用接口是否成功。
  4. 可以在出问题时抛出一个异常。
  5. 位运算的效率比乘除法的运算效率要高的多。
  6. 查找一个东西,那么首先,你需要确定说这个东西确实是在该数据结构的。
  7. 鲁棒性是指判断程序是否合乎要求,并对不合要求的输入予以合理的处理。可以采用防御性编程。
  8. 对于浮点数,在比较大小的时候,注意,并不能直接和进行相比,而是要与0.000001这样一样较小的数进行比较,小于0.0000001大于-0.0000001这样的数,称之为0。

题目

面试题11:数值的整数次方

题目
在这里插入图片描述
解法一
有可能你一上来就是一个for循环,然后自以为是解决了,其实这道题要考察的知识点,恰恰是你对输入数据的敏感度,是否能较好的分析输入数据为正数,负数,或者是0的情况。从而对最终情况进行分类讨论。
首先,先分析指数,其有负数,0,正数三种情况。然后分析底数,也有这三种情况。

最终解法
code

bool g_InvalidInput = false;

double Power(double base,int exponent)
{
	g_InvalidInput = false;
	if(equal(base,0,0)&&exponent<0)
	{
		g_InvalidInput = true;
		return 0.0;	
	}	
	
	unsigned int absExponent = (unsigned int)(exponent);
	if(exponent<0)
		absExponent = (unsigned int)(-exponent);
	double result = PowerWithUnsignedExponent(base,absExponent);
	if(exponent<0)
		result = 1.0/result; 
} 

double PowerWithUnsignedExponent(double base,unsigned int exponent)
{
	double result = 1.0;
	for(int i = 1;i<=exponent;++i)
	{
		result *=base;
	}
	return result;
}

bool equal(double num1,double num2)
{
	if((num1-num2>-0.0000001)&&(num1-num2<0.0000001))
		return true;
	else
		return false;
}

总结
反正就是要仔细考虑,对输入数据进行充分的考虑,越简单的题越要进行仔细的分析。还有一个细节就是在判断base是否等于0的时候,不能直接使用==0这种判断方式,因为计算机在表示小数时是有无法的,所以,要跟0.0000001进行比较,若比这种数要小,比-0.0000001要大,那就基本上可以证明是等于0了。

面试题12:打印1到最大的n位数

题目
在这里插入图片描述
注意:注意这个n到底有多大,有没有可能是大数问题。
考虑:既然有了上面对这个问题的考虑,于是我们选择采用字符串来解答这道题目。因为是n位十进制数,所以,我们需要使用n+1位的字符串来解决这个问题。若实际数字不够位的时候,我们采用在前面补0的方式,表达那个数字。
解法
在这里插入图片描述
code

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
//打印1到最大的n位数(C语言)
/*
考虑一下大数问题
*/
//number =19 
int Increment(char* number)//每次对Number这个字符串的数值+1 ,并返回是否已经超过最大的n位数 
{
	int isOverFlow = 0;//判断是否超过最大值
	int nTakeOver = 0;
	int nLength = strlen(number);//2 
	int i;
	for (i=nLength-1;i>=0;i--)
	{
		int nSum = number[i]-'0'+nTakeOver; //9
		if(i==nLength-1)
			nSum++;//nSum = 2
		if(nSum>=10)//若最末的那一位加完后超过10 
		{
			if(i==0)
				isOverFlow =1;//若是第0位,则置溢出标志 
			else
			{
				nSum -=10;
				nTakeOver = 1;
				number[i] = '0' +nSum;
			}	
		}	
	}	
	return isOverFlow;
} 
void PrintNumber(char *number)
{
	int i =0;
	int length = strlen(number);
	while(number[i]=='0')
		i++;
	for (int j=i;j<length;j++)
		cout<<number[j];
	cout<<"\t";	
} 
void PrintToMaxN(int n)//采用更改字符串每一位的方式来打印出每一位 
{
	if(n<0)//首先,一定要注意进行输入数据的完美判断 
		return; 
	char number[n+1];
	memset(number,'0',n);
	number[n] = '\0';
	
	while(Increment(number))//不断的在该字符串上更改每一位数字的大小,判断是否溢出 
	{
		PrintNumber(number);//打印该字符串 
	} 
	 
}
int main()
{
	int i;
	int n;
	printf("Please input the n number:");
	scanf("%d",&n);
	PrintToMaxN(n); 
	return 0;
}

总结:这道题的难点,我都觉得不在思路上面了,而是在不断的对字符串进行判断溢出,以及不断的对字符串进行加一的操作。

面试题13:在O(1)时间删除链表节点

题目
在这里插入图片描述

struct ListNode
{
	int m_nValue;
	ListNode* m_pNext;
};

解法
在这里插入图片描述
思路
关于删掉链表中某一个节点,我们不一定要找到这个节点的前一个节点进行删除,我们只需要找到当前节点的后一个节点,然后,把后一个节点的值复制到这个要删除的这个节点,然后,更改一下指针,删掉后面的这个节点。需要注意的是,有可能出现,要删除的节点是最后一个节点或者有可能是第一个节点,考虑了这两种情况之后,我们就可以开始写代码了。

code

//删除链表中的某一个节点,要求时间复杂度为O(1)
void DeleteNode(ListNode** pListHead,ListNode* pToBeDeleted)//一个是头指针,一个是要被删除的指针 
{
	if(!pListHead||pToBeDeleted)
	{
		return; 
	}	
	
	if(pToBeDeleted->m_pNext!=NULL)
	{
		ListNode* pNext = pToBeDeleted->m_pNext;
		pToBeDeleted->m_nValue = pNext->m_nValue;
		pToBeDeleted->m_pNext = pNext->m_pNext;
		
		delete pNext;
		pNext = NULL; 
	}
	else if(*pListHead==pToBeDeleted)//当只有一个节点的时候 
	{
		delete pToBeDeleted;
		pToBeDeleted = NULL;
		*pListHead = NULL;
	}
	else//若是最后一个节点的话,那么就得进行顺序查找,从头到尾进行查找 
	{
		ListNode* pNode = *pListHead;
		while(pNode->m_pNext!=pToBeDeleted)
		{
			pNode = pNode->m_pNext;
		 } 
		pNode->m_pNext = NULL;
		delete pToDeleted;
		pToBeDeleted = NULL;
	}
}

总结:关于这道题,那个idea还是较为具有创新性的,就是删除的时候,不一定要按照固定思维,从前往后进行查找,而是可以直接改变下一个节点的位置,然后,删除,下个节点。还有一个点就是需要进行考虑全面,关于给的条件,也是需要考虑清楚,到底是有没有那个节点,到底是节点在第一个位置,还是在最后一个位置,这三种情况都是需要考虑的。

面试题14:调整数组顺序使奇数位于偶数前面

分析:将指针分别指向头和尾,移动头指针,直到是偶数,移动尾指针,直到是奇数。然后两个指针指向的值进行交换。不断的重复这个过程就可以了

code

void ReorderOddEven(int *pData,unsigned int length)
{
	if(pData==NULL||length==0)
		return;
	int *pBegin = pData;
	int *pEnd = pData+length-1;
	while(pBegin<pEnd)
	{
		//向后移动pBegin,直到它指向偶数
		while(pBegin<pEnd&&(*pBegin&0x1)!=0)
			pBegin++;
		//向前移动pEnd,直到它指向奇数
		while(pBegin<pEnd&&(*pEnd&0x1)==0)
			pEnd--;
		if(pBegin<pEnd)
		{
			int temp = *pBegin;
			*pBegin = *pEnd;
			*pEnd = temp;
		}
	}
} 

总结:总结来说,还是要利用好指针,才可以较为便利的解决好这种问题。

面试题15:链表中倒数第k个节点

题目
在这里插入图片描述

struct ListNode{
	int m_nValue;
	ListNode *m_pNext;
}

思路:数一下整个链表有多少个节点,然后用两个距离为k的两个指针同时移动,直到第二个指针移动到最后的时候,我们就可以发现第一个指针已经指向了倒数第k个指针。这里需要注意的是,若链表节点数小于K,已经,传入值的健壮性。

面试题16:反转链表

题目
在这里插入图片描述

struct ListNode
{
	int m_nKey;
	ListNode* m_pNext;
}

思路:在代码测试前,最好想好测试用例,这样就心里进行测试后,再进行代码的编写,就比较不会出现问题了。
code

ListNode* ReverseList(ListNode *pHead)
{
	ListNode* pReversedHead = NULL;
	ListNode* pNode = pHead;
	ListNode* pPrev = NULL;
	
	while(pNode!=NULL)
	{
		ListNode* pNext = pNode->m_pNext;//先确定下第二个节点,保存下这个节点,后面这个节点还要用来连接链表 
		if(pNext==NULL)//若移动到最末尾了,则最后一个节点,就是反转后的头节点 
			pReversedHead = pNode;
		pNode->m_pNext = pPrev;//调转枪头,指向pre
		pPrev = pNode;//然后,进行下一个节点的调转,上面的主角现在成了配角pre了
		pNode = pNext; //然后把next指向主角。完成2个节点
		
	}
	return pReversedHead;
 } 

总结:这道题总结来说还是比较有难度的,毕竟涉及到指针的转向,其实很多人对这个东西都是迷迷糊糊的。注意,先保存下主角的下一个指针,然后,再进行转向的操作,转向目的指针可以弄为pre。

面试题17:合并两个排序的链表

题目
在这里插入图片描述
code

ListNode* Merge(ListNode* pHead1,ListNode* pHead2)
{
	if(pHead1==NULL)//若第一个链表为空,那合并后的链表一定是第二个链表
		return pHead2;
	else if(pHead2==NULL)
		return pHead1;
	
	ListNode* pMergeHead = NULL;
	if(pHead1->m_nValue<=pHead2->m_nValue)
	{
		pMergedHead = pHead1;
		pMergedHead->m_pNext = Merge(pHead1->m_pNext,pHead2);
	}
	else
	{
		MergedHead = pHead2;
		pMergedHead->m_pNext = Mergw(pHead1,pHead2->m_pNext);
	 } 
}

总结:这肯定是一个合并的过程。就是挑选出两个链表中,符合要求的那个节点,跟在新的那个要返回的合并链表的后面。然后,继续这样的操作,可使用递归了。递归要出去出去的条件。

面试题18:树的子结构

题目
在这里插入图片描述
code

struct BinaryTreeNode
{
   int m_nValue;
   BinaryTreeNode* m_pLeft;
   BinaryTreeNode* m_pRight;
}

思路:我本来的思考是可以对这A树进行遍历,得到一个字符串,然后再对B树进行遍历,只要在A字符串中找到这个B子字符串,就可以判断B是不是A的子结构了。
code

//判断第二棵子树有没有可能是第一棵子树的子树。 
bool HasSubtree(BinaryTreeNode* pRoot1,BinaryTreeNode* pRoot2)
{
	bool result = false;
	if(pRoot1!=NULL&*pRoot2!=NULL)
	{
		if(pRoot1->m_nValue==pRoot2->m_bValue)
			result = DoesTree1HaveTree2(pRoot1,pRoot2);//一个节点如果相等,那就递归下去比较下一个节点 
		if(!result)
			result = HasSubtree(pRoot1->m_pLeft,pRoot2);//如果该节点不相等,那就递归下去找左节点 
		if(!result)
			result = HasSubtree(pRoot2->m_nRight,pRoot2);//如果该节点不相等,那就递归下去找右节点 
			
	}	
} 

//这就是递归下去找下一个节点 
bool DoesTree1HaveTree2(BinaryTreeNode* pRoot1,BinaryTreeNode* pRoot2)
{
	if(pRoot2==NULL)
		return true;
	if(pRoot1==NULL)
		return false;
	if(pRoot1->m_nValue!=pRoot2->m_nValue)
		return false;
	return DoesTree1HaveTree2(pRoot2->m_pLeft,pRoot2->m_pLeft)&& DoesTree1HaveTree2(pRoot1->m_pRight,pRoot2->m_pRight);
}

总结:关于这个的解决,主要还是这个递归思想,就是不断的往下传,只要它传下去比较的东西是固定的,那么就可以采用递归的方法进行操作。

参考

  1. 剑指offer 名企面试官精讲典型编程题.pdf
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值