剑指offer--算法(7、14、16、21、25、28、59、37、43、51、46、58)

offer7

输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。句子中单词以空格符隔开。为简单起见,标点符号和普通字母一样处理。


例如输入“I am a student.”,则输出“student. a am I”。

可以采用一个栈实现,单词和空格分开处理,但是太麻烦了。

下面是博主的思路

由于本题需要翻转句子,我们先颠倒句子中的所有字符。这时,不但翻转了句子中单词的顺序,而且单词内字符也被翻转了。我们再颠倒每个单词内的字符。由于单词内的字符被翻转两次,因此顺序仍然和输入时的顺序保持一致。

非常实用。而且不需要额外的空间。


void Reverse(char* pBegin, char* pEnd)
{
	if (pBegin==NULL || pEnd==NULL || pBegin>pEnd)
		return;
	char temp;
	//交换pBegin和pEnd之间的字符
	while (pBegin<=pEnd)
	{
		temp=*pBegin;
		*pBegin=*pEnd;
		*pEnd=temp;
		pBegin++;
		pEnd--;
	}
}

void ReverseSentense(char* pS)
{
	int len=strlen(pS);
	char *pBegin,*pEnd;
	pBegin=pS;
	pEnd=pBegin+len-1;
	//先反转句子
	Reverse(pBegin, pEnd);

	pBegin=pEnd=pS;
	while (*pBegin!='\0')
	{
		//空格不反转,直接跳过
		if (*pBegin==' ')
		{
			pBegin++;
			pEnd++;
			continue;
		}
		//到单词结束时,反转单词,并更新pBegin到下一个单词
		else if (*pEnd==' '||*pEnd=='\0')
		{
			pEnd--;
			Reverse(pBegin, pEnd);
			pBegin=++pEnd;
		}
		//pEnd还在单词内部,继续寻找单词结束
		else
			pEnd++;
	}
}

 

offer14

n个数字(0,1,…,n-1)形成一个圆圈,从数字0开始,每次从这个圆圈中删除第m个数字(第一个为当前数字本身,第二个为当前数字的下一个数字)。当一个数字删除后,从被删除数字的下一个继续删除第m个数字。求出在这个圆圈中剩下的最后一个数字

经典的约瑟夫问题,链表或者数组实现就不再说了,下面是一种非常牛逼的解法,真是佩服的五体投地。

先定义一个函数 f(n,m)实现从0,1,…,n-1中计算约瑟夫剩下的最后一个值,

然后假设0,1,…,n-1第一轮删除的值是k,继续定义一个函数 g(n-1,m) 实现从k+1开始的n-1个数组成的环中实现约瑟夫,返回的是剩下的编号。

那么有  f(n,m)=g(n-1,m)

其实 f 和 g 的区别就是对编号的计数方式不同而已,现在考虑g(n-1,m)和f(n-1,m)的不同。

我们已经知道第一轮删除的值是k,假设g(n-1,m)返回的值是x,f(n-1,m)的返回值是y,虽然x!=y,但是他们表示的都是同一个数,从 k+1 开始的第y个数(需超过n后要回环)就是x,x和y的对应关系如下

    0,      1,        2,……,k-1,k,k+1,……,  n-1   ---->x---->g(n-1,m)

n-k-1,n-k,n-k+1,……,n-2,   ,  0 ,……,n-k-2  ---->y---->f(n-1,m)

那么x和y的关系就是 x=(k+1+y)%n,同时我们还值k是第一次删除的值,那么 k=(m-1)%n

那么就有x=(k+1+y)%n=((m-1)%n+1+y)%n=(m+y)%n。

f(n,m)=g(n-1,m)=( f(n-1,m)+m )%n就是一个递归公式了。


offer16

O(logn)求Fibonacci数列

还没有搞懂


offer21

定义字符串的左旋转操作:把字符串前面的若干个字符移动到字符串的尾部。如把字符串abcdef左旋转2位得到字符串cdefab。请实现字符串左旋转的函数。要求时间对长度为n的字符串操作的复杂度为O(n),辅助内存为O(1)

