fixed/unfixed
:有符号和无符号的定长浮点型fixedMxN
:带符号的定长浮点型,其中M表示按类型取的位数,N表示小数点。M应该能被8整除,N可以是0到80。fixed
默认为fixed128x18
。ufixedMxN
:无符号的定长浮点型,其中M表示按类型取的位数,N表示小数点。地址类型表示以太坊地址,长度为20字节。地址可以使用.balance
放啊获得余额,也可以使用.transfer
方法将余额转到另一个地址。
Solidity支持三种类型的变量:
undefined
和null
的概念。状态变量可以有三种作用域类型:
getter
函数。Solidity提供四种类型的数据位置:
该存储位置存储永久数据,这意味着该数据可以被合约中的所有函数访问。可类比为计算机的硬盘数据,所有数据都永久存储。与其他数据位置相比,存储区数据位置的成本较高。
Memory是临时数据,比存储位置便宜,只能在函数中访问。可类比成每个单独函数的内存RAM。
Calldata是不可修改的非持久性数据位置,所有传递给函数的值都存储在这里。此外,Calldata是外部函数参数的默认位置。
堆栈是由EVM维护的非持久性数据。EVM使用堆栈数据位置在执行期间加载变量,该位置最多有1024个级别限制。
状态变量总是存储在Storage中,不能显式地标记状态变量的位置。1
2
3
4
5
6
7
8
9
10
11pragam solidity ^0.5.0;
contract DataLocation {
// storage
uint stateVariable;
uint[] stateArray;
uint storage stateVariable; // Error
uint[] memory stateArray; // Error
}
函数参数包括返回参数都存储在Memory中。
值类型的局部变量存储在Memory中。但是对于引用类型,需要显式地指定数据位置。不能显式地覆盖具有值类型的局部变量。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25pragma solidity ^0.5.0;
contract Locations {
/* 此处是状态变量 */
// 存储在Storage中
bool flag;
uint number;
address account;
function doSomething() public {
/* 此处是局部变量 */
// 由于是值类型,故被存储在Memory中
bool flag2;
uint number2;
address account2;
// 引用类型需要显式指定数据位置
uint[] memory localArray;
// 不能显式覆盖具有值类型的局部变量
bool memory flag2; // Error
uint Storage number2; // Error
}
}
外部函数的参数(不包括返回参数)存储在Calldata中。
可以使用string()
构造函数将bytes转换为字符串。1
2bytes memory bstr = new bytes(10);
string message = string(bstr);
对于Storage数组,元素类型可以是任意的,而对于Memory数组,元素类型不能是映射类型,若它是一个公共函数的参数,那么元素类型必须是ABI类型。相比于byte[]
,bytes
应优先使用。
可以使用new
关键字在内存中创建动态数组。与存储数组相反,不能通过设置.length
成员来调整内存动态数组的长度。
使用struct
关键字定义结构体,包含多个成员。使用成员访问操作符.
访问结构的任何成员。
与数组和结构体一样,映射也属于引用类型。声明语法如下:1
mapping(_KeyType => _ValueType)
其中,_KeyType
可以是任何内置类型,但不允许使用引用类型或复杂对象,_ValueType
是任何类型。
注意,映射的数据位置只能是Storage,通常用于状态变量。映射可标记为public
,Solidity将自动为它创建getter。
函数是一组可重用代码的包装,接受输入,返回输出。
Solidity中,定义函数的语法如下,函数由关键字function
声明,后面跟函数名、参数、可见性和返回值的定义。1
2
3function function-name(parameter-list) scope returns(){
}
Solidity中,函数可以返回多个值。
函数修饰符用于修改函数的行为,可以创建带参数修饰符和不带参数修饰符:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15contract Owner {
// 定义修饰符 onlyOwner 不带参数
modifier onlyOwner {
require(msg.sender == owner);
_;
}
// 定义修饰符 costs 带参数
modifier costs(uint price) {
if (msg.value >= price) {
_;
}
}
}
视图函数不会修改状态。可以通过在函数声明中添加view
关键字来声明视图函数,getter方法是默认的视图函数。
纯函数不读取或修改状态。若发生错误,纯函数可以使用revert()
和require()
函数来还原潜在的状态更改。可以通过在函数声明中添加pure
关键在来声明纯函数。
回退函数是合约中的特殊函数,具有以下特点:
1 | // 没有名字,没有参数,不返回,标记为external,可以标记为payable |
同一作用域内,相同函数名可定义多个函数。这些函数的参数(参数类型或参数数量)必须不同,仅返回值不同不被允许。
当在智能合约中,直接向一个地址转账时,如该地址是一个合约地址,合约中可以编写代码,拒绝接受付款,导致交易失败。为避免这种情况,通常会使用提款模式。提款模式是让收款方主动来提取款项,而不是直接转账给收款方。
使用限制访问修饰符可以限制合约状态修改者或调用合约函数,常用限制访问操作如下:
constructor
关键字声明的特殊函数,每个合约执行一次,在创建合约时调用。合约中的函数和变量具有可见性,包括以下四种关键词:
external
:外部函数由其他合约调用,要在合约内部调用外部函数可以使用this.function_name()
的方式。状态变量不能标记为外部变量。public
:公共函数/变量可以在外部和内部直接使用。对于公共状态变量,Solidity为其自动创建一个getter函数。internal
:内部函数/变量只能在内部或派生合约中使用。private
:私有函数/变量只能在内部使用,派生合约无法使用。1 | pragma solidity ^0.5.0; |
Solidity中的合约继承可类比面向对象语言中的类继承,支持单继承和多继承。继承的主要特点为:
this
。super
关键字或父合同名称调用父合同的函数。super
的父合约函数调用,优先选择被最多继承的合约。构造函数是使用construct
关键字声明的特殊函数,用于初始化合约的状态变量,构造函数是可选的,可以省略。
构造函数具有以下重要特性:
类似Java中的抽象类,抽象合约至少包含一个没有实现的函数。通常抽象合约作为父合约,被用来继承,在继承合约中实现抽象函数。抽象合约也可以包含有实现的函数。
若派生合约没有抽象函数,则该派生合约也将被标记为抽象合约。
接口类似于抽象合约,使用interface
关键字创建,只能包含抽象函数,不能包含函数实现。接口关键特性如下:
enum
,struct
定义,可以使用interface_name
来访问它们。库的主要作用是代码重用,库中包含了可以被合约调用的函数。主要特征如下:
using A for B
指令可用于将库A的函数附加到给定类型B。这些函数将把调用者类型作为第一个参数(使用self
标识)。
使用内联汇编,可以在Solidity源程序中嵌入汇编代码,对EVM具有更细粒度的控制,在编写库函数时很有用。
汇编代码嵌入使用以下语法:1
assembly { ... }
举例如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25pragma solidity ^0.5.0;
library Sum {
function sumUsingInlineAssembly(uint[] memory _data) public pure returns (uint o_sum) {
for (uint i = 0; i < _data.length; ++i) {
assembly {
o_sum := add(o_sum, mload(add(add(_data, 0x20), mul(i, 0x20))))
}
}
}
}
contract Test {
uint[] data;
constructor() public {
data.push(1);
data.push(2);
data.push(3);
data.push(4);
data.push(5);
}
function sum() external view returns(uint){
return Sum.sumUsingInlineAssembly(data);
}
}
事件是智能合约发出的信号,可以被索引,以便以后可以搜索事件记录。
Solidity中,可以使用event
关键字定义事件,然后可以在函数中使用emit
关键字触发事件:1
2
3
4
5// 声明一个事件
event Deposit(address indexed _from, bytes32 indexed _id, uint _value);
// 触发事件
emit Deposit(msg.sender, _id, msg.value);
按照惯例,事件名称以大写字母开头,以区别于函数。一个事件最多有3个参数可以标记为索引。可以使用索引参数有效地过滤事件。
事件构建在Ethereum中,底层的日志接口之上,具有以下局限性:
错误处理使用以下一些重要方法:
assert(bool condition)
:如果不满足条件,此方法调用将导致一个无效的操作码,对状态所做的任何更改将被还原。这个方法是用来处理内部错误的。require(bool condition)
:如果不满足条件,此方法调用将恢复到原始状态。此方法用于检查输入或外部组件的错误。require(bool condition, string memory message)
:如果不满足条件,此方法调用将恢复到原始状态。此方法用于检查输入或外部组件的错误。它提供了一个提供自定义消息的选项。revert()
:此方法将中止执行并将所做的更改还原为执行前状态。revert(string memory reason)
:此方法将中止执行并将所做的更改还原为执行前状态。它提供了一个提供自定义消息的选项。8086CPU提供以下几大类指令。
这些指令实现寄存器和内存、寄存器之间的单个数据传送。典型指令如mov、push、pop、pushf、popf、xchg等。
这些指令实现寄存器和内存中数据的算术运算,它们的执行结果影响标志寄存器的sf、zf、of、cf、pf、af等位。典型指令如add、sub、adc、sbb、inc、dec、cmp、imul、idiv、aaa等。
除not指令外,其他逻辑指令的执行结果均影响标志寄存器的相关标志位。典型指令如and、or、not、xor、test、shl、shr、sal、sar、rol、ror、rcl、rcr等。
可以修改IP或同时修改IP和CS的指令统称为转移指令。转移指令可分为以下几类。
这些指令对标志寄存器或其他处理机状态进行设置。典型指令如cld、std、cli、sti、nop、clc、cmc、stc、hlt、wait、esc、lock等。
这些指令对内存中的批量数据进行处理,典型指令如movsb、movsw、cmps、scas、lods、stos等。此类指令可以和rep、repe、repne等前缀指令配合使用,以实现批量数据处理。
可以在代码段中使用标号来标记指令、数据和段地起始地址。同时,引入一种新的特殊标号,不但表示内存单元地地址,还表示内存单元的长度(字节单元/字单元/双字单元)。以如下程序为例:
1 | assume cs:code |
code段中使用的标号a、b后面没有”:”,它们同时描述内存地址和单元长度的标号。标号a描述了地址code:0
,且该地址后的内存单元均为字节单元;标号b描述了地址code:8
,且该地址后的内存单元均为字单元。
在其他段中同样可以使用数据标号来描述存储数据的单元地址和长度。注意,后面加有”:”的地址标号只能在代码段中使用,不能在其他段中使用。如果想在代码段中直接使用数据标号访问数据,则需要使用伪指令assume
将标号所在的段和一个段寄存器联系起来。
可以将标号当作数据来定义,此时编译器将标号所表示的地址当作数据的值。
通过依据数据,直接计算出所要找的元素位置的表,称为直接定址表。使用根据功能号查找地址表的方法,程序的结构清晰,便于扩充。若加入一个新的功能子程序,则只需在地址表中加入它的入口地址即可。
一般的键盘输入,在CPU执行完int 9
中断例程后,都放入键盘缓冲区中。键盘缓冲区中有16个字单元,可以存储15个按键的扫描码和对应的ASCII码。缓冲区的字单元中,高位字节存储扫描码,低位字节存储ASCII码。
BIOS提供int 16h
中断例程,该例程的0号功能为从键盘缓冲区中读取一个键盘输入。
int 16h
中断引发后,若缓冲区中不存在任何数据,则保持循环等待状态,一直等到int 9h
中断发生,将数据送入缓冲区即停止等待。该过程执行IF=1的相关设置指令。
最基本的字符串输入程序具备以下功能:
部分功能函数如下:
1 | charstack:jmp short charstart |
完整接收字符串输入子程序如下:
1 | getstr:push ax |
3.5英寸软盘分为上下两面,每面有80个磁道,每个磁道分为18个扇区,每个扇区的大小为512字节,总存储空间约为1.44MB。
磁盘的实际访问由磁盘控制器进行,只能以扇区为单位对磁盘进行读写。读写扇区时需要给出面号、磁道号和扇区号,面号和磁道号从0开始,扇区号从1开始。
注意,直接向磁盘扇区写入数据有可能覆盖掉重要信息。若向软盘的0面0道1扇区中写入数据,则要使软盘在现有操作系统下可以使用,则必须重新格式化。
对位于不同磁道、面上的所有扇区进行统一编号,生成逻辑扇区编号。逻辑扇区号=(面号*80+磁道号)*18+扇区号-1。
微机中常用的Intel系列微处理器的主要发展过程是:8080,8086、8088,80186,80286,80386,80486,Pentium,Pentium II,Pentium III,Pentium 4。
8086/8088不具备实现一个完善的多任务操作系统的功能,为此Intel研发了80286,该处理器具备对多任务系统的支持。随后,Intel开发了80386微处理器,在以下三个模式下工作:
任何一个通用的CPU都可以在执行完当前正在执行的指令之后,检测到从CPU外部发送过来的或内部产生的一种特殊信息,并且可以立即对所接收到的信息进行处理。这种特殊的信息称为中断信息。中断是指CPU不再接着向下执行,而是转去处理这个特殊信息。
中断信息来自内部和外部,本节主要讨论内部中断。
8086CPU使用称为中断类型码的数据来标识中断信息的来源,中断类型码为一个字节型数据,可以表示256种中断信息的来源。对于8086CPU,当CPU内部发生以下情况时,将产生相应的中断信息,其对应的中断类型码同样列出。
int n
,指令中的n为字节型立即数,是提供给CPU的中断类型码。CPU收到中断信息后,需要对中断信息进行处理。人为编写的可用来处理中断信息的程序被称为中断处理程序。一般来说,需要针对不同的中断信息编写不同的处理程序。根据CPU的涉及,中断类型码的作用就是定位中断处理程序。
CPU使用8位的中断类型码通过中断向量表找到相应的中断处理程序的入口地址。中断向量表就是中断处理程序入口地址的列表。中断向量表在内存中保存,其中存放着256个中断源所对应的中断处理程序的入口,如下图所示。
中断向量表在内存中存放,对于8086PC机,中断向量表指定放在内存地址0处。从内存0000:0000~0000:03FF
的1024个单元中存放着中断向量表。
在中断向量表中,一个表项存放一个中断向量,也就是一个中断处理程序的入口地址。对于8086CPU,这个入口地址包括段地址和偏移地址,所以一个表项占据两个字,高地址字存放段地址,低地址字存放偏移地址。
通过中断类型码找到中断向量,并用其设置CS和IP的过程称为中断过程,该工作由CPU的硬件自动完成。8086CPU收到中断信息后,引发以下中断过程。
最后一步完成后,CPU开始执行中断处理程序。
中断处理程序编写的常规步骤如下:
中断过程中,寄存器的入栈顺序是标志寄存器、CS和IP,而iret的出栈顺序是IP、CS和标志寄存器,与前者对应。iret指令的功能使用汇编语法描述为:
1 | pop IP |
重新编写一个0号中断处理程序,它的功能是在屏幕中间显示”overflow!”,然后返回到操作系统。
经过对除法溢出错误即0号中断处理程序的分析,我们将题目需求细化为以下几部分:
0000:0200
处;0000:0200
存储在中断向量表0号表项中。1 | assume cs:code |
使用rep movsb
指令实现中断处理程序的安装需要确定如下信息:
code
,偏移地址offset do0
;0:200
;明确后的安装程序如下:
1 | assume cs:code |
0号表项的地址为0:0
,其中0:0
字单元存放偏移地址,0:2
字单元存放段地址。程序如下:
1 | mov ax,0 |
1 | assume cs:code |
运行结果如下:
CPU完成指令执行后,如果检测到标志寄存器的TF位为1,则产生单步中断,引发中断过程。单步中断的中断类型码为1,引发的中断过程如下:
CPU提供单步中断,为单步跟踪程序的执行过程提供了实现机制。一般情况下,CPU在执行完当前指令后,若检测到中断信息则立即响应中断,引发中断过程。但是在某系情况下不立即响应,如执行完向ss寄存器传送数据的指令后,即便发生中断CPU也不会响应,这是因为ss:sp
联合指向栈顶,故对它们的设置应连续完成(粘连操作)。
除前文提到的常见中断信息外,还可以通过int指令引发中断过程。
int指令格式为int n
,n为中断类型码,其功能是引发中断过程。int指令的最终功能和call指令类似,都是调用一段中断例程。
BIOS称为基本输入输出系统,存放于系统板的ROM中,主要包含以下几部分内容。
和硬件设备相关的DOS中断例程中,一般都调用了BIOS的中断例程。BIOS和DOS提供的中断例程都使用寄存器ah
来传递内部子程序的编号。
FFFF:0
处的指令,转去执行BIOS中的硬件系统检测和初始化程序。int 19h
进行操作系统的引导。随后将计算机交由操作系统控制。int 10h
中断例程是BIOS提供的中断例程,其中包含了多个和屏幕输出相关的子程序。以设置光标位置为例。
1 | mov ah,2; 置光标 |
(ah)=2表示调用第10h号中断例程的2号子程序,功能为设置光标位置,可以提供光标所在的行号、列号,和页号作为参数。
(bh)=0,(dh)=5,(dl)=12,设置光标的第0页,第5行,第12列。
int 21h
中断例程是DOS提供的中断例程,其中包含了DOS提供给程序员在编程时调用的子程序。以程序返回功能为例。
1 | mov ah,4ch; 程序返回 |
(ah)=4ch表示调用第21h号中断例程的4ch号子程序,功能为程序返回,可以提供返回值作为参数。
各种存储器都和CPU的地址线、数据线、控制线相连。CPU在操控它们的时候,把它们看作一个由若干存储单元组成的逻辑存储器即内存地址空间。
PC机系统中,和CPU通过总线相连的芯片除各种存储器外,还包含以下三类:
从CPU的角度,将这些寄存器都当作端口,对它们进行统一编址,从而建立一个统一的端口地址空间。每个端口在地址空间中都对应一个地址。CPU可以直接从以下三个地方读写数据:
CPU通过端口地址来定位端口,端口地址和内存地址一样,通过地址总线来传送。在PC系统中,CPU最多可以定位64KB个不同的端口,端口地址范围为0~65535。
端口的读写指令有in和out两条,分别用于从端口读取数据和往端口写入数据。
注意,在in和out指令中,只能使用ax或al来存放从端口中读入的数据或要发送到端口中的数据。访问8位端口时用寄存器al,访问16位端口时用寄存器ax。
该芯片特征如下:
该芯片中存放着当前时间:年、月、日、时、分、秒。这6个信息的长度均为1字节,存放单元为:秒:0,分:2,时:4,日:7,月:8,年:9
,这些数据以BCD码的方式存放。高4位BCD码表示十位,低4位的BCD码表示个位。
shl是逻辑左移指令,功能为:
shr是逻辑右移指令,功能于shl相反,最后使用0补充最高位。
若移动位数大于1,必须将移动位数放在寄存器cl中。
CPU通过端口和外部设备进行联系。当外设的输入发生时,产生外中断;待CPU执行完当前指令后,可以检测到发送过来的外中断信息,引发中断过程,处理外设的输入。
PC系统中,外中断源包含可屏蔽中断和不可屏蔽中断两类。
可屏蔽中断是CPU可以不响应的外中断。当CPU检测到可屏蔽中断信息时,检查IF标志位信息。若IF=1则引发中断过程,否则不响应可屏蔽中断。中断过程中常将IF置0,该方法可以禁止其他可屏蔽中断。
可以通过sti
指令和cli
指令设置IF标志位的值。
不可屏蔽中断是CPU必须响应的外中断。CPU检测到不可屏蔽中断信息时,在执行完当前指令后立即响应,引发中断过程。对于8086CPU,不可屏蔽中断的中断类型码为2。中断过程为:
可以修改IP,或同时修改CS和IP的指令统称为转移指令。概括地讲,转移指令就是可以控制CPU执行内存中某处代码的指令。
8086CPU转移行为包括以下几类:
jmp ax
。jmp 1000:0
。由于转移指令对IP的修改范围不同,段内转移分为短转移和近转移。
8086CPU的转移指令分为以下几类:
jmp
)loop
)操作符offset在汇编语言中是由编译器处理的符号,它的功能是取得标号的偏移地址。
jmp为无条件转移指令,可以只修改IP,也可以同时修改CS和IP。jmp指令给出转移目的地址和转移的距离(段内转移、段内短转移、段内近转移)。不同的给出目的地址的方法和不同的转移位置,对应不同格式的jmp指令。
CPU在执行jmp指令的时候并不需要转移的目的地址,而是向CPU传递要转移的位移,这种设计方便程序段在内存中的浮动装配。根据位移进行转移的指令受到转移位移的限制。如果在源程序中出现转移范围越界的问题,则编译器报错。
jmp short 标号(转到标号处执行指令)
该指令中的short
符号说明其实现段内短转移,它对IP的修改范围是-128~127。jmp指令中的标号是代码段中的标号,指明了指令要转移的目的地,转移指令结束后,CS:IP应指向标号处的指令。实际上该指令的功能为(IP)=(IP)+8位位移
。
jmp near ptr 标号
该指令实现段内近转移,功能为(IP)=(IP)+16位位移
。
jmp far ptr 标号
该指令实现段间转移,又称为远转移。功能如下:(CS)=标号所在段的地址,(IP)=标号在段中的偏移地址。far ptr指明了指令用标号的段地址和偏移地址修改CS和IP。
jmp 16位reg
该指令功能为:(IP)=(16位reg)
转移地址在内存中的jmp指令有两种格式:
jmp word ptr 内存单元地址(段内地址)
功能:从内存单元地址处开始存放的字是转移的目的偏移地址。
jmp dword ptr 内存单元地址(段间转移)
功能:从内存单元地址处开始存放的两个字,高地址处的字是转移的目的段地址,低地址处是转移的目的偏移地址。即(CS)=(内存单元地址+2),(IP)=(内存单元地址)。
jcxz指令为有条件短转移指令,在对应的机器码中包含转移的位移,而不是目的地址,对IP的修改范围均为-128~127。
指令格式
jcxz 标号
(若(cx)=0,则转移到标号处执行)
操作
当(cx)=0时,(IP)=(IP)+8位位移;
8位位移=标号处地址-jcxz指令后的第一个字节的地址,使用补码表示,由编译程序在编译时加以计算。
当$(cx) /neq 0$时,程序向下执行。
综上,jcxz的功能相当于if((cx)==0) jmp short 标号
。
loop指令为循环短转移指令,在对应的机器码中包含转移的位移而非目的地址,对IP的修改范围为-128~127。
指令格式
loop 标号
((cx)=(cx)-1,若$(cx) \neq 0$,转移到标号处执行)
操作
(IP)=(IP)+8位位移
;综上,loop的功能相当于cx-- if((cx)≠0 jmp short 标号)
ret指令修改IP,retf指令修改CS和IP。
pop IP
。pop IP pop CS
。CPU执行call指令时,进行两步操作:
call指令不能实现短转移,除此之外其实现转移的方法和jmp指令的原理相同。
call 标号
(将当前的IP压栈后,转到标号处执行指令)
该指令操作如下:
(sp)=(sp)-2
((ss)*16+(sp))=(IP)
(IP)=(IP)+16位位移
CPU执行该指令时,相当于进行:
1 | push IP |
call far ptr 标号
实现段间转移。
该指令操作如下:
(sp)=(sp)-2
((ss)*16+(sp))=(CS)
(sp)=(sp)-2
((ss)*16+(sp))=(IP)
(CS)=标号所在段的段地址
(IP)=标号在段中的偏移地址
CPU执行该指令时,相当于进行:
1 | push CS |
call 16位reg
该指令操作如下:
(sp)=(sp)-2
((ss)*16+(sp))=(IP)
(IP)=(16位reg)
CPU执行该指令时,相当于进行:
1 | push IP |
转移地址在内存中的call指令同样存在两种格式。
call word ptr 内存单元地址
CPU执行该指令时,相当于进行:
1 | push IP |
call dword ptr 内存单元地址
CPU执行该指令时,相当于进行:
1 | push CS |
call指令可以与ret指令组合使用以实现子程序机制。call指令跳转之前,其后面的指令的地址将存储在栈中,所以可在子程序的尾部使用ret指令,用栈中的数据设置IP的值,从而转到call指令后面的代码处继续执行。
具有子程序的源程序框架如下:
1 | assume cs:code |
参数&结果传递问题
调用者将参数送入参数寄存器,从结果寄存器中取到返回值;子程序从参数寄存器中取到参数,将返回值送入结果寄存器。
批量数据传递
可以将批量数据放到内存中,然后将它们所在内存空间的首地址放在寄存器中,传递给需要的子程序。对于具有批量返回结果的程序也可以使用以上方法。当然也可以通过数据栈的方法批量传递数据。
寄存器冲突问题
在子程序的开始将子程序中所有用到的寄存器中的内容都保存至栈中,当子程序返回前再予以恢复,需要注意寄存器入栈和出栈的顺序。
从而编写子程序的标准框架如下:
1 | 子程序开始:子程序中使用的寄存器入栈 |
8086CPU中,标志寄存器具有以下三种作用:存储相关指令的某些执行结果、为CPU执行相关指令提供行为依据、控制CPU的相关工作方式。8086CPU的标志寄存器有16位,简称为flag,其中存储的信息通常被称为程序状态字(PSW)。
flag寄存器按位起作用,即每一位都记录特定的信息,结构如图:
如图所示,flag的第0、2、4、6、7、8、9、10、11位都具有特殊的含义。
flag的第6位是ZF即零标志位。若相关指令执行后,结果为0,则zf=1;若结果不为0,则zf=0。
注意,在8086CPU指令集中,进行逻辑运算或算术运算的运算指令的执行影响标志寄存器,而传送指令的执行对标志寄存器没有影响。
flag的第2位是PF即奇偶标志位。若相关指令执行后,其结果的所有bit位中1的个数为偶数,则pf=1;若1的个数为奇数,则pf=0。
flag的第7位是SF即符号标志位。若相关指令执行后,其结果为负数,则sf=1;若非负,则sf=0。
显然,SF标志就是CPU对有符号数运算结果的一种记录,即它可以记录数据的正负,对于无符号数运算则没有任何意义。
flag的第0位是CF即进位标志位。一般情况下,在进行无符号数运算时,它记录了运算结果的最高有效位向更高位的进位值,或从更高位的错位值。
进行有符号数运算的时候,若结果超过了机器所能表示的范围称为溢出。
flag的第11位是OF即溢出标志位。一般情况下,若有符号数运算的结果发生溢出,则OF=1;若没有发生溢出,则OF=0。
flag的第8位是TF,该标志位与单步中断有关。CPU在执行完成一条指令之后,若检测到TF=1,则产生单步中断,引发中断过程;若检测到TF=0,则继续执行,不产生任何中断。
flag的第9位是IF,该标志位决定CPU是否响应可屏蔽中断。若CPU检测到IF=1,则其在执行完成当前指令后响应中断,引发中断过程;若IF=0,则不响应可屏蔽中断。中断过程中常将IF置为0,是为了在进入中断处理程序后,禁止其他的可屏蔽中断。当然,如果在中断处理程序中需要处理可屏蔽中断,可利用指令将IF置1。8086CPU提供设置IF的指令如下:
flag的第10位是DF即方向标志位。在串处理指令中,控制每次操作后si、di的增减。
8086CPU提供下面两条指令对df位进行设置:
adc是带进位加法指令,它利用了CF位上记录的进位值。
指令格式
1 | adc 操作对象1, 操作对象2 |
功能
操作对象1=操作对象1+操作对象2+CF
由于adc指令执行后,也可能产生进位值,所以也会对CF位进行设置。因此该指令可对任意大的数据进行加法运算。
sbb是带错位减法指令,它利用了CF位上记录的错位值。
指令格式
1 | sbb 操作对象1, 操作对象2 |
功能
操作对象1=操作对象1-操作对象2-CF
sbb指令执行后,将对CF进行设置,故利用sbb指令可以对任意大的数据进行减法运算。
cmp是比较指令,其执行后将对标志寄存器产生影响,但不保存结果。其他相关指令通过识别这些被影响的标志寄存器来得知比较结果。该指令通过做减法运算,影响标志寄存器,标志寄存器的相关位记录了比较结果。CPU在执行cmp指令的时候,也包含无符号数比较和有符号数比较两种情况。
指令格式
1 | cmp 操作对象1, 操作对象2 |
功能
计算操作对象1-操作对象2但并不保存结果,仅仅根据计算结果对标志寄存器进行设置。
溢出
由于有符号数在进行减法操作时会产生溢出现象,而sf标志位只记录实际结果的正负,从而cmp指令的比较结果与sf标志位的结果并不完全等同,需要结合of标志位的情况进行判断。
of=0
of=0说明没有溢出,逻辑上真正结果的正负=实际结果的正负。
of=1
of=1说明存在溢出,逻辑上真正结果的正负$\neq$实际结果的正负。同时,若因溢出导致实际结果为负,则逻辑上结果必然为正;若因溢出导致实际结果为正,则逻辑上结果必然为负。
根据cmp指令的比较结果进行转移的指令也分为两种,即根据无符号数的比较结果进行转移的条件转移指令(检测zf和cf的值)和根据有符号数的比较结果进行转移的条件转移指令(检测sf、of和zf的值)。
常用根据无符号数的比较结果进行转移的条件转移指令如下:
格式
movsb&movsw
功能
该指令的功能是将ds:si
指向的内存单元中的字节(字)送入es:di
中,然后根据标志寄存器df位的值,将si和di递增(2)或递减(2)。采用汇编语法描述如下:
1 | mov es:[di],byte/word ptr ds:[si] |
movsb和movsw都可以和rep配合使用,从而循环实现(cx)个字节(字)的传送。
pushf的功能是将标志寄存器的值压栈,而popf是从栈中弹出数据,送入标志寄存器中。pushf和popf为直接访问标志寄存器提供了一种方法。
在Debug中,标志寄存器按照有意义的各个标志位单独表示。如上图所示,其中NV、UP、PL、NZ、PO和NC分别对应OF、DF、SF、ZF、PF和CF标志位。下面列出Debug对已知标志位的表示。
]]>and
指令
逻辑与指令,按位进行与运算。通过该指令可将操作对象的相应位设置为0,其他位不变。
or
指令
逻辑或指令,按位进行或运算。通过该指令可将操作对象的相应位设置为1,其他位不变。
用’……’的方式指明数据是以字符的形式给出的,编译器将把它们转化为相对应的ASCII码。对于用户输入或预先定义的字符,计算机采用ASCII码对其进行编码,将其转化为对应的十六进制信息存储在内存的指定空间中。文本编辑软件从内存中取出该十六进制信息,将其送到显卡的显存中。工作在文本模式下的显卡,用ASCII码的规则解释显存中的内容,显卡驱动显示器将对应字符显示在屏幕上。
一个字母,无论原来是大写还是小写,将其第5位置0,它将变为大写字母;将其第5位置1,它将变为小写字母。从而大小写转换对应的汇编语句如下:
1 | and al, 11011111B ;将al中的ASCII码的第5位置为0,变为大写字母 |
si和di是8086CPU中和bx功能相近的寄存器,si和di不能够分成两个8位寄存器来使用。
[bx+idata]
[bx+idata]表示一个内存单元,它的偏移地址为(bx)+idata
。指令mov ax,[bx+200]
的数字化描述为(ax) = ((ds)*16+(bx)+200)
。该指令也常用以下格式:
1 | mov ax,[200+bx] |
[bx+si]&[bx+di]
[bx+si]和[bx+di]含义相似,以前者为例进行说明。[bx+si]表示一个内存单元,它的偏移地址为(bx)+(si)
。指令mov ax,[bx+si]
的数字化描述为(ax) = ((ds)*16+(bx)+(si))
。指令常用格式为:
1 | mov ax,[bx][si] |
[bx+si+idata]&[bx+di+idata]
[bx+si+idata]和[bx+di+idata]含义相似,以前者为例进行说明。[bx+si+idata]表示一个内存单元,它的偏移地址为(bx)+(si)+idata
。指令mov ax,[bx+si+idata]
的数字化描述为(ax) = ((ds)*16+(bx)+(si)+idata)
。指令常用格式为:
1 | mov ax,[bx+200+si] |
经过对比我们可以得出以下结论:
以下面的编程题目为例简要说明在汇编语言中如何处理循环嵌套功能。
现要求编写汇编程序,实现将datasg段中每个单词的前4个字母改为大写字母。
汇编语言中循环功能的实现需要cx
寄存器的配合,因为该寄存器存放单层循环的循环次数。在多层循环这一情景下,我们可以使用寄存器或内存栈来保存cx
中存放的外层循环次数,以便内层循环对该寄存器的重复利用。
完整程序如下:
1 | assume cs:codesg,ss:stacksg,ds:datasg |
数据处理的两个基本问题为处理数据位置和处理数据长度。机器指令必须给出这两个问题明确或隐含的说明,否则计算机无法工作。
我们定义描述性符号reg
和sreg
,前者表示一个寄存器,后者表示一个段寄存器。
ax
、bx
、cx
、dx
、ah
、al
、bh
、bl
、ch
、cl
、dh
、dl
、sp
、bp
、si
、di
;ds
、ss
、cs
、es
。在8086CPU中,只有这4个寄存器可以用在[...]
中来进行内存单元的寻址,其他的reg例如ax
,bx
,cx
等出现在[...]
中均会报错。
在[...]
中,这4个寄存器可以单独出现,或只能以4种组合出现:bx&si
、bx&di
、bp&si
、bp&di
。
只要在[...]
中使用寄存器bp,而指令中没有显性地给出段地址,段地址就默认在ss中。比如下面的指令:
1 | mov ax,[bp] ;含义:(ax) = ((ss)*16+(bp)) |
绝大部分机器指令都是数据处理指令,处理大致可分为3类:读取、写入、运算。机器指令层不关心数据的值是多少,而关心指令执行前一刻,它将要处理的数据所在的位置。指令执行前所要处理的数据可以在3个地方:CPU内部、内存和端口。
汇编语言中使用3个概念来表达数据的位置。
立即数(idata)
对于直接包含在机器指令中的数据,在汇编语言中称为立即数,在汇编指令中直接给出。
寄存器
指令要处理的数据在寄存器中,在汇编指令中给出相应的寄存器名。
段地址(SA)&偏移地址(EA)
指令要处理的数据在内存中,在汇编指令中可用[X]
的格式给出EA,SA在某个段寄存器中。
8086CPU的指令,可以处理两种尺寸的数据,即byte和word。所以在机器指令中要指明,指令进行的是字操作还是字节操作。汇编语言采用以下方法处理这一问题:
通过寄存器名指明要处理的数据尺寸。如对ax
等16位寄存器执行的指令均为字操作,而对al
等8位寄存器执行的指令为字节操作。
在没有寄存器名存在的情况下,用操作符X ptr
指明内存单元的长度,X在汇编指令中可以为word或byte。用法举例如下:
1 | mov word ptr ds:[0],1 ;word ptr指明指令访问的内存单元是一个字单元 |
在没有寄存器参与的内存单元访问指令中,用word ptr
或byte ptr
显性地指明所要访问的内存单元的长度是很必要的。否则,CPU无法得知所要访问的单元是字单元还是字节单元。
有些指令默认了访问的是字单元还是字节单元,例如push
指令只进行字操作。
除数:有8位和16位两种,在一个reg或内存单元中。
被除数:默认放在AX或DX和AX中。
结果:默认存放在AX或AX和DX中。
格式:
1 | div reg |
应用举例
利用除法指令计算100001/100
1 | mov dx,1 |
利用除法指令计算1001/100
1 | mov ax,1001 |
除法溢出
当CPU执行div等除法指令的时候,如果结果的商过大,超出了寄存器所能存储的范围,将引发CPU的一个内部错误即除法溢出,我们可以通过以下计算方式排除溢出的情况。
给出公式$ X/N = int(H/N)65536+[rem(H/N)65536+L]/N $,其中
这个公式将可能产生溢出的除法运算X/N转变为多个不会产生溢出的除法运算。公式中等号右边的所有除法运算都可以用div指令进行,肯定不会导致除法溢出。
两个相乘的数必须均为8位或16位。若均为8位,则一个默认放在AL中,另一个放在8位reg或内存字节单元中;若均为16位,则一个默认放在AX中,另一个放在16位reg或内存字单元中。
若进行8位乘法,结果默认放在AX中;若进行16位乘法,结果高位放在DX中,低位放在AX中。
格式
1 | mul reg |
dd用来定义dword(double word,双字)类型的数据,该关键字定义的数据占两个字的大小。
dup是一个操作符,在汇编语言中和db、dw、dd等数据定义伪指令配合使用,用来进行数据的重复。dup的使用格式如下:
1 | db 重复的次数 dup (重复的字节型数据) |
汇编源程序如下:
1 | assume cs:codesg |
结果如下:
内存地址空间中,B8000H~BFFFFH共32KB的空间,为$80 \times 25$彩色字符模式的显示缓冲区。向这个地址空间写入数据,写入的内容讲立即出现在显示器上。彩色字符模式下,显示器可以显示25行,每行80个字符,每个字符包含256种属性。
每一行种,一个字符占两个字节的存储空间,低位字节存储字符的ASCII码,高位字节存储字符的属性。
显示缓冲区中,偶地址存放字符,奇地址存放字符的颜色属性。
属性字节的格式如下:
补码思想适用于有符号数的表示。首先使用00000000b~01111111b表示0~127,然后将它们按位取反加1后的数据表示负数。补码方案具有以下特点:
一个汇编语言程序从写出到最终执行的过程包括如下内容:
编写汇编程序
这一步工作的结果产生存储源程序的文本文件,常以.asm
结尾。
对源程序进行编译连接
使用汇编语言编译程序对源程序文件中的源程序进行编译,产生目标文件(以.obj
结尾)。再用连接程序对目标文件进行连接,生成可在操作系统中直接运行的可执行文件(以.exe
结尾)。这一步工作的结果产生可在操作系统中运行的可执行文件。
可执行文件包括程序、数据和相关描述信息三部分。
执行可执行文件中的程序。
程序返回
以上过程可概括如下:
汇编语言源程序中的指令包括汇编指令和伪指令。汇编指令有对应的机器码指令,可被编译为机器指令,最终被CPU执行。而伪指令没有对应的机器码指令,由编译器执行,编译器根据伪指令进行相关的编译工作。
segment&ends
:成对使用,功能为定义一个段。其中segment
说明段的开始,ends
说明段的结束。end
:是一个汇编程序的结束标记,编译器碰到伪指令end
则结束对源程序的编译。assume
:将有特定用途的段和相关的段寄存器关联起来。将源程序文件中的所有内容称为源程序,将源程序中最终由计算机执行、处理的指令或数据成为程序。程序最先以汇编指令的形式存在源程序中,经编译、连接后转变为机器码,存储在可执行文件中。
一个程序结束后,将CPU的控制权交还给使它得以运行的程序,这个过程称为程序返回。可以通过在程序的末尾添加如下返回程序段实现程序返回。
1 | mov ax,4c00h |
任何通用的操作系统,都需要提供一个称为shell的程序,用户使用这个程序来操作计算机系统进行工作。DOS系统中有一个程序command.com
,这个程序称为命令解释器,也就是DOS系统的shell。
故可执行文件在DOS中加载执行的过程可概括如下:
CS:IP
指向程序的第一条指令(即程序的入口),从而使程序得以运行。由上图可知,程序加载后,ds
中存放着程序所在内存区的段地址,对应的偏移地址为0.则程序所在的内存区地址为ds:0
。这个内存区的前256个字节中存放的是PSP,DOS用来和程序进行通信。从256字节处向后的空间存放程序。故程序的物理地址为SA+10H:0
。
一般来说,程序在编译时被编译器发现的错误是语法错误。源程序经过编译,在运行时发生的错误是逻辑错误。
DOS下汇编源程序的编辑、编译及连接需要的工具分别为编辑器edit
、编译器masm
和连接器link
。通常来说,DOSBox中不自带这些软件,下载链接如下:https://pan.baidu.com/s/1BxI4qu-3wjPmNOB5ADYFxw ,提取码:yxg5 。
在DOS命令行中输入edit 文件名.asm
指令,进行汇编源程序编辑,界面如下:
完成编辑后保存即可。
在DOS命令行中输入masm
指令进入编译模式。编译过程中,输入为源程序文件,最多可以得到三个输出即目标文件、列表文件和交叉引用文件。
[.ASM]
。若待编译文件为masm所在路径下的.asm
文件,则直接输入文件名;否则需要输入完整的路径名称及文件后缀。OBJ
文件名称,默认为[.OBJ]
。直接键入Enter则在当前目录下生成.obj
文件。当然也可以指定目录。常见编译错误包含两类:
Severe Errors
。连接的作用包括以下内容:
在DOS中输入link
指令进入连接模式。
OBJ
文件名称,默认为[.OBJ]
。如果文件不以.obj
为扩展名,则需要输入全名。masm 文件路径+文件名;
,自动忽略中间文件。link 文件路径+文件名;
,自动忽略中间文件。定义描述性符号(),其中的元素包括三种类型:寄存器名、段寄存器名和内存单元的物理地址(一个20位数据)。
约定Idata表示常量。
[bx]表示一个内存单元,偏移地址在bx
寄存器中,段地址在ds
寄存器中。
使用loop指令实现循环功能,寄存器cx
中存放循环次数。该指令的格式是:loop 标号
,CPU执行loop指令时,首先执行(cx) = (cx)-1
,随后判断cx
中的值,若不为零则跳转至标号处执行程序;若为零则向下执行。
使用cx
和LOOP指令配合实现循环功能的框架如下:
1 | mov cx, 循环次数 |
编写汇编程序并使用debug命令进行跟踪执行,验证loop语句的操作流程。
可以使用debug命令的g命令进行持续执行。如g 0012
表示从当前的CS:IP
指向的指令执行,一直到(IP)=0012h
为止。若希望将循环一次执行完,可在遇到loop指令时,使用p命令执行。
值得注意的是,在汇编源程序中,数据不能以字母开头。
在debug中的指令mov ax,[0]
表示将ds:0
处的数据送入ax
中。但在汇编源程序中,指令mov ax,[0]
被编译器当作指令mov ax,0
进行处理。Debug将[idata]
解释为一个内存单元,idata是内存单元的偏移地址;而编译器将[idata]
解释为idata。
若希望在源程序中实现将内存单元中的数据送入寄存器中,则可以使用bx
寄存器存储偏移地址,使用[bx]
的方式访问内存单元。若希望直接使用idata表示偏移地址,则需要显式地给出段地址所在的段寄存器,例如ds:[idata]
。
出现在访问内存单元的指令中,用于显式地指明内存单元的段地址的ds:
,cs:
,ss:
,es:
,在汇编语言中称为段前缀。
在8086模式中,随意向一段内存空间写入内容是危险的,因为这段空间中可能存放着重要的系统数据或代码。故向内存空间写入数据时,需要使用操作系统分配的内存空间,而不应直接用地址任意指定内存单元,向里面写入。
DOS和其他合法的程序一般都不会使用0:200~0:2ff
的256个字节的地址空间,故可以直接向该段空间内写入内容。
前面内容中讨论的程序均只包含一个代码段,称为单程序段程序,下面讨论多程序段程序。
操作系统环境中,合法地通过操作系统取得的空间都是安全地,因为操作系统不会让一个程序所用的空间和其他程序以及系统自己的空间冲突。故在操作系统允许的情况下,程序可以取得任意容量的空间。
程序可以通过两种方法获取所需空间,本文重点讨论在加载程序的时候为程序分配这种方法,即通过在源程序中定义段来获取内存空间。
考虑以下场景,我们需实现多个常数的相加,并将结果存储在特定寄存器中。这种情况下,我们可以在程序中定义希望处理的数据,这些数据就会被编译、连接程序作为程序的一部分写入可执行文件中。以下面的程序为例:
1 | assume cs:code |
dw
:即define word
,用于定义字型数据。该语句后跟随N个字型数据,其所占空间大小为2N字节。同时,dw
定义的数据处于代码段的头部,故偏移地址为0。
db
:即define byte
,用于定义字节型数据。该语句后跟随N个字节型数据,其所占空间大小为N字节。
通过debug查看上述程序的可执行文件,可以发现代码段的前16个字节是用dw
指令定义的数据,从第16个字节开始才是汇编指令对应的机器码。故我们需要显式地表明程序第一条指令的位置。
可以通过调试器修改CS:IP
指向的指令单元从而标记程序的第一条指令,但更方便的做法是利用start
标号来表明程序首条指令的位置。这个标号在伪指令end后面出现,于是end除通知编译器程序结束外,还可以通知编译器程序的入口在什么地方。修改后的程序如下:
1 | assume cs:code |
在代码段中使用数据的程序框架如下:
1 | assume cs:code |
考虑以下场景,我们需要逆序输出数据段中的内容,最佳的解决办法就是在代码段中引入栈。即在程序中通过定义数据来取得一段空间,然后将这段空间当作栈来使用。以下面程序为例:
1 | assume cs:codesg |
将CS:10~CS:2F
的内存空间当作栈使用,初始状态下栈为空,所以SS:SP
要指向栈底CS:30
。
为避免程序结构混乱,考虑将数据、代码和栈放入不同的段,并在程序头部进行寄存器关联。代码如下:
1 | assume cs:code,ds:data,ss:stack |
定义一个段的方法和定义代码段方法相同,只是不同的段要求不同的段名。
CPU如何处理定义的段中的内容完全依靠程序中具体的汇编指令,和汇编指令对CS:IP
、SS:SP
、DS
等寄存器的设置来决定。以上三个寄存器分别掌管代码段、栈段和数据段。
对于如下定义的段:
1 | name segment |
如果段中的数据占N个字节,则程序加载后,该段实际占有的空间为(N/16+1)*16
个字节。
一个典型的CPU由运算器、控制器、寄存器等器件构成。各器件功能如下:
8086CPU的所有寄存器都是16位的,可存放两个字节。AX、BX、CX、DX这四个寄存器通常用来存放一般性数据,称为通用寄存器。同时,这四个寄存器可分为两个可独立使用的8位寄存器来使用。
以寄存器AX为例,AX的低8位构成AL寄存器,高8位构成AH寄存器。AH和AL寄存器是可以独立使用的8位寄存器。注意,诸如mov ax,bl
这类的汇编语句是错误的,因为其尝试将一个8位寄存器中的值赋值至16位寄存器中。
8086CPU可以一次性处理以下两种尺寸的数据:
在写一条汇编指令或一个寄存器的名称时,不区分大小写。
8位寄存器只能存放两位十六进制的数字,若溢出则丢弃最高位。
1 | mov al, C5H |
以上代码执行结果为:158H
,而寄存器al
是8位寄存器,故舍弃最高位1,保存结果58H
。
所有的内存单元构成的存储空间是一个一维的线性空间,每个内存单元在这个空间中都有唯一的地址,将该地址称为物理地址。在CPU向地址总线上发出物理地址之前,必须要在内部先形成这个物理地址。
8086CPU是16位结构的CPU,具有以下特性:
8086CPU有20位地址总线,可以传送20位地址。然而,该CPU又是16位结构,在内部一次性处理、传输、暂时存储的地址均为16位。故8086CPU采用一种在内部用两个16位地址合成的方法来形成一个20位物理地址。
具体来讲,地址加法器采用物理地址=段地址*16+偏移地址的方法使用段地址和偏移地址合成物理地址。该方法的本质含义是:CPU在访问内存时,用一个基础地址(段地址16)和一个相对于基础地址的偏移地址相加,给出内存单元的物理地址。一般来说,这种寻址功能是”*基础地址+偏移地址=物理地址“寻址模式的一种具体实现方案。
以上寻址方式有以下几个关键点:
0~FFFFH
,仅用偏移地址来寻址最多可寻64KB个内存单元。可以根据需要,将地址连续、起始地址为16的倍数的一组内存单元定义为一个段。8086CPU有4个段寄存器:CS、DS、SS、ES。当8086CPU要访问内存时由这4个段寄存器提供内存单元的段地址。
CS和IP是8086CPU中两个最关键的寄存器,指示了CPU当前要读取指令的地址。CS为代码段寄存器,IP为指令指针寄存器。任意时刻,CPU将CS:IP
指向的内容当作指令执行。
8086CPU工作过程如下:
CS:IP
指向的内存单元读取指令,读取的指令进入指令缓冲器。IP = IP + 所读取指令的长度
,指向下一条指令。能够改变CS、IP内容的指令被统称为转移指令,最简单的转移指令是jmp
指令。可以使用形如jmp 段地址:偏移地址
的指令实现对CS、IP内容的同时修改。若只想修改IP的内容,可以使用形如jmp 某一合法寄存器
的指令完成,该指令表示使用寄存器中的值修改IP。
Windows10系统中已经剔除Debug相关的插件,需要下载DOSBox并手动安装debug.exe才可以使用Debug程序。安装指南链接如下:win10环境下如何安装和运行DOSBox和debug_mengjizhiyou的博客-CSDN博客。
Debug程序中重要指令如下:
R命令
查看、修改CPU中寄存器的内容。r命令可以直接查看所有寄存器的值,使用r+寄存器名称
语句以修改寄存器的值。
D命令
查看内存中的内容。
直接使用d命令将列出Debug预设的地址处的内容。使用命令d 段地址:偏移地址
的格式来指定查看内存的起始地址,随后接着使用d命令可列出后续内容。采用d 段地址:起始偏移地址 结尾偏移地址
来规定d命令的查看范围。
Debug将输出的内容分为三部分:中间是从指定地址开始的128个内存单元的内容,以十六进制的格式输出,每行的输出从16的整数倍地址开始,最多输出16个单元的内容。左边是每行的起始地址。右边是每个内存单元中的数据对应的可显示的ASCII码字符。
同时,D命令支持d 段寄存器:偏移地址
指令格式。
E命令
修改内存中的内容,可以写入数据和指令,在内存中它们实际上没有区别。
可以使用e 起始地址 数据 数据 数据 ...
的格式修改指定内存范围中的内容。也可以直接输入e 起始地址
,随后以询问的方式逐个修改内存单元中的内容,空格键表示指定内存单元修改完成,Enter键表示e命令执行结束。
U命令
将内存中的内容解释为机器指令和对应的汇编指令。
T命令
执行CS:IP
指向的内存单元处的指令。若需要执行指定内存处存放的指令,则需要利用r命令修改寄存器CS和IP的值,随后才可使用t命令完成指令执行。
值得注意的是,T命令在修改寄存器SS的指令时,下一条指令也紧接着被执行,这涉及中断机制。
A命令
以汇编指令的形式向内存中写入指令。
在内存中存储时,由于内存单元是字节单元,则一个字需要用两个地址连续的内存单元来存放,字的低位字节存放在低地址单元中,高位字节存放在高地址单元中。我们将起始地址为N的字单元简称为N地址字单元。
任何两个地址连续的内存单元既可被看作两个内存单元,也可 被看作一个地址为N的字单元中的高位字节单元和低位字节单元。
8086CPU中有一个DS寄存器,通常用来存放要访问数据的段地址。
可以利用mov
指令将一个内存单元中的内容送入一个寄存器中。寄存器通过寄存器名标识,而内存单元需要用内存单元地址指明。[address]
表示一个内存单元,address表示内存单元的偏移地址。指令执行时,CPU自动区ds寄存器中的数字作为内存单元的段地址。
值得注意的是,8086CPU不支持将数据直接送入段寄存器的操作,故需要使用一般寄存器完成中转。
1 | mov ds,1000H ;错误语句 |
以上三个指令均支持以下操作:
1 | * 寄存器,数据 |
其中*代表mov,add和sub三类运算。
栈是一种具有特殊访问方式的存储空间,具有后进先出(Last In First Out, LIFO)的特性。
8086CPU提供入栈和出栈指令,分别为PUSH和POP,前者将一个新的元素放到栈顶,后者从栈顶去除一个元素。8086CPU的入栈出栈操作都是以字为单位进行的。
入栈时,栈顶从高地址向低地址方向增长,出栈则相反。栈顶元素出栈后,内存对应单元的元素依然存在,但已不在栈中,当再次执行PUSH指令时,写入的新数据会将其覆盖。
8086CPU中存在段寄存器SS和栈指针 寄存器SP。栈顶的段地址存放在SS中,偏移地址存放在SP中。任意时刻,SS:SP
指向栈顶元素。在对栈进行操作前,首先需要初始化寄存器SS和SP。
例如,若对范围为22000H~2200FH范围的栈进行操作,需首先初始化 SS指向2200H,初始化SP指向000FH+1=0100H。
PUSH指令执行过程分为两步:首先执行SP = SP-2
,使SP指向新的栈顶元素;随后向SS:SP
指向的字单元中送入数据。POP指令执行过程同样分为两步:首先从SS:SP
指向的字单元中读取数据;随后执行SP = SP-2
移动SP指针。
可以看出,PUSH和POP等栈操作指令修改的只是SP寄存器,也就是说栈顶的变化范围最大为0~FFFFH。
当栈满的时候再次使用PUSH指令入栈,或栈空的时候再次使用POP指令出栈,都将发生栈顶越界问题。
我们可以将一段内存定义为一个段,用一个段地址指示段,用偏移地址访问段内的单元。根据使用者的初始化情况,一段内存可以作为代码的存储空间、数据的存储空间和栈空间,并支持这三类空间类型的组合。
数据段
数据段的地址放在DS中,使用mov、add、sub等访问内存单元的指令时,CPU就将指定内存单元中的内容作为数据来处理。
代码段
代码段的段地址放在CS中,段中第一条指令的偏移地址放在IP中,CPU将执行指定内存单元中的指令。
栈段
栈段的段地址 放在SS中,栈顶单元的偏移地址放在SP中,CPU在执行PUSH、POP等栈操作时就将指定内存单元作为栈空间使用。
本科毕业设计过程中需要使用JPBC库实现Java语言下双线性配对运算的仿真,摸索过程中遇到一些问题及特性,记录如下。本文参考李发根等编著的《基于配对的密码学》一书,首先简要介绍基于配对密码学的相关性质,随后结合JPBC文档介绍该库中部分函数的特殊性质及用法。
参考链接:
《基于配对的密码学》
链接:https://pan.baidu.com/s/1bocycprbAUtNzCopkF1v-A 提取码:oe09
JPBC jar包
链接:https://pan.baidu.com/s/1MOZCaplESGF0gVk5dNeLGQ 提取码:sa5m
JPBC DOCS
椭圆曲线密码体制(elliptic curve cryptosystem, ECC)是公钥密码体制的一个重要分支,其安全性基于椭圆曲线离散对数问题的困难性。该问题比大整数因子分解问题和有限域上的离散对数问题难得多。由于还没有找到求解椭圆曲线离散对数的亚指数算法,因此椭圆曲线密码体制可使用更短的密钥以保证相同的安全性。
有限域上的椭圆曲线是指变量和系数均为有限域中元素的椭圆曲线。有限域$GF(p)$上的椭圆曲线是指满足方程
的所有点$(x,y)$及一个无穷远点O构成的集合,其中a,b,x和y均在有限域$GF(p)$上取值,p是素数。这里将该椭圆曲线记为$E_p(a,b)$,曲线上只有有限个点,其个数N由Hasse定理确定。
Hasse定理 设E是有限域$GF(p)$上的椭圆曲线,N是E上点的个数,则满足
当$4a^3+27b^2(mod p) \neq 0$时,基于集合$E_p(a,b)$可以定义一个Abel群,其加法规则与实数域上描述的代数方法一致。设$P,Q \in E_p(a,b)$,则
如果$P = (x,y)$,那么$(x,y) + (x,-y) = O$,即点(x,-y)是P的加法逆元,表示为-P。
设$P=(x_1,y_1)$和$Q=(x_2,y_2)$,$P \neq -Q$,则$S = P+Q = (x_3,y_3)$由以下规则确定:
式中
倍点运算定义为重复加法,即$3P = P+P+P$。
定义1 椭圆曲线的阶
椭圆曲线$E_p(a,b)$上点P的阶是指满足
的最小正整数,记为$ord(P)$,其中O是无穷远点。
定义2 椭圆曲线上离散对数问题
设G是椭圆曲线$E_p(a,b)$上的一个循环子群,P是G的一个生成元,$Q \in G$。已知P和Q,求满足
的整数m,$0 \leq m \leq ord(P)-1$,称为椭圆曲线上的离散对数问题(elliptic curve discrete logarithm problem, ECDLP)。计算$mP$的过程称为点乘运算。
EC-ElGamal密码体制包含以下四个元操作:
编码与解码
将待发送的明文m编码为椭圆曲线上的点$P_m = (x_m, y_m)$,随后执行的加解密操作均针对点$P_m$,解密后的点$P_m$需执行逆向解码操作才可以获得明文。
密钥生成
在椭圆曲线$E_p(a,b)$上选取一个阶为大素数n的生成元P。随机选取整数x满足$1 < x < n$,计算$Q = xP$,将x作为私钥,Q作为公钥。
加密
为加密$P_m$,随机选取一个整数k满足$1 < k < n$,计算
则密文$c =(C_1, C_2)$。
解密
为解密密文$c = (C_1, C_2)$,计算
攻击者若妄图通过$c = (C_1,C_2)$计算出$P_m$,则必须获得k。攻击者需要通过P和kP推算出k的值,这一过程面临求解椭圆曲线上的离散对数问题。
设k为安全参数,p为k比特长的素数。令$G_1$为由P生成的循环加法群,阶为p,$G_T$为具有相同阶p的循环乘法群,a,b是$Z_p^*$中的元素。0表示$G_1$中的单位元,1表示$G_T$中的单位元。假设$G_1$和$G_T$这两个群中的离散对数问题都是困难问题。双线性配对是指满足下列性质的映射$e: G_1 \times G_1 \rightarrow G_T$。
双线性映射可以通过有限域上的超奇异椭圆曲线或超奇异超椭圆曲线中的Weil配对或Tate配对推导出来。
设计密码体制时,有时会遇到非对称的配对。
令$G_1, G_2, G_T$为具有相同阶p的群,P为$G_1$的生成元,Q为$G_2$的生成元。非对称双线性配对指满足下列性质的一个映射:$e: G_1 \times G_2 \rightarrow G_T$。
若令$G_2 = G_1$且映射$\phi$为恒等映射,此时非对称配对就变成了对称配对。尽管对称配对比较简单且应用方便,但只能从超奇异超椭圆曲线中的Weil配对或Tate配对推导出来。非对称配对比较复杂,但不仅可以从超奇异超椭圆曲线中推导出来,还可以从普通椭圆曲线中的Weil配对或Tate配对推导出来。
给定一个阶为p的循环加法群$G_1$和一个生成元P,$G_1$中的计算Diffie-Hellman(computational Diffie-Hellman,CDH)问题是给定$(P, aP, bP)$,计算$abP \in G_1$。这里$a, b \in Z_p^*$是未知整数。
给定一个阶为p的循环加法群$G_1$和一个生成元P,$G_1$中的判定Diffie-Hellman(decisional Diffie-Hellman,DDH)问题是给定$(P, aP, bP, cP)$,判断$c \equiv ab mod p$是否成立。这里$a, b, c \in Z_p^*$是未知整数。若$(P, aP, bP, cP)$满足上述条件,则称其为一个”Diffie-Hellman元组”,可采用记号$cP = DH_p(aP, bP)$来表示。
给定一个阶为p的循环加法群$G_1$和一个生成元P,$G_1$中的间隙Diffie-Hellman(gap Diffie-Hellman,GDH)问题是在DDH预言机的帮助下,求解一个给定元组$(P, aP, bP)$的CDH问题。DDH预言机可以判断$(P, aP, bP, cP)$是否满足$c \equiv ab mod p$。
给定一个阶为p的循环加法群$G_1$和一个生成元P,$G_1$中的q-强Diffie-Hellman(q-strong Diffie-Hellman,q-SDH)问题是给定$(P, xP, x^2P, … , x^qP)$,计算
给定两个阶都为p的循环加法群$G_1$和循环乘法群$G_T$,一个双线性映射$e:G_1 \times G_1 \rightarrow G_T$和一个群$G_1$的生成元P,双线性Diffie-Hellman(bilinear Diffie-Hellman,BDH)问题是给定$(P, aP, bP, cP)$,计算$e(P, P)^{abc} \in G_T$。这里的$a, b, c \in Z_p^*$是未知整数。
给定两个阶都为p的循环加法群$G_1$和循环乘法群$G_T$,一个双线性映射$e:G_1 \times G_1 \rightarrow G_T$和一个群$G_1$的生成元P,判定双线性Diffie-Hellman(decisional bilinear Diffie-Hellman,DBDH)问题是给定$(P, aP, bP, cP)$和$z \in G_T$,判断
是否成立。这里的$a, b, c \in Z_p^*$是未知整数。
给定两个阶都为p的循环加法群$G_1$和循环乘法群$G_T$,一个双线性映射$e:G_1 \times G_1 \rightarrow G_T$和一个群$G_1$的生成元P,间隙双线性Diffie-Hellman(gap bilinear Diffie-Hellman,GBDH)问题是在DBDH预言机的帮助下,求解一个给定元组$(P, aP, bP, cP)$的BDH问题。DBDH预言机可以判断一个元组$(P, aP, bP, cP, z)$是否满足
上述问题通常被视为困难问题,但其困难程度不尽相同。显然,判定问题不比计算问题更难,即如果能够求解CDH问题,那么DDH问题就容易解决;同样如果能够求解BDH问题,那么DBDH问题就容易解决。
PBC库(pairing-based cryptography library)是斯坦福大学研究人员开发的一个免费可移植C语言库。它通过提供一个抽象的接口,使程序设计人员可以不必考虑具体的数学细节,甚至不必考虑椭圆曲线和数论的相关知识就可以实现基于配对的密码体制。JPBC库(Java Pairing-Based Cryptography Library)是对PBC库的Java封装,常用于基于配对的密码学算法仿真程序编写中。
该库提供的各类API结构如下。
除JPBC文档外,整理一些优秀视频及技术博客,链接如下:
JPBC库共提供四个循环群,其中$G_1,G_2,G_T$均为阶为p的乘法循环群,而$Z_p$为整数域上的加法循环群。乘法循环群上的点是z值为0的椭圆曲线上的点,而整数循环群上的点是数,二者均可抽象为Element
数据类型并用于仿真中。生成测试元素并打印,结果如下:
1 | // 生成测试元素 |
JPBC库支持的运算如下:
测试相关运算,并打印对应结果如下:
1 | // 相关运算 |
值得注意的是,现在的密码学相关论文中,习惯将$G_1, G_2$设置为乘法循环群。但是基于椭圆曲线的双线性群构造中,$G_1, G_2$是加法循环群。所以在2005年以前的论文中,双线性群一般写成加法群的形式。JPBC库中将$G_1, G_2$表示成了乘法循环群,因此在加法循环群形式方案的仿真过程中,应特别注意将加法群改写为乘法群的写法再完成进一步仿真。由于加法群中的加法运算对应乘法群中的乘法运算,减法运算对应除法运算(即求逆元),乘法运算对应幂指数运算,而除法运算对应对数运算。故改写过程需要结合以上运算法则。
双线性群(即椭圆曲线)的初始化在JPBC中表现为对Pairing对象的初始化。JPBC库支持A、A1、D、E、F、G六种椭圆曲线,对比如下。我们可以通过代码动态产生和从文件中读取相关参数这两种方法完成上述初始化过程。
动态产生的方法大概包括以下几个步骤:
Type A曲线初始化过程中需要提供两个参数:rBit
代表$Z_p$中阶数p的比特长度,qBit
代表$G$中阶数的比特长度,生成代码如下:
1 | TypeACurveGenerator pg = new TypeACurveGenerator(rBit, qBit); |
Type A1曲线需要提供两个参数:numPrime
是阶数N中包含质数因子的数量,qBit
是每个质数因子的比特长度。由于Type A1曲线涉及到的阶数较大,故参数产生的时间较长,代码如下:
1 | TypeA1CurveGenerator pg = new TypeA1CurveGenerator(numPrime, qBit); |
当然我们可以选择事先生成参数并存放至文件中。在后续初始化过程中直接从文件中读取参数,就可以快速地完成双线性群的初始化过程。
可以利用Princeton大学封装的文件输出库将初始化后的椭圆曲线对象PairingParameters
封装至x.properties
文件中。后续使用过程中直接从对应配置文件中读取即可还原。代码如下:
1 | // Type A曲线 |
重点关注椭圆曲线循环群初始化过程中的相关事项。当确定椭圆曲线参数后重复调用getG1()
,newElement()
和newRandomElement()
方法,验证生成结果是否相同。
1 | public static void Group_Test(){ |
运行以上程序,结果如下:
可以看出,使用PairingFactory.getPairing(filename)
函数导入特定参数的椭圆曲线后,每次调用getG1()
函数生成的循环群都是相同的,故可以通过保存椭圆曲线参数至xxx.properties
文件并导入这一操作实现循环群的保存。
对于群$G_1$,每次调用G1.newElement()
函数生成的生成元g都是相同的。然而调用G1.newRandomElement()
函数随机获取的群上元素则是不同的。
该部分总结利用JPBC库编写算法仿真程序过程中需要用到的工具函数。代码如下:
1 | //16进制的byte[]数组转换为字符串 |
1 | //G1中获取随机元素,获取1,获取0 |
$H_0: {0, 1}^* \rightarrow Z_p $
1 | public static Element hashFromStringToZp(String str) { |
$H_1: {0, 1}^* \rightarrow G_1$
1 | public static Element hashFromStringToG1(String str) { |
$H_2: G_1 \rightarrow Z_p$
1 | public static Element hashFromG1ToZp( Element g1_element) { |
$H_{ch}: G_T \rightarrow Z_p$
1 | public static Element transformFromGtToZp(Element pairing_result){ |
定义writeElement(Element elem, String filename, Pairing pairing)
函数,实现将Element
对象写入文件。该函数的三个参数分别为待写入的Element
对象,写入文件路径以及对象所在椭圆曲线。返回结果为void
类型。
1 | public static void writeElement(Element elem, String filename, Pairing pairing) throws IOException { |
定义readElement(String filename, Pairing pairing)
函数,实现从文件中读取Element
对象。该函数的两个参数为读取文件路径和对象所在椭圆曲线,返回结果为Element
类型。
1 | public static Element readElement(String filename, Pairing pairing) throws IOException { |
结合文章《jPBC: java Pairing Based Cryptography》,比较jPBC和PBC之间的运算效率。用于比较效率的计算机配置为Intel@R CoreTM2 Quad CPU Q6600,2.40GHz,3 GB 内存,Ubuntu 10.04系统。JDK版本是Oracle jdk1.6.0 20。结果如下。
可以看出,由于Java语言特性的限制,JPBC库在处理乘法循环群上运算及配对运算方面效率远低于PBC库,但在处理整数循环群上运算方面效率高于PBC库。显然,可以通过预处理的方法提高JPBC库对应函数的运行效率。
]]>寒假赋闲在家,搞科研的同时希望提升一下自己的代码水平,于是回到阔别已久的LeetCode平台开始刷题。这次选择了剑指offer系列题目,编程语言采用Java。话不多说,直接开刷。
找出数组中重复的数字。
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
示例 1:
1 | 输入: |
限制:
2 <= n <= 100000
题目仅要求输出一个多次出现的数字,故自然联想到使用哈希表。完成建表之后对题目提供的数组进行一次遍历即可。该方法时间复杂度为O(N),空间复杂度为哈希表所占的额外空间O(N)。
注意题目给出的特殊条件:长度为n的数组里所有数字在0—n-1范围内。该条件说明若数组中不包含重复元素,则数组下标与元素值是一一对应的关系,即nums[i]==i
成立。于是引入原地置换的算法,提升程序运行效率,具体算法如下:
遍历数组nums,设置初始索引为i=0:
nums[i] == i
,说明数字与索引位置正确对应,无需交换,跳过该索引;nums[nums[i]] == nums[i]
,说明索引nums[i]及索引i处元素值相同,返回该重复值即可;若遍历完毕尚未返回任何值,则返回-1表示元素与索引一一对应。
1 | class Solution { |
在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
示例:
现有矩阵 matrix 如下:
1 | [ |
给定 target = 5,返回true
。
给定 target = 20,返回false
。
限制:
0 <= n <= 1000
0 <= m <= 1000
该题目将普通元素查找扩展至二维数组,最简单的方法为两次循环遍历,时间复杂度为O(N*M),其中N,M分别为二维数组的行数与列数。显然该解法没有用到矩阵从上至下递增,从左至右递增的特点,故不是最优解。
我们将矩阵逆时针旋转45°,可以发现其结构类似二叉搜索树,对每个元素来说,左分支元素更小而右分支元素更大。因此我们从根节点开始搜索,遇到大于目标元素的元素则向左继续搜索,反之则向右继续搜索,最终获得目标元素。
结合以上思想,我们进行合理转化:根节点对应矩阵右上角的元素。具体算法如下:
matrix[row_index][column_index] > target
,消去第column_index
列元素;matrix[row_index][column_index] < target
,消去第row_index
行元素;matrix[row_index][column_index] == target
,找到目标。该算法选择从右上角开始遍历,需要注意下标范围及二维数组空判断问题。
1 | class Solution { |
请实现一个函数,把字符串 s 中的每个空格替换成”%20”。
示例 1:
1 | 输入:s = "We are happy." |
限制:
0 <= s 的长度 <= 10000
根据题目要求,最简单的方法是采用String类自带的替换函数replaceAll()
,当然面试官肯定不会允许。于是就需要自己研究替换函数。注意到Java中String类型使用final关键词修饰,即String对象是不可修改的,从而考虑两种方法:
toString()
函数转换成String类型输出即可。char[]
,首先确定数组的长度为原始字符串的三倍(假设原始字符串全部由空格组成),随后采用双指针法循环遍历原始字符串和新的字符串,完成比较与添加字符的操作。1 | class Solution { |
1 | class Solution { |
Method1
Method2
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
示例 1:
1 | 输入:head = [1,3,2] |
限制:
0 <= 链表长度 <= 10000
本题目为最基础的链表逆序问题,可采用两种不同的数据结构辅助完成:
1 | /** |
1 | /** |
两种方法的时间复杂度与空间复杂度均为$O(N)$。
输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
例如,给出
1 | 前序遍历 preorder = [3,9,20,15,7] |
返回如下的二叉树:
1 | 3 |
限制:
0 <= 节点个数 <= 5000
题目中给出前序遍历和中序遍历的结果数组。树结构中前序遍历遵循根节点—>左节点—>右节点
的遍历顺序,中序遍历遵循左节点—>根节点—>右节点
的遍历顺序,我们需要结合两次遍历的结果数组完成二叉树重构。基本方法如下:
递归函数将前序遍历结果数组preorder[]
和中序遍历结果数组inorder[]
作为输入参数,显然preorder[0]
必为根节点。随后获取根节点对应元素在inorder[]
数组中的位置(可通过构造函数和采用哈希表存储的方法),设为target_index
。则在中序遍历数组中,inorder[:target_index]
即为左子树,inorder[target_inex:]
即为右子树;在前序遍历数组中,preorder[1: 1+target_index-0]
为左子树(其中target_index-0
表示从inorder[]
数组中获取的左子树的长度),preorder[target_index+1:]
为右子树。通过以上方法可以确定更新后的preorder[]
数组和inorder[]
数组,随后递归调用即可。
结合基本方法的分析,我们引入分治算法,通过递归对所有子树进行划分:
递推参数:根节点在前序遍历的索引root_index
,子树在中序遍历的左边界left_bound
,子树在中序遍历的右边界right_bound
;
终止条件:当left_bound > right_bound
,代表已经越过叶节点,此时返回null;
递推流程
建立根节点node
,节点值为preorder[root_index]
;
划分左右子树:查找根节点在中序遍历结果数组inorder[]
中的索引target_index_in_inorder
;
开启左右子树递归,关键参数如下表所示:
返回值:回溯返回node
,作为上一层递归中根节点的左/右子节点
值得注意的是,参数列表中target_index_in_inorder - left_bound + root_index + 1
含义为:根节点索引+左子树长度+1
,在中序遍历中该位置表示右子树的根节点。
1 | /** |
1 | // This method can only be used in the construction of binary tree without duplicate nodes |
两种方法的时间复杂度与空间复杂度均为$O(N)$。
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数appendTail
和deleteHead
,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead
操作返回 -1 )
示例 1:
1 | 输入: |
示例 2:
1 | 输入: |
提示:
1 <= values <= 10000
appendTail
,deleteHead
进行 10000 次调用题目主要考察栈和队列这两种存储结构的功能区别。栈具有LIFO的特点,即入栈与出栈都是对栈顶位置的元素进行操作。而队列可以理解为普通数组,可同时实现头部与尾部两向的操作。
结合以上特性,我们采用下面的思路解决问题:
本题中要求实现构造函数CQueue()
,加入队尾函数appendTail()
与删除队首函数deleteHead()
。
CQueue()
:初始化两链表结构,分别代表栈A、B,用来实现加入队尾操作与元素倒序;appendTail()
:将新元素加入栈A即可;deleteHead()
:删除过程中存在以下三种情况:1 | class CQueue { |
appendTail()
函数为$O(1)$;deleteHead()
函数在N次队首元素删除操作中共需完成N个元素的倒序;写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项。斐波那契数列的定义如下:
1 | F(0) = 0, F(1) = 1 |
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
示例 1:
1 | 输入:n = 2 |
示例 2:
1 | 输入:n = 5 |
提示:
0 <= n <= 100
题目给出斐波那契数列的递推公式,说明其具有递推关系,选择f(n+1) = f(n) + f(n-1)
为转移方程,采用动态规划完成问题求解:
dp[i]
的值代表斐波那契数列第i个数字;dp[i+1] = dp[i] + dp[i-1]
;dp[0] = 0, dp[1] = 1
;dp[n]
,即返回斐波那契数列的第n个数字。这里采用循环求余法简化运算过程。随着n的增大,f(n)
会超过Int32
甚至Int64
的取值范围,从而导致结果溢出,故需要采用求余运算,规则如下:
设正整数x,y,p,求余符号为mod
,则有(x+y) mod p = (x mod p + y mod p) mod p
,从而f(n) mod p = [f(n-1) mod p + f(n-2) mod p] mod p
。故只需在循环中每次计算sum = (a+b) mod 1000000007即可。
1 | class Solution { |
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
示例 1:
1 | 输入:n = 2 |
示例 2:
1 | 输入:n = 7 |
示例 3:
1 | 输入:n = 0 |
提示:
0 <= n <= 100
本题目与斐波那契数列解题方法类似,只不过初始参数不同,可以通过普通动态规划和简化版动态规划解决问题,后者占用更少的空间资源。
1 | class Solution { |
1 | class Solution { |
Method1
时间与空间复杂度均为$O(N)$。
Method2
时间复杂度为$O(N)$,空间复杂度为$O(1)$。
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。
示例 1:
1 | 输入:[3,4,5,1,2] |
示例 2:
1 | 输入:[2,2,2,0,1] |
本题显然需要使用二分法,目标元素为旋转点元素,一般情况下满足以下条件:该元素值小于右侧元素,大于左侧元素。算法设计过程中需要考虑以下两个特殊情况:
二分法本身分以下三种情况讨论:
nums[mid] > nums[right]
nums[mid] < nums[right]
nums[mid] == nums[right]
具体题解参考如下链接:
需要注意的是,二分法过程中经常使用mid = left + (left-right) / 2
来防止溢出。
1 | class Solution { |
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用加粗标出)。
[[“a”,”b”,”c”,”e”],
[“s”,”f”,”c”,”s”],
[“a”,”d”,”e”,”e”]]
但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。
示例1:
1 | 输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED" |
示例 2:
1 | 输入:board = [["a","b"],["c","d"]], word = "abcd" |
提示:
1 | 1 <= board.length <= 200 |
题目为典型的矩阵搜索问题,可使用深度优先搜索+剪枝
来解决。首先介绍下这两个概念:
此路无法和目标字符串匹配成功
的情况(例如,此矩阵元素和目标字符不同,此元素已被访问等),则应立即返回,此种方法成为可行性剪枝
。DFS过程如下:
board
中的行列索引i
和j
,当前目标字符在字符串word
中的索引k
。false
:1)行或列索引出现越界或(2)当前矩阵元素与目标字符不同或(3)当前矩阵元素已被访问;true
:k=len(word)-1
,代表字符串word
已全部完成匹配。board[i][j]
修改为 空字符'\0'
,代表此元素已访问过,防止之后搜索时重复访问。res
。board[i][j]
元素还原至初始值,即word[k]
。res
,代表是否搜索到目标字符串。1 | class Solution { |
假设M,N分别为矩阵的行列大小,K为字符串word
的长度。
地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?
示例 1:
1 | 输入:m = 2, n = 3, k = 1 |
示例 2:
1 | 输入:m = 3, n = 1, k = 0 |
提示:
1 | 1 <= n,m <= 100 |
本题与上题类似,均为典型的搜索&回溯问题。与上题不同的是,本题目的起点为(0,0)
而非任意点,同时每次机器人运动方向只有向右和向下两个方向。可以采用广度优先搜索与深度优先搜索两种方法解题。可参考题解如下:机器人的运动范围(回溯算法,DFS/BFS)
1 | class Solution { |
1 | class Solution { |
visited
内存储矩阵所有单元格的索引,使用$O(MN)$的额外空间。给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m-1]
。请问 k[0]*k[1]*...*k[m-1]
可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
示例 1:
1 | 输入: 2 |
示例 2:
1 | 输入: 10 |
提示:
1 | 2 <= n <= 58 |
设将长度为n的绳子切为a段,满足:$n = n_{1} + n_{2} +… + n_{a}$,则本题等价于求解$max(n_{1}*n_{2}*…*n_{a})$。提出两个假设:1)当所有绳段长度相等时,乘积最大;2)最优的绳段长度为3。进行推导如下:
根据算术几何均值不等式:
当且仅当$n_{1} = n_{2} = … = n_{a}$时成立。故将绳子以相等的长度等分为多段,得到的乘积最大。
设将绳子按照x长度等分为a段,即n=a*x,则乘积为$x^{a}$。由于n为常数,满足$x^{a} = x^{\frac{n}{x}} = (x^{\frac{1}{x}})^{n}$。
构造函数$y = x^{\frac{1}{x}}$,对x求导得:
令y’=0,则1-lnx=0,得驻点$x_{0} = 2.7$且该点为极大值点。由于切分长度x必须为整数,分别带入最接近e的整数2或3,比较可知x=3时乘积达到最大。故尽可能将绳子以长度3等分为多段时,乘积最大。
结合以上分析,本算法流程如下:
n <= 3
时,按照数学推导不应切分,但由于题目要求必须剪成两段及以上,故此时返回n-1
;n > 3
时,n可表示为n = 3*a + b
,分别求出整数部分a和余数部分b。b == 0
时,返回pow(3, a)
;b == 1
时,需将一个3+1
转换成2+2
,因此返回pow(3, a-1)*4
;b === 2
时,返回pow(3, a)*2
。dp[0]=1
,dp[1]=1
;dp[i] = Math.max(dp[i], Math.max(j*dp[i-j], j*(i-j)))
。对于循环到的长度i,假设将其剪成两段,一段长度为j,另一段为i-j。此时需要比较j*(i-j)
与j*(dp[i-j])
的大小,根据比较结果决定是否继续剪下去:若j*(i-j) > j*(dp[i-j])
,说明将长度为i的绳子剪成两段得到最大结果;否则说明需要继续分段,此时将切割长度从i转化为i-j
。dp[n]
。1 | class Solution { |
1 | class Solution { |
本题目与剪绳子I的操作过程完全相同,唯一不同的是n的范围扩展至2 <= n <= 1000
。结合上题分析可知,最终乘积呈现指数式增长,故考虑部分编程语言中int32
甚至int64
类型数据出现溢出的情况,要求最终结果需要进行模1e9+7
的处理。
本题目与上题基本思路相同,唯一区别为需要进行模运算
。故引入快速幂乘法实现该运算。然而,由于模运算的存在无法进行动态规划,于是采用贪心算法解题。
1 | class Solution { |
1 | class Solution { |
请实现一个函数,输入一个整数(以二进制串形式),输出该数二进制表示中 1 的个数。例如,把 9 表示成二进制是 1001,有 2 位是 1。因此,如果输入 9,则该函数输出 2。
示例:
1 | 输入:00000000000000000000000000001011 |
提示:
输入必须是长度为 32 的 二进制串 。
结合题目需求,可以采用逐位判断
与n&(n-1)
两种方法。
根据与运算定义,二进制数字n存在以下性质:
n&1 == 0
,则n最右一位为0;n&1 == 1
,则n最右一位为1。根据以上特点,考虑以下循环算法:
count_result
自加1,否则continue
;>>>
)。(n-1)
运算:二进制数字n最右边的1变成0,随后此1右边的0都变成1;
n&(n-1)
运算:二进制数字n最右边的1变成0,其余不变。
根据以上特点,考虑循环算法:
n == 0
时跳出循环;count_result
,同时采用n&(n-1)
持续消去数字n最右边的1。1 | public class Solution { |
1 | public class Solution { |
n&(n-1)
操作仅有减法和与运算,占用$O(1)$;设M为二进制数字n中1的个数,则需要循环M次,每轮消去一个1。实现函数double Power(double base, int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑大数问题。
示例 1:
1 | 输入: 2.00000, 10 |
示例 2:
1 | 输入: 2.00000, -2 |
说明:
针对幂运算问题都可以通过快速幂算法提高运算效率。快速幂实际上是二分思想的一种应用,现推导如下:
$x^{n} = x^{n/2}*x^{n/2}=(x^{2})^{n/2}$,令n/2为整数,则分奇偶两种情况讨论,设向下取整符号为//
:
结合以上推导,通过下述方法获取幂结果:
为加快运算速率,将不同运算转化为位运算:
n//2
等价于有符号数右移一位n>>1
;n%2
等价于判断二进制最后一位数值n&1
。1 | class Solution { |
We shall say that an n-digit number is pandigital if it makes use of all the digits 1 to n exactly once. For example, 2143 is a 4-digit pandigital and is also prime.
What is the largest n-digit pandigital prime that exists?
1 | # What is the largest n-digit pandigital prime that exists? |
The $ n^{th} $ term of the sequence of triangle numbers is given by, $ t_{n} = 1/2*n*(n+1)$; so the first ten triangle numbers are:
1, 3, 6, 10, 15, 21, 28, 36, 45, 55, …
By converting each letter in a word to a number corresponding to its alphabetical position and adding these values we form a word value. For example, the word value for SKY is $ 19 + 11 + 25 = 55 = t_{10} $. If the word value is a triangle number then we shall call the word a triangle word.
Using words.txt (right click and ‘Save Link/Target As…’), a 16K text file containing nearly two-thousand common English words, how many are triangle words?
1 | # How many are triangle words? |
The number, 1406357289, is a 0 to 9 pandigital number because it is made up of each of the digits 0 to 9 in some order, but it also has a rather interesting sub-string divisibility property.
Let $ d_{1} $ be the $ 1^{st} $ digit,$ d_{2} $ be the $ 2^{nd} $ digit, and so on. In this way, we note the following:
Find the sum of all 0 to 9 pandigital numbers with this property.
题解引入sympy.nextprime(n)
函数以持续不断地获取素数,从而用于性质判断。该函数返回大于参数n的第一个素数。
1 | # Find the sum of all 0 to 9 pandigital numbers with this property |
Pentagonal numbers are generated by the formula,$ P_{n} = n(3n−1)/2 $. The first ten pentagonal numbers are:
1, 5, 12, 22, 35, 51, 70, 92, 117, 145, …
It can be seen that $ P_{4} + P_{7} = 22 + 70 = 92 = P_{8} $. However, their difference, 70 − 22 = 48, is not pentagonal.
Find the pair of pentagonal numbers, $ P_{j} $ and$ P_{k} $, for which their sum and difference are pentagonal and $ D = |P_{k} − P_{j}| $ is minimised; what is the value of D?
题解引入operator函数库,运用其中的底层函数提高运算效率,该库内函数说明如下:
operator库函数说明:https://docs.python.org/zh-cn/3/library/operator.html。
1 | # Find the pair of pentagonal numbers, Pj and Pk |
Triangle, pentagonal, and hexagonal numbers are generated by the following formulae:
NumberType | Formulae | Example |
---|---|---|
Triangle | $ T_{n} = n*(n+1) / 2 $ | 1, 3, 6, 10, 15, … |
Pentagonal | $ P_{n} = n*(3*n-1) / 2 $ | 1, 5, 12, 22, 35, … |
Hexagonal | $ H_{n} = n*(2*n-1) $ | 1, 6, 15, 28, 45 |
It can be verified that $ T_{285} = P_{165} = H_{143} = 40755 $.
Find the next triangle number that is also pentagonal and hexagonal.
根据题意,以1—60000为生成元构造三角形数、五边形数和六边形数对应的集合,随后求交集并输出大于40755的第一个数字即可。
1 | # Find the next triangle number bigger than 40755 that is also pentagonal and hexagonal |
通过观察这三类数字的生成表达式可以获得三角形数、五边形数和六边形数的判断方法:
同时,这三类数的生成过程满足性质:$ H(n) = T(2n-1) $,即生成元n得到的六边形数一定对应更大生成元得到的三角形数。于是可以从第144个六边形数开始逐个验证其是否可表示为五边形数即可。暴力代码如下:
1 | # Find the next triangle number bigger than 40755 that is also pentagonal and hexagonal |
It was proposed by Christian Goldbach that every odd composite number can be written as the sum of a prime and twice a square.
It turns out that the conjecture was false.
What is the smallest odd composite that cannot be written as the sum of a prime and twice a square?
1 | # What is the smallest odd composite that cannot be written as the sum of a prime and twice a square? |
The first two consecutive numbers to have two distinct prime factors are:
The first three consecutive numbers to have three distinct prime factors are:
Find the first four consecutive integers to have four distinct prime factors each. What is the first of these numbers?
1 | # Find the first four consecutive integers to have four distinct prime factors each. |
The series, $ 1^{1} + 2^{2} + 3^{3} + … + 10^{10} = 10405071317 $.
Find the last ten digits of the series, $ 1^{1} + 2^{2} + 3^{3} + … + 1000^{1000} $.
1 | # Find the last ten digits of the series, sum(pow(n, n) for n in range(1, 1000)) |
The arithmetic sequence, 1487, 4817, 8147, in which each of the terms increases by 3330, is unusual in two ways: (i) each of the three terms are prime, and, (ii) each of the 4-digit numbers are permutations of one another.
There are no arithmetic sequences made up of three 1-, 2-, or 3-digit primes, exhibiting this property, but there is one other 4-digit increasing sequence.
What 12-digit number do you form by concatenating the three terms in this sequence?
1 | # What 12-digit number do you form by concatenating the three terms in this sequence? |
The prime 41, can be written as the sum of six consecutive primes:
41 = 2 + 3 + 5 + 7 + 11 + 13
This is the longest sum of consecutive primes that adds to a prime below one-hundred.
The longest sum of consecutive primes below one-thousand that adds to a prime, contains 21 terms, and is equal to 953.
Which prime, below one-million, can be written as the sum of the most consecutive primes?
1 | # Which prime, below one-million, can be written as the sum of the most consecutive primes? |
In the United Kingdom the currency is made up of pound (£) and pence (p). There are eight coins in general circulation:
1p, 2p, 5p, 10p, 20p, 50p, £1 (100p), and £2 (200p).
It is possible to make £2 in the following way:
1×£1 + 1×50p + 2×20p + 1×5p + 1×2p + 3×1p
How many different ways can £2 be made using any number of coins?
题目本质为经典的找零问题,可以通过动态规划的方法解决。
以本题给出的条件为例进行详细说明。题目给出1,2,5,10,20,50,100,200这八种面额的便士类型,要求通过组合获得200便士并求出所有组合的数量。首先将问题进行分解,结果为200便士的组合可分为以下三种:
故可得以下递推公式:
\1.PNG)
各字符含义如下:
根据以上递推公式采用动态规划的方法,将大问题分割成多个小问题并进行分类讨论处理,该方法为第一类动态规划,遵循自顶向下的问题划分原则。同时引入二维数组记录不同(t, c)组对应的方法数,以提高程序运行效率。代码如下:
1 | # How many different ways can 2 pound be made using any number of coins? |
以上算法还可以进一步优化。首先我们观察(t, c)取值不同时w(t, c)的运算结果,对应如下表:
\2.PNG)
根据以上对应关系归纳递推公式如下:
\3.PNG)
经过深入分析,我们可以得出w’(t, c)函数不同参数对应结果之间的关系图如下所示:
\4.PNG)
可以看出第一行和第一列的数值1达到最终目的地需要较长的加和路径。除此之外其他所有元素都可由最多另外两个元素相加得到。从而每个w’(t, c)的值最多决定于另外两个数值,即:
该方法为第二种动态规划方法,遵循自底向上的问题划分原则,从较小的问题开始解决,逐步解决更大的子问题。此类方法在处理以下问题时具有更高的效率:
结合以上分析,对应代码如下:
1 | # How many different ways can 2 pound be made using any number of coins? |
Euler-Project-Problem31-Overview:https://projecteuler.net/overview=031
动态规划2:https://www.jianshu.com/p/e515efee2310
硬币找零问题:https://www.cnblogs.com/anzhengyu/p/11176134.html
We shall say that an n-digit number is pandigital if it makes use of all the digits 1 to n exactly once; for example, the 5-digit number, 15234, is 1 through 5 pandigital.
The product 7254 is unusual, as the identity, 39 × 186 = 7254, containing multiplicand, multiplier, and product is 1 through 9 pandigital.
Find the sum of all products whose multiplicand/multiplier/product identity can be written as a 1 through 9 pandigital.
HINT: Some products can be obtained in more than one way so be sure to only include it once in your sum.
1 | # Find the sum of all products whose multiplicand/multiplier/product identity can be written as a 1 through 9 pandigital. |
The fraction 49/98 is a curious fraction, as an inexperienced mathematician in attempting to simplify it may incorrectly believe that 49/98 = 4/8, which is correct, is obtained by cancelling the 9s.
We shall consider fractions like, 30/50 = 3/5, to be trivial examples.
There are exactly four non-trivial examples of this type of fraction, less than one in value, and containing two digits in the numerator and denominator.
If the product of these four fractions is given in its lowest common terms, find the value of the denominator.
1 | # If the product of these four fractions is given in its lowest common terms, find the value of the denominator. |
145 is a curious number, as 1! + 4! + 5! = 1 + 24 + 120 = 145.
Find the sum of all numbers which are equal to the sum of the factorial of their digits.
Note: As 1! = 1 and 2! = 2 are not sums they are not included.
1 | # Find the sum of all numbers which are equal to the sum of the factorial of their digits |
The number, 197, is called a circular prime because all rotations of the digits: 197, 971, and 719, are themselves prime.
There are thirteen such primes below 100: 2, 3, 5, 7, 11, 13, 17, 31, 37, 71, 73, 79, and 97.
How many circular primes are there below one million?
首先引入python的sympy库筛选出10到1000000之间的所有素数,随后对其进行第二次筛选操作。由于需要对每个素数进行顺序位移,故第一批剔除任意数位为偶数的素数。第三次筛选题目要求进行顺序位移并判断结果是否在第一步获得的素数池内。最后计算长度并作差即可。
1 | # How many circular primes are there below one million |
The decimal number, $ 585 = 1001001001_{2}$ (binary), is palindromic in both bases.
Find the sum of all numbers, less than one million, which are palindromic in base 10 and base 2.
(Please note that the palindromic number, in either base, may not include leading zeros.)
1 | # Find the sum of all numbers, less than one million, which are palindromic in base 10 and base 2 |
除借助python特有的字符串逆序特性方便地解决问题外,我们设计任意进制下判定某个数字是否为回文数字的函数isPalindromes()
如下:
1 | def isPalindrome(input_number, base): |
注意在二进制运算中,可以使用&1
代替模运算,使用<<1
代替乘法运算。
由于题目需要在二进制下保持回文状态且不考虑前导零的存在,故排除所有的偶数,即循环过程中以奇数起始,步长选择为2:
1 | if __name__ == '__main__': |
在题目给出的限制范围内,解法二可保持较高的效率,但随着数字上限的扩大,程序运行时间会大大增长。故考虑”生成+判定”的方法:假设在b进制下存在回文数字xyzzyx,取前三个数字xyz为回文结,则该六位数字由三位回文结xyz定义。同时xyz亦可定义五位回文数字xyzyx。故我们可得出如下结论:
对于任意b进制,任意小于$b^{n}$的整数可作为回文结生成两个小于$b^{2n}$的回文数字,这两个回文数字的位数一奇一偶。
应用以上结论,我们选择在二进制下生成两个回文数字并检验十进制下该数字是否为回文数字。如果需要得到奇数位数的回文数字,则设置标志位为true,并进行运算生成元n/进制b
,代码如下:
1 | # Find the sum of all numbers, less than one million, which are palindromic in base 10 and base 2 |
The number 3797 has an interesting property. Being prime itself, it is possible to continuously remove digits from left to right, and remain prime at each stage: 3797, 797, 97, and 7. Similarly we can work from right to left: 3797, 379, 37, and 3.
Find the sum of the only eleven primes that are both truncatable from left to right and right to left.
NOTE: 2, 3, 5, and 7 are not considered to be truncatable primes.
1 | # Find the sum of the only eleven primes that are both truncatable from left to right and right to left |
Take the number 192 and multiply it by each of 1, 2, and 3:
192 × 1 = 192
192 × 2 = 384
192 × 3 = 576
By concatenating each product we get the 1 to 9 pandigital, 192384576. We will call 192384576 the concatenated product of 192 and (1,2,3)
The same can be achieved by starting with 9 and multiplying by 1, 2, 3, 4, and 5, giving the pandigital, 918273645, which is the concatenated product of 9 and (1,2,3,4,5).
What is the largest 1 to 9 pandigital 9-digit number that can be formed as the concatenated product of an integer with (1,2, … , n) where n > 1?
1 | # What is the largest 1 to 9 pandigital 9-digit number that can be formed as the concatenated product of an integer with (1,2, ... , n) where n > 1? |
If p is the perimeter of a right angle triangle with integral length sides, {a,b,c}, there are exactly three solutions for p = 120.
{20,48,52}, {24,45,51}, {30,40,50}
For which value of p ≤ 1000, is the number of solutions maximised?
1 | # For which value of p <= 1000, is the numnber of solutions maximised? |
An irrational decimal fraction is created by concatenating the positive integers:
0.123456789101112131415161718192021…
It can be seen that the $12^{th}$ digit of the fractional part is 1.
If $d_{n}$ represents the $n^{th}$ digit of the fractional part, find the value of the following expression.
1 | # Find the value of the following expression |
Let d(n) be defined as the sum of proper divisors of n (numbers less than n which divide evenly into n).
If d(a) = b and d(b) = a, where a ≠ b, then a and b are an amicable pair and each of a and b are called amicable numbers.
For example, the proper divisors of 220 are 1, 2, 4, 5, 10, 11, 20, 22, 44, 55 and 110; therefore d(220) = 284. The proper divisors of 284 are 1, 2, 4, 71 and 142; so d(284) = 220.
Evaluate the sum of all the amicable numbers under 10000.
根据题目抓住关键点:
d(a) == b and d(b) == a, while a != b
条件的数字对(a, b) ,故需要关注d(a) == a
的情况;1 | def getSumOfDivisors(input_number): |
从第十题的分析可以看出,任何一个数字都可以表示成素数幂乘积的形式。下面对素数幂的因子之和进行推导分析:
设p为任意素数,由于素数只包含1和其本身两个因子,则:$\sigma(p) = p + 1$;
考虑p的a次幂,$\sigma(p^{a}) = 1 + p + p^{2} + p^{3} + … + p^{a}$ (1);
式子(1)两侧同乘p,有$p*\sigma(p^{a}) = p + p^{2} + p^{3} + p^{4} + … + p^{a+1} (2)$;
(2) - (1)得,$p\sigma(p^{a}) - \sigma(p^{a}) = (p-1) \sigma(p^{a}) = p^{a+1} - 1$;
因此$\sigma(p^{a}) = (p^{a+1} - 1) / (p - 1)$
使用该公式并结合数字的素数幂分解即可较快求出因子之和,代码实现如下:
1 | def SumOfDivisors(n): |
与解法一对比,该方法将运算时间 从3s降低到0.3s。
Using names.txt (right click and ‘Save Link/Target As…’), a 46K text file containing over five-thousand first names, begin by sorting it into alphabetical order. Then working out the alphabetical value for each name, multiply this value by its alphabetical position in the list to obtain a name score.
For example, when the list is sorted into alphabetical order, COLIN, which is worth 3 + 15 + 12 + 9 + 14 = 53, is the 938th name in the list. So, COLIN would obtain a score of 938 × 53 = 49714.
What is the total of all the name scores in the file?
1 | # Count the total scores of names |
A perfect number is a number for which the sum of its proper divisors is exactly equal to the number. For example, the sum of the proper divisors of 28 would be 1 + 2 + 4 + 7 + 14 = 28, which means that 28 is a perfect number.
A number n is called deficient if the sum of its proper divisors is less than n and it is called abundant if this sum exceeds n.
As 12 is the smallest abundant number, 1 + 2 + 3 + 4 + 6 = 16, the smallest number that can be written as the sum of two abundant numbers is 24. By mathematical analysis, it can be shown that all integers greater than 28123 can be written as the sum of two abundant numbers. However, this upper limit cannot be reduced any further by analysis even though it is known that the greatest number that cannot be expressed as the sum of two abundant numbers is less than this limit.
Find the sum of all the positive integers which cannot be written as the sum of two abundant numbers.
1 | # Find the sum of all the positive integers which cannot be written as the sum of two abundant numbers |
使用上述暴力遍历的方法统计结果需要的时间较长,大概为160-180s左右。
A permutation is an ordered arrangement of objects. For example, 3124 is one possible permutation of the digits 1, 2, 3 and 4. If all of the permutations are listed numerically or alphabetically, we call it lexicographic order. The lexicographic permutations of 0, 1 and 2 are:
012 021 102 120 201 210
What is the millionth lexicographic permutation of the digits 0, 1, 2, 3, 4, 5, 6, 7, 8 and 9?
偷懒调用了python的itertools库,其中包含permutations()
即排列函数。
1 | # What is the millionth lexicographic permutation of the digits 0, 1, 2, 3, 4, 5, 6, 7, 8 and 9 |
The Fibonacci sequence is defined by the recurrence relation:
Hence the first 12 terms will be:
The 12th term, $F_{12}$, is the first term to contain three digits.
What is the index of the first term in the Fibonacci sequence to contain 1000 digits?
1 | # What is the index of the first term in the Fibonacci sequence to contain 1000 digits |
A unit fraction contains 1 in the numerator. The decimal representation of the unit fractions with denominators 2 to 10 are given:
1/2 = 0.5 1/3 = 0.(3) 1/4 = 0.25 1/5 = 0.2 1/6 = 0.1(6) 1/7 = 0.(142857) 1/8 = 0.125 1/9 = 0.(1) 1/10 = 0.1
Where 0.1(6) means 0.166666…, and has a 1-digit recurring cycle. It can be seen that 1/7 has a 6-digit recurring cycle.
Find the value of d < 1000 for which 1/d contains the longest recurring cycle in its decimal fraction part.
本题为经典的循环节计算问题。
设分母为n,若n为合数,则必可以表示成多个素数的乘积,其循环节与这些分解素数中最大的循环节保持一致。故只需要讨论所给范围内所有素数分母其倒数的循环节即可。值得注意的是,1/2与1/5都是有限小数,故2和5这两个素数需要排除。
确定循环的范围后下面进行循环节计算:通过查阅资料可知,循环节问题可以等价为大整数分解问题。给定大整数n,求使得$10^{k} \equiv 1 (mod n)$成立的最小的k,该数值即为1/n循环节的长度。根据以上分析采取10**k-1 % n == 0
作为限制条件求解。代码如下:
1 | # Find the value of d < 1000 for which 1/d contains the longest recurring cycle in its decimal fraction part |
考虑上面提到的一般化问题:求最小的k使得$a^{k} \equiv 1(mod n)$,若n与a互素,求出分母n的欧拉函数值$\Phi(n)$,则循环节长度k必为其约数;若n与a存在公因子则无解。由此可见通过暴力遍历的方法可以求出约数k,从而得出循环节。
在RSA加密中,给定n=pq,则p,q均为大质数,此时1/n循环节的长度length为gcd(p-1, q-1)的约数。假定已知length的因数分解$length = l{1}^{c{1}}l{2}^{c{2}}l{3}^{c{3}}……*l{k}^{c{k}}$,则length共有$\prod[c_{i}+1]$个约数。将这些约数分别加上1,若某个约数y(j)加1后是质数,则y(j)+1可能是大整数n的约数。对所有小于$\sqrt{n}-1$的y(j)进行检验,必能找到一个恰好满足y(j)+1 = min(p, q)的数字。通过此种方法可以将大整数分解问题转换为求循环节的问题。在最坏的情况下,一个300位的大整数只需通过小于500次转换完成分解。
关于循环节长度计算公式存在如下改进:
\1.PNG)
该公式的应用如下:
\2.PNG)
参考论文:关于循环节长度计算公式的改进:https://www.ixueshu.com/document/6d924626ac37b4ee318947a18e7f9386.html
Euler discovered the remarkable quadratic formula:
It turns out that the formula will produce 40 primes for the consecutive integer values 0≤n≤39. However, when$ n=40, 40^{2}+40+41=40*(40+1)+41$ is divisible by 41, and certainly when $ n=41,41^{2}+41+41 $is clearly divisible by 41.
The incredible formula $n^{2}−79*n+1601$ was discovered, which produces 80 primes for the consecutive values 0≤n≤79. The product of the coefficients, −79 and 1601, is −126479.
Considering quadratics of the form:
where |n| is the modulus/absolute value of n
e.g. |11|=11 and |−4|=4
Find the product of the coefficients, a and b, for the quadratic expression that produces the maximum number of primes for consecutive values of n, starting with n=0.
根据题目,可以仅通过数学推导的方法求解(条件极值问题+二次图像平移),参考链接如下:https://zhuanlan.zhihu.com/p/62137330。除此之外,还可以按照题意暴力求解,由于本题提供的a, b范围较小,故可以在较短时间内完成计算。根据题目给出的二次多项式$f(n) = n^{2} + a*n + b$可得出以下性质:
遍历代码具有两个版本,记录如下:
1 | # Find the product of the coefficients,a and b, for the quadratic expression that produces the maximum number of primes for consecutive values of n, starting with n = 0 |
引入python中的数学运算库sympy,该库继承多种数学运算,包括大量素数操作的接口:
1 | isprime(n) # Test if n is a prime number (True) or not (False). |
代码如下,其中应用到sympy.isprime()
函数进行素数判断,应用sieve.primerange(a, b)
函数生成1—1000内的所有素数。
1 | import sympy |
sympy库函数文档:http://docs.sympy.org/latest/modules/ntheory.html?highlight=prime#ntheory-functions-reference
Starting with the number 1 and moving to the right in a clockwise direction a 5 by 5 spiral is formed as follows:
\3.PNG)
It can be verified that the sum of the numbers on the diagonals is 101.
What is the sum of the numbers on the diagonals in a 1001 by 1001 spiral formed in the same way?
1 | # gridA: 5*5 there are 3 circles |
Consider all integer combinations of $a^{b}$ for 2 ≤ a ≤ 5 and 2 ≤ b ≤ 5:
If they are then placed in numerical order, with any repeats removed, we get the following sequence of 15 distinct terms:
4, 8, 9, 16, 25, 27, 32, 64, 81, 125, 243, 256, 625, 1024, 3125
How many distinct terms are in the sequence generated by $a^{b}$ for 2 ≤ a ≤ 100 and 2 ≤ b ≤ 100?
1 | final_set = set() |
Surprisingly there are only three numbers that can be written as the sum of fourth powers of their digits:
As$ 1 = 1^{4} $is not a sum it is not included.
The sum of these numbers is 1634 + 8208 + 9474 = 19316.
Find the sum of all the numbers that can be written as the sum of fifth powers of their digits.
该问题的关键为确定枚举的上界:设满足题设条件的数字为n位数,取其每位都为9的情况,构造不等式$ 10^{n} <= 9^{5}*n $,计算满足条件n的最小值即可找出上界。代码如下:
1 | # Find the sum of all the numbers that can be written as the sum of fifth powers of their powers |
In the 20×20 grid below, four numbers along a diagonal line have been marked in bold.
08 02 22 97 38 15 00 40 00 75 04 05 07 78 52 12 50 77 91 08
49 49 99 40 17 81 18 57 60 87 17 40 98 43 69 48 04 56 62 00
81 49 31 73 55 79 14 29 93 71 40 67 53 88 30 03 49 13 36 65
52 70 95 23 04 60 11 42 69 24 68 56 01 32 56 71 37 02 36 91
22 31 16 71 51 67 63 89 41 92 36 54 22 40 40 28 66 33 13 80
24 47 32 60 99 03 45 02 44 75 33 53 78 36 84 20 35 17 12 50
32 98 81 28 64 23 67 10 26 38 40 67 59 54 70 66 18 38 64 70
67 26 20 68 02 62 12 20 95 63 94 39 63 08 40 91 66 49 94 21
24 55 58 05 66 73 99 26 97 17 78 78 96 83 14 88 34 89 63 72
21 36 23 09 75 00 76 44 20 45 35 14 00 61 33 97 34 31 33 95
78 17 53 28 22 75 31 67 15 94 03 80 04 62 16 14 09 53 56 92
16 39 05 42 96 35 31 47 55 58 88 24 00 17 54 24 36 29 85 57
86 56 00 48 35 71 89 07 05 44 44 37 44 60 21 58 51 54 17 58
19 80 81 68 05 94 47 69 28 73 92 13 86 52 17 77 04 89 55 40
04 52 08 83 97 35 99 16 07 97 57 32 16 26 26 79 33 27 98 66
88 36 68 87 57 62 20 72 03 46 33 67 46 55 12 32 63 93 53 69
04 42 16 73 38 25 39 11 24 94 72 18 08 46 29 32 40 62 76 36
20 69 36 41 72 30 23 88 34 62 99 69 82 67 59 85 74 04 36 16
20 73 35 29 78 31 90 01 74 31 49 71 48 86 81 16 23 57 05 54
01 70 54 71 83 51 54 69 16 92 33 48 61 43 52 01 89 19 67 48
The product of these numbers is 26 × 63 × 78 × 14 = 1788696.
What is the greatest product of four adjacent numbers in the same direction (up, down, left, right, or diagonally) in the 20×20 grid?
1 | # Simply rotation and comparison in four types of direction |
The sequence of triangle numbers is generated by adding the natural numbers. So the 7th triangle number would be 1 + 2 + 3 + 4 + 5 + 6 + 7 = 28. The first ten terms would be:
1, 3, 6, 10, 15, 21, 28, 36, 45, 55, …
Let us list the factors of the first seven triangle numbers:
1: 1
3: 1,3
6: 1,2,3,6
10: 1,2,5,10
15: 1,3,5,15
21: 1,3,7,21
28: 1,2,4,7,14,28
We can see that 28 is the first triangle number to have over five divisors.
What is the value of the first triangle number to have over five hundred divisors?
通过观察可以发现,数字N的全部因子可分为大于$\sqrt{N}$和小于$\sqrt{N}$两部分,且这两部分的因子数量相等。故以$\sqrt{N}$为界限,统计前半部分因子数量,当大于250时跳出循环。
1 | # Triangular number |
每个整数N可以分解为如下模式:
此处$p{n}$必为素数,$a{n}$是其对应的幂指数。例如$28 = 2^{2} * 7^{1}$。继续推广下去,对于任意正整数N,其因子的数量D(N)可用如下式子表示:
故首先采用埃氏筛法构造大素数表,随后对循环内每个数字进行分解以获得$a{1},a{2},a_{3}$等参数,连乘可得D(N)。对该常规方法进行改进,首先对三角形数进行等差数列求和,结果为$t = n * (n+1) / 2$,此处n和n+1必互素。随后采用如下公式分解D(t):
$D(t) = D(n/2) * D(n+1)$, if n is even
$D(t) = D(n) * D((n+1)/2)$, if n is odd
实现代码如下:
1 | # Highly divisible triangular number |
理解上述代码过程中,值得注意的是countD()函数的返回结果为n*(n-1) / 2而非n*(n+1) / 2,从而代码每次迭代计算的D(t)由传入while循环之前的n值表示,与上面提到的性质对应。
Work out the first ten digits of the sum of the following one-hundred 50-digit numbers.
37107287533902102798797998220837590246510135740250
46376937677490009712648124896970078050417018260538
74324986199524741059474233309513058123726617309629
91942213363574161572522430563301811072406154908250
23067588207539346171171980310421047513778063246676
89261670696623633820136378418383684178734361726757
28112879812849979408065481931592621691275889832738
44274228917432520321923589422876796487670272189318
47451445736001306439091167216856844588711603153276
70386486105843025439939619828917593665686757934951
62176457141856560629502157223196586755079324193331
64906352462741904929101432445813822663347944758178
92575867718337217661963751590579239728245598838407
58203565325359399008402633568948830189458628227828
80181199384826282014278194139940567587151170094390
35398664372827112653829987240784473053190104293586
86515506006295864861532075273371959191420517255829
71693888707715466499115593487603532921714970056938
54370070576826684624621495650076471787294438377604
53282654108756828443191190634694037855217779295145
36123272525000296071075082563815656710885258350721
45876576172410976447339110607218265236877223636045
17423706905851860660448207621209813287860733969412
81142660418086830619328460811191061556940512689692
51934325451728388641918047049293215058642563049483
62467221648435076201727918039944693004732956340691
15732444386908125794514089057706229429197107928209
55037687525678773091862540744969844508330393682126
18336384825330154686196124348767681297534375946515
80386287592878490201521685554828717201219257766954
78182833757993103614740356856449095527097864797581
16726320100436897842553539920931837441497806860984
48403098129077791799088218795327364475675590848030
87086987551392711854517078544161852424320693150332
59959406895756536782107074926966537676326235447210
69793950679652694742597709739166693763042633987085
41052684708299085211399427365734116182760315001271
65378607361501080857009149939512557028198746004375
35829035317434717326932123578154982629742552737307
94953759765105305946966067683156574377167401875275
88902802571733229619176668713819931811048770190271
25267680276078003013678680992525463401061632866526
36270218540497705585629946580636237993140746255962
24074486908231174977792365466257246923322810917141
91430288197103288597806669760892938638285025333403
34413065578016127815921815005561868836468420090470
23053081172816430487623791969842487255036638784583
11487696932154902810424020138335124462181441773470
63783299490636259666498587618221225225512486764533
67720186971698544312419572409913959008952310058822
95548255300263520781532296796249481641953868218774
76085327132285723110424803456124867697064507995236
37774242535411291684276865538926205024910326572967
23701913275725675285653248258265463092207058596522
29798860272258331913126375147341994889534765745501
18495701454879288984856827726077713721403798879715
38298203783031473527721580348144513491373226651381
34829543829199918180278916522431027392251122869539
40957953066405232632538044100059654939159879593635
29746152185502371307642255121183693803580388584903
41698116222072977186158236678424689157993532961922
62467957194401269043877107275048102390895523597457
23189706772547915061505504953922979530901129967519
86188088225875314529584099251203829009407770775672
11306739708304724483816533873502340845647058077308
82959174767140363198008187129011875491310547126581
97623331044818386269515456334926366572897563400500
42846280183517070527831839425882145521227251250327
55121603546981200581762165212827652751691296897789
32238195734329339946437501907836945765883352399886
75506164965184775180738168837861091527357929701337
62177842752192623401942399639168044983993173312731
32924185707147349566916674687634660915035914677504
99518671430235219628894890102423325116913619626622
73267460800591547471830798392868535206946944540724
76841822524674417161514036427982273348055556214818
97142617910342598647204516893989422179826088076852
87783646182799346313767754307809363333018982642090
10848802521674670883215120185883543223812876952786
71329612474782464538636993009049310363619763878039
62184073572399794223406235393808339651327408011116
66627891981488087797941876876144230030984490851411
60661826293682836764744779239180335110989069790714
85786944089552990653640447425576083659976645795096
66024396409905389607120198219976047599490197230297
64913982680032973156037120041377903785566085089252
16730939319872750275468906903707539413042652315011
94809377245048795150954100921645863754710598436791
78639167021187492431995700641917969777599028300699
15368713711936614952811305876380278410754449733078
40789923115535562561142322423255033685442488917353
44889911501440648020369068063960672322193204149535
41503128880339536053299340368006977710650566631954
81234880673210146739058568557934581403627822703280
82616570773948327592232845941706525094512325230608
22918802058777319719839450180888072429661980811197
77158542502016545090413245809786882778948721859617
72107838435069186155435662884062257473692284509516
20849603980134001723930671666823555245252804609722
53503534226472524250874054075591789781264330331690
1 | all_numbers = given_50_digit_numbers |
The following iterative sequence is defined for the set of positive integers:
n → n/2 (n is even)
n → 3n + 1 (n is odd)
Using the rule above and starting with 13, we generate the following sequence:
13 → 40 → 20 → 10 → 5 → 16 → 8 → 4 → 2 → 1
It can be seen that this sequence (starting at 13 and finishing at 1) contains 10 terms. Although it has not been proved yet (Collatz Problem), it is thought that all starting numbers finish at 1.
Which starting number, under one million, produces the longest chain?
NOTE: Once the chain starts the terms are allowed to go above one million.
按照题意直接进行暴力遍历,可以采用递归和迭代的方法。采用靠近计算机底层的位运算代替普通运算可以适当提高效率:n & 1 代替 n % 2,n >> 1代替 n / 2。
1 | # Iteration |
二者效率对比如下,可以看出迭代调用所需时间明显小于递归调用。
\1.PNG)
无论采用递归或是迭代的方法进行暴力遍历,我们都可以明确看出有部分数值经过重复计算,导致运算时间过长。于是进行以下优化:
代码复现如下,但效率无法达到相关题解中提及的1.5s。
1 | # Find the longest Collatz sequence chain |
Starting in the top left corner of a 2×2 grid, and only being able to move to the right and down, there are exactly 6 routes to the bottom right corner.
\2.PNG)
How many such routes are there through a 20×20 grid?
本题目为简单格子路径问题,可以采用迭代、递归与组合数三种方法求解。
将题目所给信息转换为一般问题,即求从(0, 0)点运动到(m, n)点的所有路径数量,该数量等于(0, 0)点到点(m-1, n)和(0, 0)点到点(m, n-1)的路径数量之和。以此类推,当m或n等于0时,(0, 0)点到达该点只存在一直向右或向下两条道路,此时递归算法返回1。值得注意的是,该方法存在重复计算问题,故可引入大容量数组存储可能需要的计算结果。
递归法较易编写,但需要消耗较多计算资源,故考虑结合动态规划进行迭代求解。如果说递归法是”执果索因”,动态规划就是”由因导果”。首先建立20x20数组,由于第一行和第一列所有元素到达点(0, 0)只有一条路径,故数组中对应位置全部设置为1。随后从第二行第二列开始按照grid[i][j] = grid[i][j-1] + grid[i-1][j]
进行数组赋值,目标位置grid[m][n]
即为待求结果。代码如下:
1 | # Lattice paths problem |
以上两种方法时间复杂度均为O(mn),我们可以使用组合数学以提高效率。
首先分析一般问题的本质,即从点(0, 0)到点(m, n)共需要走m+n步,其中需要向下走m步,向右走n步。于是引出简单无顺序组合问题即$\binom{m+n}{m}$。然而本题给出m=n=20,从而得到如下公式:
\3.PNG)
至此我们得到复杂度为O(n)的算法,实现如下:
1 | # Solution2 |
方格问题升级之路(详细讨论格子路径问题):https://blog.csdn.net/cookieZZ/article/details/70306757
格子路径问题+组合数学:https://www.cnblogs.com/yhm138/p/13610626.html#102-lattice-paths-without-restrictions-无限制格子路径
$2^{15} = 32768$ and the sum of its digits is 3 + 2 + 7 + 6 + 8 = 26.
What is the sum of the digits of the number $2^{1000}$?
1 | # Power digit sum |
If the numbers 1 to 5 are written out in words: one, two, three, four, five, then there are 3 + 3 + 5 + 4 + 4 = 19 letters used in total.
If all the numbers from 1 to 1000 (one thousand) inclusive were written out in words, how many letters would be used?
NOTE: Do not count spaces or hyphens. For example, 342 (three hundred and forty-two) contains 23 letters and 115 (one hundred and fifteen) contains 20 letters. The use of “and” when writing out numbers is in compliance with British usage.
最简单的方法为建立三个字典,分别存储个位数字1-9,十位数字1-9(即10-90)以及11-19,然后判断输入数字的位数并进行相关处理。该方法需要讨论的情况较多,例如三位数字便需要讨论100,1X0,10X,11X,1XX五种情况,代码如下:
1 | # Count the letters of numbers 1 to 1000 |
由于三位数字与两位数字相比仅增加了对百位数字的讨论,本质为增加X hundred and
这几个字符。故可以1-99为基础进行适当求和,从而省略了三位数字包含字符数量的判断,节约了运算时间,关键代码如下:
1 | for item in xrange(1, 100): |
By starting at the top of the triangle below and moving to adjacent numbers on the row below, the maximum total from top to bottom is 23.
\4.PNG)
That is, 3 + 7 + 4 + 9 = 23.
Find the maximum total from top to bottom of the triangle below:
\5.PNG)
NOTE: As there are only 16384 routes, it is possible to solve this problem by trying every route. However, Problem 67, is the same challenge with a triangle containing one-hundred rows; it cannot be solved by brute force, and requires a clever method! ;o)
根据题目说明采用动态规划进行逐级递归。
首先将所给的字符串类型转换为二维数组,随后进行分析,核心思想为将上一行的数字更新为其本身与下一行相邻两数字中较大数字之和:以倒数第二行元素为例,63可更新为63+max(04, 62)即125,66可更新为66+max(62, 98)即164…以此类推,更新结束后二维数组第一个元素即为所求最长路径。代码如下:
1 | # Find the maximum total from top to bottom of the triangle below: |
You are given the following information, but you may prefer to do some research for yourself.
How many Sundays fell on the first of the month during the twentieth century (1 Jan 1901 to 31 Dec 2000)?
1 | # How many Sundays fell on the first of the month during the twentieth century (1 Jan 1901 to 31 Dec 2000)? |
1 | import calendar |
python提供calendar
模块实现日历功能,提供对日期的操作函数,常用函数说明如下:https://www.cnblogs.com/liuxiaowei/p/7263888.html。
n! means n × (n − 1) × … × 3 × 2 × 1
For example, 10! = 10 × 9 × … × 3 × 2 × 1 = 3628800,
and the sum of the digits in the number 10! is 3 + 6 + 2 + 8 + 8 + 0 + 0 = 27.
Find the sum of the digits in the number 100!
1 | # Count the sum of digits of 100! |
If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23.
Find the sum of all the multiples of 3 or 5 below 1000.
1 | # below 1000 |
Each new term in the Fibonacci sequence is generated by adding the previous two terms. By starting with 1 and 2, the first 10 terms will be:
1, 2, 3, 5, 8, 13, 21, 34, 55, 89, …
By considering the terms in the Fibonacci sequence whose values do not exceed four million, find the sum of the even-valued terms.
1 | # 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ... |
\1.png)
\2.png)
The prime factors of 13195 are 5, 7, 13 and 29.
What is the largest prime factor of the number 600851475143 ?
1 | # Largest prime factor of designated number 600851475143 |
本题也可以使用厄拉多塞筛法寻找输入数字范围内所有的素数。因为素数的倍数一定不是素数,所以我们找到一个素数时可以将其倍数从所给范围内排除。这种方法称为素数筛。例如求100以内的素数:
1 | n = 100 |
基于以上思想,在找输入数字因数时可以将合数筛出,代码如下:
1 | ans = [] |
设输入的数字为n,则可以通过遍历方法暴力搜索其素因子,若出现非1及其本身的素因子,则可断定该数字为素数。常见的遍历范围为1—n-1,其实将遍历范围调节至1—$\sqrt{n}$亦可实现素数判定的目的。
对于所有可能成为数字x素因子的n-1个数字,偶数中除了2均不是质数,且奇数的因数没有偶数,因此可以继续优化。首先将n与2进行比较,其次判断2是否为n的素因子,最后从3开始以2为增幅逐次判断数字n是否包含奇数因子。
1 | def isPrime(n): |
所有数字均可表示为6n,6n+1,6n+2,6n+3,6n+4,6n+5的形式,除2和3以外,所有的素数都可以表示为6n+1和6n+5的形式,如果输入数字x是6n+1和6n+5的整数倍,则必为合数。
1 | def isPrime2(n): |
该素数判别方法应用费马小定理对素数进行概率判定,若输入数字N通过t次测试,则N不是素数的概率仅为$(1/4)^{t}$,随着通过测试次数的增加,N是素数的概率无穷逼近于1。在实际运用中,可首先用300—500个小素数对N进行测试,以提高测试通过的概率与算法的速度。
具体步骤如下:
相关代码如下:
1 | import random |
除了上述的高级试除法外,还可以使用素数筛查的方法。常见的素数筛查包括埃拉托斯特尼筛法和欧拉筛法。
埃氏筛法由希腊数学家埃拉托斯特尼提出,用以简单鉴定素数,方法如下:要获取自然数n(上界)内的全部素数,必须剔除所有小于等于sqrt(n)的素数的倍数,经过此种筛查后,剩下的就是素数。
欧拉筛法是埃氏筛法的改进。采用欧拉筛法进行筛选过程中,对于含多个因子的数字需要进行多次筛选,耗费部分运行时间。例如,对于合数20,可分解为2*10,4*5,故至少需要筛选两次。欧拉筛过程中引入语句if i%prime[j] == 0: break
,保证每个合数只被这个合数最小的质因子筛除,而且不出现重复筛除。
代码实现可参考:素数筛法详解(欧拉筛&埃氏筛)。
A palindromic number reads the same both ways. The largest palindrome made from the product of two 2-digit numbers is 9009 = 91 × 99.
Find the largest palindrome made from the product of two 3-digit numbers.
1 | ''' |
2520 is the smallest number that can be divided by each of the numbers from 1 to 10 without any remainder.
What is the smallest positive number that is evenly divisible by all of the numbers from 1 to 20?
1 | ''' |
分析题目,首先以1—10之间的数字为例,我们进行以下操作:
现令N为可被2—k间所有数字整除的最小数字,分析求解N的过程。参考上述k=10时的分析,首先确定所有小于k的素数,存入列表P。随后确定列表中每个元素的次数,令$P[i]^{a[i]} = k$,两侧同时进行对数运算并向下取整,得$a[i] = floor(log(k) / log(P[i]))$。值得注意的是,当$P[i]^{2} > k$时,a[i]==1,故只需要计算满足$P[i] <= \sqrt(k)$对应素数的次数a[i]。最终$N=P[0]^{a[0]}P[1]^{a[1]}P[2]^{a[2]}*……..$
The sum of the squares of the first ten natural numbers is,
The square of the sum of the first ten natural numbers is,
Hence the difference between the sum of the squares of the first ten natural numbers and the square of the sum is $3025 - 385 = 2640$.
Find the difference between the sum of the squares of the first one hundred natural numbers and the square of the sum.
1 | ''' |
By listing the first six prime numbers: 2, 3, 5, 7, 11, and 13, we can see that the 6th prime is 13.
What is the 10 001st prime number?
题目只给出素数的个数,最简单的思路就是循环计数与素数判断相结合。素数判断过程中,现补充事实如下:
1 | # Method1: Round and prime judgement |
本题也可以采用埃拉托斯特尼筛法进行求解,求解的关键是确定第10001个素数的上界。为确保一定会出现第10001个素数,取较大的上界为1000000。
1 | # Method2: a sieve of Eratosthenes |
The four adjacent digits in the 1000-digit number that have the greatest product are 9 × 9 × 8 × 9 = 5832.
73167176531330624919225119674426574742355349194934
96983520312774506326239578318016984801869478851843
85861560789112949495459501737958331952853208805511
12540698747158523863050715693290963295227443043557
66896648950445244523161731856403098711121722383113
62229893423380308135336276614282806444486645238749
30358907296290491560440772390713810515859307960866
70172427121883998797908792274921901699720888093776
65727333001053367881220235421809751254540594752243
52584907711670556013604839586446706324415722155397
53697817977846174064955149290862569321978468622482
83972241375657056057490261407972968652414535100474
82166370484403199890008895243450658541227588666881
16427171479924442928230863465674813919123162824586
17866458359124566529476545682848912883142607690042
24219022671055626321111109370544217506941658960408
07198403850962455444362981230987879927244284909188
84580156166097919133875499200524063689912560717606
05886116467109405077541002256983155200055935729725
71636269561882670428252483600823257530420752963450
Find the thirteen adjacent digits in the 1000-digit number that have the greatest product. What is the value of this product?
1 | ''' |
A Pythagorean triplet is a set of three natural numbers, a < b < c, for which,
For example, $3^{2} + 4^{2} = 9 + 16 = 25 = 5^{2}$.
There exists exactly one Pythagorean triplet for which a + b + c = 1000.
Find the product abc.
可以采用放缩的方法,以题目所给的1000为例,设$ a=m1k,b=m2k,c=m3*k$,这里m1,m2,m3为小于50的勾股数。故只需要寻找满足1000 % (m1+m2+m3) == 0的勾股数并相应扩大k倍,使其满足a+b+c == 1000即可。
1 | ''' |
1 | # Find the only Pythagorean triplet(a, b, c), for which a+b+c = 1000 |
下面通过勾股数的一些性质简化解法二中的代码,提高程序运行效率。
如果勾股数(a, b, c)满足gcd(a, b, c) = 1,定义该类勾股数具有素数性质。同时,任意组勾股数(a, b, c)可表示为:
由于勾股数可通过倍乘进行放缩,故可对(a, b, c)进行运算,得到以下结果:
使用以上性质,我们可得:
所以想要找到勾股数组合(a, b, c)满足a+b+c = s,我们需要在1—s/2之间寻找除数m,并寻找s/2m的奇除数k(此处k=m+n,k满足m < k < 2m,并且m,k互素)。随后令n = k - m,d = s/2mk,将该结果插入式(9.2)中,代码如下:
1 | import math |
The sum of the primes below 10 is 2 + 3 + 5 + 7 = 17.
Find the sum of all the primes below two million.
1 | # Sum after the sieve of Eratosthenes |
除2以外的所有偶数均为合数,故只需要对所给范围2—N内的奇数进行素性判断。我们建立下标i与奇数2*i+1的对应关系。首先建立布尔类型数组,其长度为(N-1)/2,初始值均为False,表示全为素数。设p=2*i+1,则$p^{2}=4i^{2}+4i+1$,其对应的数组下标为2*i+1;设m=k*p,则m+2*p对应的下标为j+p。
同样参考埃氏筛的思路,下一步需要找外部循环的下标范围、内部循环的初始值以及步进参数。结合以上分析进行讨论。由$2i_max+1<= \sqrt(N)$可得i的范围为$(\lfloor N\rfloor -1) / 2$,该范围是外部循环的下标范围。对于奇数p,所有小于p^2的数字中若为合数则必可被小于p的素数整除,故内部循环的初始值设定为p^2,对应下标为2\i*(i+1)。又$p^{2}+p = 4i^{2}+4i+1+2i+1 = 4i^{2}+6i+2 = 2(2i^{2}+3i+1)$,可知若以p为步进,则得到的结果必为合数,无需进行判断。同时$p^{2}+2p = 2(2i+1)+1 = 4i^{2}+8*i+3 $,可以看出该结果必为合数,同时该结果为p的倍数,将数组对应下标位置标记为True。代码如下:
1 | # Some imporvement of the sieve of Eratosthenes |
以题中所给的2000000为例,解法一运行速度为8s左右,解法二运行速度大大提升,只需要0.417s。
\3.png)
]]>近日考研期间,适逢深信服”翔鹰计划”线上实习结束,公司给了秋招提前批的面试机会,准备了大概两天就去面试了,岗位是技服工程师。由于前期通过笔试,故直接进入技术面、业务面和HR面。
邮件中面试官约定的事件是上午11:00,大概提前两分钟左右进入房间调试设备,不过不知道是牛客网平台原因还是公司网络原因,面试官声音一直断断续续,中间还出现突然静音的情况,于是后期换了腾讯会议。
面试时间为45分钟,首先进行自我介绍,然后面试官进行提问。问题包括技术问题和非技术问题合计共十个左右,具体如下。
简要介绍VLAN
回答了VLAN的定义,虚拟局域网。主要作用为将同一网段根据功能划分成不同的虚拟子网。以公司为例,为保证主管、业务、销售、技术等各个部门之间网络的专有通信,引入VLAN技术,实现各部门人员之间在对应虚拟子网内部的高效安全通信,不会产生业务互相影响的情况。
简要介绍交换机
交换机是二层设备,位于数据链路层,主要作用为端口数据帧转发。提到了ARP协议,提出每个交换机维护一个ARP表,以保证不同主机之间的正常通信。顺便讲了一下ARP广播单播,以及相同网段之间主机如何进行通信。
针对ARP协议的攻击
简单讲了ARP欺骗和ARP洪泛攻击,重点介绍了攻击手法及危害。
ARP欺骗的防御方法
由于对ARP欺骗的防御掌握不深入,所以当时简单提到了修改帧结构及增加校验部分,以及进行相关检测。面试官追问哪个设备进行相关检测,然后就没有答上来。面试结束后,参考FreeBuf上的一篇文章:浅谈ARP欺骗的实现与防御,归纳两个防御手段:
对路由选择协议的认识
回答了路由优先级顺序:直连路由>静态路由>动态路由>默认路由。静态路由部分只提到需要在路由器上手动配置。动态路由部分提到了内部网关协议与外部网关协议。外部网关协议包括BGP,内部网关协议又分为基于距离矢量的协议和基于链路状态的协议。详细介绍了RIP协议和OSPF协议的工作原理,重点介绍衡量路径优劣的标准。
网络排查问题:公司中一楼无法正常连网,二楼可正常连网但网速较慢,三楼可正常上网,针对此种情况如何排查解决。
由于公司和个人网络的排查有所差别,同时我也没有了解过公司的网络排查标准,于是按照个人网络问题进行排查。首先讲了个人PC无法正常联网的排查步骤:
网速较慢这个问题没怎么关注过,简单提到了猜想——公司访问流量限速,当然这是在胡说八道,但是可以用来缓解尴尬,委婉地告诉面试官不怎么会。
攻击网站的常见手段
回答了SQL注入、XSS、CSRF、一句话木马,然后追问一句话木马的工作原理,笼统地回答了一下。
了解磁盘冗余阵列RAID吗?
由于昨天准备的时候刚看到RAID的相关知识,简要回答了RAID 0,RAID 1,RAID 3,RAID 5,RAID p+q等组合方式的特点及优缺点。RAID p+q以RAID 10为例进行详细介绍。
了解虚拟化技术吗?
将虚拟化与云计算技术结合起来回答,大致提到了SaaS,PaaS,IaaS,公有云,私有云,混合云等名词并进行解释。面试官让说出自己对虚拟化的理解,结合aDesk云桌面产品进行了相关说明(感觉自己讲的语无伦次)。本来还想加上虚拟现实技术,后来面试官话锋转到下个问题,只好作罢。
常见的加密算法
终于问到密码学了!(不是)分成对称加密和非对称加密两部分进行回答。对称加密主要介绍了DES,AES,RC4,RC5。非对称加密介绍了三大困难问题及对应的RSA,DH交换和ECC密码体制。顺带提到了消息摘要算法和数字签名算法。
以上提到的算法的实际应用
刚开始想回答数字证书等知识,后来确定回答主题为非对称加密+对称加密:对称加密具有加密效率高的特点,相应地安全性较低;非对称加密无法处理大量数据,但安全性较高。故流行的通信方式为通信双方使用非对称加密算法对对称加密密钥进行加解密,从而实现密钥共享,随后使用该密钥结合对称加解密算法进行消息传输。围绕该主题并结合HTTPS加密过程客户端与服务器的相应操作进行作答。
有没有考过网络认证证书+计算机网络知识学习途径
没考过证书,计网知识除”翔鹰计划”培训外,还上过学校的对应课程,进行过系统学习。
经历的压力较大的事情
聊了聊”美亚杯”比赛的事情(现在感觉当时的回答驴唇不对马嘴)
“基于数字水印与神经网络的图像攻击检测系统”项目相关
这个项目中负责的部分
水印的提取与嵌入算法的实现,水印置乱算法实现,信噪比检验。
项目中遇到哪些难题,如何解决?
只提到当时嵌入提取算法matlab转python实现过程中出现的问题,即无论怎么调整参数,使用python语言实现的算法嵌入水印后的图像都具有明显的水印标记,无法通过信噪比测试。解决方法就是python调matlab脚本(说出来我自己都觉得low,但是当时这个bug调了大概一晚上,所以印象很深刻)。
项目中学到了什么?
从技术和团队合作两方面进行回答。结合自己负责的技术部分简单讲了一下这些算法,同时提到了matlab和python编程。团队合作方面,首先提到了模块化即各司其职,相互配合,以写代码为例提出即使补充注释,方便后期代码整合;然后提到积极讨论,制定多个方案并进行测试。
合作过程中,如果发生冲突或受到质疑如何解决?
这种问题就非常简单了,首先强调团队精神即荣辱与共,大家为共同目标奋斗;然后回答鼓励大家积极讨论,针对同一解决方案各抒己见;最后就是提出质疑需要有理有据。
热爱读书吗?读过印象最深刻的书是什么?
这个问题就俨然成为一股清流。先问了问是技术书还是其他书,得到回答是都可以。后来好像也没怎么介绍看的书,这个问题就奇奇怪怪地一扫而过了。
邮件约定面试时间为上午11:00,大概提前五分钟进入面试房间,11:20左右结束,共面试25分钟左右。主要问题包含自我介绍及相关问题。
简单介绍了一下自己的学校、专业及学习情况,日常爱好,相关技术栈,参加的比赛及获奖情况,实习经历等。‘
简要介绍印象深刻的项目经历(扮演角色,工作内容,学习内容,结果)
把参与20年作品赛的项目拿出来详细讲了讲,感觉面试官对合作而非技术细节感兴趣。
团队合作时有没有受到质疑?如何解决的?
结合项目经历简单讲了一下解决办法(其实没什么质疑
参加过什么实习?聊一下实习经历
啊这…显然没参加过什么实习,提到了”翔鹰计划”但是面试官没让继续说下去,这个问题就结束了(感觉不是很满意?
大学有没有和人发生过冲突?评价下自己的性格,最讨厌什么样的人?
这三个问题感觉是对面试者人际交往能力和性格的把握吧,就正常回答了一下。
你本科学的是网络空间安全,和网络安全有什么区别?
这个问题算是遇到的比较有趣的非常规问题了,简单讲了一下国家讲网络空间作为”第五空间”的发展战略,以及这个专业广阔的发展前景,还聊了聊网络空间安全和信息安全的区别与联系。
网络空间安全对口的公司有什么?
举了BAT,360,奇安信,绿盟,安恒等经典公司,当然提到了深信服的网络安全、超融合、云计算等标杆。
在学校参加过何种比赛?
由于校园经历偏作品赛比较多,CTF比赛没拿到什么好成绩,所以就简要介绍了几个本专业作品赛的情况。
课余时间如何安排生活?
看技术书+保持几个爱好。
看过哪些技术书籍+日常逛哪些论坛?
举了几个密码学技术书籍,外加计算机网络自顶向下、深入理解计算机系统、软件测试等。论坛聊了Freebuf、先知社区等。
简单聊了下技服工程师跨省调度的政策,就是不能回原籍省份工作也不能在学校所在省份工作。
邮件约定时间为下午16:45,进入房间大概等到17:00开始面试,整个时长为25min。首先进行自我介绍,然后面试官了解情况,大概问了如下几个问题:
概括本科三年生活。
从技术和日常生活的角度进行概括,技术上提到了CTF比赛和参加的项目,生活角度没怎么提。
本科最有挑战和最有成就的事情。
最有挑战的事情选择了其中一个做过的项目,最有成就的事情选择了”美亚杯”比赛经历。
为什么选择”翔鹰计划”实习?
一是为了了却去年的遗憾,二是线上实习的形式比较安全高效,结合开学时间与暑假安排的考虑,最终选择了”翔鹰计划”。
技服工程师这个岗位是做什么的,为什么选择这个岗位?
简单介绍了一下自己对技术服务的理解。选择原因确定为两点:个人代码水平不高+热爱与人打交道。
追问:为什么不选择售前经理?
个人还是比较热爱技术。(感觉这个岗位和技术关系不大)
概括一下自己的优点和缺点(各两条)。
评价一下自己是个怎么样的人?
有没有长时间坚持的事情?
运动。
确定不考研了吗,为什么?
总感觉说正在备考拿不到offer,于是胡乱编造了一些理由。
期望岗位是什么?想去什么样的公司?
这个问题回答的比较粗略,给面试官一种没有准备好进入工作+对未来没有明确规划的感觉。可能是因为真的没有准备好本科毕业直接工作吧。事后仔细想想,个人期望岗位还是管理岗,对专注安全的乙方公司没什么向往,对大厂还是比较向往的(阿里、百度、腾讯、字节)。除此之外当个测试工程师或者去公司实验室进行安全研究感觉也挺好。现阶段还是一门心思学习,重点还是提升下学历吧。
还有没有投递其他公司?收到过其他offer吗?
实话实说,并没有,甚至连华为提前批都没打算投。。。
工作情况相关
随机工作省份可以接受吗?偏远地区呢?
个人不能接受过于偏远的地区,对其他工作地点没有太大的执念。不回原籍和学校所在地倒是无所谓。
家庭状况:独生子女?父母同意吗?女朋友呢?
啊这…
最后简单聊了一下拿到offer的后续安排以及薪资问题。
各种意义上来说这次面试都是人生中经历的第一次完整的企业面试,不仅体验了整套面试流程,还发现了一些重点问题,并且引发一些思考。
Documents and Settings/用户
存储用户设置,包括用户文档、上网浏览信息、配置文件等数据
Windows目录
Windows安装目录,用来放置Windows程序的使用数据、设置等文件。不建议修改此目录下数据,易造成Windows系统使用异常
Program File
应用程序文件夹,一般软件默认安装位置。当然此处也包含系统自带的应用程序。Windows10系统中,64位用户多出一个Program Files(x86)文件夹,用作系统中32位软件的安装目录
Temp目录 临时文件目录
文件路径:C:\Users\user\AppData\Local\Temp
上面存在许多垃圾文件,包括使用压缩软件等解压的临时文件。此目录也是病毒检测过程中快速扫描的位置。
注册表是Windows操作系统中的一个核心数据库,其中存放各种参数,直接控制Windows的启动、硬件驱动程序的装装载以及一些Windows应用程序的运行。
恶意病毒通常通过修改注册表的键HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main Start Page项对应的URL值来修改IE起始页面。
开机时系统会在前台或后台自动运行的程序。查看方式为msconfig命令。
将文件、程序等放入位于C:\Users\user\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup的启动文件夹中即可实现开机自启动。
设备管理器常被用来查看和更改设备属性、更新设备驱动程序、配置设备设置和卸载设备。所有设备通过设备驱动程序与Windows进行通信。
使用设备管理器可以安装和更新硬件设备的驱动程序、修改这些设备的硬件设置以及通过查看硬件设备状态信息来排查问题。
Windows任务管理器提供了有关计算机性能的信息,并显示了计算机上所运行的程序和进程的详细信息(哪个用户创建了哪个进程或程序,该进程或程序占用了多少CPU及其他系统资源)。
进程是正在运行的程序实例。每个进程存在属于自己的地址空间,一般包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储活动过程调用的指令和本地变量。
组策略在部分意义上可控制用户可以或无法在计算机上执行什么操作,提供了操作系统、应用程序和活动目录中用户设置的集中化管理和配置。在运行模式下输入gpedit.msc可以打开组策略配置。
默认情况下,Mircosoft Windows每90分钟刷新一次组策略,随机偏移为30分钟。在域控制器上,Microsoft Windows每隔5分钟刷新一次。
可以列出用于定义资源和对象权限的任意访问控制列表(DACL)中的组。Windows安全组策略其实是组策略中关于安全设置的部分,囊括了账户安全策略、Windows防火墙配置等配置目录。
在运行任务栏输入secpol.msc,修改安全组相关配置之后,需要重新登录Windows用户方可生效。
工作组就是将不同的电脑按功能分别列入不同的组中,以方便管理。
Windows 9x/NT/2000引入工作组概念后,若要访问某个系列的资源,需要在网上邻居内找到对应的工作组名,即可找到该系列资源。
工作组中一切设置在本机上进行,包括各种策略、用户登录等过程,对应密码也存放在本机数据库中进行验证。
域作为工作组的升级版,计算机的各种策略通过域控制器统一设定,用户名和密码的验证过程也在域控制器中完成。因此,用户信息可以实现在域中电脑上的漫游。
在域模式下,至少有一台服务器负责每台联入网络的电脑和用户的验证工作,被称为域控制器(Domain Controller,DC)。域控制器包含由这个域的账户、密码、属于这个域的计算机等信息构成的数据库。
Windows用户所有的登录注销、安全策略更改都会以安全日志的形式记录。
日志位置:计算机管理—>系统工具—>事件查看器—>Windows日志—>安全
通过发送Internet控制消息协议(ICMP)验证与其他TCP/IP计算机的IP级连接回显请求消息。显示相应的回音回复信息的接受以及往返时间。该命令可用于解决连接、可访问性和名称解析等问题。
Ping IP/域名 -t
:长时间执行Ping命令,以推断连接健壮性Ping IP/域名 -n number
: 指定发送数据包的数量Ping IP/域名 -l length
: 指定发送数据包的长度(默认长度为32Bytes)一般使用Ping IP/域名 -l big-number -n big-number
探测连接稳定性,其中big-number为大于1000的数字,需要多次尝试以找到合适的数据包长度。
显示和修改地址解析协议缓存中的条目,其中包含一个或多个用于存储IP地址及其解析结果的以太网或令牌环物理地址的表。计算机上安装的每个以太网或令牌环网络适配器都存在单独的表。
Arp -a
:显示所有的地址信息及接口信息Arp -s ip_addr mac_addr
:静态配置ARP地址表项Arp -d
:用于删除当前ARP信息确定通过发送Internet控制消息协议(ICMP)回显请求或以递增的生存时间(TTL)字段值向目标发送消息。路径显示源主机和目标之间路径中路由器的近/侧路由器接口列表。无参数使用。
Tracert -d
:不将地址解析成主机名
Tracert -h maximum_hops
:搜索目标的最大跃点数
显示并修改本地IP路由表中的输入。无参数使用。需要以管理员身份打开以进行相关配置。常用命令结构:Route command ip_addr mask mask_number gateway_addr
。
command
Route PRINT
:打印路由表Route ADD
:添加静态路由(临时,重启后消失)Route DELETE
:删除路由信息Route CHANGE
:修改现有路由的网关和跃点数Route -p
:使得对路由表的添加操作永久生效
显示所有当前TCP/IP网络配置值,并刷新动态主机配置协议(DHCP)和域名系统(DNS)设置。无参数使用时,为所有适配器显示Internet协议版本4和IPv6地址、子网掩码和默认网关。
Ipconfig /all
:显示所有网络适配器的所有信息Ipconfig /release
:释放当前所有网卡的DHCP信息Ipconfig /renew
:释放当前网卡的所有DHCP信息并重新获取Ipconfig /displaydns
:展示当前DNS缓存信息Ipconfig /flushdns
:清理当前DNS缓存信息显示有源TCP连接,计算机在哪个端口被侦听,以太网统计,IP路由表,IPv4统计和IPv6统计。无参数使用时,网络显示激活TCP连接。
Netstat -a
:展示当前监听的所有网口信息Netstat -n
:展示所有TCP&UDP连接信息及端口详细信息Netstat -o
:展示当前连接的PIDNetstat -p
:指定当前监听协议经济学角度:云计算依赖资源的共享以达成规模经济,类似基础设施如电力网等。
云计算现阶段的发展还远远未达到基础设施建设水平。
云计算是新技术+IT业务模式的创新,随着数字化时代的发展,IT消费模式产生重大转变:云计算通过技术将IT资源池化和服务化,通过互联网提供IT服务,而用户由网络浏览器或轻量级终端软件来获取和使用这些IT服务。整个IT市场商业模式正实现“从产品到服务”的转型。
IaaS(Infrastructure as a Service):基础架构即服务
用户通过网络使用计算机(物理机或虚拟机)、存储空间、网络连接等完善的计算机基础设施服务。
PaaS(Platform as a Service):平台即服务
将软件研发的平台作为一种服务提交给用户,意在加快SaaS应用的开发速度。
SaaS(Software as a Service):软件即服务
通过Internet提供软件的模式,用户无需购买软件,而是向提供商租用基于Web的软件来管理企业经营活动。
通过Internet为外部客户提供服务的云。典型公有云包括Amazon EC2、阿里云、腾讯云等。
优点
所有应用服务数据等均存放在公有云提供商处,客户无需硬件投资与建设,使用成本低;
缺点
数据存放在供应商处,安全性存在风险。公有云的可用性不受使用者控制,存在不确定性。
由企业或机构独享使用和掌控的云,仅供自己内部人员或分支机构使用,一般部署在企业或机构的数据中心。
优点
数据安全性和系统可用性可控,对现有IT流程管理影响小、IT资源利用率高;
缺点
投资较大。
同一份数据、同一套应用,同时采用私有云技术构建自己的IT服务平台,同时又采购了公有云服务商提供的IT服务。为保证数据安全,企业将核心数据和关键技术存放于私有云上,而将面向用户的服务托管在公有云上,具有较高的弹性。一般是需要具备可控的前提下,具备一定的弹性或可靠性。多应用于潮汐应用及混合云灾备。
优点
具备较大弹性,并且可以在保障可控性的同时兼顾建设成本;
缺点
IT业务管理界面不统一,需要投入相应的混合云管理成本。
数据中心是云计算后端基础设施的承载体,云计算依托数据中心提供各种云计算服务。数据中心内部除包含基础物理设施外,还包含网络、安全、优化、存储、服务器、操作系统、虚拟机及应用软件等成分。
随着云计算技术的发展与并行计算思想的出现,未来x86服务器在市场中将占据主导地位。
又称CISC(复杂指令集)架构服务器,即通常所讲的PC服务器,它基于PC机体系结构,使用Intel或其它兼容x86指令集的处理器芯片和Windows/Linux操作系统的服务器。
包括大型机、小型机和UNIX服务器,它们是使用RISC(精简指令集)或EPIC(并行指令代码)的处理器,并且主要采用UNIX和其他专用操作系统的服务器。
中国业内习惯上称UNIX服务器为小型机,其最引以为傲的特点就是高RAS——高可靠性、高可用性与高服务性。随着CPU和虚拟化技术的发展,x86服务器的可靠性与可用性不再是问题,为用户提供更多选择,小型机的竞争力逐渐下降。
Central Process Unit,是一块超大规模的集成电路,是一台计算机的运算核心和控制核心。常见参数如下:
为适应信息化发展,Intel推出VT(Virtualization Technology,虚拟化技术)系列以满足不同的上层操作系统对底层处理器的调用,如VT-x、VT-d、VT-c。
Memory,也被称为内存储器,用于暂时存放CPU中的运算数据,以及与硬盘等外部存储器交换的数据。内存主频与CPU主频一样,习惯上被用来表示内存的速度,代表着该内存所能达到的最高工作频率。可分为ROM只读存储器、RAM随机存储器和Cache高速缓存三类。
Disk,数据的最终归属地。可分为机械硬盘HDD与固态硬盘SSD。
常见接口为PCI、PCI-X和PCI-E。
光模块的作用为光电转换,发送端将电信号转换为光信号,通过光纤传送后,接收端再将光信号转换为电信号。光模块具有传输速率高,传输距离远的特点。
用来做从设备到光纤布线链路的跳接线。常见LC接口的光模块为:SFF、SFP、SFP+和XFP。常见SC接口的光明模块为GBIC。而FC、ST接口多用于光纤配线架。
管理和控制计算机硬件与软件资源的计算机程序,具有承上启下的功能:对上有效管理系统资源,为应用软件提供基础的底层环境,提高系统资源使用效率。对下屏蔽硬件物理特性和操作细节,为用户使用计算机提供便利。
]]>黑客可以利用僵尸网络展开更多的危害行为,如APT攻击最常采用的跳板就是僵尸网络。黑客利用僵尸网络来实现渗透、监视、窃取敏感数据等目的,造成严重危害。
僵尸网络主要危害有:
基于七层应用的深度数据包检测可实现终端安全可控。AF中实现了可视化的应用管控与全面的应用安全。可视化应用管控中包含应用识别与流量管控两大模块。
应用控制策略可对应用/服务的访问做双向控制,AF存在一条默认拒绝所有应用/服务的控制策略。
Web过滤指针对符合设定条件的访问网页数据进行过滤,包括URL过滤与文件过滤,同时可以针对HTTPS URL进行过滤。
将所有经过网关的需要进行病毒检测的数据报文透明地转交给网关自身的协议栈,通过网关自身的协议栈将文件全部缓存下来后,再送入病毒检测引擎进行病毒检测。
依赖于状态检测技术以及协议解析技术,简单地提取文件的特征与本地签名库进行匹配。由于流扫描方式只针对部分数据进行扫描,故查准率低于代理扫描方式,属于轻量级检测技术。
该杀毒体系具有以下特点:
僵尸网络(Botnet)是指骇客利用自己编写的分布式拒绝服务攻击程序将数万个沦陷的机器组织成一个个控制节点用来发送伪造包或垃圾数据包,使预定攻击目标瘫痪并拒绝服务。通常蠕虫病毒也可以被用来组成僵尸网络。
通过对当前的网络层及应用层行为与安全模型进行偏离度分析,能够发现隐藏的网络异常行为,并根据行为特征确定攻击类型,发现特征匹配无法发现的攻击。
外发流量异常功能是一种启发式的DOS攻击检测手段,能够检测源IP不变的SYN Flood、UDP Flood等泛洪攻击。该功能原理为:当特定协议的外发包pps超过配置的阈值时,基于5分钟左右的抓包样本检测数据包是否为单向流量、是否有正常响应内容,得出分析结论并将发现的攻击提交日志显示。
AF僵尸网络防护排除方式具有以下三种:
通过蜜罐技术解决内网存在DNS服务器时,用于定位内网感染僵尸网络主机的真实IP地址。防止配置过程中忽略蜜罐设置,导致后续无法溯源的问题,策略配置界面新增DNS服务器服务界面。
AF检测到的风险主机可以推送杀毒通知。重定向页面支持自定义,同时支持下载病毒查杀软件。
注意:杀毒通知推送设定的时间内,风险主机下载工具并查杀后也无法直接上网,需要等待指定时间,或管理员取消推送才可正常访问网站。重定向页面只对HTTP生效,HTTPS及NAT场景均不生效。
勒索病毒是一种新型电脑病毒,主机感染勒索病毒文件后,会自动运行勒索程序,遍历本地所有磁盘指定类型文件进行加密操作,加密后文件无法读取。随后生成勒索通知,要求受害者支付虚拟货币作为赎金。主要包含两种场景:
srv.sys驱动出现问题
网络攻击包括被动威胁和主动威胁。其中被动威胁主要指截获这一手段,主动威胁包括篡改、中断和伪造这三种手段。
泛洪攻击中,攻击者利用交换机中存储的MAC地址表的自动学习机制不断发送不同MAC地址给交换机,从而填满整个MAC表,此时交换机只能进行数据广播,攻击者凭此获得信息。由于交换机之间具有级联机制,故与受感交换机相连的所有交换机MAC地址表均被填满,整个网络出现数据发送缓慢、丢包甚至瘫痪的情况。可以在交换机上设置相应的端口保护机制以限制单个端口最大MAC数据接收条目数量。
当A、B需要通讯时,A发送ARP请求询问B的MAC地址。攻击者冒充B持续发送ARP响应给A,并传递攻击者主机MAC地址,随后A发送给B的正常数据包都会被转发到攻击者主机处。
ICMP具有多个控制报文如重定向和网络不可达等,常用于指导数据包的正确路由。
解决方法:通过修改注册表关闭ICMP不可达报文及重定向报文的处理功能。
SYN报文是TCP连接的第一个报文,攻击者通过大量发送SYN报文,造成大量未完全建立的TCP连接,从而占用被攻击者的资源,以达到拒绝服务攻击的目的。
攻击者通过篡改DNS服务器上的DNS数据破坏域名与IP地址的对应关系,当用户输入访问网站的URL后,该域名被解析成攻击者事先设置好的钓鱼网站对应的IP地址,随后用户主机会访问该IP地址进入钓鱼网站。
网络设备性能充裕
防火墙、路由器、交换机性能富余。
网络带宽资源充裕
保持一定比例的网络带宽余量。
异常流量清洗
通过抗D设备清洗异常流量。
通过CDN分流
多节点分担DDoS攻击流量。
分布式集群
每个节点分配足够资源数据回发瘫痪攻击源。
缓冲区溢出攻击利用编写不够严谨的程序,通过向程序的缓冲区写入超过预定长度的数据,造成缓存溢出,从而破坏程序的堆栈结构,导致程序执行流程的改变,达到破坏系统或提取关键信息的目的。攻击者如果可以精确控制内存跳转地址,就可以执行指定代码,获得权限或破坏系统。
缓冲区溢出的防范可以通过以下方面考虑:
strcat_s()
,strcpy_s()
,gets_s()
等。由于现阶段大量程序执行过程中均会由操作系统分配随机起始地址,故对于攻击者来说获取准确的函数返回地址并实现恶意代码执行较为困难。
作为一种恶意程序,勒索病毒可以感染设备、网络与数据中心并使其瘫痪,直至用户支付赎金使系统解锁。该类病毒执行过程如下:受害者主机植入勒索病毒后,病毒本身调用加密算法库解密自身数据并回连服务器,随后通过脚本文件执行HTTP GET请求并下载加密后的文件,随后在受害者主机环境中进行文件解密并将该文件封装成动态链接库,随后通过wscript执行DLL文件通过遍历系统文件的方法收集计算机信息。勒索病毒会将电脑中的各类文档进行加密,让用户无法打开,并弹窗限时勒索付款提示信息,达到勒索赎金的目的。
第三阶段:2017年前后,通过系统漏洞或弱口令等方式发起蠕虫式攻击,攻陷单点设备后还会在内网中横向扩散,以WannaCry和Satan为代表。
第四阶段:加密货币的出现改变勒索格局,加密货币具有匿名性与去中心化的特点,解决攻击者的传统问题,脱离货币交易链的追查与监管,黑色产业因此蓬勃发展。
感染媒介—>C&C通信—>文件加密—>横向移动
作为一种恶意程序,挖矿程序可以自动传播,在未授权的情况下占用系统资源,为攻击者牟利,使得受害者机器性能明显下降,影响正常使用。挖矿病毒占用CPU或GPU等计算资源,自动创建后门与混淆进程,同时该病毒定期改变进程名与PID并检测系统中是否存在对应的挖矿软件,若被查杀则再次从远端服务器上获取资源。同时该病毒通过扫描SSH文件感染其他机器,实现横向传播。
完整的木马程序一般由服务器程序与控制器程序两部分组成,当受害者主机安装了木马的服务器程序后,拥有控制器程序的攻击者就可以通过网络控制受害者主机。木马程序通常注入正常程序中,当用户执行正常程序时启动。同时,木马程序自动在任务管理器中隐藏,并以”系统服务”的方式欺骗操作系统,包含具有未公开并且可能产生危险后果的功能的程序,具备自动恢复与打开特殊端口的功能。
蠕虫是一种可以自我复制并通过网络传播的代码,通常无需人为干预即可实现传播。蠕虫病毒入侵并完全控制一台计算机后,就会把这台主机作为宿主,进而扫描并感染其他计算机。蠕虫病毒具有不依赖宿主程序、利用漏洞主动攻击、通过蠕虫网络隐藏攻击者位置的特点。该类病毒易造成拒绝服务与隐私信息丢失。
宏病毒是一种寄存在文档或模板的宏中的计算机病毒,具有感染文档、传播速度快、病毒制作周期短、可实现多平台交叉感染的特点。宏病毒通过调用系统命令造成系统破坏,除此之外,感染宏病毒的文档无法正常打印,并具有封闭或改变文件存储路径、非法复制文件等行为。
采用一种或多种传播手段,将大量主机感染僵尸程序,从而在控制者和被感染主机之间形成一个一对多控制网络。僵尸网络的形成过程包含加入、传播和控制三个阶段。
僵尸程序多指实现恶意控制功能的程序代码,控制服务器多指控制和通信的中心服务器。
社会工程攻击通常被认为是一种欺诈他人以收集信息、行骗和入侵计算机系统的行为,可以通过定期更换各种系统账号密码或使用高强度密码等防御该类攻击。
拖库是指黑客入侵有价值的网络站点并将注册用户的资料数据库全部盗走的行为。
洗库指在获取大量用户数据后,黑客通过一系列的技术手段和黑色产业链将有价值的用户数据变现的行为。
撞库指黑客利用获得的私密数据在其他网站上进行登录尝试的行为。
攻击者通常不直接通过自己的系统向目标发动攻击,而是先攻破若干中间系统并将其作为”跳板”,借助这些计算机完成攻击行动。用户可以通过安装防火墙以控制流量进出、更改系统默认登录用户为普通用户并做好权限控制等手段抵御此类攻击。
钓鱼攻击是一种企图从电子通讯中,通过伪装成信誉卓著的法人媒体以获得如用户名、密码和信用卡明细等个人敏感信息的犯罪诈骗过程。鱼叉式钓鱼攻击指针对特定受害公司或组织的钓鱼攻击,其钓鱼页面设计与整体操作流程具有定制化、精准化的特点,成功率较高。可以通过保证网络站点与用户之间的安全传输、加强网络站点的认证过程与监管等方式防御此类攻击。
攻击者首先通过观察或猜测确定特定目标经常访问的网站,并入侵其中一个或多个网站,植入恶意软件。当目标组织或组织中部分成员访问该类网站时会被重定向到恶意网址,导致恶意软件执行,最终造成该组织机器的大量感染。为抵御此类攻击,运维人员通常在浏览器或软件上进行安全杀毒和检测工作,若检测到恶意内容,则持续监控该网站流量并阻止恶意流量。此外,运维人员可以通过定期更新补丁的方式减少浏览器漏洞。
保密性Confidentiality
确保信息不暴露给未授权的实体或进程。
完整性Integrity
只有得到允许的用户才能修改实体或进程,并且能够判别出实体或进程是否被篡改。
可用性Availability
得到授权的实体可获得服务,攻击者不能占用所有资源而阻碍授权者的工作。
可控性Controllability
可控性主要指对危害国家信息(包括利用加密的非法通信活动)的监视审计。
不可否认性Non-repudiation
为出现的安全问题提供调查的依据和手段,使用审计、监控、防抵赖等安全机制使得攻击者无法否认相关操作。
将企业数据区域进行安全等级划分,分成非安全区、半安全区、安全区和核心安全区四个部分。
各安全域访问原则如下:
用于业务域内部及业务域之间,基于应用策略,实现主机东西向流量访问控制。
全面探测服务器主机和网络上的威胁活动,进行入侵行为主动IP封堵与恶意文件隔离。
结合传统技术与人工智能,采用机器学习模型实现针对病毒木马、僵尸网络及暴力破解行为的检测。
采用IP黑白名单机制及文件隔离机制,监控进程的可疑行为,以即时拦阻恶意代码。
Denial of Service,是一种拒绝服务攻击,常用来使服务器或网络瘫痪。
Distributed Denial of Service,简称为DDoS攻击,是一种分布式拒绝服务攻击。
每目的IP激活阈值指当针对策略设置的目的IP组内某IP发起的SYN请求速率数据包超过设定值,则触发AF的SYN代理功能。
每目的IP丢包阈值指当针对策略设置的目的IP组内某IP发起的SYN请求速率数据包超过设定值,则AF不再启用SYN代理,直接丢弃SYN包。
Intrusion Detection Systems,即入侵检测系统,对网络、系统的运行状况进行监视,尽可能发现各种攻击企图、攻击行为或攻击结果。通过旁路镜像模式部署,多用于被动检测。
Intrusion Prevention Systems,即入侵防御系统,可对网络、系统的运行状况进行监视,并可发现阻止各种攻击企图、攻击行为。通过路由模式、透明模式及并联模式部署,多用于主动检测。
常见的暴力破解方法包括字典法与规则破解法。
漏洞攻击防护通过对数据包应用层里的数据内容进行威胁特征检查,并与漏洞攻击防护规则库进行比对,如果匹配则拒绝该数据包,从而实现应用层漏洞攻击的防护。
Web Application Firewall,即Web应用防护,主要用于保护Web服务器不受攻击,而导致软件服务中断或被远程控制。WAF常见攻击手段包括:
通过将SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意SQL命令的目的。
GET请求提交的内容经过URL编码直接在URL栏中显示。
POST提交的内容不会直接显示在URL部分,而是呈现在POST包的DATA字段中。
Cross Site Request Fogery,即跨站请求伪造,攻击者盗用受害用户的身份,以其名义发送恶意请求,对服务器来说这个请求是合法的,但完成了攻击者所期望的操作,如以受害用户的名义发送邮件和信息,盗取账号,添加系统管理员等非法操作。
在【策略】-【安全策略】-【安全防护策略】-【高级设置】中新增URL参数排除后,Web应用防护的网站攻击检测将跳过这些参数的检查。主要用于正常业务下某些请求参数因携带特征串而被检测为攻击的情况,可以只针对这类参数进行排除。
在【内置数据中心】-【日志查询】-【WEB应用防护】中查询日志,找出误判日志然后点击日志后面的”添加例外”。
高危行为联动封锁:仅封锁具有高危行为特征的IP,优先保证用户流畅上网、业务稳定;
任意攻击行为联动封锁:对任意具有攻击特征的IP执行访问封锁,最大化业务和用户的安全防御能力。
深信服网页防篡改解决方案采用文件保护系统+下一代防火墙紧密结合,文件监控+二次认证功能紧密联动,保证网站内容不被篡改,其中文件保护系统采用业界防篡改技术中最先进的文件过滤驱动技术。
tamper.exe
进行卸载,卸载时需要输入客户端密码。