关于微软面试100题系列中两道题目的个人解法

        最近为了准备找工作,刷了July大神整理的微软100题系列,中间有两道题目在原博主文中并没有给出很好地解决方案,在此文中给出个人的解法,希望抛砖引玉,请求大神给予指导。


第一道是30题,原题描述如下:

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

分析:这是一道广为流传的 google面试题。

        这道题目很明显是可以用递归实现的。考虑三个数字:

        321,111。

        1.对于321,首先考虑个位数:个位数大于等于1,在0~9中1只能出现1次,因此这里各位出现1个1;接着考虑十位,十位大于1,可以得知在0~20中只能是0~9中出现1次、10~19中出现11次,也就是一共20次,那么考虑当十位数不是2,而是3,或者4时,增加的也仅仅是1个或者两个1.也就是说在此情况下(注意此时仅考虑当前位,不考虑其他位,也就是整十整百之类的数)1出现的次数可以总结为countN(n,k)=10^(k-1)+n*countN(10,k-1);其中n是当前位的值,k则是其倍率(十位时k=2,百位时k=3).同样的道理可以用在百位上,将所有位出现的加在一起即可得到最终结果。

         2.对于111,个位采用同样的方式进行判断,但是十位时我们发现这一位等于1,那么我们首先需要将低于当前位的值记录下,因为最后的结果中需要加上它,以此为例,我们需要记住最终结果需要加上1,同时由于即便当前位的低位全部为0我们也需要加1,也就是说最终结果的组成部分需要+2,(10,11中各自由于十位的1而加1,注意此时我们仅仅计算当前位的,比当前位低的已经计算完成),然后我们对当前位(在此是1)所代表的值(10)减去1,然后对其递归调用计数函数。在此例中是计算9的计数问题。然后我们将所有结果叠加即可得到最后结果。

          我发现数字无非这两种情况,因此总可以递归实现其求解。算法详情见下文。

//30.在从1到n的正数中1出现的次数
#include
int countN(int n,int k){//k为位数,这里的n不能为1,除非为个位数
	if (k == 1){
		if (n >= 1)
			return 1;
		else
			return 0;
	}
	if (n > 1){
		return (pow(10,(k-1)) + n*countN(10, k - 1));
	}
	else if (n == 1)
		return -1;
	else
		return 0;
}
int count1(int num){
	//判断num的位数
	int unit = 0;//位数
	int count = 0;//1的个数
	int mod = 0;//余数
	int tem = num*10;//除以10的结果
	while (tem /10 != 0){
		tem /= 10;
		++unit;
		mod = tem % 10;
		if (mod != 1||(mod==1&&unit==1)){
			int temp = countN(mod, unit);
			count += temp;
		}
		else{
			int temp2 = (num % int(pow(10, (unit - 1))) + 1);//当前位为1
			temp2 = temp2 + count1(pow(10, (unit - 1))- 1);
			count += temp2;
		}
	}
	return count;
}
int main(){
	int n = 11;
	std::cout << count1(n) << std::endl;
	system("pause");
}

第二道题目是32题,原题描述如下:

        有两个序列a,b,大小都为n,序列元素的值任意整数,无序;
        要求:通过交换a,b中的元素,使[序列a元素的和]与[序列b元素的和]之间的差最小。
        例如: 
        var a=[100,99,98,1,2, 3];
        var b=[1, 2, 3, 4,5,40];

         据说这一题是微软的面试题。

         当我第一次看到这个题目,最简单的思路就是直接合在一起排序,然后逐个抽取数字分配到两个数组中。这当然是大错特错的。奈何本人比较愚笨,只能查看答案。然而答案给出的代码也是不正确的。网上所传的算法我看到的目前有三种,前两种算法可以参照一下原博主July大神的博文http://blog.csdn.net/v_july_v/article/details/6126444,但是可以很负责任地告诉大家这两种方法都是错的,从大神贴出来的答案也是可以很明显看出的。第三种方法链接在此

