剑指Offer

一、面试题

(1)斐波那契数列

long long Fibonacci(int n)
{
	int result[2]={0,1};
	if(n<=1)
		return result[n];
	long long fibone = 1;
	long long fibtwo =0;
	long long fibN = 0;
	for(int i =2;i<=n;++i)
	{
		fibN= fibone+fibtwo;
		fibtwo = fibone;
		fibone = fibN;
	}
	return fibN;
}
int main()
{
	long long value = Fibonacci(6);
	cout<<value<<endl;
}
(二)连续最大子序列的和

int FindMaxSum(int * data,int length)
{
	if(data == NULL || length<0)
		return 0;
	int cursum =0;
	int greatsum = 0x80000000;
	for(int i =0;i<length;++i)
	{
		if(cursum<0)//重点,当他小于零时,舍去这个数。
			cursum = data[i];
		else
		{
			cursum += data[i];
			
		}
		if(cursum>greatsum)
				greatsum=cursum;
	}
	return greatsum;
}
int main()
{
	int data[]={1.-2,3,10,-4,7,2,-5};
	int value = FindMaxSum(data,sizeof(data)/sizeof(int));
	cout<<value<<endl;
	return 0;
}
三、旋转数组的最小值
旋转之后的数组其实是俩个排序的数组,因为在排序数组中我们可以用二分查找,时间复杂度是O(nlogn),
而且最小的那个数字一定是俩个排序数组的分界点。
int MinInorder(int *numbers,int left,int right)
{
	int min = numbers[left];
	for(int i = left+1; i<= right;++i)
	{
		if(numbers[i] < min)
		{	
			min = numbers[i];
		}
	}
	return min;
}
int Min(int* numbers,int length)
{
	if(numbers ==NULL || length <0)
        return 0;
	int index1 =0;
	int index2 = length-1;
	int indexmid = index1;
	while(numbers[index1] >= numbers[index2])
	{
		if(index2-index1 == 1)
		{
			indexmid = index2;
			break;
		}
		indexmid = (index1+index2)/2;
		if(numbers[index1] == numbers[indexmid] && numbers[index1] == numbers[index2])
			return MinInorder(numbers,index1,index2);/////1/////////////////////
		if(numbers[indexmid] >numbers[index1])
			index1= indexmid;
		if(numbers[indexmid] < numbers[index2])
			index2 = indexmid;
	}
	return numbers[indexmid];
}
int main()
{
	int numbers[]={1,1,1,0,1,};
	int value = Min(numbers,sizeof(numbers)/sizeof(int));
	cout<<value<<endl;
	return 0;
}
至于为什么会有1,是因为考虑到这种情况:1 0 1 1 1或者1 1 1 0 1这俩种情况下,就不能在进行二分法查找了。
四、面试题11: 计算数值的整数次方(指数为负数,0的负数次幂是不合法的)我们一般只考虑了指数为正数的情况;
bool g_invalidinput = false;
double PowUnsigned(double base,int absexp)
{
	double result = 1.0;
	for(int i =1;i<= absexp;++i)
		result *= base;
	return result;
}
bool equal(double base,double value)
{
	if(base-value> -0.000001 && base-value < 0.000001)
		return true;
	else
		return false;
}
double Pow(double base,int exp)
{
	if(equal(base,0.0) && exp <0)
	{
		g_invalidinput = true;
		return 0.0;
	}
	double absexp = (unsigned int)(exp);
	if(exp <0)
		absexp = (unsigned int)(-exp);
	double value = PowUnsigned(base,absexp);
	if(exp< 0)
		value = 1.0/value;
	return value;
}
int main()
{

	int result = Pow(2,3);
	cout<<result<<endl;
	return 0;
}

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

