ACM经验总结(09/21 version02)

头文件

#include <iostream>
#include <string>
#include <cstring>
#include <cmath>
#include <sstream>
#include <iomanip>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <map>
#include <functional>
#include <queue>
#include <cctype>
#include <climits>
#include <stack>
using namespace std;

常用函数

位运算

  • 左移。将二进制左移,右边补0

    • 1 << N 相当于 2 N 2^N 2N
  • 异或。位不同为1,位相同为0。

    • a ^ b ^ c = a ^ c ^ b:交换律
    • x ^ 0 = x:任何数和0异或为其本身
    • x ^ x = 0:相同的数异或为0

快速排序

sort(v.begin(),v.end(),cmp)

  • v是迭代器,如果只想将一部分排序,可以使用类似sort(v,v+3)
  • cmp为比较函数,默认是升序排序,也可以重载进行自定义。return true时x排在y前面。
bool cmp(const int& x, const int& y)
{
  return x > y;
}
int main()
{
  vector<int> vec{ 1,3,2 };
  sort(vec.begin(), vec.end(), cmp);
  cout << vec[0] << " " << vec[1] << " " << vec[2];//3 2 1
  return 0;
}

小贴士

  • 'c'-'a''7'-'0'用来表示字符的次序。'a'<= ch <='z'用来判断为小写字母。ch+('A'-'a')用来小写变大写
  • 线性时间复杂度遍历两次三次都没关系,所以可以第一次收集频率,第二次收集位置等等
  • 使用字符串流来按行读取输入
  • int格式的数据取值范围为 [ − 2 31 , 2 31 − 1 ] [-2^{31},2^{31}-1] [231,2311],这两个界可以分别用常量INT_MININT_MAX表示(需加上头文件#include <climits>
  • 链表最好一直带一个头节点(不存储数据的结点)。如果给出的链表没有,可以自己new一个,然后NewNode->next=head,也就是说,如果head可能被删除,仍然可以用NewNode->next表示第一个数据

Cctype

包含很多字符串处理函数的库。

#include <cctype>

检测

isalnum(ch);//是否为字母数字
isalpha(ch);//是否为字母
islower(ch);//是否为小写字母
isupper(ch);//是否为大写字母
isdigit(ch);//是否为数字

转换

注意:非字母的字符不会处理。

tolower(ch);//转为小写字母
toupper(ch);//转为大写字母

String

字符串。

#include <string>

string str;

基本操作

初始化

遍历

for(int i=0; i<str.length(); i++)
{
  char c=str[i];
}

元素编辑

进阶操作

  • 字符串所有小写字母变为大写
    transform(str.begin(),str.end(),str.begin(),toupper);
  • 字符串所有大写字母变为小写
    transform(str.begin(),str.end(),str.begin(),tolower);

Vector

迭代器,可以充当动态数组

#include <vector>

vector<char> s;

基本操作

初始化

vector<char> s1(3,'a');//['a','a','a']
vector<char> s2{ 'a','b','c' };//['a','b','c']
vector<char> s3(3);//['\0','\0','\0']

遍历

for(int i=0; i<s.size() ;i++)
{
    char c = (*it);
}

vector<char>::iterator it;
for (it = s.begin(); it != s.end(); it++)
{
    char c = (*it);
}

元素编辑

int l=s.size();//返回元素个数
char c=s[2];//类似数组的存取方法
s.push_back('A');//在尾部增加元素
s.pop_back();//删除尾部元素

Map

散列表。map由红黑树构建,自动会排序键,适合排序题,unordered则适合查询题,省去排序时间。

#include <map>

map<string,int> m;
unordered_map<int,int> mm;

基本操作

初始化

vector<char> s1(3,'a');//['a','a','a']
vector<char> s2{ 'a','b','c' };//['a','b','c']
vector<char> s3(3);//['\0','\0','\0']

遍历

map<string,int>::iterator it;
for(it = m.begin(); it!=m.end(); ++it)
{
  cout<< it->first << " " << it->second << endl;
}

元素编辑

int l=m.size();//返回元素个数
m["a"]=1;//如果存在,则赋值;如果不存在,则会自动增加键值对。
m.erase("a");//删除该键
if(m.count("a"))//count只会是0(不存在)或1(存在),因为键不会重复

递归

将问题分解为越来越小的子问题,调用递归函数递归地解决。对于数学问题通常要写出递归式

基础案例

阶乘

$$
f(n)=\left { \begin{matrix}
1&n=1\
n\cdot f(n-1)&n>1

\end{matrix} \right.
$$

  • 递归出口:n=1 时,f=1
  • 递归传递:返回参数 n 的值
  • 单次递归内的过程:用 n 乘以“递归传递”的返回值
int fact(int n)
{
    if (n <= 1) return 1;
    return n * fact(n - 1);
}

两两交换链表节点

题目链接

  • 递归出口:headhead->next为空指针
  • 递归传递:返回交换后的链表头指针
  • 单次递归内的过程:先将head指向“递归传递”的返回值,再将head->next指回head
ListNode* swapPairs(ListNode* head) {
  if (head == NULL || head->next == NULL) {
    return head;
  }
  ListNode* next = head->next;
  head->next = swapPairs(next->next);
  next->next = head;
  return next;
}

DFS

一种先进后出的数据结构。可以理解为只在尾部进行增删操作。

#include <stack>

stack<int> S;

基本操作

S.push(1);//元素插入到尾部
int a=S.top();//获取尾部元素的值
S.pop();//删除尾部元素
int len=S.size();//获取栈长度
if(S.empty())//判断栈是否为空

链表

一种链式存储的数据结构。

struct ListNode {
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};

快慢指针

题目链接

题目链接

声明两个指针slowfast,让他们以不同的方式遍历链表。

  • 起跑线不同:slowfast的初始结点不同,步长相同。这样遍历时fast始终领先于slowN个节点
ListNode *slow = head;
ListNode *fast = slow;
while (N--)
{
  fast = fast->next;
}
//开始遍历
  • 步长不同:slowfast的初始结点相同,步长不同。这样遍历时fast会越来越快
ListNode *slow = head;
ListNode *fast = slow->next;
//开始遍历
while (fast != NULL)
{
  slow = slow->next;
  fast = fast->next==NULL ? fast->next : fast->next->next;
}

一种非线性存储的数据结构。树的大家族中最常用的就是二叉树了:

struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

建立二叉树

  • 按根-左-右输入顺序建立
TreeNode* InitTree()
{
  int val;
  TreeNode* root;
  cin>>val;
  if(val==0) return NULL;
  else
  {
    root=new TreeNode(val);
    root->left=InitTree();
    root->right=InitTree();
  }
  return root;
}

TreeNode* root=InitTree();

遍历二叉树

因为二叉树的特殊结构,所以递归是一个很好的遍历方式:

void XXXOrder(TreeNode* T)
{
  if(T!=NULL)
  {
    visit(T);//根
    XXXOrder(T->left);//左
    XXXOrder(T->right);//右
  }
}

只要交换代码的顺序就能轻易实现先序、中序、后序遍历。
但是递归容易产生较高的复杂度,因此常常写成借助栈的非递归形式。非递归形式的难点是难以记忆,但其实本质上的原理还是一样的:

void XXXOrder(TreeNode* T)
{
  map<TreeNode*,int> visitTimes;//键不存在代表未访问过,存在代表已压栈过
  stack<TreeNode*> S;
  TreeNode* CurrNode;
  S.push(T);
  while(!S.empty())
  {
    CurrNode=S.top();
    S.pop();
    if(CurrNode==NULL) continue;
    //这是第一次访问根,需要按照所求遍历(如右左根)相反的方向压栈
    if(visitTimes.count(CurrNode)==0)
    {
      visitTimes[CurrNode]=0;S.push(CurrNode);//根
      S.push(CurrNode->left));//左
      S.push(CurrNode->right));//右
    }
    //这是第二次访问,需要进行输出
    else
    {
      visit(CurrNode);
    }
  }
}

