我的春招复习笔记

文章目录

面经

仅供学习参考,禁止商业用途

计算机网络

一、TCP与UDP区别总结:

1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接

2、TCP提供可靠的服务。通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付

3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的

4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信

5、TCP首部开销20字节;UDP的首部开销小,只有8个字节

6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道

7、UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)

三、HTTP报文

POST /somedir/page.html HTTP/1.1    
//以上是请求行:方法字段、URL字段和HTTP版本字段
Host: www.user.com
Content-Type: application/x-www-form-urlencoded
Connection: Keep-Alive
User-agent: Mozilla/5.0.    
Accept-lauguage: fr  
//以上是首部行
(此处必须有一空行)  //空行分割header和请求内容 
name=world   请求体
HTTP/1.1 200 OK    
//以上是状态行:协议版本字段、状态码、相应状态信息
Connection:close
Server:Apache/2.2.3(CentOS)
Date: Sat, 31 Dec 2005 23:59:59 GMT
Content-Type: text/html
Content-Length: 122
//以上是首部行
(此处必须有一空行)  //空行分割header和实体主体
(data data data data)//响应实体主体

**200 OK:**请求成功,信息在返回的响应报文中

301 Moved Permanently:请求的对象已经被永久转移了,新的URL定义在响应报文中的Location:首部行中。客户软件将自动获取新的URL

400 Bad Request:一个通用差错代码,指示该请求不能被服务器理解

404 Not Found:被请求的文件不在服务器上

505 HTTP Version Not Supported:服务器不支持请求报文使用的HTTP协议版本
<4开头的状态码通常是客户端的问题,5开头的则通常是服务端的问题>

三、GET和POST区别

GET的请求参数一般以?分割拼接到URL后面,POST请求参数在Body里面

GET参数长度限制为2048个字符,POST一般是没限制的

GET请求由于参数裸露在URL中, 是不安全的,POST请求则是相对安全
之所以说是相对安全,是因为,如果POST虽然参数非明文,但如果被抓包,GET和POST一样都是不安全的。(HTTPS该用还是得用)

四、HTTPS

image-20200208120945644

五、TCP、UDP报文

image-20200131003330154 image-20200208122518934

六、三次握手,四次挥手

​ SYN=1(同步位),请求连接,seq=x(任意值)

​ SYN=1,ACK=1,seq=y,ack=x+1

​ ACK=1,seq=x+1,ack=y+1

​ FIN=1,seq=u

​ ACK=1,seq=v,ack=u+1。此时客户端停止发送数据

​ 服务器发完数据后,发送第三条

​ FIN=1,ACK=1,seq=w,ack=u+1

​ ACK=1,seq=u+1,ack=w+1

七、ARP协议(寻找同一个局域网内的地址)

寻找mac地址

八、ICMP网际控制报文协议

​ 包含差错报文和询问报文0和8

九、HTTP实现断点续传

在Http的请求上多定义了断点续传相关的HTTP头 Range和Content-Range字段。

可以通过标识文件最后修改时间和对文件进行唯一标识来确定是不是同一个文件