要求空间复杂度为O(1),一般是要求在原字符串上操作,

博客里的算法很赞,

我们还是把字符串看成有两段组成的,记位XY。左旋转相当于要把字符串XY变成YX。我们先在字符串上定义一种翻转的操作,就是翻转字符串中字符的先后顺序。把X翻转后记为XT。显然有(XT)T=X

我们首先对XY两段分别进行翻转操作,这样就能得到XTYT。接着再对XTYT进行翻转操作,得到(XTYT)T=(YT)T(XT)T=YX。正好是我们期待的结

void TurnString(char *s,int len)
{
	if (s!=NULL)
	{
		int i=0;
		char t;
		len--;
		while (i<len)
		{
			t=*(s+i);
			*(s+i)=*(s+len);
			*(s+len)=t;
			i++;
			len--;
		}
	}
}

void LeftRotateString(char *s,int m)
{
	if (s!=NULL)
	{
		int len=strlen(s);
		if (m<=len)
		{
			TurnString(s,m);
			TurnString(s+m,len-m);
			TurnString(s,len);
		}

	}
}

offer25

输入一个整数n,求从1nn个整数的十进制表示中1出现的次数。

编程之美上也有类似的,还没弄明白



offer28

输入一个字符串,打印出该字符串中字符的所有排列。例如输入字符串abc,则输出由字符abc所能排列出来的所有字符串abcacbbacbcacabcba

可以考虑用递归实现,首先选出一个值,和首字母交换,然后输出剩下值的全排列,直到每一个字母都和首字母交换。

这里还需要一个检查函数,用来排除重复字符的情况,加入从首字符开始到当前要交换的字符,已经有和当前要交换字符相同的,那么这个字符就不需要交换了,因为已经交换过了。

bool check(char *pBegin,char *pEnd)
{
	char *p=pBegin;
	while (p!=pEnd)
	{
		if (*p==*pEnd)
			return false;
		p++;
	}
	return true;
}

void pailie(char *a,char *pBegin)
{
	//前面已经全排列玩完毕,输出字符串
	if (*pBegin=='\0')
	{
		printf("%s \n",a);
	}
	else
	{
		for (char *p=pBegin;*p!='\0';p++)
		{   //从pBegin指向的字符开始,之后的每一个字符都和pBegin字符交换位置
			if (check(pBegin,p))
			{
				swap(pBegin,p);
				//从pBegin+1开始全排列
				pailie(a,pBegin+1);
				swap(p,pBegin);
			}
		}

	}
}

offer59

输入一个字符串,输出该字符串中字符的所有组合。举个例子,如果输入abc,它的组合有abcabacbcabc

上一题求排列,这是求组合。假设字符串长度为len,那么我们需要写出 1、2、……、len 不同长度的组合。

在写出长度为M的组合时,我们可以选择首字母然后从剩下的里面选出 m-1 个字符,也可以不选首字符,然后从剩下的字符里面选出M个字符。可以用递归实现。


//该函数实现pStart开始的字符串选择n个字符
//temp是一个全局变量,用来存储已经选择的字符
void Combination(char *pStart,int n)
{
	if (pStart==NULL||n>strlen(pStart))
		return;
	//n为0表示前面已经选择完成,
	if (n==0)
	{
		temp[tempSign]='\0';
		puts(temp);
	//	printf("\n");
		return;
	}
	//选择首字符,从剩下的选择n-1个
	temp[tempSign++]=*pStart;
	Combination(pStart+1,n-1);
	//不选首字符,从剩下的选择n个
	tempSign--;
	Combination(pStart+1,n);
}

void PrintCom(char *pStart,int n)
{
	int i;
	for (i=1;i<=n;i++)
	{
		Combination(pStart,i);
	}
}


offer37

我们把只包含因子2、3和 5 的数称作丑数(Ugly Number)。例如68都是丑数,但14不是,因为它包含因子7。习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第1500个丑数。

我们可以创建一个数组,里面存放着已经找到的丑数。加入现在已经找到第 m 个丑数,那么在寻找 m+1 个丑数时,(m+1)肯定是从 m 之前的丑数中某个数 x2或者x3或者x5得出来的。

