剑指offer(C语言)41-50

PS:有些题是怎么降低一些本身很简单的题的时间复杂度,空间复杂度,对我而言没什么意义,也不是搞算法的。就选择性略过了

41 数组流中的中位数

题目:如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
思路:最直观的就是排序,排序的时间复杂度是O(nlogN),寻找复杂度是O(1).插入时时间复杂度为O(N)
优化一:其实不用完全排列好,只要保证左边比他小,右边比他大就好了。使用前面的Partition寻找中位数(采用快排的思路),可以将时间复杂度降为O(N),插入要排序,所以时间复杂度是O(N).
优化二:使用二叉搜索树,可以把插入新数据的时间复杂度降到O(log(n)),找中位数的复杂度也是O(log(n))。这个时间复杂度是平均时间复杂度,当二叉树严重失衡时,两个时间复杂度都可能降为O(N)
优化三:二叉树不好就用AVL(平衡二叉树),保证树的左右子树高度差不高于1,有了这个改动,可以花O(log(n))时间往书中添加一个新节点,这样寻找中位数的时间为O(1).(AVL树不好写,不推荐)
优化四:既然是中位数,可以把数据平均分成两部分,保证左边部分都比右边部分小。然后左边能随时得到最大值,右边能随时得到最小值,这可以用堆的数据结构。注意一些细节:1.如何实现平均分配:可以第奇数个往左分,第偶数个往右分。2.如何保证新插入的数据不打乱左边部分比右边部分小:插完之后将左右堆顶的数据比较,如果反了就交换一下。小顶堆没问题,大顶堆重构一下。

42 连续子数组的最大和

题目:输入一个整型数组,数组里有正数也有负数。数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。要求时间复杂度为O(N)
思路:最好想的思路是枚举中所有种可能,排列组合,n×(n-1)/2,不满足。
优化一:从第一个数开始,每增加一个数就有可能引起当前最大和的变化,变化的情况主要有这么几种:1.新加入的值比之前的最大值大 2.新加入的值比加上之前最大的累加值,比最大值大。所以我们需要记录两个数,一个是当前的最大值,一个是当前的累加最大值(因为是连续的,所以只有累加值才能加上新的值)有点小细节,如果数组中的数都是负数,如果按普通逻辑把初始值设为0,那么最后算出的最大值就是0了。所以有两种方法,一种是初始化为最小的负数,而是初始化为第一个数。
其实上面的做法就是动态规划的思想,使用f(i)表示以第i个数字结尾的子数组的最大和,我们要求的是max(f(i)),可以用以下递归公式求f(i):
在这里插入图片描述
这个公式得到的f(i)就是curSum ,然后greatSum就是max(f(i)),两种思路异曲同工。

#include <stdio.h>
#include <stdlib.h>
#define bool unsigned int
#define true 1
#define false 0
#define none 2

int FindGreatSum(int* data,int length)
{
	int curSum;
	int greatSum;
	int i;
	if(data==NULL || length<=0)return;

	curSum = data[0];
	greatSum = data[0];
	for(i=1;i<length;i++)
	{
		if(curSum<=0)
			curSum = data[i];
		else
			curSum+=data[i]; 
		greatSum = (greatSum>curSum?greatSum:curSum);
	}
	return greatSum;
}

void main()
{
	int data[] = {1,-2,3,10,-4,7,2,-5};
	//int data[] = {-1,-2,-3,-5,-4};
	printf("%d",FindGreatSum(data,sizeof(data)/sizeof(int)));
}

43 1-n整数中1出现的次数

题目:输入一个整数n,求1-n这n个整数的十进制表示中1出现的次数。例如,输入12,1-12这些整数中包含1的数字有1、10、11和12,1一共出现了5次
思路:

#include <stdio.h>
#include <stdlib.h>
#define bool unsigned int
#define true 1
#define false 0
#define none 2

