编程珠玑之第二章

1. 问题1

问题描述: 给定一个最多包含40亿个随机排列的32位整数的顺序文件,找出一个不在文件中的32位整数(在文件中至少缺失一个这样的数,为什么?).

(1)在具有足够内存的情况下, 如何解决该问题?

(2)如果有几个外部的"临时"文件可用,但是仅有几百字节的内存, 又该如何解决该问题?

问题思路: 由于2^32 = 4 294 967 296,超过给出的40亿,因此必定存在32位的整数不在此文件中.

(1) 当内存足够的情况下,使用位图法来寻找不在文件中的整数, 即位表示中,出现次数为0的整数.

此时需要的内存空间:(2^32)/8 = 最大数/一个字节可以表示8位 = 2^29B = 2^9MB = 512MB.

(2) 内存不够时,但可以使用外部临时文件, 使用二分查找.

       由于存储数据都是32位的, 可以根据32位中的某一位将原始数据分为两部分. 例如从最高位开始,统计最高位为1的数据个数,即大于等于2^31的元素,并将其存储在一个文件中;同时也统计最高位为0的数据个数,即小于2^31的数,并将其存储在另外一个文件中;

        接着比较最高位为1和最高位为0的个数大小, 若这两部分数的个数一样, 说明两边缺失数据个数是一样的, 由于只用找出一个缺失的个数,故随机选择一边进行下一步操作既可;

       若这两部分数据个数不一样, 说明个数少的缺失的数据个数肯定比个数多的那部分要多, 一个缺一个不缺或两个都缺, 此时肯定选择个数小的那部分进行下一步操作.

      最后,处理第二高位,递归进行,直到所有位都遍历结束,就可以找到缺失的值.

下面是java实现的代码,实际操作时应该从文件中读取数据,并将数据写入文件中:

<span style="font-size:14px;">package test;
/*
 * 从随机排列的文件中,找出不在文件中的整数
 * 重要思想是二分搜索
 */
public class FindLostNumber {
	public static void main(String[] args){
		int bits = 4; //数据是4位,用位操作比较简单,可以很简单明确的将数据分为大小一样的两部<span style="font-family:Arial;">分</span>
		
		int[]  arr = {0,1,2,3,4,5,6,7,9,10,11,13,14,15};
		
		int lostNumber = findLostNumber(arr,bits);
		System.out.print("the lost number is :"+lostNumber);
	}
	
	public static int findLostNumber(int[] arr,int bits){
		int num0 = 0, num1 = 0; //分别统计当前位是0和1的数的数目
		int len = arr.length;
		int[] arr0 = new int[len];
		int[] arr1 = new int[len];
		int lostNumber = 0;
		for(int i = bits-1;i>=0;i--){
			int flag = 1 << i;
			num0=0;
			num1=0;
			for(int j=0;j<len;j++){
				if((flag & arr[j]) == 0)
					arr0[num0++] = arr[j];
				else
					arr1[num1++]=arr[j];
			}
			if(num0 < num1){
				arr = arr0;
				len = num0;
			}else{//代表缺失的数在当前位是1的这边,故缺失值的当前位为1
				arr = arr1;
				len = num1;
				lostNumber += flag;
			}
		}
		return lostNumber;
	}
}
</span>
2. 问题2

问题描述: 将一个n元一维向量向左旋转i个位置, 例如,当n=8且i=3时,向量abcdefgh旋转为defghabc.简单的代码使用一个n元的中间向量在n步内完成该工作.能否仅使用数十个额外字节的存储空间,在正比于n的时间内完成向量的旋转?

问题求解:

解决方案1: 将ab转换成ba, 首先对a求逆,得到a'b,然后只对b求逆,得到a'b',最后再对整体求逆,得到(a'b')',此时结果就是ba.即先局部翻转,然后再整体翻转.

相同解法的一个问题:输入一个英文句子,翻转句子中单词的顺序,但是单词内字符的顺序不变.

解法就是,首先将每个单词单独翻转,然后再整个句子翻转即可.

伪代码如下:

reverse(0,i-1); //cbadefgh

reverse(i,n-1);//cbahgfed

reverse(0,n-1);//defghabc

java实现代码如下:

<span style="font-size:14px;">package test;
/*
 * 将一个n元向量向左移动i个位置
 */
public class Reverse {

	//实现在数组中从low到high元素的整体翻转
	public static void reverse(char[] arr,int low,int high){
		char temp;
		for(int i= 0;i<=(high-low)/2 ;i++){
			temp=arr[low+i];
			arr[low+i] = arr[high-i];
			arr[high-i] = temp;
		}
	}
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
			String str = "abcdefgh";
			int len= str.length();
			int i =3;
			char[] arr = str.toCharArray();
			
