参考自:https://blog.csdn.net/insistGoGo/article/details/7749328
一、给出一个顺序文件,它最多包含40亿个随机排列的32位整数。
问题:找出一个不在文件中的32位整数。
注意:题目中没有说,这40亿个数是否是含有重复的数据
条件限制:
1、如果有足够的内存,如何处理?
2、如果内存仅有上百字节(内存不足)且 可以用若干外部临时文件,如何处理?
二、类似字符串循环移位
举例:比如abcdef 左移三位,则变成defabc
条件限制:空间限制:可用内存为几十字节
时间限制:花费时间与n成比例
三、给出一个英语字典,找出所有变位词集。
举例:abc 的变位词为:abc acb bca bac cba cab
~~~~~~~~~~~~~~~~~~分界线~~~~~~~~~~~~~~~~~~~~
一、给出一个顺序文件,它最多包含40亿个随机排列的32位整数
问题:找出一个不在文件中的32位整数。
条件限制:
1、如果有足够的内存,如何处理?
2、如果内存仅有上百字节(内存不足)且 可以用若干外部临时文件,如何处理?
思路:由于2^32 = 4 294 967 296个数据,超过给出的40亿,所以肯定有数据不在文件中。
1、如果给出的内存是没有限制的
思想:我们可以使用位图法,寻找不在文件的数
申请的空间为(2^32)/8 = 最大数/一个字节可以表示8位 = 2^29B = 2^9MB = 512MB内存
之后,使用位图法。此时出现次数为0的数据为缺失的整数
2、如果内存仅有上百字节(内存不足)且 可以用若干外部临时文件,如何处理?
思想:类似二分查找。可以根据某一位(操作时,可以从最高位 到 最低位依次处理),把待处理的数据分成两部分。在一部分中,此位为0,另一部分此位为1。
之后,分别统计落在两个部分的数的个数。(此时我们不考虑数据是否重复)
如果,没有缺失,那么这两部分数的个数应该是相等的。
如果,数据有缺失,那么两部分数可能相等,也可能不等
两部分相等的情况:两段都缺失,但缺失的个数相等
两部分不等的情况:一个缺一个不缺 或 都缺但缺的个数不同
基于此,把数据分成两部分后,我们可以去数据量小的那个部分寻找缺失的数。如果两个部分的数相等,我们可以随意选择一个部分寻找缺失的数据。此时,可以递归的处理紧挨着的下一位。直到遍历完所有的位后,就可以找到一个缺失的数
package Algorith.编程珠玑.第二章;
/**
* @Classname 找缺失的数
* @Description TODO
* @Date 2019/4/6 8:48
* @Created by 大大
*/
public class 找缺失的数 {
/**
* 这里使用数组存,也可以将其写入文件,再读取
* @param maxBits 这个数占字节的比特位
* @param len 这个数的长度
**/
public static int FindLostNum(int arr[],int len,int maxBits)
{
int lostNum = 0;
int checkNum = 0;
//记录两个数组存放数值的个数
int locZero = 0;
int locOne = 0;
//最开始设置数组分别存放开端位是0
int[] arrZero = new int[len];
//最开始设置数组分别存放开端位是1
int[] arrOne = new int[len];
//最高位分完,再分第二位,依次,总的思二分查找
for (int bit = maxBits - 1;bit >= 0;bit--)
{
locZero = 0;
locOne = 0;
checkNum = 1 << bit;
for (int i = 0;i < len;i++)
{
if (((arr[i] & checkNum)!=0)?true:false)//条件成立,该位为1
{
arrOne[locOne++] = arr[i];
}
else
{
arrZero[locZero++] = arr[i];
}
}
if (locOne > locZero)//该bit位上是1的总数 大于 是0的总数
{
arr = arrZero;
len = locZero;
}
else
{
lostNum += checkNum;
arr = arrOne;
len = locOne;
}
}
return lostNum;
}
public static void main(String[] args) {
/*int len = 10;
int maxBits = 4;
int arr[10] = {1,2,3,4,5,6,7,9,0};
*/
/*int len = 1;
int maxBits = 1;
int arr[10] = {1};*/
int len = 15;
int maxBits = 4;
int []arr = {0,1,2,3,4,5,6,7,7,9,10,11,13,14,15};
int lostNum = 找缺失的数.FindLostNum(arr,len,maxBits);
System.out.println(lostNum);
}
}
二、类似字符串循环移位
举例:比如abcdef 左移三位,则变成defabc
条件限制:空间限制:可用内存为几十字节
时间限制:花费时间与n成比例
方法一:临时数组方法
将前n个元素复制到一个临时数组,再将后面的依次前移,最后将临时数组加上,时间复杂度O(n),但需要额外的n空间
方法二:1位1位旋转,
如abcd->bcda->cdab->dabc 不需要额外的空间,但时间复杂度O(n^2)
方法三、求逆
直观的想法:先局部翻转,在整体翻转
字符串abcdefgh->cba defgh ->cba hgfed -> defghabc 时间复杂度O(n)
需要三步:
reverse(0,i-1); //cba defgh
reverse(i,n-1); //cba hgfed
reverse(0,n-1); //defghabc
代码:
#include <iostream>
#include <assert.h>
#include <sstream>
using namespace std;
void Reverse(char* str,int start,int end)
{
assert(str != NULL);
char tmp;
int mid = (start + end)/2;
for (int i = start,j = end;i <= mid;i++,j--)
{
tmp = str[i];
str[i] = str[j];
str[j] = tmp;
}
}
/*把字符串循环左移k位*/
void LeftRotateString(char* str,int k)
{
assert(str != NULL && k > 0);
int strLen = strlen(str);
Reverse(str,0,k-1);
Reverse(str,k,strLen-1);
Reverse(str,0,strLen-1);
}
int main()
{
char str[81] = "abcde";
//char str[81] = "abcdef";
LeftRotateString(str,3);
cout<<str<<endl;
system("pause");
return 1;
}
方法四、杂耍算法
举例:原串:0123456789 结果:3456789012
直观的想法:由于要对数组整体向左循环移动k位,那么我们就可以对数组中的每一个元素向左移动k位(超过数组长度的可以取模回到数组中),此时总是能获得结果的。如(0-3+10)%10=7表明下标为0移动到数组下标为7的位置,(3-3)%10=0表明移动到数组下标为0的位置
步骤:(k表示循环移动的位数)
1)先将x[0]移到临时变量t中
2)将x[k]移动到x[0]中,x[2k]移动到x[k]中,依次类推
3)将x中的所有下标都对n取模,直到我们又回到从x[0]中提取元素。不过这时我们从t中提取元素,结束。
循环的终止条件:当我们要从循环的起始位置点中提取元素时,此次循环结束
由于k,2k...之间的偏移量是相同的,所以整个操作实际上就是讲序列向左移动k个位置
注意:从下标0开始,按照上述步骤移动位置时,一次循环并不一定能够把所有数移到目标位置。这还与n和k是否互质有关
如果,n与k互质,从0开始,每一个元素向前移动k个位置,一次循环就可以处理完所有元素,最后一个元素会从0位置取元素
如果,n与k不互质,仅仅从0开始,每次向前移动k个位置。终止时是不能把所有元素放到目的地的。这是要需要进行gcd(n,k)次循环。即第一次是从0开始,每次向前移动k个位置,直至循环结束。第二次是从1开始,每次向前移动k个位置,直至循环结束。第三次...直到第gcd(n,k)-1次。而且每次循环的最后一个元素都会回到该循环的起点
我们这里把包含gcd(n,k)的元素称为一段,可以看出程序需要进行gcd(n,k)循环才能够把所有数移到目标位置
#include <iostream>
#include <assert.h>
#include <sstream>
using namespace std;
/*获取m和n的最大公约数*/
int gcd(int m,int n)
{
int tmp;
if (m < n)
{
tmp = m;
m = n;
n = tmp;
}
if (m % n == 0)
{
return n;
}
else
{
return gcd(n,m%n);
}
}
//*把字符串循环左移k位*/
void LeftRotateString(char* str,int k)
{
assert(str != NULL && k > 0);
int strLen = strlen(str);
int gcdNum = gcd(strLen,k);
for (int i = 0;i < gcdNum;i++)
{
int first = i;
int next = (first + k) % strLen;
char tmp = str[i];//每段的起始位置,注意不能写成0
while(next != i)
{
str[first] = str[next];
first = next;
next = (first + k) % strLen;
}
str[first] = tmp;//临时变量中存储每一趟的循环的最后一个字符
}
}
int main()
{
//char str[81] = "abcde";
char str[81] = "abcdef";
LeftRotateString(str,3);
cout<<str<<endl;
system("pause");
return 1;
}
三、给出一个英语字典,找出所有变位词集。
举例:abc 的变位词为:abc acb bca bac cba cab
思想:
1、为每一个单词生成一个标签,并使所有的变位词有相同的标签
2、根据标签收集单词,每一个标签对应一个集合,这个集合包括其所有的变位词
每个单词标签的生成:把这个单词所包含的字母,按照字母顺序排列。这样,所有变位词的标签就全相等了。
如mississippi可以写成i4m1p2s4
可以用键值对记录每个字符出现次数,变位词不管则么变,每个字符出现次数是固定的只是顺序不同