http://m.blog.chinaunix.net/uid-26456800-id-3363899.html,一方面博主说的我也不是特别懂,另一方面本人比较懒,不喜欢看别人写的特别是用其他语言写的代码,就索性自己琢磨。事先声明我不清楚自己的算法是不是和其他人一样,也是不是最优,但是我测试的几组不同情况下的确实满足。
          我的思路是:首先将两组数组合为一组然后进行排序(这里使用快排)。随后我将整个数组分成前后长度相等的两部分,以原题为例:
           合并排序后:1,1,2,2,3,3,4,5,40,98,99,100;
           拆分:
           a1:1,1,2,2,3,3
           a2:4,5,40,98,99,100
           然后计算两个数组的和的差值(334)。然后我们从数组头开始,若交换1,4,判断差值会不会减少(绝对值)。如果减少就交换,否则指针后移直到数组尾。随后我们可以得到这样两个数组:
           a1:4,5,40,98,3,3
           a2:1,1,2,2,99,100
           我们将两个数组重新排序,重复进行比较操作直到遇到当所有元素都没有发生交换或者某次交换后差值为0时返回。
           我们得到的最后结果是:
           a1:3,3,4,5,40,100;
           a2:1,1,2,2,98,99.
           差值:48.
           至于是不是最优,原谅本人比较愚笨,暂时没看出来。。。。。。
           具体代码实现如下:
//32.有两个序列a,b,大小为n,序列元素的值为任意整数,无序;要求通过交换a,b中的元素,使序列a的和与序列b的和之间的差最小。
//writer&idea:JayLeeUSTC
//date:2016.07.22
#include
void swap(int&a,int&b){
	int temp = a;
	a = b;
	b = temp;
}
void QuickSort(int* a,int left,int right){//快速排序
	if (left < right){//当left=right的时候说明仅有一个元素
		int i = left, j = right, val = a[left];
		while (i < j){
			while (ival)
				--j;
			if (i < j)
				a[i++] = a[j];
			while (i < j&&a[i] < val)
				++i;
			if (i < j)
				a[j--] = a[i];
		}
		a[i] = val;
		QuickSort(a, left, i - 1);
		QuickSort(a, i+1, right);
	}
}
int sum(int*a, int len){
	int temp = 0;
	for (int i = 0; i < len; ++i){
		temp += a[i];
	}
	return temp;
}
void Arrange(int a1[],int a2[],int len){
	int *a3 = new int[2*len];
	for (int i = 0; i < len; ++i){
		a3[i] = a1[i];
		a3[i + len] = a2[i];
	}//合并序列
	QuickSort(a3, 0, 2*len-1);//排序
	for (int i = 0; i < len; ++i){
		a1[i] = a3[i];
		a2[i] = a3[i+ len];
	}//排序后的拆分序列
	bool sign = true;//符号标识符,正数为true,负数为false。
	bool ifswap = false;
	int prev = sum(a2, len) - sum(a1, len);//存储上一次运算的差值。
	while (true){
		for (int i = 0; i < len; ++i){
			if (sign){//符号为正,第二组序列之和较大
				int sumch = prev - 2 * a2[i] + 2 * a1[i];
				if (sumch0)//交换当前项后和的差值变小但是未变号
				{
					swap(a1[i], a2[i]);
					prev = sumch;
					ifswap = true;
				}
				else if (sumch < 0 && abs(sumch) < prev)//交换当前项后和的差值变小且变号
				{
					swap(a1[i], a2[i]);
					ifswap = true;
					prev = abs(sumch);
					sign = false;
				}

				else if (sumch == 0||sumch==prev)//找到最佳值或交换对总和无变化
					return;
			}
			else if (!sign){//第一组序列之和较大
				int sumch = prev - 2 * a1[i] + 2 * a2[i];
				if (sumch0){
					swap(a1[i], a2[i]);
					ifswap = true;
					prev = sumch;
				}
				else if (sumch < 0 && abs(sumch < prev)){
					swap(a1[i], a2[i]);
					ifswap = true;
					prev = abs(sumch);
					sign = true;
				}
				else if (sumch == 0 || sumch == prev)//找到最佳值或交换对总和无变化
					return;
			}
		}
		if (!ifswap)
			return;
		//重新排序
		QuickSort(a1, 0, len - 1);
		QuickSort(a2, 0, len - 1);
		ifswap = false;
	}


}
int main(){
	int a1[4] = { 1, 4, 6, 700};
	int a2[4] = { 2, 3, 5, 800};
	int len=sizeof(a1)/sizeof(a1[0]);
	Arrange(a1, a2, len);
	for (int i = 0; i < len; ++i)
		std::cout << a1[i] << " ";
	std::cout << std::endl;
	for (int i = 0; i < len; ++i)
		std::cout << a2[i] << " ";
	std::cout << std::endl;
	std::cout <<"The final result is:"<
         写在最后:其实自己水平很渣,看到个位大牛的解题思路有时候就像个小孩子一样激动。希望对算法感兴趣的同道中人一起讨论一起学习!

            
           



你可能感兴趣的:(关于微软面试100题系列中两道题目的个人解法)