int NumberOf1(unsigned int n)
{
	int count=0;
	while(n)
	{
		if((n%10)==1)
			count++;
		n = n/10;
	}
	return count;
}
int NumberOf1Between1AndN(unsigned int n)
{
	int i = 0;
	int count = 0;
	for(i=1;i<=n;i++)
	{
		count+=NumberOf1(i);
	}
	return count;
}
int main()
{
	int count = NumberOf1Between1AndN(100);
	printf("%d",c

44-46略

47 礼物的最大价值

题目:在一个m×n的棋盘的每一格斗放有一个礼物,每个礼物都有一定的价值(价值大于0).你可以从棋盘的左上角开始拿格子里的礼物,并每次向左或者向下移动一格,直到到达棋盘的右下角。给定一个棋盘及其上面的礼物,请计算你最多能拿到多少价值的礼物
思路:动态规划类问题,每一个格子的礼物,只跟他左边和上边的数值有关,所以从左上角开始遍历,新建一个二维数组用来存储当前位置的最大礼物。最大礼物是左边和上边的最大值加上该位置的值。

#include <stdio.h>
#include <stdlib.h>
#define bool unsigned int
#define true 1
#define false 0
#define none 2

int FindGreatSum(int* values,int rows,int cols)
{
	int i,j;
	int left,up;
	int max;
	int **maxValues = (int**)malloc(sizeof(int*)*rows);
	if(values == NULL||rows<0||cols<0)return;
	for(i=0;i<rows;i++)
		maxValues[i] = (int*)malloc(sizeof(int)*cols);
	for(i=0;i<rows;i++)
	{
		for(j=0;j<cols;j++)
		{
			left = 0;
			up = 0;
			if(i>0)
				up = maxValues[i-1][j];
			if(j>0)
				left = maxValues[i][j-1];
			if(up>left)
				maxValues[i][j]=up + values[i*cols+j];
			else
				maxValues[i][j]=left + values[i*cols+j];
		}
	}
	max = maxValues[rows-1][cols-1];
	for(i=0;i<rows;i++)
		free(maxValues[i]);
	free(maxValues);
	return max;
}
void main()
{
	int max;
	int values[] = {1,10,3,8,12,2,9,6,5,7,4,11,3,7,16,5};
	max = FindGreatSum(values,4,4);
	printf("%d",max);
}

48 最长不含重复字符的子字符串

题目:请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长字符串的长度。假设字符串中只包含a-z的字符。例如,在字符串“arabcacfr”中,最长的不含重复字符的子字符串是“acfr”,长度为4
思路
暴力法:一个长度为n的字符串中有n×(n-1)/2个子字符串,把所有字符串遍历一遍,然后先判断有无重复字符,如果没有再更新当前最长长度。
优化
动态规划:找到字符串长度从1到n的包含最后一个字符的最长长度,一定要注意不是最长长度,是包含最后一个字符的最长长度。因为最长长度一定是某种情况中包含最后一个字符的长度,而且只有包含最后一个字符,才能建立起新加一个字符后的最长长度。
所以这道题就变成了这样的步骤:
1.判断前面是否有该字符
2.如果有该字符,判断该字符与当前字符之间的距离d与当前长度之间的关系,也就是判断当前长度有没有包含这个重复字符。比如abcdaeb,当查到第二个b时,当前最长长度为2.向前查找两个数,没找到b,所以可以把b加进来
2.1 另一种思路就是从当前位置向前查到当前长度个数,看看有没有,比如abcdaeb,数组中第二个存的位置为1,当前位置为6,
2.2 还有一种优化,建立一个数组,存放最近的一个字符出现的位置,每次去查位置就好了。比如abcdaeb,数组中第二个存的位置为1,当前位置为6,差为5,但是长度只为2,所以没影响。
3.如果发现该字符在之前的当前长度包含中,那么要重新更新当前长度,长度为该字符两个位置的差值。比如abcdaeb,找到第二个a时,当前长度更新为4-0为4,也就是bcda
4.过程中不断更新最大长度,让他成为所有当前长度中的最大值。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define bool unsigned int
#define true 1
#define false 0
#define none 2

int LontestSubstring(char* str)
{
	int i;
	int curLength = 0;
	int maxLength = 0;
	int position[26];
	int preIndex;
	if(str==NULL)return false;
	for(i=0;i<26;i++)position[i]=-1;
	for(i=0;i<strlen(str);i++)
	{
		preIndex = position[str[i]-'a'];
		if(preIndex<0 || i-preIndex>curLength)
			curLength++;
		else
			curLength = i-preIndex;
		position[str[i]-'a'] = i;
		if(curLength>maxLength)maxLength = curLength;
	}
	return maxLength;
}
void main()
{
	char str[] = "abcacfrar";
	printf("%d",LontestSubstring(str));
}

49 丑数

题目:我们把只保函因子2 3 5 的数成为丑数。求按从小到的的顺序的第1500个丑数。例如,6 8 都是丑数,但14不是,因为它包含因子7.习惯上我们把1当作第一个丑数
思路:把i从1开始增长,每个数都判断是不是丑数,也就是不断的除以2 3 5 ,看最后是不是1.是丑数就把Index加1,等于第n个丑数时结束。这首思路很直观,但是缺点就是当n太大的时候,计算时间非常长,当n=1500时程序要运行30S
优化:之所以费时间是因为把所有数都要判断是不是丑数,可以换一种思路,所有丑数都是前面的某个丑数×2或者3或者5得到的,所以我们可以拿一个数组存储所有丑数。但是怎么保证这个数组时有序的呢。可以存储每一个×2大于当前最大的丑数,×3大于当前最大的丑数,×5大于当前最大丑数的值,然后取这三个数分别×2 3 5 中最小的那个。比如说现在丑数是1,×2 3 5 比当前丑数大的都是1,取三个里面最小的那个是2,第二个丑数为2;现在1×2不比2大了,所以把乘2的这个数更新为2,然后比较3(1×3),4(2×2),5(1×5)这三个谁最小,3最小,第三个丑数为3.再把乘三的这个数更新为2,这样一直往下找就能按顺序找出所有的丑数了,以空间换时间
两种方法得到的结果一致在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define bool unsigned int
#define true 1
#define false 0
#define none 2

bool IsUgly(int number)
{
	while(number%2==0)number/=2;
	while(number%3==0)number/=3;
	while(number%5==0)number/=5;
	return (number==1)?true:false;
}

int GetUglyNumber1(int index)
{
	int number = 0;
	int uglyFound = 0;
	if(index<1)return 0;
	while(uglyFound<index)
	{
		number++;
		if(IsUgly(number))
			uglyFound++;
	}
	return number;
}

int GetUglyNumber2(int index)
{
	int *pUglyNumbers = (int*)malloc(sizeof(int)*index);
	int uglyIndex = 1;
	int* pMultiply2 = pUglyNumbers;
	int* pMultiply3 = pUglyNumbers;
	int* pMultiply5 = pUglyNumbers;
	int ugly;
	if(index<1)return 0;
	pUglyNumbers[0] = 1;
	while(uglyIndex<index)
	{
		pUglyNumbers[uglyIndex] = *pMultiply2*2;
		if(*pMultiply3*3<pUglyNumbers[uglyIndex])pUglyNumbers[uglyIndex] = *pMultiply3*3;
		if(*pMultiply5*5<pUglyNumbers[uglyIndex])pUglyNumbers[uglyIndex] = *pMultiply5*5;

		while(*pMultiply2*2<=pUglyNumbers[uglyIndex])
			pMultiply2++;
		while(*pMultiply3*3<=pUglyNumbers[uglyIndex])
			pMultiply3++;
		while(*pMultiply5*5<=pUglyNumbers[uglyIndex])
			pMultiply5++;
		uglyIndex++;
	}
	ugly = pUglyNumbers[index-1];
	free(pUglyNumbers);
	return ugly;
}
void main()
{
	printf("%d\n",GetUglyNumber1(1500));
	printf("%d",GetUglyNumber2(1500));
}

50-1 第一个只出现一次的字符

题目:在字符串中找出第一个只出现一次的字符。如输入“abaccdeff”,则输出b
思路:从头到尾遍历字符串,然后锁定该字符,再从头到尾遍历,如果在除了该位置的所有位置都没出现这个字符,就把他输出。时间复杂度是O(N2)
优化:建立哈希表,由于char只有256个字符,所以可以建立256大小的简单哈希表。第一次遍历时,用哈希表存储每个字符出现的次数,第二次遍历时区查表,要是次数为1,就输出。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define bool unsigned int
#define true 1
#define false 0
#define none 2

char FindNotRepeatingChar2(char* str)
{
	int length = strlen(str);
	int i;
	int hash[256];
	if(str == NULL)return '\0';
	for(i=0;i<256;i++)hash[i]=0;
	for(i=0;i<length;i++)
	{
		hash[str[i]]++;
	}
	for(i=0;i<length;i++)
	{
		if(hash[str[i]]==1)return str[i];
	}
	return '\0';
}

char FindNotRepeatingChar1(char* str)
{
	int length = strlen(str);
	int i;
	int j;
	char temp;
	if(str == NULL)return '\0';
	for(i=0;i<length;i++)
	{
		temp = str[i];
		for(j=0;j<length;j++)
		{
			if(str[j]==temp && j!=i)break;
		}
		if(j==length)return temp;
	}
	return '\0';
}
void main()
{
	char* str = "abaccdeff";
	printf("%c\n",FindNotRepeatingChar1(str));
	printf("%c\n",FindNotRepeatingChar2(str));
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值