《编程之美》整理

读书笔记

Posted by AspenStars on February 11, 2020

第一章 游戏之乐

1.2 中国象棋

该题本质上比较简单,但因为限制条件(只能使用1个字节),所以考察较深

  • 解法1:将1个字节的8位分为前后4位,并定义二进制操作,对前后4位分别进行操作
  • 解法2:使用位域,将一个字节直接分为前后四位,可以直接进行加减

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct ChineseChess
{
    unsigned char a : 4;
    unsigned char b : 4;
} i;

int main(){
    for(i.a = 1; i.a <= 9; i.a++){
        for(i.b = 1; i.b <= 9; i.b++){
            if(i.a % 3 != i.b % 3){
                printf("A=%d, B=%d\n", i.a, i.b);
            }
        }
    }
    return 0;
}

位域

参考:C语言位域(位段)详解

有些数据在存储时并不需要占用一个完整的字节,只需要占用一个或几个二进制位即可。例如开关只有通电和断电两种状态,用 0 和 1 表示足以,也就是用一个二进位。
正是基于这种考虑,C语言提供了一种叫做位域的数据结构。

在结构体定义时,我们可以指定某个成员变量所占用的二进制位数(Bit),这就是位域。

C语言标准规定,位域的宽度不能超过它所依附的数据类型的长度。通俗地讲,成员变量都是有类型的,这个类型限制了成员变量的最大长度,:后面的数字不能超过这个长度。

  • unsigned int,长度为 4 个字节,共计 32 位
  • unsigned char,长度为 1 个字节,共计 8 位

例子:

1
2
3
4
5
struct bs{
    unsigned m;
    unsigned n: 4;
    unsigned char ch: 6;
};
  • :后面的数字用来限定成员变量占用的位数。
  • 成员 m 没有限制,根据数据类型即可推算出它占用 4 个字节(Byte)的内存。
  • 成员 n、ch 被:后面的数字限制,不能再根据数据类型计算长度,它们分别占用 4、6 位(Bit)的内存。

位域的存储规则:

  • 当相邻成员的类型相同时,如果它们的位宽之和小于类型的 sizeof 大小,那么后面的成员紧邻前一个成员存储,直到不能容纳为止;如果它们的位宽之和大于类型的 sizeof 大小,那么后面的成员将从新的存储单元开始,其偏移量为类型大小的整数倍
  • 当相邻成员的类型不同时,不同的编译器有不同的实现方案,GCC 会压缩存储,而 VC/VS 不会
  • 如果成员之间穿插着非位域成员,那么不会进行压缩
NOTE:

位域成员往往不占用完整的字节,有时候也不处于字节的开头位置,因此使用&获取位域成员的地址是没有意义的,C语言也禁止这样做。地址是字节(Byte)的编号,而不是位(Bit)的编号

无名位域:位域成员可以没有名称,只给出数据类型和位宽,一般用来作填充或者调整成员位置。因为没有名称,无名位域不能使用。

1.3 翻烙饼

经典题目,最朴素的想法是第一次将最大的饼想办法翻转到最下面,然后问题就缩减到n-1,再依次进行,最终问题的大小为需要进行的翻转次数为$2 * (n-1)$

可以以上述算法为上界,进行搜索,得到最优解,思考上界和下界的问题

第二章 数字之魅

2.1 二进制数中1的个数

经典题目 要注意数为负, 循环移位溢出等情况

  • 解法1:每次去掉最右边的1,记录数量,知道数变为0
    1
    
    n = n & (n-1)
    
  • 解法2:循环移位,每次判断是否为1
  • 解法3:如果是8位二进制,可以将256个数建表,通过查表得到,时间复杂度位$O(1)$,牺牲了空间复杂度

2.2 阶乘

1. 阶乘末尾0的个数

末尾0必定是由10这个因数组成,而10又是由2和5组成,明显2会多于5,所以找有因数里多少个5即可

1
2
3
4
while(n){
    count += n / 5;  // 从最大的数开始计算其包含5的数量
    n /= 5;  // 减为1/5,中间没有5的倍数,直到n为0
} 

2. 阶乘的2进制最低位1的位置

实质上是求阶乘中因数2的数量,可以按前面的思路
还可以根据规律,等于N - (N的二进制表示中1的数量)

2.3 寻找发帖水王

即找出超过1/2的元素,遍历数组元素,从第一个开始计数,如果计数n=0, 则将当前数作为候选水王,如果n>0,当前数与候选水王相同时,n=n+1,否则n=n-1 一次遍历即可得到

如果有3个,发帖数超过1/4,则设定3个候选水王,按上述规则进行遍历

第三章 结构之法

3.1 字符串移位包含问题

将源字符s复制为ss,另外可通过对比目标字符串长度进一步减少空间复杂度

3.2 电话号码对应的英语单词

1. 根据电话号码生成单词

对应生成即可,思考循环和递归两种方法

2. 根据电话号码,找一个有意义的单词

  • 用1的方法,对每个生成单词查表
  • 将字典中有意义的单词生成为号码字典,再根据号码去查单词(反向思维)