ボードを持参せずにSTLを使用する必要がある場合、コードは手動でのみ引き裂くことができます。
さらに、手書きの単語は、知識ポイントのより深い理解とゲーム中のより速い速度を示します。(私は引退しましたが)
ただし、筆記面接ではSTLを使用できない可能性が高いため、今回は基本的なアルゴリズム(メモリ)の理解度をテストします。
一般的に言えば、アルゴリズムの競争の高度なガイドに表示されるコードは引き裂かれる必要があります
基本的なアルゴリズム
二分法
- $ \ geq x $の最小値を単調増加シーケンス\(a \)で検索
while(l<r){
int mid = (l+r)>>1;/*右移运算 相当于除2并且向下取整*/
if(a[mid]>=x) r=mid;
else l=mid+1;
}
return a[l];
- 単調に増加するシーケンスで\(A \)ルックアップ\(\当量X \)の数最大の一つ
while(l<r){
int mid = (l+r+1)>>1;
if(a[mid]<=x) l=mid;
else r=mid-1;
}
return a[l];
最小の回答の回答が選択されている場合は、\(l = L、r = R + 1 \)を初期化します。回答が\(R + 1 \)の場合は見つかりません
最大の回答を満たすものを選択した場合は、\(l = L-1、r = R \)を初期化します。回答が\(L-1 \)の場合、それは見つかりません
upper_bound()
やと同様に、lower_bound()
存在しない場合は最後の添え字+1を返し、ベクトルはend()
並べ替え
バブルソート
隣接比較、右シフト大
void bubble_sort() {
for (int i = n - 1; i > 0; --i) {
bool flag = false;
for (int j = 0; j + 1 <= i; ++j)
if (a[j] > a[j + 1]) {
std::swap(a[j], a[j + 1]);
flag = true;
}
if (!flag) return ;
}
return ;
}
並べ替えを選択
最大の右シフトであるキーを選択します
void selection_sort() {
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
if(a[i] > a[j])
std::swap(a[i], a[j]);
}
}
}
ソートを挿入
手前の場合は、挿入したい位置に挿入するキーを選択
int i,j,key;
for(i = 2;i <= n;++i){
key = a[i];
j = i - 1;
while(j > 0 && a[j] > key){
a[j+1] = a[j];
j--;
}
a[j] = key;
}
クイックソート
選択されたものの間の数、左はそれより小さく、右はそれより大きい必要があり、次に再帰的な分割と征服
void quick_sort(int l, int r) {
if (l >=r ) return ;
int i = l - 1, j = r + 1, x = a[(l + r) >> 1];
while (i < j) {
do j--; while (a[j] > x);
do i++; while (a[i] < x);
if (i < j) std::swap(a[i], a[j]);
else quick_sort(l, j), quick_sort(j + 1, r);
}
}
マージソート
int merge_sort(int l, int r) {
if (l >= r) return 0;
int mid = (l + r) >> 1, res = 0;
res += merge_sort(l, mid);
res += merge_sort(mid + 1, r);
int i = l, j = mid + 1, cnt = 0;
while (i <= mid && j <= r)
if (a[i] <= a[j])
b[cnt++] = a[i++];
else {
res += mid - i + 1;
b[cnt++] = a[j++];
}
while (i <= mid) b[cnt++] = a[i++];
while (j <= r) b[cnt++] = a[j++];
for (int i = l, j = 0; j < cnt; ++i, ++j) a[i] = b[j];
return res;
}
ヒープソート
大きなルートヒープの実装:xノードは子ノードより大きくなければなりません
struct heap{
int *A;
int length;
heap(int n){
length = n;
A = new int[n+5];
for(int i=1;i<=n;++i){
cin>>A[i];
}
}
void Max_Heapify(int pos){
int l = pos*2;
int r = pos*2+1;
int largest;
if(l <= length && A[l] > A[pos]){
largest = l;
}else{
largest = pos;
}
if(r <= length && A[r] > A[largest]){
largest = r;
}
if(largest != pos){
swap(A[pos],A[largest]);
Max_Heapify(largest);
}
}
void build_max_heap(){
for(int i=length/2;i>=1;--i){
Max_Heapify(i);
}
}
void heapSort(){
build_max_heap();
int n = length;
for(int i=length;i>=2;--i){
swap(A[1],A[i]);
length--;
Max_Heapify(1);
}
for(int i=1;i<=n;++i){
cout<<A[i]<<" ";
}
}
};
ハッシュ
ただ式を覚えておいてください:
\(H(x)\):文字列のハッシュ値\(x \)
\(p \):16進数
\(p ^ x \)は、\(p \)ベースの下で\(x \)ビットだけ左にシフトされた値を意味します
コードが前処理されている限り\(f(x)\):プレフィックスハッシュ値\(p(x)\):\(p ^ x \)
多数
ほとんどのインタビュアーは、オブジェクト指向になりたいと言いましたか?読みやすいですか?
大量の追加を維持するために3つのスタックを使用します(インタビュアーは読みやすい方法だと言っています)
#include<bits/stdc++.h>
using namespace std;
class bigNum{
private:
stack<int>num1,num2,sum;
public:
char *Num;
bigNum(char *s){
Num = s;
}
bigNum* add(bigNum* &right){
for(int i=0;Num[i];++i){
num1.push(Num[i] - '0');
}
for(int i=0;right->Num[i];++i){
num2.push(right->Num[i] - '0');
}
int flag = 0,tmp,x,y;
while(!num1.empty() || !num2.empty()){
x = num1.empty() == 1 ? 0:num1.top();
y = num2.empty() == 1 ? 0:num2.top();
tmp = x + y + flag;
flag = tmp/10;
sum.push(tmp%10);
if(!num1.empty()) num1.pop();
if(!num2.empty()) num2.pop();
}
if(flag){
sum.push(flag);
}
char *ans = new char[sum.size()+1];
int pos = 0;
while(!sum.empty()){
ans[pos++] = sum.top() + '0';
sum.pop();
}
return new bigNum(ans);
}
void printNum(){
cout<<Num<<endl;
}
};
char a[150],b[150];
int main(){
cin>>a;
cin>>b;
bigNum *left = new bigNum(a);
bigNum *right = new bigNum(b);
bigNum *sum = left ->add(right);
sum ->printNum();
return 0;
}
文字列
辞書ツリー
void ins(char *str){
int len=strlen(str);
int p=0;
for(int i=0; i<len; i++){
int ch=str[i]-'a';
if(!tire[p][ch])
tire[p][ch]=++ant;
p=tire[p][ch];
}
cnt[p]++;
}
int srh(char *str){
int ans=0;
int len=strlen(str);
int p=0;
for(int i=0; i<len; i++){
int ch=str[i]-'a';
p=tire[p][ch];
if(!p) return ans;
ans+=cnt[p];
}
return ans;
}
実際、ポインターを書く方法はありますが、インタビュアーはポインターしか理解できないかもしれません
const int maxn = 26;
struct tireNode{
tireNode* next[maxn];
bool endpos;
tire(){
endpos = 0;
}
};
struct tire{
tireNode *root;
tire(){
root = new tireNode();
}
void Insert(char *str){
tireNode *now = root;
while(*str != '\0'){
int pos = *str - 'a';
if(now->next[pos] == nullptr){
now -> next[pos] = new tireNode();
}
now = now -> next[pos];
str++;
}
now -> endpos = 1;
}
bool Find(char *str){
tireNode *now = root;
while(*str != '\0'){
int pos = *str - 'a';
if(now -> next[pos] == nullptr){
return 0;
}
now = now -> next[pos];
str++;
}
return now -> endpos;
}
};
KMP
\(次[i] \):文字列\([0 ... i] \)の接頭辞と接尾辞の最大一致位置を示します。接尾辞も接尾辞もそれ自体を含めることはできません
したがって、\(Next [0] = -1 \)です。これは、最初の文字がこの最大一致位置になってはならないためです。
したがって\(I \)、1から始まる(J \)\から始まる-1
ループの過程で維持されるループ不変式によれば、$ Next [i] \(最終的には\と等しくなければなりません) j \(つまり、\) [0 ... j] \(\で最長でなければなりません) i $は、末尾のサフィックスがパターン文字列のプレフィックスと一致する文字列です。
void get_Next(char *p){
Next[0] = -1;
int i = 1,j = -1;
while(p[i] != '\0'){
while(j != -1 && p[i] != p[j+1]) j = Next[j];
if(p[i] == p[j+1])++j;
Next[i++] = j;
}
}
int kmp(char *s,char *p){
int i = 0,j = -1;
int ans = 0;
while(s[i] != '\0'){
while(j != -1 && ( p[j+1] == '\0' || s[i] != p[j+1]))j = Next[j];
if(s[i] == p[j+1])++j;
f[i++] = j;
if(p[j+1] == '\0'){
//视情况
}
}
return ans;
}
ACオートマトン
重要な点は、fafailシーケンスから空でない最初の\(tr [u] [i] \)を見つけることです。時間を節約するために、パス圧縮が使用されます。
namespace AC {
int tr[N][26], tot;
int e[N], fail[N];
void insert(char *s) {
int u = 0;
for (int i = 1; s[i]; ++i) {
if (!tr[u][s[i] - 'a']) tr[u][s[i] - 'a'] = ++tot;
u = tr[u][s[i] - 'a'];
}
e[u]++;
}
queue<int> q;
void build() {
for (int i = 0; i < 26; ++i) {
if (tr[0][i]) {
q.push(tr[0][i]);
}
}
while (!q.empty()) {
int u = q.front();
q.pop();
for (int i = 0; i < 26; ++i) {
if (tr[u][i]) {
fail[tr[u][i]] = tr[fail[u]][i];
q.push(tr[u][i]);
} else {
tr[u][i] = tr[fail[u]][i];
}
}
}
}
int query(char *t) {
int u = 0, res = 0;
for (int i = 1; t[i]; i++) {
u = tr[u][t[i] - 'a'];
for (int j = u; j && e[j] != -1; j = fail[j]) {
res += e[j], e[j] = -1;
}
}
return res;
}
}
数学
線形ふるい
採用プロパティ:素数ではない数はすべて最小の素因数を持つ
void get_prime(){
int pos = 0;
for(int i=2;i<N;++i){
if(!check[i]) {
Prime[pos++]=i;
}
for(j=0;j < pos && i*Prime[j] < N;++j){
check[i * Prime[j]] = true;
if(i % Prime[j] == 0) {
break;
}
}
}
}
おおよその数
正の除数セットを取得する複数の方法
for(int i = 1;i <= n;++i){
for(int j = 1;j <= n/i;++j){
factor[i*j].push_back(i);
}
}
単一の数の除数:除算を試してください
for(int i = 1;i * i <= n;++i){
if(n % i == 0){
factor[++m] = i;
if(i != n/i) factor[++m] = n/i;
}
}
データ構造
バランスツリー
少なくともそれは反転します。ルートノードでない場合は、Rotate
inline void Rotate(int x) {
int y = s[x].fa, z = s[y].fa, chk = get(x);
//y与x的子节点相连
s[y].ch[chk] = s[x].ch[chk ^ 1];
s[s[x].ch[chk ^ 1]].fa = y;
//x与y父子相连
s[x].ch[chk ^ 1] = y;
s[y].fa = x;
// x与y的原来的父亲z相连
s[x].fa = z;
if(z) s[z].ch[y == s[z].ch[1]] = x;
//只有x和y的sz变化了
maintain(y);
maintain(x);
}
ツリー配列
int ask(int x){
int ans = 0;
for(;x;x -= x & -x) ans += c[x];
return ans;
}
void add(int x,int y){
for(;x <= N;x += x & -x) c[x] += y;
}
ラインツリー
struct node{
int l,r;
int sum,add;//和,延迟标记
}t[maxn << 2];
void build(int p,int l,int r){//后序遍历
t[p].l = l,t[p].r;
if(l == r) {t[p].sum = s[l];return;}
int mid = (l+r) >> 1;
build(p << 1,l,mid);
build(p << 1 |1,mid + 1,r);
t[p].sum = t[p << 1].sum + t[p << 1 | 1].sum;//更新来自子结点的答案
}
void spread(int p){
if(add(p)){//如果有标记
t[p << 1].sum = t[p].add * (t[p << 1].r - t[p << 1].l + 1);
t[p << 1 | 1].sum = t[p].add * (t[p << 1 | 1].r - t[p << 1 | 1].l + 1);
t[p << 1].add += t[p].add;
t[p << 1 | 1].add += t[p].add;
t[p].add;
}
}
void change(int p,int l,int r,int d){
/*if(t[p].l == t[p].r){
t[p].sum += d;
}如果不用延迟标记
*/
if(l <= t[p].l && r >= t[p].r){
t[p].sum += d * (t[p].r - t[p].l + 1);
t[p].add += d;
return;
}
spread(p);
int mid = t[p].l + t[p].r >> 1;
if(l <= mid) change(p << 1,l,r,d);
if(r > mid) change(p << 1 | 1,l,r,d);
t[p].sum = t[p << 1].sum + t[p << 1 | 1].sum;//更新来自子结点的答案
}
long long ask(int p,int l,int r){
if(l <= t[p].l && r >= t[p].r){
return t[p].sum;
}
long long ans = 0;
int mid = (t[p].l + t[p].r) >> 1;
if(l <= mid) ans += ask(p << 1,l,r);
if(r > mid) ans += ask(p << 1|1,l,r);
return ans;
}
グラフ理論
最短dij
- 初期化d [1] = 0、残りのノードは正の無限大です
- 最小の\(d [x] \)\(x \)とマークされたノード\(x \)を持つマークされていないノードを検索します
- ノードのすべての出力エッジをスキャンします\(x \)\((x、y、z)\)、\(d [y]> d [x] + z \)の場合、\(d [x] + z \)更新\(y \)
memset(d,inf,sizeof(d));
d[1] = 0;
q.push(make_pair(0,1));
while(!q.empty()){
int x = q.top().second;
q.pop();
if(v[x]) continue;
v[x] = 1;
for(int i=head[x];i;i=e[i].next){
int y = e[i].to;
int z = e[i].val;
if(d[y] > d[x] + z){
d[y] = d[x] + z;
q.push(make_pair(-d[y],y));
}
}
}
最小全域木
- セットを確立してチェックし、各ポイントがセットを構成します
- すべてのエッジを重みの小さいものから大きいものへと並べ替え、各エッジを順番にスキャンします\((x、y、z)\)
- \(X、Y \) 、このエッジを無視し、同じセット(通信)に属します
- それ以外の場合は、\(x、y \)があるセットをマージし、答えに\(z \)を追加します
int get(int x){
if (x == fa[x]) return x;
return fa[x] = get(fa[x]);
}
void solve(){
for(int i=1;i<=n;++i) fa[i] = i;//1
sort(e + 1,e + 1 + m,cmp);//2
int ans = 0;
for(int i = 1;i <= m;++i){
int x = get(e[i].x);
int y = get(e[i].y);
if(x != y){
fa[x] = y;
ans += e[i].z;
}
}
}
ハンガリーのアルゴリズム
bool dfs(int x){
for(int i=head[x];i;i=e[i].next) {
if(!vis[y = e[i].to]){
vis[y] = 1;
if(!match[y] || dfs(match[y])) {
match[y] = x;
return true;
}
}
}
}
for(int i = 1;i <= n; ++i){
memset(vis,0,sizeof(vis));
if (dfs(i)) {ans++;}
}