2008金山暑期实习在线考试(2008-06-14)小记

提前十几分钟登录的系统,8:54 左右就可以看到题了。题目是这样的:

1、编程计算从 1 到 2008080808 之间的整数有多少个含有数字7。

2、结构RECT可以表示一个平面上的矩形区域: struct RECT {   int left, right, top, bottom; }; 编程计算两个矩形区域A和B构成的区域总面积。

3、将给定的字符串以词为单位倒序,同时给定一个子串,若字符串中有部分内容与子串相同则该部分不倒序。 例:给定字符串“The quick brown fox jumps over the lazy dog”和子串“brown fox”, 倒序结果是“dog lazy the over jumps brown fox quick The”

看到先把三题都看了下,第一题不是最简单的,但是还是喜欢从第一题做起。虽然是网上考试,还是有点点紧张,思路有点混乱。首先验证了下 2008080808 这个数是不是足够大,大到原有数据类型存不下。所幸,double 类型可以从容存下,那就说明考的不是大数处理,数字这么大仅仅是为了让穷举的代价变得高了而已——显然不能用穷举法。接下去的半个多小时在想算法,当然拿小的数字乱想先,10 以内怎么样,100 以内怎么样,不过也不知道真的在干什么,思路很杂,什么也有用没想到。过了这么久,也越来越紧张了。忽然闪过一个想法,100 到 150 以内的满足题意的数的个数跟 0 到 50 之间的不是一样吗?那么,2008080808 就可以拆成 2000000000、8000000、80000、800、8 这几个部分了;再,200 以内的这样的数的个数是 100 以内的这样的数的个数的 2 倍。所以,最终要解决的是,对于 X0000 (n 个 0)这样的数,结果是多少。X0000 (n 个 0)以内,也就是 n 位数的范围了。对于一个 n 位数,有几个含数字 7 是好算的,它的个数等于 C(n, 1)*10^(n-1)(至少一位是 7)-C(n,2)*10^(n-2)(至少两位是 7)+C(n,3)*10^(n-3)(至少三位是 7)-…+(-1)^(n-1)C(n,n)*10^(n-n)(至少 n 位是 7)。当时是按这个算的。其实这个式子可以进一步化简,结果是 10^n-9^n,这点我土了,程序里面在算那个复杂的式子。也许当时没那么悠闲让我算来算起。换种思路,结果是 C(n,1)*9^(n-1)(有且仅有 1 位是 7)+C(n,2)*9^(n-2)(有且仅有 2 位是 7)+C(n,3)*9^(n-3)(有且仅有 3 位是 7)+…+C(n,n)*9^(n-n)(有且仅有 n 位是 7),再算一步当然也是 10^n-9^n 咯。

做第二题的时候已经过了差不多 80 分钟了。不过这题还算比较简单。但是那个 struct 我很想改成 class,可是又不知道允不允许。算了,还是纯 C 来吧。为了使用方便,我还是在这个 struct 里加了几个构造函数。这题主要考点是算重叠的情形下覆盖面积是多少。那么我把重叠部分算出来不就好了。起先做第一题顺带考虑第二题的时候我想到重叠要分好几种情况,左右还是上下,还是左上右下,等等。做这题的时候意识到重叠部分不还是一个矩形么?它的 left 是原来两个的 left 的较大者,它的 right 是原来两个的 right 的较小者,它的 top 是原来两个的 top 的较大者,它的 bottom 是原来两个的 bottom 的较小者,这样面积就好算了。于是覆盖面积就是原来两个矩形的面积和减去重叠面积。

第三题我原来打算用 STL 里的 string 和 stack 的,试了下,因为我对 STL 不是很熟悉,所以非常不顺手。我还是自己写吧。这题想的时间也不是很多,倒序输出可以利用递归解决,剩下的就是字符串比较问题了。做完后还有大概一小时。

还有时间,去网上查查看,第一题有没有什么更好的算法。新算法没找到,在百度知道看到一个求 500 以内不含数字 4 的问题,后面有人给出答案。我想验证一下,可是这题是算 7 的。其实算法已经是通用的了,没必要拘泥于 7、拘泥于 2008080808 的。于是我把它改成一个可以求 n 以内含数字 d (d 不等于 0,等于 0 的情况不能这样算,没时间考虑了) 的数字的个数的。再去验证 500 里含 4 的个数,176 个,于是不含的 324 个,跟那帖的回答中的多数是符合的,于是我就放心了。