1、首先想到的就是先计算出最大的n位数max,然后在从1到max,但是如果数字很大,不管是int还是long long 类型都不够存储。
所以改用字符串存储,并且用字符串的没一位表示0-9之间的某个字符,用来表示数字中的一位,因为数字最大是n位,所以需要
n+1位的字符串,并且第n+1位是'\0';当数字不够n位时,数字的前半段补0;
Increment函数是将字符串的数字每一次+1;
Print函数因为在打印的时候可能会出现098,我们只需要打印98,所以对字符串的打印也需要做特殊处理。
bool Increment(char *number)
{
	bool isoverflow = false;
	int nTakeOver = 0;
	int length = strlen(number);
	for(int i = length-1;i>=0;--i)
	{
		int nSum = number[i]+nTakeOver-'0';
		if(i == length-1)
			nSum++;//每一次给字符串的数字+1;
		if(nSum>=10)
		{
			if(i == 0)
				isoverflow = true;//只有第一个字符(下标为0)的基础上产生进位的时,就已经是最大的N位数了。
			else
			{
			nSum-=10;
			nTakeOver =1;
			number[i]= nSum+'0';
			}
		}
		else
		{
			number[i] = nSum+'0';
			break;
		}
	}
	return isoverflow;
}
void Print(char *number)
{
	bool isbegining0 = true;
	int length = strlen(number);
	for(int i =0;i<length;++i)
	{
		if(isbegining0 && number[i] !='0')
			isbegining0=false;
		if(!isbegining0)
			printf("%c",number[i]);
	}
	cout<<" ";
/*	for(int i =0;i<length;++i)
	{
		if(number[i] !='\0')
			printf("%c",number[i]);
	}
这是错误的,因为本意是碰到第一个非零的值开始打印,
如果写成这个样子,后面再碰到0就不会打印了,很明显就错了;
*/
}
void Print1toMax(int n)
{
	if(n<=0)
		return ;
	char * number = new char[n+1];
	memset(number,'0',n);
	number[n]='\0';
	while(!Increment(number))
	{
		Print(number);
	}
}
int main()
{
	Print1toMax(3);
	return 0;
}
面试题:在O(1)时间内删除结点

给定单向链表的头结点和一个指定的结点,在O(1)时间内删除该节点;
分析:在单向链表中,要删除一个指定的结点,最常规的方法是在链表中顺序查找目标节点的前一个结点,
然后让前一个结点的next指针指向目标节点的下一个结点。但是这样做的时间复杂度是O(1)。
第二种方法:因为已知了要删除的结点,那么他的下一个结点也是知道的,既然这样的话就可以用下一个节点的值替换目标
结点的值,然后把目标结点的下一个删除,也就达到了删除目标结点的作用。但是如果是删除最后一个结点的话,就只能顺序删除了。
因为它没有下一个结点了。当单向链表只有一个结点时候,这个结点既是头结点也是尾结点,那么在删除完这个结点之后需要将头结点置为NULL
struct listNode
{
	int m_nvalue;
	listNode* next;
};
void DeleteNode(listNode** head,listNode* ptobedelete)
{
	if(head == NULL)
		return;
	if(*head==ptobedelete)//链表只有一个结点,删除头结点;
	{
		delete ptobedelete;
		*head = NULL;
	}//链表的结点有很多个,要删除最后一个结点
	else if(ptobedelete->next == NULL)
	{
		listNode* pr = *head;
		while(pr->next!= ptobedelete)
			pr = pr->next ;
		pr->next= NULL;
		delete ptobedelete;
		ptobedelete = NULL;
	}//删除中间结点
	else
	{
		listNode* pnext = ptobedelete->next;
		ptobedelete->m_nvalue=pnext->m_nvalue;
		ptobedelete->next=pnext->next;
		delete pnext;
		pnext=NULL;
	}
}
面试题:实现一个函数将字符串中的每个字符都替换成%20.


//如果是从头开始的话,空格后面的字符每次都需要向后移动,所以时间复杂度是0(n^2);
//若从尾开始,并且先计算出空格的数目,那样每个字符只需要移动一次时间复杂度是O(n);
void ReplaceBlank(char string[],int length)
{
	if(string == NULL || length<0)
		return;
	int orignallength=0;
	int numberofblack=0;
	int i=0;
	while(string[i] !='\0')
	{
		++orignallength;
		if(string[i] == ' ')
			++numberofblack;
		++i;
	}
	int newlength=orignallength-numberofblack+numberofblack*3-1;
	int indexoforignal=orignallength-1;
	int indexofnew = newlength;
	while(indexoforignal >=0 && indexofnew >indexoforignal)
	{
		if(string[indexoforignal] != ' ')
		string[indexofnew--] = string[indexoforignal--];
		else
		{
			string[indexofnew--]='0';
			string[indexofnew--]='2';
			string[indexofnew--]='%';
			--indexoforignal;
		}
	}


}
int main()
{
	char string[20]="we";
	int length = sizeof(string)/sizeof(char);
	ReplaceBlank(string,length);
	for(int i=0;i<length;++i)
		cout<<string[i]<<endl;
	return 0;
}
面试题:从尾到头打印链表