注意这种写法C++并不能取得较高的效率.

先序遍历

顺序是 根节点>左子树>右子树。

  • 遇到非空结点就访问。并把左子树作为下一个访问对象
  • 如果结点为空,说明根结点与左子树访问完毕。取出栈顶结点,去访问他的右子树。
void PreOrder(TreeNode *T)
{
  stack<TreeNode *> S;
  TreeNode* p = T;
  while (p!=NULL || !S.empty())
  {
    if (p!=NULL)
    {
      //1.Root
      int v=p->val; //visit p
      S.push(p);
      p = p->left; //2.Left
    }
    else
    {
      p=S.top();
      S.pop();//退栈
      p = p->right; //3.Right
    }
  }
}

中序遍历

顺序是 左子树>根节点>右子树。

  • 遇到非空结点就先入栈。保存住根节点的信息。然后先访问左子树。
  • 如果结点为空,说明栈顶存的是最左边未访问的子树了。此时取出栈顶结点,进行访问。然后再访问右子树
void InOrder(TreeNode *T)
{
  stack<TreeNode *> S; //Save Roots
  TreeNode *p = T;
  while (p != NULL || !S.empty())
  {
    if (p != NULL)
    {
      S.push(p);
      p = p->left; //1.Left
    }
    else
    {
      p = S.top(); //2.Root
      S.pop();
      int v = p->val; //visit p
      p = p->right;   //3.Right
    }
  }
}

后序遍历

顺序是 左子树>右子树>根节点。注意,空树需要单独判断,因为先访问子树。关于后序遍历有较多的非递归解法:

  • 迭代:只有当上一个访问的节点是当前节点的子节点时,才说明当前节点左右子树都访问完毕,才可以访问当前结点并出栈。不然就按右子树>左子树的顺序入栈。
  • 逆向输出。将先序遍历访问结点的顺序改为根>右>左,然后逆向输出,即为左>右>根。这个方法需要两个栈。
void PostOrder(TreeNode *T)
{
  stack<TreeNode *> S;
  stack<int> result;
  TreeNode* p = T;
  while (p!=NULL || !S.empty())
  {
    if (p!=NULL)
    {
      //1.Root
      int v=p->val; //visit p
      result.push(v);
      S.push(p);
      p = p->right; //2.Right
    }
    else
    {
      p=S.top();
      S.pop();//退栈
      p = p->left; //3.Left
    }
  }
}

队列

BFS

KMP匹配算法

例题与答案

题目链接

详解链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值