假设我们已经知道丑数 T2是最小的 x2收比(m)大的数,T3是最小的 x3收比(m)大的数,T5是最小的 x5收比(m)大的数。那么(m+1)一定是 T2x2 T3x3 T5x5 中的最小值。

找出(m+1),还需要更新T2T3、T5、的值,以便下一次找到(m+2),T2左边的值x2以后小于(m)那么也肯定小于(m+1),我们只需要从 T2 开始,向右找到第一个 x2 后比(m+1)大的值,作为新的 T2,T3和T5同理。

现在我们已知(m+1)和T2T3、T5寻找(m+2)的情况和已知(m)和T2T3、T5寻找(m+1)的情况一样了。

循环很容易,初始状态是 (m)=1,T2T3、T5=1。

int min3(int num1, int num2, int num3)
{
	int min;
	min=num1<num2? num1: num2;
	min=min<num3? min: num3;
	return min;
}

int FindUgly(int Index)
{
	int *Ugly=(int*)malloc(sizeof(int)*Index);
	Ugly[0]=1;
	int nextUglyIndex=1;
	int* pUgly2=Ugly;
	int* pUgly3=Ugly;
	int* pUgly5=Ugly;

	while (nextUglyIndex<Index)
	{
		Ugly[nextUglyIndex]=min3(*pUgly2*2, *pUgly3*3, *pUgly5*5);

		while (*pUgly2*2 <= Ugly[nextUglyIndex])
			pUgly2++;
		while (*pUgly3*3 <= Ugly[nextUglyIndex])
			pUgly3++;
		while (*pUgly5*5 <= Ugly[nextUglyIndex])
			pUgly5++;

		nextUglyIndex++;
	}

	int UglyNum=Ugly[Index-1];
	free(Ugly);
	return UglyNum;
}

offer43

n个骰子扔在地上,所有骰子朝上一面的点数之和为S。输入n,打印出S的所有可能的值出现的概率





offer51

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。

例如:如果输入如下矩阵:

1              2              3              4
5              6              7              8
9              10           11           12
13           14           15           16

则依次打印出数字1, 2, 3, 4, 8, 12, 16, 15, 14, 13, 9, 5, 6, 7, 11, 10

#define WIDTH 4
#define HEIGHT 5

int a[]=
{ 1, 2, 3, 4,
  6, 7, 8, 9,
 11,12,13,14,
 16,17,18,19,
 21,22,23,24};

//这个函数打印从int *p 开始的宽width高height矩阵的最外围
void PrintCircle(int *p,int width,int height)
{
	int i,j;
	for (i=0;i<width;i++)
	{
		printf("%d, ",*(p+i));
	}
	for (j=1;j<height;j++)
	{
		printf("%d, ",*(p+width-1+j*WIDTH));
	}
	for (i=1;i<width;i++)
	{
		printf("%d, ",*((p+width-1+(height-1)*WIDTH)-i));
	}
	for (j=1;j<height-1;j++)
	{
		printf("%d, ",*((p+(height-1)*WIDTH)-j*WIDTH));
	}
}

void PrintAll(int *p,int width,int height)
{
	while (width&&height)
	{
		//先打印矩阵最外围,然后宽度和高度都减去2,编程小矩阵,继续打印,直到宽度或者高度有一个为0
		PrintCircle(p,width,height);
		width-=2;
		height-=2;
		p+=WIDTH+1;
	}
}


offer46

输入一个字符串,输出该字符串中对称的子字符串的最大长度。比如输入字符串“google”,由于该字符串里最长的对称子字符串是“goog”,因此输出4。

曼彻斯特算法的时间复杂度是O(n),搞懂了贴上来。






offer58

8×8的国际象棋上摆放八个皇后,使其不能相互攻击,即任意两个皇后不得处在同一行、同一列或者同一对角斜线上。下图中的每个黑色格子表示一个皇后,这就是一种符合条件的摆放方法。请求出总共有多少种摆法。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


还没搞懂



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值