//1.一般的思路是将链表遍历一遍,先将链表逆置,然后在从头到位打印结点;
//这样做有一个问题是打印一般是只读操作,如果按照1思路就会把链表的结构修改了
//2.定义一个栈,先遍历一遍链表然后依次存入栈中,然后从栈中把元素取出,就是把链表逆置了。
#include<stack>
struct listnode
{
	int m_nkey;
	listnode* m_pnext;
};
void PrintListRevers(listnode* head)
{
	stack<listnode*> st;
	listnode* pnode = head;
	while(pnode !=NULL)
	{
		st.push(pnode);
		pnode =pnode->m_pnext;
	}
	while(!st.empty())
	{
		pnode = st.top();
		cout<<pnode->m_nkey<<"--->";
		st.pop();
	}
}
面试题:重建二叉树
//重建二叉树
//给定二叉树的前序遍历和中序遍历请重建该二叉树
struct BinaryTreeNode
{

	int  m_pvalue;
	BinaryTreeNode* m_pleft;
	BinaryTreeNode* m_pright;
};

BinaryTreeNode* constructcore(int *startpreorder,int *endpreorder,int *startinorder,int *endinorder);
BinaryTreeNode* construct(int *preorder,int *inorder,int length)
{
	if(preorder == NULL || inorder == NULL || length<=0)
		return NULL;
	else
		return constructcore(preorder,preorder+length-1,inorder,inorder+length-1);
}
BinaryTreeNode* constructcore(int *startpreorder,int *endpreorder,int *startinorder,int *endinorder)
{
	int rootvalue = startpreorder[0];
	BinaryTreeNode* root = new BinaryTreeNode();
	root->m_pvalue = rootvalue;
	root->m_pleft = root->m_pright = NULL;
	if(startpreorder == endpreorder)
	{
		if(*startpreorder == *endpreorder)
		{
			return root;
		}
		else
			throw std::exception("Inval input");

	}
	//在中序遍历中找到根结点
	int *rootinorder = startinorder;
	while(rootinorder<= endinorder && *rootinorder!= rootvalue)
	  ++rootinorder;
	if(rootinorder == endinorder && * rootinorder != *endinorder)
	{
        throw std::exception("Invalid input");
	}
	int length = rootinorder-startinorder;
	int * leftpreorderend = startpreorder+length;
	if(length > 0)
	{
		//构建左子树
		root->m_pleft = constructcore(startpreorder+1,leftpreorderend,startinorder,rootinorder-1);
	}
	if(startpreorder +length  <endpreorder)
	{
		//构建右字树
		root->m_pright = constructcore(leftpreorderend+1,endpreorder,rootinorder+1,endinorder);
	}
	return root;
}
int main()
{
	int preorder[]={1,2,4,7,3,5,6,8};
	int inorder[]={4,7,2,1,5,3,8};
	int length = sizeof(preorder)/sizeof(int);
	BinaryTreeNode* root= construct(preorder,inorder,length);

	return 0;
}
面试题14 调整数组顺序,使得奇位数位于偶数前面
题目:给定一个数组,实现一个函数,使得数组的奇数位在前面,偶数位在后面。
一般的思路:
1、扫描数组,每次遇到偶数,把该偶数后面的数据迁移,
2、并且再次判断这个位置的数字,执行步骤1;
这样做的时间复杂度是O(n^2);
提高效率的算法:
用俩个指针,一个指向数组的开头,它指向后移动,第二个指针初始化指向数组的最后一个,它指向前移动,
在俩个指针相遇的之前,第一个指针总是位于第二个指针的前面,如果第一个指针指向偶数,第二个指针指向奇数,则交换俩个数字。
#include<iostream>
using namespace std;
void Reorderoddevent(int *pData,int length)
{
	if(pData == NULL && length<0)
		return;
	int *pBegin = pData;
	int *pEnd = pData+length-1;
	while(pBegin<pEnd)
	{
		if((*pBegin % 2==0) && (*pEnd %2 ==1))
		{
			int temp=*pBegin;
			*pBegin=*pEnd;
			*pEnd = temp;
		    pBegin++;
			pEnd--;
		}
		pBegin++;
	}

}
int main()
{
	int data[]={1,4.7,11,8,13,2,10,9,5,19};
	int length = sizeof(data)/sizeof(int);
	Reorderoddevent(data,length);
	for(int i =0;i<length;++i)
	{
		cout<<data[i]<<" ";
	}
	return 0;
}	
面试题 15 链表中倒数第K个结点
#if 1
struct ListNode
{
	int m_nValue;
	ListNode* m_pnext;
};
ListNode* FindKtotail(ListNode * phead,unsigned int x)
{
	if(phead == NULL || x == 0)//我们规定倒数第1结点是尾结点。
		return NULL;
    ListNode* pAhead = phead;
	ListNode*pBehind = phead;
	for(int i=0;i<x-1;++i)
	{
		if(pAhead->m_pnext !=NULL)//链表的总结点数一定要大于倒数的第x个结点;
			pAhead = pAhead->m_pnext ;
		else
			return NULL;
	}
	while(pAhead->m_pnext != NULL)
	{
		pAhead = pAhead->m_pnext ;
		pBehind = pBehind->m_pnext ;
	}
	return pBehind;

}
#endif
面试题17 合并俩个已经排序的链表,使得新链表的顺序依然有序。应该考虑代码的健壮性,当输入的其中之一的链表是空链表的时候
应当返回另一个链表。
struct ListNode
{
	int m_nvalue;
	ListNode* m_pnext;
};
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 )
			pMergeHead->m_pnext = Merge(pHead1->m_pnext ,pHead2);
		else
			pMergeHead->m_pnext =Merge(pHead1,pHead2->m_pnext );
		return pMergeHead;
}
面试题18 , 树的子结构
//重建二叉树
//给定二叉树的前序遍历和中序遍历请重建该二叉树
struct BinaryTreeNode
{

