抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

我看了看手上的牌,明白自己无牌可出

很久没有写博客了,最近也是换了电脑,也就把博客迁移过来顺便写一篇博客吧

前言

最近也是金九银十的秋招季,投出去的简历也是陆陆续续受到了答复,笔试自己也要开始来准备了,总之好好抓紧吧。

笔试中,算法毫无疑问是一个大头,很多东西可能不考,唯独算法一定会考,要过第一趟笔试,算法是无法避免的,很多东西还是得多多规划总结,才没那么容易忘,所以还是写写好。

如何入手

当你拿到一道题目,首先应该按顺序干如下几件事:

  1. 看懂题目在说什么

  2. 搞明白题目需要你做什么

  3. 找到思路

  4. 写代码

某种程度上说,并不是完全按照步骤一步步做,因为从其他步骤也可能更好地入手,对这几方面大概探一下吧

题目在说什么

对于算法,我们大多数时候需要的是针对输入做一个输出,所以这个问题可以被简化为:

  1. 输入

  2. 输出

  3. 条件

重点关注以上三点就可以理解题目在说什么了

需要做什么

在明白输入输出条件后,我们还需要明白题目需要做什么,也就是所谓操作,一般来说分成如下两种:

  1. 数据处理:将抽象的数据转换为算法能够进行处理的数据结构

  2. 搜索求解:寻找问题的答案

找到思路

这一步骤可谓是最难的了,基本上也是这部分才是算法真正考验的地方,我还是大体来说一下这个思路怎么出来的过程吧(我自己的经验)

  1. 模拟:模拟问题的操作来求出结果,一般在小规模问题下做一遍也更能理解题目含义

  2. 可行解:是否能有暴力的方法能够求出,还是需要做其他文章

  3. 复杂度分析:数据总共有多少,如何去遍历,真正笔试时候还是要以求解优先,但是对于问题求解的规模是怎么样的也更能辅助你获得答案

  4. 确认算法:以上三步服务于这一步

  5. 实验:在小规模问题下实验思路的可行性

写代码

能用模板多用模板,不仅加快速度,更重要的是降低失误,对于像二分查找这种容易搞混的东西,在考场才研究+1-1这种细枝末节的东西往往花费时间,更搞乱了自己的心态,所以大体来说还是需要让自己做的更胸有成竹一些,写代码的步骤大概就是这样,少点犹豫。

  1. 输入处理成数据结构

  2. 套模板

  3. 输出

题型的模板究竟都有那些,主流的哪几类算法是怎么运作的,接下来就会大概谈谈。

算法及其模板

DFS

使用递归的回溯法

class Node{
    int val;
    vector<Node> childNodes;
}

void DFS(Node node, int steps, int n)
{
    if(steps == n) return;
    for(Node child : node.childNodes)
    {
        // 做操作
        DFS(child, steps + 1, n);  
        // 撤销操作
    }
}

BFS

相较于DFS,一般能算出最优,而且不依赖递归,更容易debug找问题,不过如果是需要记录之前的状态,它会比DFS要难写不少(需要保存)

class Node{
    int val;
    vector<Node> childNodes;
}

void BFS(Node root)
{
    queue<Node> q;
    q.push(root);
    while(!q.empty())
    {
        // 获取队头
        Node node = q.front();
        q.pop();

        for(Node child : node.childNodes)
        {
            q.push(child);
        }
    }
}

上述两种算法都可以用备忘录来优化

滑动窗口算法

因为不怎么用经常忘掉,大体来说是维护窗口,一般这类题目的特征是当序列的子串不满足条件时,序列一定不满足条件,而一般题目目的是寻找最大窗口

void SlidingWindow(string s, string t)
{
    unordered_map<char, int> need, window;
    for (char c : t) need[c]++;

    int left = 0, right = 0;
    int valid = 0;
    while(right < s.size())
    {
        char c = s[right];
        right++;
        // 更新窗口数据

        // 判断窗口左边是否需要收缩
        while (window need shrink)
        {
            char c = s[left];
            left++;
            // 更新窗口
        }
    }
}

不要倒在起点

与力扣不同,笔试的环境不一定足够好(比如不允许使用自己的IDE),总之还是需要做好一些准备的

特殊输入处理

不带次数直到结束

while(cin >> n);

根据每行获得输入

#include<iostream>
#include<sstream>
#include<string>
using namespace std;

int main()
{
    string line;
    while(getline(cin, line))
    {
        stringstrem ss;
        ss << line;
        int tmp;
        while(ss >> tmp);
    }
}

模板类的使用

vector

构造

vector<T> v;
vector<T> v(n); // 数量为n
vector<T> v(n, val); // 数量为n,值为val

插入

v.push_back(a); // 尾部添加
v.insert(it, a); // 在迭代器的前一个位置添加

删除

v.pop_back(); // 尾部删除
v.erase(it); // 删除迭代指向
v.clear(); // 清空

常用的指向

v.front(); // 返回首元素引用
v.back(); // 返回尾元素引用
v.begin(); // 迭代器指向首元素
v.end(); // 迭代器指向

增加删除vector中下标为i的元素

v.insert(v.begin() + i, num); // 插入num
v.erase(v.begin() + i); // 删除

其他

v.size(); // 数组大小

unordered_set

初始化

unordered_set<T> set; // T不能用vector但可以用string

当你需要存储vector时可以如此操作

unordered_set<string> set;
string str = "";
for(auto a: v)
{
    str += to_string(a) + "-";
}
set.emplace(str);

添加元素

set.emplace(val);
set.insert(val); // 效率低点

查找该元素个数

set.count(key);

查找该元素引用(先确定元素个数不为0)

set.find(key);

删除元素

set.erase(val);

遍历元素

for(auto element: set)
{
    cout << element << endl;
}

其他

排序

sort从小到大排序

#include<algorithm>

vector<int> v;
sort(v.begin(), v.end()); // 升序

从大到小

sort(v.begin(), v.end(), greater<int>()); // 降序

自定义

// 根据第二个元素从小到大
bool cmp(vector<int> a, vector<int> b)
{
    return a[1] < b[1];
}

vector<vector<int>> nums;
sort(nums.begin(), nums.end(), cmp);

在类中,cmp函数要定义为static

class Solution{
public:
    static bool cmp(string a, string b)
    {
        return a.length() < b.length(); // 字符串长度升序排序
    }
    
    void Solve(vector<string>strs)
    {
        sort(strs.begin(), strs.end(), cmp);
    }
}

评论