			reverse(arr,0,i-1);
			reverse(arr,i,len-1);
			reverse(arr,0,len-1);
			
			String str1="";
			for(int j=0;j<len;j++)
					str1+=arr[j];
			System.out.print(str1);
	}

}
</span>
解决方案2: 杂耍算法

思路: 对数组中的每一个元素向左移动i位,超过数组长度的可以取模回到数组中,最终就会得到结果.

步骤:(i表示循环移动的位数)

(1)先将x[0](即第一个存储的元素)保存在临时变量temp中;

(2)将x[i]移动到x[0]中,x[2i]移动到x[i]中,依次类推

(3)将x中的所有下标都对x.length取模,直到再次需要从x[0]中提取元素.然后此时从temp中提取元素,结束.

循环的终止条件: 当从循环的起始位置点提取元素时,此时循环终止.

注意: 当n和i互质时, 只需要一次循环就可以处理完所有元素; 当n与i不互质时,将原始数据分为一块块,每块大小为i,每次处理各个块的相同位置的元素.

java实现代码如下:

<span style="font-size:14px;">//求2个数的最大公约数i<n
	public static int gcd(int n, int i){
		if(n<i){//确认n大于i
			int temp=i;
			i=n;
			n=temp;
		}
		
		if(n%i==0)
			return i;
		else{
			return gcd(i,n%i);
		}
	}
	
	//杂耍算法
	public static void rotate(char[] arr, int i, int len){
		int iter = gcd(len,i);
		for(int j=0;j<iter;j++){
			int firstNum = j;
			int next = firstNum + i;
			char temp = arr[firstNum];
			while(next != j){
				arr[firstNum] = arr[next];
				firstNum = next;
				next = (next + i)%len;
			}
			arr[firstNum] = temp;
		}
	}</span>
解决方案3: 块交换.

旋转向量其实就是交换向量ab,得到向量ba.a代表x的前i个元素.若a比b短, 则将b分成b1b2,且使得b2的长度和a一样,交换a和b2,得到b2b1a,则序列a此时处于最终位置,此时旋转向量的目标变为旋转b2b1;若a比b长,则将a分为a1a2,其中a1的长度和b的长度一样,交换a1和b,得到ba2a1,此时序列b到达最终位置,则原来的旋转向量目标变为旋转a2a1.递归上述过程,直到a和b的长度相等时,此时之需要交换a和b这两个序列即可.

java实现代码如下:

<span style="font-size:14px;">//采用块变换来实现左旋转,依次为数组,旋转开始位置,涉及旋转字符串长度,,旋转位数
	public static void rotate3(char[] arr, int start, int len, int bits){
		
		
		int leftstart = start;
		int rightstart = start + bits;
		
		int leftlen = bits;
		int rightlen = len-bits;
		
		if(leftlen < rightlen){
			swap(arr,leftstart,leftstart+len-bits,bits);
			rotate3(arr,leftstart,len-bits,leftlen);
		}else if(leftlen > rightlen){
			swap(arr,leftstart,rightstart,rightlen);
			rotate3(arr,leftstart+rightlen,len-rightlen,leftlen-rightlen);
		}else{
			swap(arr,leftstart,rightstart,rightlen);
		}
	}
	
	
	public static void swap(char[] arr, int l1, int r1,int len){
		char temp;
		for(int i =0;i<len;i++){
			temp = arr[l1+i];
			arr[l1+i] = arr[r1+i];
			arr[r1+i] = temp;
		}
	}</span>
3. 问题3

问题描述: 给定一个英语字典,找出其中所有的变位词集合.例如,'pots'/'stop'/'tops'互为变位词,因为没一个单词都可以通过改变其它单词中字母顺序得到.

问题求解: 原始问题可以转换为2个子问题:选择标识和集中具有相同标识的单词.

(1)为每一个单词生成一个标签, 且其所有的变位词都得有一样的标签: 一种方法: 把每个单词所包含的字母按照字母顺序排序, 在此基础上改进的方法是字母+出现次数,例如mississippi可以转换为i4m1p2s4,可以将出现一次的1省略.第二种方法: 使用一个包含26个整数的数组来标识每个字母出现的次数.

(2)根据标签收集单词,每个标签对应一个集合,这个集合包含其所有的变位词.可以使用hashmap来存储,键为标签,值为set,存储所有的变位词.

参考:

http://blog.csdn.net/insistgogo/article/details/7749328


你可能感兴趣的:(旋转,二分)