	int  m_pvalue;
	BinaryTreeNode* m_pleft;
	BinaryTreeNode* m_pright;
};

BinaryTreeNode* constructcore(int *startpreorder,int *endpreorder,int *startinorder,int *endinorder);
BinaryTreeNode* construct(int *preorder,int *inorder,int length)
{
	if(preorder == NULL || inorder == NULL || length<=0)
		return NULL;
	else
		return constructcore(preorder,preorder+length-1,inorder,inorder+length-1);
}
BinaryTreeNode* constructcore(int *startpreorder,int *endpreorder,int *startinorder,int *endinorder)
{
	//前序遍历的第一个值为根结点。
	int rootvalue = startpreorder[0];
	BinaryTreeNode* root = new BinaryTreeNode();
	root->m_pvalue = rootvalue;
	root->m_pleft = root->m_pright = NULL;
	if(startpreorder == endpreorder)
	{
		if(*startpreorder == *endpreorder)
		{
			return root;
		}
		else
			throw std::exception("Inval input");

	}
	//在中序遍历中找到根结点
	int *rootinorder = startinorder;
	while(rootinorder<= endinorder && *rootinorder!= rootvalue)
	  ++rootinorder;
	if(rootinorder == endinorder && * rootinorder != *endinorder)
	{
        throw std::exception("Invalid input");
	}
	int length = rootinorder-startinorder;
	int * leftpreorderend = startpreorder+length;
	if(length > 0)
	{
		//构建左子树
		root->m_pleft = constructcore(startpreorder+1,leftpreorderend,startinorder,rootinorder-1);
	}
	if(startpreorder + length  <endpreorder)
	{
		//构建右字树
		root->m_pright = constructcore(leftpreorderend+1,endpreorder,rootinorder+1,endinorder);
	}
	return root;
}
bool DoesTree1hasTree2(BinaryTreeNode* phead1,BinaryTreeNode* phead2)
{
	if(phead2==NULL)
		return true;
	if(phead1 ==NULL)
		return false;
		if(phead1->m_pvalue != phead2->m_pvalue)
			return false;
		else
		{
			return DoesTree1hasTree2(phead1->m_pleft ,phead2->m_pleft)&&DoesTree1hasTree2(phead1->m_pright ,phead2->m_pright);
		}
}
判断是否有子树
bool HasSubTree(BinaryTreeNode* pHead1,BinaryTreeNode* pHead2)
{
	bool result = false;
	if(pHead1!=NULL && pHead2 != NULL)
	{
		if(pHead1->m_pvalue == pHead2->m_pvalue )
		{
			result = DoesTree1hasTree2(pHead1,pHead2);
		}
		if(!result)
		{
			result = HasSubTree(pHead1->m_pleft ,pHead2);
		}
		if(!result)
		{
			result = HasSubTree(pHead1->m_pright ,pHead2);
		}
	}
	return result;
}
int main()
{
	int preorder[]={1,2,4,7,3,5,6,8};
	int inorder[]={4,7,2,1,5,3,6,8};
	int length = sizeof(preorder)/sizeof(int);
	BinaryTreeNode* root = construct(preorder,inorder,length);
	int preorder1[]={3,5,6,8};
	int inorder1[]={5,3,6,8};
	int length1 = sizeof(preorder)/sizeof(int);
	BinaryTreeNode* root1 = construct(preorder1,inorder1,length1);
	bool result = HasSubTree(root,root1);
	if(result)
	{
		cout<<"yes"<<endl;
	}
	else
		cout<<"No"<<endl;

	return 0;
}













































                
内容概要:本文深入探讨了Kotlin语言在函数式编程和跨平台开发面的特性和优势,结合详细的代码案例,展示了Kotlin的核心技巧和应用场景。文章首先介绍了高阶函数和Lambda表达式的使用,解释了它们如何简化集合操作和回调函数处理。接着,详细讲解了Kotlin Multiplatform(KMP)的实现式,包括共享模块的创建和平台特定模块的配置,展示了如何通过共享业务逻辑代码提高开发效率。最后,文章总结了Kotlin在Android开发、跨平台移动开发、后端开发和Web开发中的应用场景,并展望了其未来发展趋势,指出Kotlin将继续在函数式编程和跨平台开发领域不断完善和发展。; 适合人群:对函数式编程和跨平台开发感兴趣的开发者,尤其是有一定编程基础的Kotlin初学者和中级开发者。; 使用场景及目标:①理解Kotlin中高阶函数和Lambda表达式的使用法及其在实际开发中的应用场景;②掌握Kotlin Multiplatform的实现式,能够在多个平台上共享业务逻辑代码,提高开发效率;③了解Kotlin在不同开发领域的应用场景,为选择合适的技术栈提供参考。; 其他说明:本文不仅提供了理论知识,还结合了大量代码案例,帮助读者更好地理解和实践Kotlin的函数式编程特性和跨平台开发能力。建议读者在学习过程中动手实践代码案例,以加深理解和掌握。
内容概要:本文深入探讨了利用历史速度命令(HVC)增强仿射编队机动控制性能的法。论文提出了HVC在仿射编队控制中的潜在价值,通过全面评估HVC对系统的影响,提出了易于测试的稳定性条件,并给出了延迟参数与跟踪误差关系的显式不等式。研究为两轮差动机器人(TWDRs)群提供了系统的协调编队机动控制案,并通过9台TWDRs的仿真和实验验证了稳定性和综合性能改进。此外,文中还提供了详细的Python代码实现,涵盖仿射编队控制类、HVC增强、稳定性条件检查以及仿真实验。代码不仅实现了论文的核心思想,还扩展了邻居历史信息利用、动态拓扑优化和自适应控制等性能提升策略,更全面地反映了群体智能协作和性能优化思想。 适用人群:具备一定编程基础,对群体智能、机器人编队控制、时滞系统稳定性分析感兴趣的科研人员和工程师。 使用场景及目标:①理解HVC在仿射编队控制中的应用及其对系统性能的提升;②掌握仿射编队控制的具体实现法,包括控制器设计、稳定性分析和仿真实验;③学习如何通过引入历史信息(如HVC)来优化群体智能系统的性能;④探索中性型时滞系统的稳定性条件及其在实际系统中的应用。 其他说明:此资源不仅提供了理论分析,还包括完的Python代码实现,帮助读者从理论到实践全面掌握仿射编队控制技术。代码结构清晰,涵盖了从初始化配置、控制律设计到性能评估的各个环节,并提供了丰富的可视化工具,便于理解和分析系统性能。通过阅读和实践,读者可以深入了解HVC增强仿射编队控制的工作原理及其实际应用效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值