这时候已经 11:50 多了,赶紧交,呵呵。

后来吃饭的时候想到了两点疏忽之处:

1、矩形的边长,我直接 right-left,bottom-top 了,没有去 +1,测试数据也是按照不 +1 凑的。虽然题目没有明确说 left、right、top、buttom 的具体含义,可是我想一般还是要 +1 的吧。

2、另一个是第三题的输出字符串中忘了手动赋上结束符 '/0' 了。VC 中会对数组自动赋 0,但是 C/C++ 标准里没有规定要这样。也正因为VC 帮我做了,所以调试的时候我一直没发现这个问题。

更囧的是,那天晚上我想到了一个更严重的问题,第二题中,我只算了有重叠的情形,没重叠的情形没有区分,减去了那个“负重叠矩形”的面积……

我本来以为我没希望了的。惊喜的是前天收到实习邀请邮件了,哈哈。

三个题的代码(当时交的时候的版本,原样保留,错误都保留着):

#include <iostream>

#include <math.h>

using namespace std;



int fact_table[10];



// 计算 9! 以内阶乘表

void init_fact_table()

{

    fact_table[0] = 1;

    for(int i=1; i<10; i++)

    {

        fact_table[i] = fact_table[i-1] * i;

    }

}



// 查表法计算阶乘, n<=9, 由于是自己使用, 不对 n 作检查

int fact(int n)

{

    return fact_table[n];

}



// 计算组合数 C(n, m), 即从 n 个元素中取出 m 个元素的取法数(m<=n)

// 同样不检查 n 和 m 了

int combi(int n, int m)

{

    return fact(n)/fact(m)/fact(n-m);

}



// 计算形如 10000 的数内有多少个含有digit

// 参数 zeros 表示后面有几个 0

// 公式 C(zeros, 1)*10^(zero-1) -C(zeros, 2)*10^(zero-2) +C(zeros, 3)*10^(zero-3) - ……

double _count(int digit, int zeros)

{

    double res = 0;

    int sign = 1;



    for(int i=1; i<=zeros; i++)

    {

        res += sign*combi(zeros, i)*pow(10.0, zeros-i);

        sign = -sign;

    }



    return res;

}



// 计算形如 X0000 的数内中有多少个含有 digit

// 参数 zeros 表示后面有多少个 0

// 参数 first_num 表示首位数字

double _count(int digit, int zeros, int first_num)

{

    double res = 0;



    if(first_num<digit)  // 如果最高位小于 digit,就是 count(zeros) 的结果乘以它既可

    {

        return first_num*_count(digit, zeros);

    }

    else if(first_num==digit)  // 最高位正好是 7(digit),加上 700000… 这个数

    {

        return (first_num)*_count(digit, zeros)+1;

    }

    else  // 最高位大于 digit,则 digit 开头的全部是,这部分有 pow(10.0, zeros) 个

    {

        return (first_num-1)*_count(digit, zeros)+pow(10.0, zeros);

    }

}



// 计算 num 内的数有多少个含有 digit(digit != 0)

double count(double num, int digit)

{

    // 算法解释

    // 假设 num 是 2008080808

    // 拆成 0 到 2000000000

    // 2000000000 到 2008000000

    // 2008000000 到 2008080000

    // 2008080000 到 2008080800

    // 2008080800 到 2008080808 这几个部分

    // 相当于分别计算 0 到 8000000、0 到 80000、0 到 800、0 到 8

    // 每一个部分按照上面的算

    // 如果某一次碰到那一位是数字 digit 作特殊处理(当然,仅仅计算 2008080808 内含 7 的话用不到)



    double next = num, res = 0;



    while(next>=1)

    {

        double first_num = next; // 最高位

        int bits = 0;  // 最高位以外的位数



        while(first_num>=10)

        {

            bits ++;

            first_num /= 10;

        }



        res += _count(digit, bits, (int)first_num);

        next = fmod(next, pow(10.0, bits));



        if(first_num == digit)

        {

            res += next;  // 剩余未统计的 next 个数全部含有 digit 了

            break;  // 计算结束

        }

    }



    return res;



}



int main()