十、HTTP1.1 HTTP2.0 Websocket

  • HTTP 1.1增加host字段
  • 默认长连接
  • HTTP 1.1中引入了Chunked transfer-coding,范围请求,实现断点续传(实际上就是利用HTTP消息头使用分块传输编码,将实体主体分块传输)
  • HTTP 1.1管线化(pipelining)理论,客户端可以同时发出多个HTTP请求,而不用一个个等待响应之后再请求,但是这种模式会有线头阻塞,没有改变阻塞问题,浏览器默认没开`

HTTP/2实现了多路复用,解决了HTPP1.1线头阻塞的问题,真正意义上实现了管线化的理论,大幅度的提升了web性能

HTTP2将原来的传输文本封装成二进制帧数据发送

websocket。有状态的。长链接的 全双工的

操作系统

一、进程与线程的概念

进程是对运行时程序的封装,是系统进行资源调度和分配的的基本单位,实现了操作系统的 并发;

线程是进程的子任务,是 CPU 调度和分派的基本单位,用于保证程序的实时性,实现进程内 部的并发;线程是操作系统可识别的最小执行和调度单位。

二、进程,线程的通信方式

进程间通信主要包括管道、消息队列、信号量、信号、共享内存、以及 socket。

线程包括临界区,互斥量,信号量,事件

三、请问单核机器上写多线程程序,是否需要考虑加锁

当某个线程时间片耗尽时,操作系统会将其挂起,然后运行另一个线程。 如果这两个线程共享某些数据,不使用线程锁的前提下,可能会导致共享数据修改引起冲突

四、多线程和多进程的不同

多线程之间共享同一个进程的 地址空间,线程间通信简单,同步复杂,线程创建、销毁和切换简单,速度快,占用内存少,适 用于多核分布式系统线程间会相互影响,一个线程意外终止会导致同一个进程的其他线程 也终止,程序可靠性弱。

而多进程间拥有各自独立的运行地址空间,进程间不会相互影响,程序 可靠性强,但是进程创建、销毁和切换复杂,速度慢,占用内存多,进程间通信复杂,但是同步 简单,适用于多核、多机分布。

五、死锁的产生条件

互斥条件,请求和保持条件,不可剥夺条件,环路等待条件

六、解决死锁

资源一次性分配,从而剥夺请求和保持条件,资源有序分发

七、锁的分类

1、条件变量

与互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直 到某特殊情况发生为止。通常条件变量和互斥锁同时使用

2、互斥锁 (Mutex)

3、自旋锁

自旋锁与互斥量功能一样,唯一一点不同的就是互斥量阻塞后休眠让出cpu,而自旋锁阻塞后不会让出cpu,会一直忙等待,直到得到锁。
  自旋锁在用户态使用的比较少,在内核使用的比较多!自旋锁的使用场景:锁的持有时间比较短,或者说小于2次上下文切换的时间。

4、读写锁(Read-Write Lock)

适 用于一个特定的场合。比如对于一段线程间访问的数据,如果程序大部分时间都是在读取,而只有很少的时间才会写入,那么使用前面几种锁时,每次读取也是同样 要申请锁的,而这时其它的线程就无法再对此段数据进行读取。可是,多个线程同时对一段数据进行读取时,是不存在同步问题的,那么这些读取时设置的锁就影响 了程序的性能。读写锁的出现就是为了解决这个问题的。

对于一个读写锁,有两种获取方式:共享(Shared)或独占 (Exclusive)。如果当前读写锁处于空闲状态,那么当多个线程同时以共享方式访问该读写锁时,都可以成功;而此时如果一个线程以独占的方式访问该 读写锁,那么它会等待所有共享访问都结束后才可以成功。在读写锁被独占访问的过程中,再次共享和独占请求访问该锁,都会进行等待状态。

八、epoll和select/poll的区别

epoll是实现I/O多路复用的一种方法,有水平触发和边缘触发两种工作模式,区别在于两种模式的返回就绪状态的时间不同。水平触发和select/poll的方式一样

  • 水平触发

    • ​ 读:缓冲内容不为空返回读就绪
    • ​ 写:缓冲区还不满返回写就绪
  • 边缘触发

    • ​ 读:
      • ​ 缓冲区由不可读变为可读
      • ​ 新数据到达,缓冲区中待读数据变多时
    • ​ 写:
      • ​ 当缓冲区由不可写变为可写
      • ​ 当有旧数据被发送走,即缓冲区中的内容变少的时候

    epoll之所以高效,是因为epoll将用户关心的文件描述符放到内核里的一个事件表中,而不是像select/poll每次调用都需要重复传入文件描述符集或事件集。比如当一个事件发生(比如说读事件),epoll无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入就绪队列的描述符集合就行了。

select不足的地方:

1 每次select都要把全部IO句柄复制到内核

2 内核每次都要遍历全部IO句柄,以判断是否数据准备好

3 select模式最大IO句柄数是1024,太多了性能下降明显

epoll的特点

1 每次新建IO句柄才复制并注册到内核

2 内核根据IO事件,把准备好的IO句柄放到就绪队列

3 应用只要轮询(epoll_wait)就绪队列,然后去读取数据

只需要轮询就绪队列(数量少),不存在select的轮询,也没有内核的轮询,不需要多次复制所有的IO句柄。因此,可以同时支持的IO句柄数轻松过百万。

poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历fd。

九、僵尸进程

僵尸进程是当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程。如果父进程先退出 ,子进程被init接管,子进程退出后init会回收其占用的相关资源

十、c++的多线程

头文件:thread类

1. thread()

join()挂起其他线程,运行当前子线程

detach()当前子线程分离到后台运行,不影响主线程的运行和结束

sleep()暂停,可以选择时间

数据结构

排序算法

image

LRU缓存

class LRUCache {
private:
    int cap;
    // 双链表:装着 (key, value) 元组
    list<pair<int, int>> cache;
    // 哈希表:key 映射到 (key, value) 在 cache 中的位置
    unordered_map<int, list<pair<int, int>>::iterator> map;
public:
    LRUCache(int capacity) {
        this->cap = capacity; 
    }
    
int get(int key) {
    auto it = map.find(key);
    // 访问的 key 不存在
    if (it == map.end()) return -1;
    // key 存在,把 (k, v) 换到队头
    pair<int, int> kv = *map[key]
    cache.erase(map[key]);
    cache.push_front(kv);
    // 更新 (key, value) 在 cache 中的位置
    map[key] = cache.begin();
    return kv.second; // value
}

void put(int key, int value) {

    /* 要先判断 key 是否已经存在 */ 
    auto it = map.find(key);
    if (it == map.end()) {
        /* key 不存在,判断 cache 是否已满 */ 
        if (cache.size() == cap) {
            // cache 已满,删除尾部的键值对腾位置
            // cache 和 map 中的数据都要删除
            auto lastPair = cache.back();
            int lastKey = lastPair.first;
            map.erase(lastKey);
            cache.pop_back();
        }
        // cache 没满,可以直接添加
        cache.push_front(make_pair(key, value));
        map[key] = cache.begin();
    } else {
        /* key 存在,更改 value 并换到队头 */
        cache.erase(map[key]);
        cache.push_front(make_pair(key, value));
        map[key] = cache.begin();
    }
}
};

快速排序

int AdjustArray(int s[], int l, int r) //返回调整后基准数的位置  
    {  
        int i = l, j = r;  
        int x = s[l]; //s[l]即s[i]就是第一个坑  
        while (i < j)  
        {  
            // 从右向左找小于x的数来填s[i]  
            while(i < j && s[j] >= x)   j--;    
            if(i < j)   
            {  
                s[i] = s[j]; //将s[j]填到s[i]中,s[j]就形成了一个新的坑  
                i++;  
            }  
      
            // 从左向右找大于或等于x的数来填s[j]  
            while(i < j && s[i] < x)  i++;    
            if(i < j)   
            {  
                s[j] = s[i]; //将s[i]填到s[j]中,s[i]就形成了一个新的坑  
                j--;  
            }  
        }  
        //退出时,i等于j。将x填到这个坑中。  
        s[i] = x;  
      
        return i;  
    }

void quick_sort1(int s[], int l, int r)  
{  
    if (l < r)  
    {  
        int i = AdjustArray(s, l, r);//先成挖坑填数法调整s[]  
        quick_sort1(s, l, i - 1); // 递归调用   
        quick_sort1(s, i + 1, r);  
    }  
}

插入 归并 冒泡稳定

堆排序

void downAdjust(int low,int high){
	int i=low,j=2*low;
	//最后一个非叶子结点,j是他的子节点
	    while(j <= high) {
        if(j + 1 <= high && b[j] < b[j + 1]) j = j + 1;
        if (b[i] >= b[j]) break;
        swap(b[i], b[j]);
        i = j; j = i * 2;
}
}

void createHeap(){
	for(int i=n/2;i>=1;i--)
				downAdjust(i,n);
} 

void heapSort(){
  createHeap();
  for(int i=n;i>1;i--){
    swap(heap[i],heap[1]);
    downAdjust(1,i-1);
  }
}

判断平衡二叉树

public class Solution {
    public int depth(TreeNode root){
        if(root == null)return 0;
        int left = depth(root.left);
        if(left == -1)return -1; //如果发现子树不平衡之后就没有必要进行下面的高度的求解了
        int right = depth(root.right);
        if(right == -1)return -1;//如果发现子树不平衡之后就没有必要进行下面的高度的求解了
        if(left - right <(-1) || left - right > 1)
            return -1;
        else
            return 1+(left > right?left:right);
    }

    public boolean IsBalanced_Solution(TreeNode root) {
        return depth(root) != -1;
    }
}

乱序数组找第k大的数

计数排序,找到最大值,建立数组,给每个值算出个数;

K 个一组翻转链表

四个指针,记录已翻转的节点,即将反转的节点,以及他们的边缘节点,循环翻转

ListNode* reverseKGroup(ListNode* head, int k) {
   
        ListNode*p=head;
        int size=0;
        while(p!=NULL){
            size++;
            p=p->next;
        }
        int mul=size/k;
 if(head==NULL||size<k)return head;
  
  
ListNode*pHead,*cur=head,*past=NULL,*pre;
for(int j=0;j<mul;j++){
    pHead=cur;
    past=NULL;
        for(int i=0;i<k;i++){
            ListNode* temp=cur->next;
            cur->next=past;
            past=cur;
            cur=temp;
        }
        
        
        if(j==0)head=past;
        else pre->next=past;
        pre=pHead;
}
        pre->next=cur;
    return head;
    }

三数之和

排序之后,for遍历,每一个此时的值定位current值,此值之后定义左右两个指针,移动相加判断,<0,l++,>0,r–;

m来判断是否唯一。

DFS

背包,资源分配等求一维线性最值或全排列问题

void dfs(int x,int sum){
    if(x>n)return;	//当前数量超过背包容量,资源总数,或数字总数,return
    else{		
        if(sum>maxP)maxP=sum;
        for(int i=0;i<8;i++)//遍历一维数组
            if(vis[i]==0){//遍历没有被访问过的节点
                vis[i]=1;
                dfs(x+w[i],sum+pian[i]);
                vis[i]=0;//回溯
            }
    }
}

另一种写法

void dfs2(int index,int sumC,int sumW){
    if(index==8){

        return;
    }
        dfs2(index+1,sumC,sumW);//跳过index
    if(sumW <= n) {
        if (  sumC > maxP)
            maxP = sumC;
        dfs2(index + 1, sumC + pian[index], sumW + w[index]);//包含index
    }
}

地图问题

回溯标记是否需要还原

​ 如果问题是求,所有可能的情况,如所有的可能路径,所有的排列,则需要需要还原。此时 123和132被认为是两条路

​ 如果问题是求面积,求最佳路径,则不需要

添加dir数组,进行移动

岛屿的最大面积
void dfs(int ax,int ay,vector<vector<int>>& grid){
        w++;
        for(int k=0;k<4;k++){
            int x=ax+dir[k][0];
            int y=ay+dir[k][1];
            if(checkV(x,y,grid)){
                visited[x][y]=1;
                dfs(x,y,grid);
            }
        }
    }

第二种递归解法

 int maxAreaOfIsland(int grid[][]) {
        int max = 0; // 记录最大岛屿面积
       
        for (int i = 0; i < grid.length; i++) {
            for (int j = 0; j < grid[0].length; j++) {
                if (grid[i][j] == 1) { // 
                    int count = DFS(grid, visited, i, j, 0);
                    max = max > count ? max : count;
                }
            }
        }
        return max;
    }

  int DFS(int grid[][],bool visited[][],int x,int y,int count) {
        if (!valid(grid, visited, x, y)) {
            return count;
        }
        visited[x][y] = true;
        for (int i = 0; i < 4; i++) { // 上下左右进行遍历
            count = DFS(grid, visited, x + move[i][0], y + move[i][1], count);
        }
        return count+1; // 更新岛屿面积
    }


解数独

编写一个程序,通过已填充的空格来解决数独问题。

一个数独的解法需遵循如下规则:

数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
空白格用 ‘.’ 表示

// row[x][u]表示第x行是否已经填过数字u(0-8)
    // col[y][u]表示第y行是否已经填过数字u(0-8)
    // box[x / 3][y / 3][u]表示第[x/3,y/3]个box是否已经填过数字u(0-8)

    bool row[9][9] = {0}, col[9][9] = {0}, cell[3][3][9] = {0};
    void solveSudoku(vector<vector<char>>& board) {
        for (int i = 0; i < 9; i ++ ) {
            for (int j = 0; j < 9; j ++ ) {
                char c = board[i][j];
                if (c != '.') {
                    int u = c - '1'; // u: '1' - '1' = 0
                    row[i][u] = col[j][u] = cell[i / 3][j / 3][u] = true;
                }
            }
        }

        dfs(board, 0, 0);
    }

    bool dfs(vector<vector<char>>& board, int x, int y) {
        if (y == 9) x ++ , y = 0; // 先改变
        if (x == 9) return true; // 填完所有格子,返回true

        if (board[x][y] != '.') return dfs(board, x, y + 1);
        
        for (int i = 0; i < 9; i ++ ) {
            if (!row[x][i] && !col[y][i] && !cell[x / 3][y / 3][i]) {
                board[x][y] = i + '1';
                row[x][i] = col[y][i] = cell[x / 3][y / 3][i] = true;

                // 如果下面搜索后是对的,就提前返回,不恢复现场(因为要修改board);
                // 如果是false就恢复现场(这个方法很巧妙)
                if (dfs(board, x, y + 1)) return true; 
                board[x][y] = '.';
                row[x][i] = col[y][i] = cell[x / 3][y / 3][i] = 0;

            }
        }
        return false;
    }

BFS

​ bfs通常是用来地图最优解

离建筑物最近的距离
 int shortestDistance(vector<vector<int>>& grid) {
    int res = INT_MAX,  m = grid.size(), n = grid[0].size();

    vector<vector<int>> sum(m,vector<int>(n,0));//计算每个1点到该0点的最短长度
    vector<vector<int>> link(m,vector<int>(n,0));//计算有几个1点与该0点连通
    int dirs[4][2]={{0,-1},{-1,0},{0,1},{1,0}};
    int one=0;
    queue<pair<int, int>> q;
    for (int i = 0; i < grid.size(); ++i) {
        for (int j = 0; j < grid[i].size(); ++j) {
            if (grid[i][j] == 1) {
                one++;
                vector<vector<int>> visted(m,vector<int>(n,0));
                int cnt=0;

                q.push({i, j});
                while (!q.empty()) {//进行bfs
                    int si=q.size();
                    cnt++;
                    while(si-->0){
                        int a = q.front().first, b = q.front().second; q.pop();
                        for (int k = 0; k < 4; ++k) {
                            int x = a + dirs[k][0], y = b + dirs[k][1];
                            if (x >= 0 && x < m && y >= 0 && y < n && visted[x][y] == 0&&grid[x][y]==0) {
                                sum[x][y]+=cnt;
                                visted[x][y]=1;
                                link[x][y]++;
                                q.push({x, y});

                            }
                        }
                    }
                }

            }
        }
    }
    for (int i = 0; i < grid.size(); ++i)
        for (int j = 0; j < grid[i].size(); ++j)
            if(res>sum[i][j]&&sum[i][j]>0&&link[i][j]==one)
                res=sum[i][j];
          
    return res == INT_MAX ? -1 : res;
}

双指针

环形链表 II

fast和slow两个指针,fast一次走两格,slow一格,第一次相遇后。

fast置于head,fast走一格,slow走一格,相遇即为环入口

动态规划

最长回文子串

P(i,j)=(P(i+1,j−1) and S[i]==S[j])

string longestPalindrome(string s) {
        int len=s.size();
        if(len==0||len==1)
            return s;
        int start=0;//回文串起始位置
        int max=1;//回文串最大长度
        vector<vector<int>>  dp(len,vector<int>(len));//定义二维动态数组
        for(int i=0;i<len;i++)//初始化状态
        {
            dp[i][i]=1;
            if(i<len-1&&s[i]==s[i+1])
            {
                dp[i][i+1]=1;
                max=2;
                start=i;
            }
        }
        for(int l=3;l<=len;l++)//l表示检索的子串长度,等于3表示先检索长度为3的子串
        {
            for(int i=0;i+l-1<len;i++)
            {
                int j=l+i-1;//终止字符位置
                if(s[i]==s[j]&&dp[i+1][j-1]==1)//状态转移
                {
                    dp[i][j]=1;
                    start=i;
                    max=l;
                }
            }
        }
        return s.substr(start,max);//获取最长回文子串
    }
不同的二叉搜索树

n个节点BST,有多少种

0个节点是1,1个节点是1

2个节点 是G[2]=G[0]*G[1]+G[1]*G[0]

两个节点的二叉树左子树可能为1和0

 int numTrees(int n) {
        vector<int> dp(n + 1, 0);
        dp[0] = 1;
        dp[1] = 1;
        
        for(int i = 2; i <= n; i++)
            for(int j = 1; j <= i; j++){
                dp[i] += dp[j - 1] * dp[i - j];
            }
        return dp[n];
    }
股票问题

https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iii/submissions/

最多进行两笔交易

dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])

int maxProfit(vector<int>& prices) {
        if(prices.size()==0)return 0;
        int dpi10=0,dpi20=0,dpi11=-999999,dpi21=-999999;
        for(int i=0;i<prices.size();i++)
            {
                dpi10=max(dpi10,dpi11+prices[i]);
                dpi11=max(dpi11,-prices[i]);
                dpi20=max(dpi20,dpi21+prices[i]);
                dpi21=max(dpi21,dpi10-prices[i]);
            }
            
    return dpi20;
}
青蛙过河
bool canCross(vector<int>& stones) {
        if(stones[1] != 1) return false;
        int len = stones.size();
        if(len == 2) return true;
        bool dp[len+5][1200]; //在第i个石头并且是跳j步过来的可以不?
        memset(dp,0,sizeof(dp));
        dp[1][1] = true;
        for(int i=2;i<len;i++){
            for(int j=1;j<i;j++){   //遍历前面的所有石头
                int dist = stones[i] - stones[j];  //前面的石头一定是跳dist步过来的
                if(dist > 1100) continue;
                dp[i][dist] |= dp[j][dist-1]|dp[j][dist]|dp[j][dist+1];
                if(i == len-1 && dp[i][dist])
                {
                    return true;
                }
            }
        }
        return false;
        
    
    }








接雨水

img

接雨水

img

ans[i]=min(maxleft[i],maxright[i])-height[i];

int trap(vector<int>& height) {
        if(height.size()==0)return 0;
        int ans=0;
        int n=height.size();
        vector<int>maxleft(n);
        vector<int>maxright(n);
        maxleft[0]=height[0];
        maxright[n-1]=height[n-1];
        for(int i=1;i<n;i++)
        maxleft[i]=max(maxleft[i-1],height[i]);
        for(int i=n-2;i>=0;i--)
        maxright[i]=max(maxright[i+1],height[i]);
        for(int i=1;i<n-1;i++)
            ans+=min(maxleft[i],maxright[i])-height[i];
        
        return ans;
            
    }
通配符匹配

看s是否和p匹配。

'?' 可以匹配任何单个字符。
'*' 可以匹配任意字符串(包括空字符串)。
//bool dp[i][j]表示s[i-1]和p[j-1]的匹配情况
//dp[0][0]=true
//
/*
1.若p[i - 1] == '*' ,则
dp[0][i] = dp[0][i - 1];


2.s.charAt(i - 1) == p.charAt(j - 1) || p.charAt(j - 1) == '?'
f[i][j] = f[i - 1][j - 1];


3.p.charAt(j - 1) == '*'
f[i][j] = f[i][j - 1] || f[i - 1][j];
 
*/







bool isMatch(string s, string p) {
		int n = s.size(), m = p.size();
		vector< vector<bool> > dp(n + 1, vector<bool>(m + 1, false)); 
        dp[0][0] = true;

		// initialize 
		for (int i = 1; i <= m; ++i) {
			if (p[i - 1] == '*' && dp[0][i - 1]) 
            dp[0][i] = dp[0][i - 1];
		}

		for (int i = 1; i <= n; ++i) {
			for (int j = 1; j <= m; ++j) {
				if (s[i - 1] == p[j - 1] || p[j - 1] == '?') {
					dp[i][j] = dp[i - 1][j - 1];// ismatch, move on
				}
				else if (p[j - 1] == '*') {
					dp[i][j] = dp[i][j - 1] || dp[i - 1][j];
				}
			}
		}
		return dp[n][m];
	}

二叉树

二叉搜索树与双向链表

「栈实现中序遍历」

 Node* treeToDoublyList(Node* root) {
     if(root==NULL)return NULL;
     stack<Node*>s;
      Node*head=root,*pre=NULL;
     while(root||!s.empty()){
        if(root){
            s.push(root);
            root=root->left;
        }else{
            root=s.top();
            s.pop();
            if(pre==NULL){
                head=root;
            }else{
                pre->right=root;
                root->left=pre;

            }
            pre=root;
            root=root->right;
        }
     }
     pre->right=head;
     head->left=pre;
     return head;
    }

树的子结构

判断B是不是A的子结构

bool helper(TreeNode* A, TreeNode* B) {
        if (A == NULL || B == NULL) {
            return B == NULL ? true : false;
        }
        if (A->val != B->val) {
            return false;
        }
        return helper(A->left, B->left) && helper(A->right, B->right);
    }

    bool isSubStructure(TreeNode* A, TreeNode* B) {
        if (A == NULL || B == NULL) {
            return false;
        }
        return helper(A, B) || isSubStructure(A->left, B) || isSubStructure(A->right, B);
    }

二叉树的最近公共祖先

1 p, q 分别位于 x 的左子树和右子树;return root;
2 p, q 都在 x 的左子树. return left
3 p, q 都在 x 的右子树 return right

 TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root==NULL)return NULL;
        if(root->val==p->val||root->val==q->val)
            return root;
        TreeNode* left=lowestCommonAncestor( root->left,  p,  q);
        TreeNode* right=lowestCommonAncestor( root->right,  p,  q);
        if(left&&right)return root;
        else if(left) return left;
        else if(right) return right;
        return NULL;
    }

1122344

双指针

删除排序数组中的重复项

//双指针去重

int removeDuplicates(vector<int>& nums) {
        if(nums.size()==0) return 0;
        int i=0;
        for(int j=1;j<nums.size();j++){
            if(nums[i]!=nums[j]){
                ++i;
                //i++;
                nums[i]=nums[j];
            }
        }
        
        return i+1;
    }

二分法

搜索旋转排序数组
输入: nums = [4,5,6,7,0,1,2], target = 0

对其进行二分搜索

 int search(vector<int>& nums, int target) {
        if (nums.size() == 0) {
            return -1;
        }
        int start = 0;
        int end = nums.size() - 1;
        int mid;
        while (start <= end) {
            mid = start + (end - start) / 2;
            if (nums[mid] == target) {
                return mid;
            }
            //前半部分有序,注意此处用小于等于
            if (nums[start] <= nums[mid]) {
                //target在前半部分
                if (target >= nums[start] && target < nums[mid]) {
                    end = mid - 1;
                } else {
                    start = mid + 1;
                }
            } else {
                if (target <= nums[end] && target > nums[mid]) {
                    start = mid + 1;
                } else {
                    end = mid - 1;
                }
            }

        }
        return -1;
    }

c++

static

static 存在于静态存储区

智能指针

智能指针的作 用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间

auto_ptr, shared_ptr, weak_ptr,unique_ptr

编译

预处理阶段:对源代码文件中文件包含关系(头文件)、预编译语句(宏定义)进行分析和 替换,生成预编译文件。

重载前后++

 
// 前置++i -----------先++,再调用(一致)  
UPInt& UPInt::operator++()  
{  
   *this += 1;  
   return *this;  
// 或return ++privateVal;  
}  
  
//后置 i++ -----------先调用原值,再++  
const UPInt UPInt::operator++(int)  
{  
   UPInt oldValue = *this;  
   ++(*this);  
   return oldValue;  
} 

手动写一个string

#include <iostream>
#include <string.h>
using namespace std;

class MyString
{
    public:
        MyString(const char* str = NULL)
        {
            if(str == NULL)
            {
                m_data = new char[1];
                m_data = '\0';
            }
            else
            {
                int len = strlen(str)+1;
                m_data = new char[len];
                strcpy(m_data,str);
            }
        }
        MyString (const MyString& str)
        {
            if(&str!=this)
            {
                int len = strlen(str.m_data)+1;
                delete[] m_data;
                m_data = new char[len];
                strcpy(m_data,str.m_data);
            }
        }

        MyString& operator=(const MyString& str)
	{
      if(&str!=this)
     {
           MyString strTemp(str);
                
          char * temp = strTemp.m_data;
          strTemp.m_data = m_data;
          m_data = temp;
      }
     return *this;
	}

        virtual ~MyString(){
            if(m_data!=NULL)
            {
                delete[] m_data;
                m_data = NULL;
            }
        }

    private:
        char *m_data;
};
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值