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));
}