{

    init_fact_table();

    //printf("%.0lf/n", count(100, 7));  // 结果 19

    //printf("%.0lf/n", count(160, 7));  // 结果 25

    //printf("%.0lf/n", count(167, 7));  // 结果 26

    //printf("%.0lf/n", count(168, 7));  // 结果 26

    //printf("%.0lf/n", count(170, 7));  // 结果 27

    //printf("%.0lf/n", count(180, 7));  // 结果 36

    //printf("%.0lf/n", count(1000, 7));  // 结果 271

    printf("%.0lf/n", count(2008080808, 7));  // 结果 1229473242

    //printf("%.0lf/n", count(500, 4));  // 176

    return 0;

}
#include <iostream>

#include <math.h>

using namespace std;



struct RECT

{

    int left, right, top, bottom;

    RECT() : left(0), right(0), top(0), bottom(0) {}

    RECT(int left, int right, int top, int bottom) : left(left), right(right), top(top), bottom(bottom)

    {

        // 确保 left <= right, top<=bottom

        // 这样计算重叠面积的时候简便些

        // 当然由于 struct 的数据成员是 public 的,数据可以在外部随意改

        // 这个 struct 又是自己用的

        // 这个检查似乎没什么必要

        // 只是意思一下,我要在这里保证 left、right 和 top、bottom 的大小关系

        // 在下面 overlay() 里就不作检查了

        if(this->left > this->right)

        {

            swap(this->left, this->right);

        }

        if(this->top > this->bottom)

        {

            swap(this->top, this->bottom);

        }

    }

    RECT(RECT &that)

    {

        this->left = that.left;

        this->right = that.right;

        this->top = that.top;

        this->bottom = that.bottom;

    }

};



int max(int a, int b)

{

    return a>=b?a:b;

}



int min(int a, int b)

{

    return a<=b?a:b;

}



// 返回两个矩形的重叠部分

RECT overlay(RECT a, RECT b)

{

    RECT res;



    res.left = max(a.left, b.left);

    res.right = min(a.right, b.right);

    res.top = max(a.top, b.top);

    res.bottom = min(a.bottom, b.bottom);



    return res;

}



// 计算一个矩形的面积

int area(RECT r)

{

    return (r.right-r.left)*(r.bottom-r.top);

}



// 计算两个矩形的面积(重叠的面积只记一次)

int area(RECT a, RECT b)

{

    return area(a) + area(b) -area(overlay(a, b));

}



int main()

{

    RECT a(1, 11, 2, 62), b(8, 28, 0, 40);  // a 600, b 800, 重叠 3*38=114

	printf("%d/n", area(a, b));  // 结果 1286

	return 0;

}
#include <iostream>

#include <string.h>

using namespace std;



char instr[80], substr[80], outstr[80];

int out_pos, substrlen;



// 比较 instr 的 start 处开始是否与 substr 相同

bool compare(int start)

{

    for(int i=0; i<substrlen; i++)

    {

        if(instr[start+i] == '/0')

        {

            return false;

        }

        if(instr[start+i] != substr[i])

        {

            return false;

        }

    }



    return true;

}



// 输出 instr 的 start 开始、长为 len 的部分

void output(int start, int len)

{

    for(int i=0; i<len; i++)

    {

        outstr[out_pos++] = instr[start+i];

    }



    outstr[out_pos++] = ' ';

}



// 递归倒序整个字符串

void reverse(int start)

{

    if(instr[start] == '/0') // 递归结束条件

    {

        return;

    }



    while(instr[start] == ' ')  // 找到一个非空格字符(这里假定单词间以空格分隔, 不考虑 /t 之类的)

    {

        start++;

    }



    if(compare(start))  // 如果当前位置开始与子串一样

    {

        reverse(start + substrlen);  // 跳过这个子串长度,处理下面的部分

        output(start, substrlen);  // 输出这个子串

    }

    else  // 如果当前位置开始与子串不一样

    {

        int next=start;

        while(instr[next] != ' ' && instr[next] != '/0')  // 找当前单词结束处

        {

            next++;

        }

        reverse(next);  // 处理下一个单词

        output(start, next-start);  // 输出这个单词

    }



}



int main()

{

    //gets(instr);

    //gets(substr);

    strcpy(instr, "The quick brown fox jumps over the lazy dog");

    strcpy(substr, "brown fox");

    substrlen = strlen(substr);  // 这个长度多次用到,在这里先算出来

    reverse(0);

    printf("%s/n", outstr);

    return 0;

}

(原发表于 CSDN:https://blog.csdn.net/cnStreamlet/article/details/2576153)

posted on 2008-06-22 17:13  溪流  阅读(54)  评论(0编辑  收藏  举报