zoukankan      html  css  js  c++  java
  • [No0000166]CPU的组成结构及其原理

    中央处理器(Central Processing Unit, CPU)

    CPU
    的基本架构和工作原理其实百科上讲得已经相当清楚了,不过我觉得有些事情呢还是给个例子出来比较方便学习。
    本文会先从内存地址,计算机的一般架构之类的基础知识出发,然后逐步为读者"拼装"出一个超级简单的8-bit CPU。。。就像下图这样(大图点开)

    这就是本文的目标:拼装这样一个结构的CPU

    -----------------------------------------------------------------------------------------------------------------------

    上面那个大图里有几个梯形的符号
    它们叫做数据选择器(Multiplexer),也叫多路选择器或多路开关
    已经知道这个东西是干啥的童鞋直接跳过此楼吧。。

    这图是一个21选择器,A,B,S为输入,Z为输出,它们可取的值当然都只有01
    怎么工作的呢?
    以该图为例:
    S=1的时候,输出值Z = 输入值B
    S=0的时候,输出值Z = 输入值A
    比如说A=1,B=0,S=0的时候输出是多少?S=0就是说:选择A的值输出,也就是说输出值Z=A=1
    就这么简单

    同样地,我们也可以有41选择器

    只不过控制输入S变成了两位(00,01,10,11,分别对应一二三四),道理还是一样的

    如果你对这个东东怎么做成的感兴趣的话。。。下面就是41选择器的其中一种电路 = =

    哦,还有。。本文使用的逻辑门符号均是ANSI/IEEE Std 91-1984中的Distinctive shape,不是用方框符号。。。= =

    你只要知道数据选择器是干啥的就好,不用惦记上边那电路。。

    ------------------------------------------------------------------------------------------------------------

    1. 计算机架构(Computer Architecture)

    CPU
    、内部存储器(Internal Storage Device)和输入/输出设备(Input/Output Device, I/O)是电子计算机三大核心部件。内部储存器可以是硬盘,内存,缓存等;输入设备可以是鼠标,键盘;输出设备可以有屏幕,音箱等等。。

    当你打开电脑硬盘上安装的某个程序时,你的操作系统会把硬盘上的相应内容放入内存中。至于怎么放,在内存的什么地方放那可是一门大学问,光这个就够一般人喝一壶的。。相关知识可以在大学的操作系统课程里学到

    比如说你放个音乐。要放音乐,先用鼠标点开一个mp3文件,于是你就使用了一个输入设备。这个输入设备会把一个中断请求(Interrupt Request)送到CPU那边,结合来自操作系统的信息后CPU就知道:哦,你用鼠标点开那个mp3文件了!于是:
    1. CPU
    执行操作系统里关于文件关联的代码,于是你的电脑就知道要用WMP打开文件了
    2.
    你的操作系统开始把WMP这个程序里含有的指令和mp3文件的内容从硬盘上拉进内存里(还是CPU的工作)
    3.
    然后你的CPU开始一条一条地(双核的话那你就当成两条两条地好了)执行内存里的WMP程序指令(也就是如何解码mp3),并且把解码后的PCM比特流传到声卡上,再由声卡把数字信号转换成模拟信号送到音箱/耳机(输出设备)里。So now you have music

    又比如说你要编辑一个txt文件。还是先得用鼠标点开文件,又用了一次输入设备。于是:
    1. CPU
    执行操作系统里关于文件关联的代码,于是你的电脑就知道要用notepad打开文件了
    2.
    你的操作系统把notepad的程序指令和文件内容拉进内存
    3.
    然后你的CPU又开始执行Notepad程序的指令了
    4.
    每当你敲一次键盘(还是输入设备!!),都会向CPU发送一个中断请求好让CPU知道你敲了某个键。比如说你敲个Y,那么CPU就会把Y这个字符写进内存里。然后你要保存的时候操作系统就会把内存里改过的东东倒进硬盘里!~

    。。。好吧我承认实际过程跟这儿说的不大一样并且复杂得多,有些细节会在后面详细讲,but that's the basic idea.

    这大约就是CPU,输入/输出设备和内存之间的互动方式了。

    ---------------------------------------------------------------------------------------------------

    2.内存(Memory)

    内存就是暂时存储程序以及数据的地方,比如当我们在使用WPS处理文稿时,当你在键盘上敲入字符时,它就被存入内存中,当你选择存盘时,内存中的数据才会被存入硬盘。一断电内存上的东东就没了

    内存里的数据是根据内存地址(Memory Address)来组织的。每个地址都是独特的,每个地址一般来说对应着一个字节(byte)=8 bit,我们管这叫Byte addressable memory.

    32 bit的系统上,内存地址的长度就是32 bit。那么一个32 bit长度的二进制数最大可以表示的数是多少呢?很简单,2^32 = 4294967296。也就是说,32 bit的内存地址最大可以对应4294967296字节的内存!

    这个数字换算一下就可以得出它相当于4GB。现在你知道为什么32位系统不支持4GB以上的内存了吗?

    ------------------------------------------------------------------------------------------------------

    3. 指令编码(Instruction Encoding)

    终于要说点正经的了。。前面说过CPU会执行内存/缓存中的程序指令,可是这些指令是以什么样的形式储存在内存里的呢?要知道所谓的指令其实就是一长串的01而已。那CPU如何从这些01里知道指令是什么呢?这就是指令编码的内容了。

    先说说CPU。您说,CPU能干啥?其实很简单,无非就是加减乘除,读写内存,逻辑运算什么的。若是复杂些的CPU可能指令集要大些,不过基本的指令大概就这些。CPU内部也有自己的储存单元,叫做寄存器(Register),也是暂时用来放数据的地方,速度特别快,容量特别小。
    就拿我经常用的NIOS II来说,它内部有32个寄存器,它可以执行的指令包括(不好意思我要用汇编语言了=_=):

    add rA,rB,rC #
    把寄存器rB,rC里的数加起来,结果放入寄存器rC
    addi rB, rA, IMM16 #
    rA里的数跟一个16位的数相加,结果放入rB
    beq rB,rA,LABEL #
    rA=rB,则跳到LABEL指定的内存地址开始执行指令,否则继续按照内存地址顺序执行指令
    stwio rB, b_o(rA) #
    从内存地址rA+b_o处读取一个字节,数据放入rB
    ldwio rB, b_o(rA) #
    从内存地址rA+b_o处开始写入一个字节,写入的数据在rB


    等等

    总结起来,CPU可以有以下几个功能:
    1.
    进行寄存器之间的运算和比较
    2.
    由寄存器内指定的地址读写内存
    3.
    分支指令,类似于C语言里的if语句。比如跳到某个寄存器里指定的内存地址开始读取并执行指令

    当然,更复杂的指令集是有可能的,不过这里就不说了

    ---------------------------------------------------------------------------------------------

    我知道读者可能好几楼没见着个图有点烦躁了,不过请有些耐心,等开始拼装CPU的时候图片绝对多。。。

    Anyway
    ,继续说指令编码
    大致来说,上面的指令可以分为三大类:I-type,R-type,J-type
    P.S.
    这种分类适用于MIPS架构的处理器,其他我就不知道了

    1. I-type
    32 bit为例。一条I-type指令包括四个元素:
    两个寄存器编号,一个16位数字和一个操作码

    31-27
    位代表指令里寄存器rA的编号
    26-22
    位代表寄存器rB的编号
    21-6
    位是一个16位的二进制数
    5-0
    位是操作码

    例子:
    NIOS II
    汇编指令 addi r6,r7,310表示把寄存器r7里的数加上310,结果放入寄存器r6。如果我们规定addi运算对应的六位操作码是000011,那么请问整条指令的编码是?
    解答:
    寄存器r6的编号是6,即00110
    寄存器r7的编号是7,即00111
    数字310对应的二进制数是0000000100110110
    addi
    的操作码是000011
    所以整条指令的编码就是00110 00111 0000000100110110 000011
    -----------------------r6-----r7--------310---------addi------
    32位!
    这就是I-type指令在内存里存在的形式!!~

    ----------------------------------------------------------------------------

    2. R-type

    还是以32 bit为例。一条R-type指令通常包括四个元素:
    三个寄存器编号,一个操作码

    31-27
    位是寄存器rA的编号
    26-22
    位是寄存器rB的编号
    21-17
    位是寄存器rC(一般来说这个是目标寄存器)的编号
    16-6
    位是OPX,是操作码
    5-0
    位。。你当它没用吧,写上000000就好 = =

    例子:
    汇编指令 add r10,r9,r8是典型的R-type指令。它表示把寄存器r9,r8里的数加起来,然后把结果写入寄存器r10(目标寄存器)。若规定add运算的操作码为00000011111,请问整条指令的编码是?
    解答:
    寄存器r10的编号是10,即01010
    寄存器r9的编号是9,即01001
    寄存器r8的编号是8,即01000
    add
    运算操作码是00000011111
    OP = 000000
    所以整条指令的二进制编码是01001 01000 01010 00000011111 000000
    ---------------------------r9-----r8---r10------add-------OP
    32位!

    -----------------------------------------------------------------------------------------

    3. J-Type

    一条32bitJ-Type指令包含两个元素:
    一个26位的数字(通常是内存地址)和一个6位的操作码

    31-6
    位是数字
    5-0
    位是操作码

    例子:
    汇编指令 call ROUTINE_3是典型的J-Type指令,它表示该指令执行完毕后CPU将从ROUTINE_3开始的内存地址读取并执行其他指令。若ROUTINE_3开头指令的内存地址是0x00002b3ccall的操作码是000000,请问整条指令的二进制编码是?
    解答:
    16
    进制数0x00002b3c = 0000 0000 0000 0000 0010 1011 0011 1101
    call
    操作码是000000
    所以整个指令的编码是00000000000000000010101100111101 000000
    ------------------------------ROUTINE_3--------------call--
    还是32位!

    4.再说些CPU的事情

    让我试着用图片总结一下前面的基础知识。。

    上图概括了CPU和计算机其他部分的互动方式。该图与实际的计算机有很大差距,but you get the idea...现在让我们把注意力集中在CPU身上!

    CPU
    只知道执行指令,而指令是在内存里的(实际上不一定,但是为了让事情简单些,我们假设指令都是在内存里的)。所以CPU需要从内存里取出指令,这一步叫做提取(Fetch)

    CPU
    还需要知道这条指令是干什么的,所以被编码过的指令会被传到CPU的控制电路那边解码以正确设置控制信号,这样CPU才能正确执行指令,这一步叫解码(Decode)

    上面两步完成后CPU就可以执行该条指令了,也就是执行(Execute)
    运算后的结果经常需要保存,用来进行下一个指令的运算。那保存在哪里呢?无非就是寄存器组和内存。这一步叫做写回(Writeback)

    CPU
    的结构简图(省略了到输入/输出设备的连接)

    时钟信号(Clock Signal)说白了其实就是一个频率很高的方波,就像这样:

    它控制着CPU内核的工作节奏,每当时钟信号由01(rising edge)的时候,CPU里面的元件就会做点什么。

    数据通路(Datapath)是一个能够执行任何指令集内的指令的电路,但是它需要控制电路告诉它应该在什么时候执行什么指令。数据通路包括了寄存器组,算术逻辑单元(Arithmetic logic unit, ALU)以及很多其他的元件。

    控制电路负责解码指令并且正确设置控制信号,于是数据通路就能根据这些控制信号知道应当执行哪一条指令。

    ----------------------------------------------------------------------------------------------------------

    5. 好了,开始搭CPU = =

    如果读者到目前为止都还能懂的话,那么恭喜!你终于有了足够的基础知识来搭建一个简单的CPU了。
    当然了,CPU这玩意不是说搭就搭的。我们的CPU能干些什么?能执行些什么指令?指令是怎么编码的?它由哪些小模块组成?都有哪些控制信号?这些问题都必须有明确的回答。

    从现在开始,我强烈建议读者拿几张空白的纸出来记下这些问题的回答,因为我们即将面对的是众多的指令,模块以及控制信号。这可比拼装家具复杂多了,如果不记下来的话到时大概会头晕目眩。

    当初LZ对这个CPU做一丁点儿小改动的时候,可得对着一张电路图,大大的控制信号表格以及超长的Verilog HDL代码,花了不少时间和草稿纸呢

    I will be back tomorrow or after 3 hours...~

    --------------------------------------------------------------------------------------------------------------

    我们的CPU能做什么?

    从现在开始将进入本文最复杂,最能绕晕人的部分,请做好准备。。
    下面要开始说明这个CPU的规格,信息量略大,推荐写在纸上记着。

    现在我们对下面的行**几个简化约定(要是不简化的话,读者就会看到一大堆密密麻麻的描述文字),请务必记好。

    TMP = MEM[R2]
    这个语句表示从寄存器R2指定的内存地址读取数据,然后把读到的数据赋值给TMP
    举个例子:如果寄存器R2里的数字是0001 0011
    而内存地址0001 0011处所存的数据是1111 1111
    那么这个语句就表示TMP被赋值1111 1111TMP = 1111 1111

    MEM[R2] = TMP
    这个语句表示TMP的值被写入内存,写入的位置是内存地址R2
    举个例子:如果TMP = 1111 1110R2 = 0000 0001
    那么这个语句就表示内存地址0000 0001处的数据变成了1111 1110

    R1 = TMP
    这个语句表示寄存器R1写入TMP的值
    举个例子:如果TMP = 0000 1111
    那么这个语句表示寄存器R1里的数字变成了0000 1111

    PC = PC + 1
    几乎每个指令都会带有这个语句,意思是PC寄存器里的数字加1
    PC
    寄存器中有指令所在的内存地址。每执行完一条指令后,这个内存地址一般都会加1,好让CPU调出下一条指令

    -------------------------------------------------------------------------------------------------

    前面说过,我们的CPU8-bit的,也就是说它最多只能支持2^8=256个内存地址。我们的CPU内部将会有四个通用寄存器(General Purpose Register)R0~R3,一个PC寄存器(Program Counter Register),每个寄存器容量为8 bit。这个CPU不支持中断,意味着它不接受键盘和鼠标的输入,只会从内存里读取并执行指令。另外,CPU内部还有两个特殊的比特位,NZ。如果某个运算的结果是负数,那么N就会被设定为1;如果某个运算结果为零,那么Z就被设定为1。我们将会在跳转指令里用到这两个比特位。

    CPU可以执行10种指令:

    1. LOAD R1 (R2)
    实现方法:
    TMP = MEM[R2]
    R1 = TMP
    PC = PC + 1
    这条指令是把内存地址R2处的数据读出来,然后放进寄存器R1里。接着PC寄存器加一为下一条指令做准备。看出来了吗?下面将不再有这种文字描述,全部使用简写。

    2. STORE R1 (R2)
    实现方法:
    MEM[R2] = R1
    PC = PC + 1

    3. ADD R1 R2 [
    加法运算]
    实现方法: 
    TMP = R1 + R2
    R1 = TMP
    IF (TMP == 0) Z = 1; ELSE Z = 0;
    IF (TMP < 0) N = 1; ELSE N = 0;
    PC = PC + 1

    4. SUB R1 R2 [
    减法运算]
    实现方法:
    TMP = R1 - R2
    R1 = TMP
    IF (TMP == 0) Z = 1; ELSE Z = 0;
    IF (TMP < 0) N = 1; ELSE N = 0;
    PC = PC + 1

    5. NAND R1 R2 [NAND
    逻辑运算]
    实现方法:
    TMP = R1 NAND R2
    R1 = TMP
    IF (TMP == 0) Z = 1; ELSE Z = 0;
    IF (TMP < 0) N = 1; ELSE N = 0;
    PC = PC + 1

    6. ORI IMM5 [OR
    逻辑运算]
    实现方法:
    TMP = R1 OR IMM5, IMM5
    是一个5-bit的二进制数
    R1 = TMP
    IF (TMP == 0) Z = 1; ELSE Z = 0;
    IF (TMP < 0) N = 1; ELSE N = 0;
    PC = PC + 1

    7. SHIFT L/R R1 IMM2 [
    移位运算]
    实现方法:
    IF (L) THEN TMP = R1 << IMM2
    ELSE TMP = R1 >> IMM2
    R1 = TMP
    IF (TMP == 0) Z = 1; ELSE Z = 0;
    IF (TMP < 0) N = 1; ELSE N = 0;
    PC = PC + 1

    8. BZ IMM4 [
    如果Z=1,就跳过IMM4个指令]
    实现方法:
    IF (Z == 1) PC = PC + 1 + (SIGN-EXTEND8(IMM4))
    ELSE PC = PC + 1

    9. BNZ IMM4 [
    跟上一条指令相反]
    实现方法:
    IF (Z == 0) PC = PC + 1 + (SIGN-EXTEND8(IMM4))
    ELSE PC = PC + 1

    10. BPZ IMM4 [
    N = 0,就跳过IMM4个指令]
    实现方法:
    IF (N == 0) PC = PC + 1 + (SIGN-EXTEND8(IMM4))
    ELSE PC = PC + 1

    这些描述都比较抽象,做拼装的时候这些东西应该会表现得更具体些。

    ---------------------------------------------------------------------------------------------------

    CPU的指令编码

    5个指令的编码方式都是:
    7-6
    位是寄存器R1的编号
    5-4
    位是寄存器R2的编号
    3-0
    位是操作码


    ORI
    指令的编码


    SHIFT
    指令的编码


    三个跳转指令的编码

    --------------------------------------------------------------------------------------------------------------------------

    数据通路的设计

    这个CPU的数据通路将由以下部件组成:
    寄存器

    data in ----------
    写入寄存器数据,8条线,因为是8-bit
    data out ----------
    输出寄存器的数据,8条线,因为是8-bit
    控制信号write ---------- 是否允许写入数据。是的话write = 1, 否则 write = 0
    clock ----------
    时钟信号
    。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

    数据选择器

    相信诸位都应该知道这玩意怎么工作的吧?当然输入输出都是8条线
    。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

    通用寄存器组

    通用寄存器R0-R3都在这里面。
    reg1,reg2 ----------
    说明指令里涉及哪两个寄存器
    regw ----------
    指明要往哪个寄存器里写数据(应该是2条线,没画出来)
    data0 ----------
    reg1指定的寄存器中输出数据
    data1 ----------
    reg2指定的寄存器中输出数据
    dataw ----------
    实际写入寄存器的数据从这里进去(应该是8条线,没画出来)
    控制信号write ---------- 是否允许写入数据?是的话write = 1,否则write = 0
    clock ----------
    时钟信号
    。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

    算术逻辑单元(ALU)

    这个模块就是CPU做运算的地方了。它能实现加减法,NANDOR运算,以及移位运算。
    In0,In1 ----------
    输入
    控制信号ALUop ---------- 告诉ALU应该做哪个运算
    Z,N ----------
    前面提过的特殊比特位,ALU要负责根据运算结果设置ZN
    OUT ----------
    运算结果输出

    -----------------------------------------------------------------------------------------------------------------

    继续说CPU组件。。。

    指令内存

    addr ----------
    指定从哪个内存地址读取指令
    Out ----------
    从内存里输出的指令在这里去往CPU
    控制信号Read ---------- 是否允许读取指令?是的话Read = 1, 否则 Read = 0
    我们假设指令内存是只读(Read Only)
    。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

    数据内存

    addr ----------
    指定从哪个内存地址读取数据
    Din ----------
    往内存里写的数据从这里进去
    Dout ----------
    从内存里读取的数据从这里出去
    Clock ----------
    时钟信号
    控制信号MemWrite ---------- 是否允许数据写入内存?是的话MemWrite = 1, 否则为0
    控制信号MemRead ---------- 是否允许读取?是的话MemRead = 1,否则为0
    。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

    现在我们要用上面的模块去组装CPU的数据通路。

    首先,无论执行的是哪一条指令,CPU都必须先从内存里把指令调出来,前面说过这一步叫做提取。此步骤由下图的电路执行。

    这个电路由一个PC寄存器(Program Counter Register)和指令内存组成。PC寄存器里储存的是该条指令所在的内存地址,然后指令内存会根据PC寄存器指定的地址向CPU输出相应的指令。控制信号PCWrite决定是否允许更改PC寄存器里的数字,如果允许那么PCWrite = 1, 否则为0

    -------------------------------------------------------------------------------------------------

    能执行加法指令的电路

    前面说过加法指令ADD R1 R2的编码形式如下

    该指令会把寄存器R1R2里的数字加起来,然后把结果写回R1
    INST
    线的3-0位是操作码,会被送到控制电路那边解码。解码后控制电路会设置好各个控制信号使得CPU的数据通路执行加法运算。我们以后再详细说说控制电路的事情,现在让我们来看看在数据通路里,INST线7-4位是如何使用的。


    上图是一个可以执行加法指令的电路(图里的数字有点小错误,不要在意)
    INST 7-6
    位代表R1的编号,作为通用寄存器组的reg1regw输入
    INST 5-4
    位代表R2的编号,作为reg2的输入
    然后R1R2里的数据从data0,data1输出,送到ALU做加法运算(ALUop会告诉ALU做加法运算)
    TMP = R1 + R2
    完成
    然后加法运算的结果被送到寄存器组的dataw输入。这时RFWrite = 1。由于此时regw指定的寄存器编号是R1,所以加法运算的结果就被写回了寄存器R1
    R1 = TMP
    完成
    此外,ALU还会把NZ这两个特殊的比特位根据运算结果设置好
    IF (TMP == 0) Z = 1; ELSE Z = 0;
    IF (TMP < 0) N = 1; ELSE N = 0;
    完成

    这条指令还没完,我们需要把PC寄存器里的数字加一,这样CPU才能取得下一条指令。因此还得加点东西。

    有了上面那个电路之后
    PC = PC + 1
    就可以完成了

    同样的电路也可以用作执行SUBNAND指令。唯一不同的是控制信号ALUop会让ALU做减法或者NAND运算。

    -----------------------------------------------------------------------------------------

    执行ORI指令的电路

    ORI IMM5
    的编码

    这个指令会把寄存器R1里的数字与一个5-bit的二进制数做OR运算,然后把结果写回R1
    要实现这个指令只需把上面的电路稍作更改即可


    可以看到这个电路增加了两个数据选择器(图中的reg0,reg1应分别为reg1,reg2)
    首先,因为ORI指令总是在寄存器R1上进行操作,不像ADD,SUB,NAND等其他指令需要指定在哪些寄存器上进行操作,所以我们加入一个控制信号为R1Sel的选择器。
    当执行ORI指令时,R1Sel = 1,这样reg1的输入在执行ORI指令时总会是01
    所以data0输出也总是会输出R1的数据到ALU
    这时,ALU的另外一个输入应当是指令里的IMM5,而不是从寄存器组那边过来的输入
    于是我们加入另一个控制信号为ALU2的选择器,这样我们就可以选择是从寄存器组还是从INST线那边输入ALU数据。
    当执行ORI指令时,ALU2 = 1,这样ALU就会把INST 7-3位的5-bit二进制数作为输入
    然后控制信号ALUop告诉ALU进行OR运算
    TMP = R1 OR IMM5
    完成
    结果写回R1
    R1 = TMP
    完成
    ALU
    根据运算结果设置NZ
    IF (TMP == 0) Z = 1; ELSE Z = 0;
    IF (TMP < 0) N = 1; ELSE N = 0;
    完成
    PC
    寄存器加一,CPU为下一条指令做好准备
    PC = PC + 1
    完成

    -----------------------------------------------------------------------------------------------------------------


    -----------------------------------------------------------------------------------------------------

    能执行内存读取以及SHIFT指令的电路

    LOAD R1 (R2)
    STORE R1 (R2)
    由于这两条指令里的R2部分总是作为地址使用,所以寄存器R2的输出要连到数据内存的addr输入;而R1STORE指令中是作为数据源的寄存器使用的,所以连接到Datain输入。R1LOAD指令中是作为放内存读出数据的寄存器使用的,所以连回到寄存器组的dataw输入。中间加了一个RFin选择器,这样寄存器组就可以选择是从ALU还是从数据内存那边写入数据。

    现在我们要把数据内存加入我们的电路里,如下图:

    最后我们还把ALU2选择器扩展了一下,使得执行SHIFT指令时ALU能够选择从INST线读到运算需要的数据(图中的INST 5-2应当为INST 4-3,因为SHIFT指令的IMM2在指令编码的4-3)

    然后这个CPU的数据通路就基本完成了!!

  • 相关阅读:
    Android Media Playback 中的MediaPlayer的用法及注意事项(二)
    Android Media Playback 中的MediaPlayer的用法及注意事项(一)
    34. Search for a Range
    33. Search in Rotated Sorted Array
    32. Longest Valid Parentheses
    31. Next Permutation下一个排列
    30. Substring with Concatenation of All Words找出串联所有词的子串
    29. Divide Two Integers
    28. Implement strStr()子串匹配
    27. Remove Element
  • 原文地址:https://www.cnblogs.com/Chary/p/No0000166.html
Copyright © 2011-2022 走看看