人群中多看了一眼

来源:互联网 发布:gta5男角色捏脸数据 编辑:程序博客网 时间:2024/04/28 20:36

数据结构设计

1.设计一个魔方(六面)的程序
思路:
把魔方从正面看展开成一个平面,如图1所示。设计一个类,其中Spacexy[SPACE][LEN][LEN];中的SPACE为0~5表示六个面,每个数字代表哪一面见图1.LEN为0~2,[LEN][LEN]表示某个面的3*3的9个格子。

类中的方法是根据展开的平面设计的,具体的某个面的某个格子由Spacexy[SPACE][LEN][LEN];定位。

注: [LEN][LEN] = [i][j] ;i, j按照矩阵的方式取
这里写图片描述

实现:

#include<iostream>using namespace std;class MagicCube{    private:        enum{LEN = 3, SPACE = 6};        enum color{red, yellow, black, blue, green, purple};        enum color Spacexy[SPACE][LEN][LEN];    public:        MagicCube();        ~MagicCube(){ };        void LeftRotate(int j);        void UpRoate(int i);        void PrintCube(); }; void MagicCube::UpRoate(int j){    color tmp[LEN];    for(int i=0;i<LEN;i++)        tmp[i] = Spacexy[0][i][j];    // 0    for(int i=0;i<LEN;i++)        Spacexy[0][i][j] = Spacexy[5][i][j];    // 5    for(int i=0;i<LEN;i++)        Spacexy[5]][i][j] = Spacexy[2][LEN-1-i][j];    // 2    for(int i=0;i<LEN;i++)        Spacexy[2][LEN-1-i][j] = Spacexy[4][i][j];    // 4    for(int i=0;i<LEN;i++)        Spacexy[4][i][j] = tmp[i];}void MagicCube::LeftRotate(int i){    color tmp[LEN];    for(int j=0;j<LEN;j++)        tmp[j] = Spacexy[0][i][j];    // 0    for(int j=0;j<LEN;j++)        Spacexy[0][i][j] = Spacexy[1][i][j];    // 1    for(int j=0;j<LEN;j++)        Spacexy[1][i][j] = Spacexy[2][i][j];    // 2    for(int j=0;j<LEN;j++)        Spacexy[2][i][j] = Spacexy[3][i][j];    // 3    for(int j=0;i<LEN;j++)        Spacexy[3][i][j] = tmp[j]; }

算法

树结构

二叉树

1.二叉树层次遍历
题目:
输入一颗二元树,从上往下按层打印树的每个结点,同一层中按照从左往右的顺序打印。

     8    / \   6   10  / \  / \ 5   79   11

输出:
8 6 10 5 7 9 11

思路:
用队列,出队打印,并压入左右儿子。

实现:

struct Node{    int data;    Node* left;    Node* right;}void levelPrint(Node* root){    if(root==NULL) return;    queue<Node*> record;    record.push(root);    while(!record.empty()){        cout<<record.front()->data<<' ';        if(record.front()->left!=NULL)            record.push(record.front()->left);        if(record.front()->right!=NULL)            record.push(record.front()->right);        record.pop();    }    cout<<endl;}

2.镜像二叉树
题目:
请完成一个函数,输入一个二叉树,该函数输出它的镜像。

// 原二叉树     8    / \   6   10  / \  / \ 5   79   11// 镜像二叉树     8    / \   10   6  / \  / \ 11  97   5

二叉树结点的定义如下:

struct BinaryTreeNode  {      int data;      BinaryTreeNode *Left;      BinaryTreeNode *Right;  };  

思路:
递归思路:
把左子树和右子树变成镜像子树后,在把左右子树的root相互交换。

迭代思路:
类似层次遍历那种思路,不过用的是栈

实现:

// 递归实现void Mirror(BinaryTreeNode *root){    if(root==NULL) return;    Mirror(root->Left);    Mirror(root->Right);    BinaryTreeNode *tmp = root->Left;    root->Left = root->Right;    root->Right = tmp;}
// 迭代实现// 这种相当于先序遍历镜像树void Mirror(BinaryTreeNode *root){    stack<Node*> record;    if(root!=NULL)        record.push(root);    while(!record.empty()){        Node* tmproot = record.top();        record.pop();        if (tmproot->Left!=NULL)            record.push(tmproot->Left);        if (tmproot->Right!=NULL)            record.push(tmproot->Right);        Node* tmp = tmproot->Left;        tmproot->Left = tmproot->Right;        tmproot->Right = tmp;    }}

补充:
这个要写个测试样例。

3.求二叉树中结点的最大距离
题目:
如果我们把二叉树堪称一个图,父子结点之间的连线看成是双向的。我们姑且定义“距离”为两个结点之间的边的个树。求一棵二叉树中相距最远的两个结点之间的边的个树。

思路:
可以用递归的思路去做,设父结点为F,左子树为L,右子树为R,则树F相距最远无非有这三种可能:

  1. 树L相距最远
  2. 树R相距最远
  3. 树L到结点F的最长距离+1+树R到结点F的最长距离

实现:

struct BinaryTreeNode  {      int data;      BinaryTreeNode *Left;      BinaryTreeNode *Right;  };int TreeMaxLength(BinaryTreeNode *root, int &depth){    int ld, rd;    if(root==NULL){        depth = 0;        return 0;    }    lm = TreeMaxLength(root->Left, ld);    rm = TreeMaxLength(root->Right, rd);    depth = (ld>rd?ld:rd)+1;    // bridge是边的条数而不是结点的个数    int bridge = ld+rd;    int tmp = lm>rm?lm:rm;    return (tmp>bridge?tmp:bridge);}

4.从数组生成一棵二叉树
实现:

#include<iostream>using namespace std;struct TreeNode  {    TreeNode *left;    TreeNode *right;    int val;    TreeNode(int x=0)     : val(x), left(NULL), right(NULL){}  };  TreeNode* CreateBinaryTree(int a[], int i, int n){    // 数组中的值-1代表结点null    if(i>n-1 || a[i]==-1) return NULL;    TreeNode *p = new TreeNode(a[i]);    p->left = CreateBinaryTree(a, i*2+1, n);    p->right = CreateBinaryTree(a, i*2+2, n);    return p;}void Destory(TreeNode *root){    if(root==NULL) return ;    Destory(root->left);    Destory(root->right);    delete root;}

5.打印一棵二叉树
实现:

#include <cmath>  #include <iostream>  #include <vector>  using namespace std;//using std::vector;  //using std::cout;  //using std::endl;  //using std::max;  void PrintBinaryTree(TreeNode *root);  struct TreeNode  {    TreeNode *left;    TreeNode *right;    int val;    TreeNode(int x=0)     : val(x), left(NULL), right(NULL){}  };  static int MaxLevel(TreeNode *root)  {    if(root == NULL) return 0;    return max(MaxLevel(root->left), MaxLevel(root->right)) + 1;  }  // test whether all elements in vector are NULL  static bool IsAllElementsNULL(const vector<TreeNode*> &nodes)  {    vector<TreeNode*>::const_iterator it = nodes.begin();    while(it != nodes.end()){      if(*it) return false;       ++it;    }    return true;  }  static void PrintWhiteSpaces(int num)  {    for(int i=0; i<num; ++i)      cout << " ";  }  void PrintNode(vector<TreeNode*> &nodes, int level, int max_level)  {    if(nodes.empty() || IsAllElementsNULL(nodes)) return; // exit    int floor = max_level - level;    int endge_lines = 1 << (max(floor-1, 0));    int first_spaces = (1 << floor) - 1;    int between_spaces = (1 << (floor+1)) - 1;    PrintWhiteSpaces(first_spaces);    // print the 'level' level     vector<TreeNode*> new_nodes;    vector<TreeNode*>::const_iterator it = nodes.begin();    for(; it != nodes.end(); ++it){      if(*it != NULL){        cout << (*it)->val;        new_nodes.push_back((*it)->left);        new_nodes.push_back((*it)->right);      }      else{        new_nodes.push_back(NULL);        new_nodes.push_back(NULL);        cout << " ";      }      PrintWhiteSpaces(between_spaces);    }    cout << endl;    // print the following /s and \s    for(int i=1; i<= endge_lines; ++i){      for(int j=0; j<nodes.size(); ++j){        PrintWhiteSpaces(first_spaces - i);        if(nodes[j] == NULL){          PrintWhiteSpaces(endge_lines + endge_lines + i + 1);          continue;        }        if(nodes[j]->left != NULL)          cout << "/";        else          PrintWhiteSpaces(1);        PrintWhiteSpaces(i+i-1);        if(nodes[j]->right != NULL)          cout << "\\";        else          PrintWhiteSpaces(1);        PrintWhiteSpaces(endge_lines + endge_lines - i);      }      cout << endl;    }    PrintNode(new_nodes, level+1, max_level);  }  // wrapper function  void PrintBinaryTree(TreeNode *root)  {    int max_level = MaxLevel(root);    vector<TreeNode*> nodes;    nodes.push_back(root);    PrintNode(nodes, 1, max_level);  }int main()  {    TreeNode *root(NULL);    root = new TreeNode(1);    root->left = new TreeNode(2);    root->right = new TreeNode(3);    root->left->left = new TreeNode(4);    root->right->right = new TreeNode(7);    root->left->left->left = new TreeNode(8);    root->left->left->right = new TreeNode(9);    PrintBinaryTree(root);    // root = Destroy(root);    return 0;  }  

6.非递归实现二叉树的前序中序后序遍历
实现:
递归前序遍历

void preorderRecursive(TreeNode *node){    if(node == NULL) return;    visit(node);    preorderRecursive(node->left);    preorderRecursive(node->right);}

非递归前序遍历,用栈

void preorderRecursive(TreeNode *node){    stack<TreeNode*> s;    if(node!=NULL) s.push(node);    while(!s.empty()){        TreeNode *tmp = s.top();        s.pop();        visit(tmp);            if(tmp->left!=NULL) s.push(tmp->left);        if(tmp->right!=NULL) s.push(tmp->right);    }}

非递归中序遍历,没有左结点,则对结点进行访问

void inorderRecursive(TreeNode *node){    stack<TreeNode*> s;    TreeNode *tmp = node;    while(!s.empty() || tmp!=NULL){        if(tmp!=NULL){            s.push(tmp);            tmp = tmp->left;        }        else{            tmp = s.top();            s.pop();            visit(tmp);              tmp = tmp->right;        }    }}

非递归后序遍历,利用非递归前序遍历进行转化

void postorderRecursive(TreeNode *node){    stack<TreeNode *> sTraverse, sVisit;    if(node!=NULL) sTraverse.push(node);    while(!sTraverse.empty()){        Node *tmp = sTraverse.top();        sTraverse.pop();        sVisit.push(tmp);        // 先右结点        if(tmp->right != NULL) tmp = tmp->right;        // 后左结点        if(tmp->left != NULL) tmp = tmp->left;    }    while(!sVisit.empty()){        visit(sVisit.top());        sVisit.pop();    }}

二叉查找树

1.队伍晋级
题目:
n 支队伍比赛,分别编号为 0, 1, 2, …, n-1,已知它们之间的实力对比关系,
存储在一个二维数组 w[n][n]中,w[i][j] 的值代表编号为 i,j 的队伍中更强的一支。
所以 w[i][j]=i 或者 j,现在给出它们的出场顺序,并存储在数组 order[n]中,
比如 order[n] = {4, 3, 5, 8, 1……},那么第一轮比赛就是 4 对 3, 5 对 8,…….
胜者晋级,败者淘汰,同一轮淘汰的所有队伍排名不再细分,即可以随便排,
下一轮由上一轮的胜者按照顺序,再依次两两比,比如可能是 4 对 5,直至出现第一名
编程实现,给出二维数组 w,一维数组 order 和用于输出比赛名次的数组 result[n],
求出 result。

思路:
就是用target追踪最近的一个空位,把胜利者移动到空位上(与原来target位置的元素交换交换)。然后递归就ok了。

实现:

#include<iostream>#include<vector>using namespace std;void swap(int *target, int i, int j){    if(i==j) return;    int tmp = target[i];    target[i] = target[j];    target[j] = tmp;}void finalResult(int *result, int n, int **w){    if(n==1) return;    int trace = 0;    int win;    for(int i=0;i<n;i+=2){        if(i==n-1)            win = i;        else            win = (w[result[i]][result[i+1]]==result[i])? i:i+1;        swap(result, trace, win);        trace++;    }    finalResult(result, trace, w);}int main(){    int n;    cin>>n;    int *test = new int[n];    for(int i=0;i<n;i++){        cin>>test[i];    }    int **w = new int*[n];    for(int i=0;i<n;i++){        w[i]  = new int[n];    }    for(int i=0;i<n;i++){        for(int j=0;j<n;j++){            cin>> w[i][j];        }    }    finalResult(test, n, w);    cout<<"Result:"<<endl;    for(int i=0;i<4;i++){        cout<<test[i]<<" ";    }    cout<<endl;    delete []test;    for(int i=0;i<n;i++)        delete []w[i];    delete []w;}

其他:
no-copying merge: merge order to result, then merge the first half from order, and so on.
in place matrix rotation: rotate 01, 23, …, 2k/2k+1 to 02…2k, 1, 3, … 2k+1…

2.矩阵逆时针旋转90度(Inplace rotate square matrix by 90 degrees)
题目:
Given an square matrix, turn it by 90 degrees in anti-clockwise direction without using any extra space.

例子:

Input 1  2  3 4  5  6 7  8  9Output: 3  6  9  2  5  8  1  4  7 Input: 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 Output: 4  8 12 16  3  7 11 15  2  6 10 14  1  5  9 13

思路:

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 First Cycle: 1  (2)   3   4 (5)          (8)  9           12 13  14  (15) 16Second Cycle:(6)   710  (11)

实现:

void rotateMatrix(int **m, int n){    // head代表第几层    for(int head=0;head<n/2;head++){        // round追踪圈上的数        // n/2为那一层的长度        for(int round = head;round<n/2-1;round++){            int tmp = m[head][round];            m[head][round] = m[round][n-1-head];            m[round][n-1-head] = m[n-1-head][n-1-round];            m[n-1-head][n-1-round] = m[n-1-round][head];            m[n-1-round][head] = tmp;        }    }}

3.从数据流中随机去m个数
题目:
有一个很大很大的输入流,大到没有存储器可以将其存储下来,且只输入一次,如何从这个输入流中随机取得m个记录。

思路:
算法(数据流共有n个数据):
If m >= n, just keep it.
For m < n, generate a random number R=rand(n) in [0, n), replace a[R] with new number if R falls in [0, m).

我们可以证明算法的可行性,当m < n时,我们考虑第i个数据被选上的概率
a) i<=m时,概率为i不被第m+1, …, n个数据所替换的概率,即

p(i)=(1mm+11m)(1mm+21m)...(1mn1m)=mn

b) i>m时,概率为i被换进去且不被第i+1, …, n个数据所替换的概率,即
p(i)=mi(1mi+11m)...(1mn1m)=mn

综上,随机性可以保证!

优先队列

字典树

Union Find

线段树

线性结构

链表

1.判断两个链表是否相交(链表可能有环)
题目:
给出两个单向链表的头指针,比如h1、h2,判断链表是否相交,如果不相交返回NULL;如果相交,返回指向第一个相交节点的指针;时间复杂度控制在O(n)的前提下。

思路:
http://www.cnblogs.com/xuhj001/p/3389142.html
http://blog.csdn.net/yff1030/article/details/8587217
http://blog.csdn.net/mydreamremindme/article/details/10951297/

2.逆转单链表

3.合并两个有序链表
题目:
有两个升序链表h1, h2,返回h1以及h2合并后的有序链表

实现:

struct Node{    int e;    Node* next;};Node *merge(Node *h1, Node *h2){    if(h1 == NULL) return h2;    if(h2 == NULL) return h1;    Node *head;    if(h1->e>h2->e)        head = h2;    else{        head = h1;        h1 = h2;    }    Node *pretmph = head;    Node *tmph = head->next;    Node *tmp1 = h1;    while(tmp1!=NULL){        while(tmph != NULL && tmph->e<tmp1->e){            pretmph = tmph            tmph = tmph->next        }        pretmph->next = tmp1;        // 交换tmp1以及tmph        tmp1 = tmph;        tmph = pretmph->next;    }    return head;}

4.输出链表的倒数第k个结点
题目:
输入一个单向链表,输出该链表中倒数第k个结点。链表的倒数第0个结点为链表的尾指针。

思路:
维护两个间隔为k的指针,k…0,遍历到链表尾结点

实现:

Node* lastK(Node *head, int k){    Node *pre = head;    Node *mark = head;    //保证mark与pre之间间隔k个结点,所以mark为NULL(-1)时,pre(k)刚刚好指向倒数第k个。    for(int i=0; i<k+1; i++){        if(mark==NULL)            return NULL;        mark = mark->next;    }    while(mark!=NULL){        mark = mark->next;        pre = pre->next;    }    return pre;}

哈希表

1.判断数组中是否存在两数之和相等的情况
题目:
Given an array a[] of n integers, the 4-SUM problem is to determine if there exist distinct indices i, j, k, and l such that a[i]+a[j] = a[k]+a[l]. Design an algorithm for the 4-SUM problem that takes time proportional to n2(under suitable technical assumptions).

思路:
把数组的两两和存在哈希表中(共n(n1)/2种可能)

实现:

#include<unordered_set>using namespace std;bool isExist(int a[], int n){    unordered_set<int> record;    for(int i=0;i<n;i++)        for(int j=i+1;j<n;j++){                //判断set中是否包含值i+j            if(record.find(i+j) != record.end())                return true;            else                record.insert(i+j);        }    return false;}

2.Given an array of integers, return indices of the two numbers such that they add up to a specific target.
Example:
Given nums = [2, 7, 11, 15], target = 9,
Becasuse nums[0]+nums[1] = 2 + 7 =9,
return [0, 1].

思路:
遇到num(2)就把key = target-num(7), value = 0插入字典,遇到num(7)发现字典中有key = 7的项,所以return [0, 1]

实现:

def twoSum(nums, target):    diff = {}    for i, num in enumerate(nums):        if num in diff:            return (diff[num], i)        else:            diff[target-num] = i    return ()

3.Suppose that cows of the same breed get into an argument …..
题目:
Example: If we write a function crowded_cows(cow_list, k), we have
crowded_cows([7, 3, 4, 2, 3, 4], 3) == 4 (3和另外一个3间隔少于等于4头牛,所以牛3的群有2个;牛4同理,但它出现比较后,所以输出的是牛4)
crowded_cows([7, 3, 4, 2, 3, 10, 4], 3) == 4 (3和另外一个3间隔少于等于3头牛,所以牛3的群有2个;牛4同理,但它出现比较后,所以输出的是牛4)
crowded_cows([7, 3, 1, 0, 4, 2, 16, 28, 3, 4], 3) == -1

思路:

实现:

def crowded_cows(cows, interval):    # record[cowId] = [index, num]    record = {}    # maxcow: [maxIndex, maxNum]    maxcow = [-1, 1]    for i, cow in enumerate(cows):        if cow in record:            # <= interval不对,因为index2-index1 = 1,中间间隔元素为0            if i-record[cow][0] < interval:                record[cow] = [i, record[cow][1]+1]                if record[cow][1] > maxcow[1]:                    maxcow = record[cow]            else:                record[cow] = [i, 1]        else:            record[cow] = [i, 1]    if maxcow[0] == -1:        return -1    else:        return cows[maxcow[0]] 

4.URL去重
题目:
有大量的字符串格式的URL,如何从中去除重复的,优化时间空间复杂度

思路:

  1. 将URL存入hash链表,每个URL读入到hash链表中,遇到重复的就舍弃,否则加入到链表里面,最后遍历得到所有不重复的URL。空间复杂度M,时间复杂度为O(N+N/M),M为不重复的URL,N为总URL数,但是M无法预测,所以存在风险,可能内存不足以存储所有的不重复URL。 (我搞不大懂时间复杂度怎么来的?)

  2. 为了解决内存可能不足的问题,需要把hash链表变化成普通的hash表,每个hash表元素指向一个文件文件,这个文件记录了所有该hash值对应的无重复的URL,那么在加入URL的时候就遍历对应文件中的URL,没有重复则加入到文件中。这样做时间复杂度没有提升,但是每次都要读写文件,消耗的时间应该是上一种方式的三倍,而对内存的要求比较小。一个改进是加入URL的时候进行排序,这样能减少比对的次数(B树/红黑树还是什么其他数据结构?!)

其他:
复习好哈希表的章节,再好好思考。

5.设计一个系统处理词语搭配问题
题目:
设计一个系统处理词语搭配问题,比如说中国和人民可以搭配,
则中国人民人民中国都有效。要求:
*系统每秒的查询数量可能上千次;
*词语的数量级为10W;
*每个词至多可以与1W 个词搭配
当用户输入中国人民的时候,要求返回与这个搭配词组相关的信息。

思路:
This problem can be solved in three steps:

  1. identify the words
  2. recognize the phrase
  3. retrieve the information

Solution of 1: The most trivial way to efficiently identify the words is hash table or BST. A balanced BST with 100 words is about 17 levels high. Considering that 100k is not a big number, hashing is enough. (存储所有的词)

Solution of 2: Since the phrase in this problem consists of only 2 words, it is easy to split the words. There won’t be a lot of candidates. To find a legal combination, we need the “matching” information. So for each word, we need some data structure to tell whether a word can co-occur with it. 100k is a bad number – cannot fit into a 16bit digit. However, 10k*100k is not too big, so we can simply use array of sorted array to do this. 1G integers, or 4G bytes is not a big number, We can also use something like VInt to save a lot of space. To find an index in a 10k sorted array, 14 comparisons are enough. (分词和匹配检索)

Above operation can be done in any reasonable work-station’s memory very fast, which should be the result of execution of about a few thousands of simple statements.

Solution of 3: The information could be to big to fit in the memory. So a B-tree may be adopted to index the contents. Caching techniques is also helpful. Considering there are at most 10^9 entries, a 3 or 4 level of B-tree is okay, so it will be at most 5 disk access. However, there are thousands of requests and we can only do hundreds of disk seeking per second. It could be necessary to dispatch the information to several workstations. (返回每个词的相关信息)

其他:
细节待完善。

6.有一千万条短信,有重复,以文本文件的形式保存,一行一条,有重复。
请用5分钟时间,找出重复出现最多的前10条

思路:
1. 用哈希表对短信进行存储计数
2. 对哈希表进行遍历,并用大小为10的小根堆,找出重复出现最多的前10条。

其它:
1. 用有序的红黑树,效果会不会更好。
2. 哈希函数的设计
3. 应该是等计数结束后再遍历构造小根堆会比较好
4. 对新进入的url,更新哈希表,然后直接更新小根堆(要判断新进入的url是否本来就在小根堆中),不需要遍历。

更多

7.收藏了1万条 url,现在给你一条url,如何找出相似的url

思路:
用字典树对url进行存储,看对相似程度的需求,找到某棵子树,这颗子树包含了所有所需的相似url。

1.栈的push/pop序列
题目:
  输入两个整数序列。其中一个序列表示栈的push顺序,判断另一个序列有没有可能是对应的pop顺序。为了简单起见,我们假设push序列的任意两个整数都是不相等的。

  比如输入的push序列是1、2、3、4、5,那么4、5、3、2、1就有可能是一个pop系列。因为可以有如下的push和pop序列:push 1,push 2,push 3,push 4,pop,push 5,pop,pop,pop,pop,这样得到的pop序列就是4、5、3、2、1。但序列4、3、5、1、2就不可能是push序列1、2、3、4、5的pop序列。

思路:

实现:

bool isPopSeries(int push[], int pop[], int n){    stack<int> helper;    int i=0;    int j=0;    for(i=0;i<n;i++){        helper.push(push[i]);        if(push[i]==pop[j]){            helper.pop();            j++;        }    }    while(!stack.empty()&&stack.top()==pop[j]){        if(j==n-1) return true;        stack.pop();        j++;    }    return false;}

2.设计⼀个min函数的栈
题⽬:
定义栈的数据结构,要求添加⼀个min函数,能够得到栈的最⼩元素。
要求函数min,push,pop的时间复杂度都是O(1).

思路:
维护两个stack,其中一个维护min。

实现:

#include<iostream>#include<stack>using namespace std;class MinStack{public:    // empty()不应该整合于push, pop, min方法上    bool empty(){ return elements.empty();}    void push(int e){        if(empty() || e<=mins.top()) mins.push(e);        elements.push(e);    }    void pop(){        if(elements.top()==mins.top()) mins.pop();        elements.pop();    }    int min(){ return mins.top();}private:    stack<int> elements;    stack<int> mins;};

数组

队列

区间

矩阵

数据结构

动态规划

最长公共子序列(LCS)

1.数列s1与s2最长公共子序列(LCS)
题目:
⼦串(Substring):串的⼀个连续的部分
⼦序列(Subsequence):从不改变序列的顺序,⽽从序列中去掉任
意的元素⽽获得的新序列;

例子:
字符串acdfg同akdfc的最⻓公共⼦串为df,⽽他们的最⻓公共⼦序列是adf。

思路:

  1. c[i, j]: s1取长度为i的部分与s2取长度为j的部分的最长公共子序列的长度;
    c[i,j]=0,c[i1,j1]+1,max(c[i1,j],c[i,j1]),if i==0||j==0if s1[i1]==s2[j1]if s1[i1]!=s2[j1]
  2. 可以从矩阵c得到最长公共子串的内容

实现:

#include <iostream>#include <string>#include <vector>#include <algorithm>using namespace std;void lcs(const string &s1, const string &s2, string &result){    int len1 = s1.length();    int len2 = s2.length();    // vector<int>后面一定要加空格,不然会当成>>,出现编译错误    vector<vector<int> > matrix(len1+1, vector<int>(len2+1));    for(int i=0;i<=len1;i++)        matrix[i][0] = 0;    for(int j=1;j<=len2;j++)        matrix[0][j] = 0;    for(int i=1;i<=len1;i++)        for(int j=1;j<=len2;j++){            if(s1[i-1]==s2[j-1])                matrix[i][j] = matrix[i-1][j-1] + 1;            else                matrix[i][j] = matrix[i-1][j] > matrix[i][j-1] ? matrix[i-1][j] : matrix[i][j-1];        }    // 最长公共子串的长度为matrix[len1][len2]    // 下面打印最长公共子串    int i = len1;    int j = len2;    while(i!=0 && j!=0){        if(s1[i-1] == s2[j-1]){            result.push_back(s1[i-1]);            i--;            j--;        }        else{            if(matrix[i-1][j]>matrix[i][j-1])                i--;            else                j--;        }    }    // 翻转字符串    reverse(result.begin(), result.end());}

复杂度:

时间复杂度O(len1*len2),空间复杂度O(len1*len2)

拓展:

  1. 空间复杂度可以远远小于O(len1*len2),为O(min(len1, len2)*2)
  2. 两个字符串对应的最长公共子序列不一定唯一,可以输出所有LCS内容

2.LCS的应用:最长递增子序列LIS
题目:
找出给定数组最⻓且单调递增的⼦序列。
如:给定数组A{5,6,7,1,2,8},则其最⻓的单调递增⼦序列为{5,6,7,8},⻓度为4

思路:
其实此问题可以转换成最⻓公共⼦序列问题只需要将原始数组进⾏排序A’{1,2,5,6,7,8}
因为,原始数组A的⼦序列顺序保持不变,⽽且排序后A’本⾝就是递增的,这样,就保证了两个序列的最⻓公共⼦序列的递增特性。如此,若想求数组A的最⻓递增⼦序列,其实就是求数组A与它的排序数组A’的最⻓公共⼦序列 。

实现:

  1. 先把数组A整成有序数组B
  2. 再求数组A和数组B的最长公共子序列

其他:
其实可以直接使用动态规划/贪心算法去做。

最长公共子串

1.数列s1与s2最长公共子串(LCS)
题目:
⼦串(Substring):串的⼀个连续的部分
⼦序列(Subsequence):从不改变序列的顺序,⽽从序列中去掉任
意的元素⽽获得的新序列;
例子:
字符串acdfg同akdfc的最⻓公共⼦串为df,⽽他们的最⻓公共⼦序列是adf。

思路:

  1. 动态规划最重要的是定义出状态转移方程:
    下面是最长公共子序列的定义,显然不适合于最长公共子串:
    c[i, j]: s1取长度为i的部分与s2取长度为j的部分的最长公共子串的长度;
    c[i,j]=0,c[i1,j1]+1,max(c[i1,j],c[i,j1]),if i==0||j==0if s1[i1]==s2[j1]if s1[i1]!=s2[j1]

    下面是最长公共子串的状态转移方程的定义:
    c[i, j]: s1取长度为i的部分,s2取长度为j的部分,这两部分尾部重合的长度为c[i, j],如abcdef与kbfffffdef的值为3 (def);
    c[i,j]=0,c[i1,j1]+1,0,if i==0||j==0if s1[i1]==s2[j1]if s1[i1]!=s2[j1]
  2. 用maxInfo最最长公共子串进行追踪

    实现:

#include<iostream>#include<vector>#include<string>using namespace std;struct Info{    int i;    int j;    int value;};void lcs(const string &s1, const string &s2, string &result){    int len1 = s1.length();    int len2 = s2.length();    Info maxInfo = {0, 0, 0};    vector<vector<int> > matrix = (len1+1, vector<int>(len2+1));    for(int i=0;i<=len1;i++)        matrix[i][0] = 0;    for(int j=1;j<=len2;j++)        matrix[0][j] = 0;    for(int i=1;i<=len1;i++)        for(int j=1;j<=len2;j++){            if(s1[i-1]==s2[j-1]){                 matrix[i][j] = matrix[i-1][j-1] + 1;                 if(matrix[i][j]>maxInfo.value){                     maxInfo.i = i;                     maxInfo.j = j;                     maxInfo.value = value;                 }            }            else                matrix[i][j] = 0;        }    result = s1.substr(maxInfo.i-maxInfo.value, maxInfo.value);}

复杂度:

时间复杂度O(len1*len2),空间复杂度O(len1*len2)

背包问题

1.01背包问题
http://blog.csdn.net/mu399/article/details/7722810
http://blog.csdn.net/kangroger/article/details/38864689
2.完全背包问题
http://blog.csdn.net/kangroger/article/details/38864689
3.和等于m的所有的可能的组合
题目:
输入两个整数n和m,从数列1, 2, 3, …, n中随意取几个数,使其和等于m,要求将其中所有的可能组合列出来。

思路:
set[i][sum]:代表利用前i个数字得到和为sum的组合
set[i-1][sum]:代表利用前i-1个数字得到和为sum的组合
set[i-1][sum-1]:代表利用前i-1个数字得到和为sum-i的组合
很明显有以下的等式:
set[i][sum] = set[i-1][sum] V set[i-1][sum-i]

实现:

// 递归//利用标记数组进行打印void Print(bool *record, int length){    for(int i=1;i<=length;i++){        if(record[i])            cout<<i<<' ';    }    cout<<endl;}// 打印的时候需要引入一个数组进行元素选择的标记void AllCombination(int num, int sum, bool *record, int length){    // 考虑到(1, 1, record, length)的情况,sum==0的判断必须放前面    if(sum == 0){        Print(record, length);        return;    }    if(sum < 0 || num <= 0) return;    // set[i-1][sum-i]    record[num] = true;    AllCombination(num-1, sum-num, record, length);    // set[i-1][sum]    record[num] = false;    AllCombination(num-1, sum, record, length);}/*    // set[i-1][sum-i]    record[num] = true;    AllCombination(num-1, sum-num, record, length);    // set[i-1][sum]    record[num] = false;    AllCombination(num-1, sum, record, length);变成下面这样可不行,因为record[num]最终置为true了:    // set[i-1][sum]    record[num] = false;    AllCombination(num-1, sum, record, length);    // set[i-1][sum-i]    record[num] = true;    AllCombination(num-1, sum-num, record, length);要进行修改:    // set[i-1][sum]    record[num] = false;    AllCombination(num-1, sum, record, length);    // set[i-1][sum-i]    record[num] = true;    AllCombination(num-1, sum-num, record, length);    // 把num算进去的情况,已经考虑完了,所以要把record[num]重置    // 想一下5, 5, 10要求sum=10    record[num] = false;*/

4.有两个序列 a,b,大小都为 n,序列元素的值任意整数,无序
题目:
要求通过交换 a,b 中的元素,使[序列 a 元素的和]与[序列 b 元素的和]之间的差最小。
例如:
var a=[100,99,98,1,2,3];
var b=[1,2,3,4,5,40];

思路(递归):

1. input[] = [a, b]
2. len为考虑input数组的第0位到第len-1位
3. n为考虑从input数组中挑选的数的个数
4. target为目前挑选出来的数组成的集合和剩下的集合之间的差

首先:
要令abs(target) = abs(sum(a)-sum(b))最小相当于abs(target) = abs(sum(input)-2*sum(b))最小。

minDiff(input, len, n, target) 等价于下面两种情况:
1.挑选第len-1个元素: minDiff(input, len-1, n-1, target-2*input[len-1]);
2.不挑选第len-1个元素: minDiff(input, len-1, n, target);

minDiff(input, len, n, target) = abs(situation1) < abs(situation2) ? situation1:situation2

上面的做法只能求得minDiff的值,但不能把a, b集合打印出来,所以要用mark数组,追踪每条路径。用path数组,追踪拥有最小Diff的路径。

思路(动态规划):
就是把matrix[i][j][sum]搞出来,其实就是把matrix[len][n]的sum的所有可能通过动态规划搞出来,然后看哪个sum令abs(target-2*sum)最小。

实现:

// 大功告成,递归#include<iostream>using namespace std;int findMax(int input[], int len){    int max = 1 << 31;    for(int i=0;i<len;i++){        if(input[i]>max)            max = input[i];    }    return max;}int abs(int num){    return num >= 0 ? num : -num; }int sum(int input[], int len){    int result = 0;    for(int i=0;i<len;i++)        result += input[i];    return result;}void printPath(int input[], int len, bool *mark){    for(int i=0;i<len;i++){        if(mark[i])            cout<<input[i]<<", ";    }    cout<<endl;}int minDiff(int input[], int len, int n, int target, bool *mark, int& markLen, bool *path, int& minResult){    if(len < n){        return ~(1<<31);    }    if(n==0){        if(abs(target)<abs(minResult)){            minResult = target;            for(int i=0;i<markLen;i++)                path[i]=mark[i];        }        return target;    }    mark[len-1] = true;    int min2 = minDiff(input, len-1, n-1, target-2*input[len-1], mark, markLen, path, minResult);    mark[len-1] = false;    int min1 = minDiff(input, len-1, n, target, mark, markLen, path, minResult);    bool test = abs(min1) < abs(min2);    return test? min1 : min2;}int main(int argc, char** argv) {    int test[] = {-3, 9, 10, 65, 5, 6, 13, 55};    int len = 8;    bool *mark = new bool[len];    bool *path = new bool[len];    int minResult = ~(1<<31);    //cout<<sum(test, len)<<endl;    cout<<"minDiff: "<<minDiff(test, len, 3, sum(test, len), mark, len, path, minResult)<<endl<<endl;    cout<<"set1: (";    for(int i=0;i<len;i++){        if(path[i])            cout<<test[i]<<",";    }    cout<<")"<<endl<<endl;    cout<<"set2: (";    for(int i=0;i<len;i++){        if(!path[i])            cout<<test[i]<<",";    }    cout<<")"<<endl<<endl;      return 0;    delete []mark;    delete []path; }
// 大功告成,动态规划/*To do:1. 首先Record类写得很烂,怎么才能让外部迭代Record内的record,还想不到怎么弄,所以搞了很丑getSum, size这种成员函数2. 然后对于看是否是合适的subsum,我是把整个record遍历来找的,是否还有其他方法呢?3. 如果只是为了求最小差的话,其实matrix[i][j]的空间复杂度可以降低很多,首先在算的过程中如果只保存matrix[j] = matrix[1~len][j] = matrix[len][j],因为matrix[i-1][j]的所有subsum会作为一种情况被append到matrix[i][j]中,相当于把前面的拷贝一次到后面,不如不拷贝而直接维护一个vector<Record>就ok了,不过这就不能区分这j个元素的具体组成;再进一步,其实我们利用matrix[j]时只用两列来迭代运算就好;不过,注意的是如果需要得到集合的具体内容而非最小差一个值的话,matrix[i][j]必不可少!4. 有人不用vector<vector<Record> >,直接用matrix[i][j][subsum],这样的话索引起来会非常快,但是subsum可能是负数,而且作为一个稀疏矩阵有点浪费空间,我觉得应该用vector<vector<map<int> > > record这样会比较好,map<int>存储了相应i, j的所有subsum,首先map不会大(可以用哈希map会大一点,但红黑map会比较小,这里要考证一下),索引快(哈希map会比红黑map快一点)。最后,在搜索最接近的subsum时显然是红黑map会比较好,因为它是一种BST*/#include <iostream>#include <vector>using namespace std;int min(int a, int b){    return a<b ? a : b;}int abs(int num){    return num >= 0 ? num : -num; }int sum(int input[], int len){    int result = 0;    for(int i=0;i<len;i++)        result += input[i];    return result;}class Record{public:    void append(int subs, bool sele){           Node newElement = {subs, sele};         record.push_back(newElement);    }           bool isEmpty(){        return record.empty();    }    bool isSelected(int sum){        for(int i=0;i<record.size() && record[i].selected;i++){            if(record[i].subsum==sum)                return true;        }        return false;    }    int size(){        return record.size();    }    void closest(int target, int &subs, bool &sele){        int index = -1;        int min = ~(1<<31);        for(int i=0;i<=record.size();i++){            int tmp = abs(target-2*record[i].subsum);            if(min>tmp){                min = tmp;                index = i;                if(min == 0) break;             }        }        subs = record[index].subsum;        sele = record[index].selected;    }    int getSum(int index){          return record[index].subsum;    }private:    struct Node{        int subsum;        bool selected;    };    vector<Node> record;    };void minDiff(int input[], int len, int n, int target, vector<int> &result){    vector<vector<Record> > matrix(len+1, vector<Record>(n+1));    for(int i=1;i<=len;i++){        for(int j=1;j<=min(i, n);j++){            if(matrix[i-1][j-1].isEmpty()){                             matrix[i][j].append(input[i-1], true);            }            else{                           for(int k=0;k<matrix[i-1][j-1].size();k++){                    matrix[i][j].append(input[i-1]+matrix[i-1][j-1].getSum(k), true);                }            }            for(int k=0;k<matrix[i-1][j].size();k++){                matrix[i][j].append(matrix[i-1][j].getSum(k), false);            }        }    }    int tmpTarget = target;    int i = len;    int j = n;    cout<<"begin: "<<tmpTarget<<endl;    while(j>=1 && i>=j){        bool sele;        int subs;           matrix[i][j].closest(tmpTarget, subs, sele);        if(sele){            result.push_back(input[i-1]);            tmpTarget = 2*subs - 2*input[i-1];            i--;            j--;        }               else            i--;    }     }int main(int argc, char** argv) {    int test[] = {-3, 9, 10, 65, 5, 6, 13, 55};    int len = 8;    int countSet1 = 3;    int target = sum(test, len);    vector<int> result;    minDiff(test, len, countSet1, target, result);    for(int i=0;i<result.size();i++)        cout<<result[i]<<" ";    cout<<endl;    return 0;}

5.多米诺骨牌

6.数组最大子数组和的最大值

7.动态规划之钢条分割

8.计算两个字符串的相似度

9.求每一对顶点之间的最短路径: Floyd-Warshall算法

图论

1.判断有向图是否强连通/无向图是否连通

2.判断有向图/无向图是否有环
思路:

3.求一个有向图的割点
题目:
求⼀个有向连通图的割点。割点的定义是,如果除去此结点和与其相关的边,有向图不再连通,描述算法。(有向图的连通指的是弱连通)

思路:
通过深搜优先生成树来判定。从任一点出发深度优先遍历得到优先生成树,对于树中任一顶点V而言,其孩子节点为邻接点。由深度优先生成树可得出两类割点的特性:

  1. 若生成树的根有两棵或两棵以上的子树,则此根顶点必为割点。因为图中不存在连接不同子树顶点的边(深搜可以保证此特性),若删除此节点,则树便成为森林;
  2. 若生成树中某个非叶子顶点V,其某棵子树的根和子树中的其他节点均没有指向V的祖先的回边,则V为割点。因为删去v,则其子树和图的其它部分被分割开来。

利用深搜算法并定义:
这里写图片描述

任务一:求顶点v的low值
定义:
1. visited[v](圈内序号):深度优先搜索遍历图时访问顶点v的次序号
2. low[v](圈外序号)=Min{visited[v],low[w],visited[k]},其中w是顶点v在深度优先生成树上的孩子节点;k是顶点v在深度优先生成树上由回边联结的祖先节点。
low[v]可以看作v在w分支上可以联结的最早序号:若low[v]=visited[v],则v不能向上追溯 | 若low[v]=low[w],则v通过子节点w向上追溯 | 若low[v]=visited[k],则v通过自身向上追溯到k

任务二:顶点v是否为割点(绿色)判定条件:
如果对于某个顶点v,存在孩子节点w(注意是存在)low[w]>=visited[v],则该顶点v必为关节点。因为当w是v的孩子节点时low[w]>=visited[v],表明w及其子孙均无指向v的祖先的回边,那么当删除顶点v后,v的孩子节点将于其他节点被分割开来,从来形成新的连通分量。

实现:

// c++实现#include <iostream>#include <string.h>#define MAX_NUM_0 11 using namespace std;const int MAX_NUM = MAX_NUM_0+1;int count = 0;// visited记录结点对应的访问序号 int visited[MAX_NUM];bool cut[MAX_NUM];// g[][MAX_NUM]     g[i][] is the out edges//                  g[i][0] is number of out edges//                  g[i][j] is j st out edges of i// s: 当前搜索结点 // low: s本身能向前追溯到的序号 int dfs(int g[][MAX_NUM], int s){    visited[s] = ++count;    // 任务1:计算s的low     int low = visited[s];    for(int j=1;j<=g[s][0];j++){        if(visited[g[s][j]]==0){            int childLow = dfs(g, g[s][j]);            // 任务1:计算s的low            if(childLow<low){                low = childLow;            }            // 任务2:计算s是否为割点             if(childLow>=visited[s]){                cut[s] = true;            }        }        // 任务1:计算s的low        else if(visited[g[s][j]]<low){            low = visited[g[s][j]];        }    }    return low;}void PrintCutPoints(int g[][MAX_NUM], int n){    memset(visited, 0, sizeof(int)*MAX_NUM);    memset(cut, 0, sizeof(bool)*MAX_NUM);    for(int i=1;i<=n;i++){        if(visited[i]==0)            dfs(g, i);    }    int num = 0;    for(int i=1;i<=n;i++)        if(cut[i]){            cout<< i <<" ";            num++;        }    cout<<endl<<"Total cut points : "<<num<<endl;}int main(int argc, char** argv) {    // 因为是由g[1][]开始的,所以g[0][]没有实际意义    // 对应上述示例图     int g[][MAX_NUM] = {    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},    {2, 2, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0},    {1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},    {1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},    {1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},    {1, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},    {3, 7, 8, 4, 0, 0, 0, 0, 0, 0, 0, 0},    {1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},    {1, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},    {2, 6, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0},    {1, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}    };     int n = 11;     PrintCutPoints(g, n);    return 0;}

其他:
有向图强连通分支算法
1. Kosaraju算法
2. Tarjan算法
3. Gabow算法

无向图强连通分支算法

搜索/递归/回溯

回溯

递归

分治法

排列

非递归

排序

快速排序

排序

归并排序

数学

卡特兰数

离散数学

1.根据上排给出十个数,在其下排填出对应的十个数*(网上没有正确的解答,我算是把它给终结了)

题目:
要求下排每个数都是先前上排那十个数在下排出现的次数。上排的十个数如下:
【0,1,2,3,4,5,6,7,8,9】

举一个例子,
数值: 0,1,2,3,4,5,6,7,8,9
分配: 6,2,1,0,0,0,1,0,0,0
0 在下排出现了6 次,1 在下排出现了2 次,
2 在下排出现了1 次,3 在下排出现了0 次….
以此类推..

思路:
关键是下排每个数都是先前上排那十个数在下排出现的次数,即隐含着(角度1)在上排中挑选十个数,这十个数可以重复,然后放到下排的位置。(角度2)所以总的出现次数就等于下排有多少个位置要放。
假设:设总共有n个数,上排a[0…n-1],下排b[0…n-1]

1)下排n个数的累加和为n,即b[0]+b[1]+…+b[n-1] = n
2)ai*bi的累加和也为n,即a[0]*b[0]+a[1]*b[1]+…+a[n-1]*b[n-1] = n
3)对于b中任意一个元素b[j], 都存在i,a[i] = b[j].
4)对于b中任意一个元素b[j],都有b[j] >= 0
5)如果a中存在负数,其在b中出现的次数一定为0. 如果a中数值大于n,则其出现次数也为0.
6)考察等式n1i=0b[i]=n1i=0a[i]b[i]等价于n1i=0(a[i]1)b[i]=0。若a中没有0,则b中元素全为0;若a中含有0,则b[0]=k>=1,然后b[k]>=1。

讨论:
1. 如果a中存在负数,其在b中出现的次数一定为0. 如果a中数值大于n,则其出现次数也为0.
2.* 若a中没有0,则b中元素全为0;*
3. 如果a中有0,则a中至少有1个或者以上非0数值在b中出现次数非0;(下面进行分类讨论)

  • a中只有1个非0数值在b中出现的次数非0 (1)
    a 0 b[0]
    b b[0] x
    由(1)得,x = b[0]
    b[0] + b[0] = b[0] *b[0];b[0] = 2 或 b[0] = 0 (舍去);b[0] = 2
    a 0 2
    b 2 2
    -> 结论
    a 0 2
    b 2 2 0 0

  • a中有2个或者以上非0数值在b中出现的次数非0 (2)
    由(2)得,任意i,有a[i]*b[i] < n(注意不等式不能取等)
    考虑任意n/2<=a[i] < n,有b[i] < 2;b[i] = 1 或 b[i] = 0;在此范围内只能有一个数值出现1次(否则和会大于n)或者在此范围内任何一个数值出现为0次;考虑在此范围内任何一个数值出现为0次的情况,则在范围1 <= a[i] <= n/2-1中,最多有n/2-1个位置可以放非零的数,所以n个位置中至少有n/2+1个位置要放上零,所以b[0] >= n/2+1,所以与在范围n/2 <= a[i] < n内任何一个数值出现为0次的情况矛盾。因此结论为在范围n/2 <= a[i] < n内必有有且只有一个数值,其出现次数必为1次。(3)
    由(3)得,b[1]>=1;若b[1] = 1,则b[1]应该为2(矛盾);因此b[1]>1;
    若b[1]=w>1,则b[w]>=1;若b[w]>1,则为了满足对应关系,需要找到另外的b[v] = w,如此类推为了一一对应而进行填坑,反而导致了更多的坑,最终不能满足一一对应关系(矛盾),所以b[w]必为1。此时情况如下:
    1 w
    w 1
    若w=1(显然不成立);若w>2,则也是上面的道理,需要借助他人,导致坑越来越大,显然也是不成立的;因此,w必然为2。此时情况如下:
    0 1 2 … n-4 …
    n-4 2 1 0 1 0

结论(只有下面3种情况):
1.
a 0 2
b 2 2 0 0
2.
a 0 1 2 … n-4 …
b n-4 2 1 0 1 0
3.a中没有0元素
a …
b 0

2.全排列

3.组合算法 - 从n中选m个数
http://blog.csdn.net/garrison_Z/article/details/44950027
http://blog.csdn.net/garrison_Z/article/details/44950027

4.A说不知道,B说不知道,C说不知道,然后A说知道了
题目:
有4张红色的牌和4张蓝色的牌,主持人先拿任意两张,再分别在A、B、C三人额头上贴任意两张牌,
A、B、C三人都可以看见其余两人额头上的牌,看完后让他们猜自己额头上是什么颜色的牌,
A说不知道,B说不知道,C说不知道,然后A说知道了。
请教如何推理,A是怎么知道的。
如果用程序,又怎么实现呢?

思路:
从A的角度看,B/C两者是对称的,只考虑下面三种的情况即可
1. (B, C)=(RR, BB) -> A = RB
2. (B, C)=(RB, RB) -> A = RB
若A=RR,则B判断不了,代表了C最多只有一个R;若C为BB,又因为A不能判断,所以B就知道自己不可能是BB,所以B就知道自己为RB了;
3. (B, C)=(RR, RB)-> A
若A=BB,又因为C不能为BB或RR,所以C知道自己是RB了;
实现:

其他

1.圆形是否与正方形相交

思路:
注意浮点数如何比较大小(1e-6)

实现:

struct Coordinate{    double x;    double y;};struct Circle{    // c为圆心    Coordinate c;    double r;};struct Square{    // 正方形的四个结点    Coordinate *nodes;    int n;};double HyperDistance(Coordinate n1, Coordinate n2){    return (n1.x-n2.x)*(n1.x-n2.x)+(n1.y-n2.y)*(n1.y-n2.y);}bool interset(Circle c, Square s){    for(int i=0;i<s.n;i++){        diff = c.r*c.r-HyperDistance(c.c, s.nodes[i]);        // 判断diff是否等于0        if(abs(diff)<1e-6)            return true;        // 判断diff是否大于0        else if(diff > 0)            return true;        else            return false;    }}

2.快速算底数a的n次幂 O(logn)
题目:
实现函数int Qpow(int a,int n)

思路:
递归思路:

  1. 若n为偶数
    an=an/2an/2
  2. 若n为奇数
    an=aan/2an/2

迭代思路:

n=n0+n12+n222+n323+...+nk2k

an=an0an12an222an323...ank2k

ni=0,则ani2i=1,不影响乘积结果,所以我们只需考虑ni=1的部分。
注意:
a2ia2i=a2i+1

关于取模的问题(防止结果溢出+防止中间结果过大)

实现:

// 递归实现int Qpow(int a, int n){    if(n==0) return 1;    int tmp = Qpow(a, n/2);    return (n%2==0 ? 1 : a)*tmp*tmp;}
// 迭代实现int Qpow(int a, int n){    int result = 1;    int base = a;    while(n!=0){        if(n%2==1) result *= base;         base *= base;        n /= 2;    }    return result;}
// 位运算+迭代实现int Qpow(int a, int n){    int result = 1;    int base = a;    while(n!=0){        if(n&1==1) result *= base;        base *= base;        n >>= 1;    }    return result;}

3.斐波拉契数列
题目:
0 0
1 1
2 1
3 2

思路:
递归思路:
特别简单,但时间复杂度非常大为O(2n),空间复杂度为O(n)。看下图就十分明了:
这里写图片描述

迭代思路:
时间复杂度为O(n),空间复杂度为O(1)

最优算法思路(矩阵快速幂):
时间复杂度为O(logn),空间复杂度为O(1)

[F(n)F(n1)]=[F(n1)F(n2)][1110]

[F(n)F(n1)]=[F(1)F(0)][1110]n1

实现:

// 递归 O(2^n)int Fibonacci(int n){    if(n==0) return 0;    if(n==1) return 1;    return Fibonacci(n-1)+Fibonacci(n-2)}
// 非递归int Fibonacci(int n){    if(n==0) return 0;    if(n==1) return 1;    int fa = 0;    int fb = 1;    int fc = 0;    for(int i=2;i<=n;i++){        fc = fb + fa;        fa = fb;        fb = fc;    }    return fc;}
// 最优算法typedef vector<vector<int> > mat;  typedef long long LL;  // 用于模运算,防止结果溢出const int M = 1e4;  mat A(2,vector<int>(2));A[0][0] = 1;A[0][1] = 1;A[1][0] = 1;A[1][1] = 0;// 矩阵乘法mat mul(mat &A, mat &B){    // 初始化: 长度+元素    mat C(A.size(), vector<int>(B[0].size()))    // (A.size() x B.size())*(B.size() x B[0].size()) = (A.size() x B[0].size())    for(int i=0;i<A.size();i++)        for(int j=0;j<B[0].size();j++)            for(int k=0;k<B.size();k++)                C[i][j] += (a[i][k] * a[k][j]) % M;    return C;}mat pow(mat A, LL n){    mat R(A.size(), vector<int>(A.size()));    // base = A    // R需要先设为单位矩阵,R默认为0矩阵    for(int i = 0; i < R.size(); i++)        R[i][i] = 1;    // R=mul(R, base); base = mul(base, base)会更好理解    while(n!=0){        if (n/2==1) R = mul(R, A)        A = mul(A, A);        n >>= 1;    }    return R;}

4.求解圆圈中最后剩下的数字
题目:
将 0,1,2….n-1 一共n个数字排成一个圆圈。从数字0开始每次从这个圆圈里面删除第m个数字( 1 =< m <= n, 比如第1个数字为0)。求出这个圆圈里面最后剩下的那个数字。

思路:
remain(n, m)指的是0, 1, …, n按照题目规则,从0开始,一直删除第m个数字,最后剩下的数字。
remain(n-1, m)指的是0, 1, …, n-1按照题目规则,从0开始,一直删除第m个数字,最后剩下的数字。
有以下递推规律:
remain(n, m) = (remain(n-1, m) + m)%n
举例: n = 4 m = 2
从数列的第一个数字开始抽
0, 1, 2, 3 -> 2, 3, 0 <=> 2+2 , 0+2, 1+2 <=> 0, 1, 2 +2

实现:

// 递归int remain(int n, int m){    if(n==1) return 0;    return (remain(n-1, m)+m)%n;}
// 迭代int remain(int n, int m){    int result = 0;    for(int i=2;i<=n;i++){        result = (result + m) % i    }    return result;}

5.跳台阶问题
题目:
一个台阶总共有n级,如果一次可以跳1级,也可以跳2级。求总共有 多少总跳法?

思路:
本质就是个斐波拉契数列
Count(n) = Count(n-1) + Count(n-2)
Count(1) = 1
Count(2) = 2

实现和算法复杂度分析见斐波拉契数列

6.从1到n的正数中1出现的次数
题目:
输入一个整数n,求从1到n这n个整数的十进制表示中1出现的次数。

例子:
输入12,则从1到12这些整数中包含1的数字有1,10,11和12。因此,1一共出现5次(注意:11中1出现了2次)

思路:

  1. 首先求1到n这n个整数的十进制表示中1出现的次数可分为统计:个位中出现1的次数+十位中出现1的次数+百位中出现1的次数+…
  2. 我们考虑第i位,数字可以分成top-target-tail三部分,例如考虑数字n=121056
    ​ i = 2 -> top = 1210; target = 5; tail = 6
    ​ i = 3 -> top = 121; target = 0; tail = 56
    ​ i = 4 -> top = 12; target = 1; tail = 056 = 56
    ​ 我们进行分类讨论:
    1. 当target > 1时,出现1的次数为(top+1)10i1
    2. 当target = 1时,出现1的次数为(top+1)10i1err,其中err指的是与target>1时相比不存在的可能,易得err=10i1(tail+1)
    3. 当target = 1时,出现1的次数为(top+1)10i1err,其中err指的是与target>1时相比不存在的可能,易得err=10i1

实现:

int countForOne(int num){    int count = 0;    int base = 10;    int top = -1;    while(top!=0){        top = num / base;        int tail = num % (base/10);        int target = num % base / (base/10);        int err;        if(target == 0)            err = base/10;        else if(target == 1)            err = base/10 -(tail+1);        else            err = 0;        count += (top+1) * (base/10) - err;        base *= 10;    }    return count;}

时间复杂度:
O(logn)

高精度

博弈论

概率

其他

子数组

二进制

1.整数的二进制表示中1的个数
题目:输入一个整数,求该整数的二进制表达中有多少个1
例如输入10,由于其二进制表示为1010,有两个1,因此输出2。

思路:
xxxxx10000 & (xxxxx10000-1) = xxxxx00000
看需要多少次这样的操作,把所有的1都消调

实现:

int countOf1(int n){    int count = 0;    while(n!=0){        n &= (n-1);        count++;    }    return count;}

两根指针

1.在有序数组查找和为dest的两个数
题目:
输入一个已经按升序排序过的数组和一个数字, 在数组中查找两个数,使得它们的和正好是输入的那个数字。要求时间复杂度为O(n)。如果有多对数字的和等于输入的数字,输出任意一对即可。

例如:1, 2, 4, 7, 11, 15; dest: 15
输出:4和11

思路:
首尾各2根指针,若和大于dest,则尾指针–;若和小于dest,则首指针++;

实现:

void find2Numbers(int a[], int n, int dest){    int *head = a, *tail = a+n-1;    while(head!=tail){        int sum = *head + *tail;        if(sum == dest){            cout<<*head<<' '<<*tail<<endl;            return;        }        else if(sum > dest)             tail--;        else            head++;    }    cout<<"No such two numbers!"<<endl;    return;}

前后遍历历史

贪心算法

1.蜂窝结构的图,进行搜索最短路径
这里写图片描述
题目:
注意坐标原点的选择,只选择含原点的六边形的边与x轴的负方向重合的点为坐标原点(0, 0)
思路:
分情况讨论
1. 水平方向尽量走水平方向去靠近目的地
2. 垂直方向尽量走垂直方向去靠近目的地

点的坐标可以表示为(x1+1/2x2,sqrt(3)/2y)

实现:

#include <iostream>using namespace std;//  struct Point{    int x1;    int x2;    int y;};int abs(int x){    return x>=0? x : -x;}void printPoint(Point x){    cout<<"Location: ("<<x.x1<<", "<<x.x2<<"| "<<x.y<<")"<<endl;}// abs(x1-x2) % 2 == 1 x1:+1 x2:+-1// abs(x1-x2) % 2 == 0 x1:-1 x2:+-1// a->b void CellPath(Point a, Point b){    int lenx1 = b.x1-a.x1;    int lenx2 = b.x2-a.x2;    int leny = b.y-a.y;    Point tmp = a;    printPoint(tmp);    while(lenx1!=0 || lenx2!=0 || leny!=0 ){        bool dirx1 = abs(tmp.x1-tmp.x2)%2 == 1;         if(lenx1>0 && dirx1){            cout<<"Instruction: x1 +1"<<endl;            lenx1--;            tmp.x1++;        }        else if(lenx1<0 && !dirx1){            cout<<"Instruction: x1 -1"<<endl;            lenx1++;            tmp.x1--;        }        else if(leny>0 && dirx1){            cout<<"Instruction: y +1; x2 -1"<<endl;            leny--;            lenx2++;            tmp.y++;            tmp.x2--;        }        else if(leny>0 && !dirx1){            cout<<"Instruction: y +1; x2 +1"<<endl;            leny--;            lenx2--;            tmp.y++;            tmp.x2++;        }        else if(leny<=0 && dirx1){            cout<<"Instruction: y -1; x2 -1"<<endl;            leny++;            lenx2++;            tmp.y--;            tmp.x2--;        }        else if(leny<=0 && !dirx1){            cout<<"Instruction: y -1; x2 +1"<<endl;            leny++;            lenx2--;            tmp.y--;            tmp.x2++;        }        printPoint(tmp);    }} int main(int argc, char** argv) {    Point a;    a.x1=0;    a.x2=1;    a.y=1;    Point b;    b.x1=-3;    b.x2=-3;    b.y=3;    CellPath(a,b);    return 0;}

排序算法

枚举法

基本实现

1.1+2+3+…+n不能用while、for、if等实现(这年头什么鬼题目都有)
题目:
求1+2+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字以及条件判断语句(A?B:C)

实现:

// 递归int sum(int n){    int tmp = 1;    // 当n==1时,tmp = sum(n-1)+n不会执行,跳到return tmp;    // 当n!=1是,tmp = sum(n-1)+n会执行;    (n!=1)&&(tmp = sum(n-1)+n);    return tmp;}

关于C++类的静态变量和静态函数

// 利用类的静态变量和静态函数class Intern(){public:    Intern(){        i++;        sum += i;    }    static void Reset(){        i = 0;        sum = 0;    }    static int getSum(){        return sum;    }private:    static int i;    static int sum;       };int Sum1toN(int n){    Intern::Reset();    Intern *tmp = new Intern[100];    delete []tmp;    return Intern::getSum();}

C++模板元编程

// 这个待理解和梳理// 利用模板元编程template<int N>  struct add{      enum {result = N + add<N-1>::result};  };  template<>  struct add<0>  {      enum {result=0};  };  

C++宏

// 这个待理解和梳理// 宏// 如果Y&(1<<i)得到非0,则计算X+=(Y<<i)#define T(X, Y, i) (Y & (1<<i)) && X+=(Y<<i)int Sum1toN(int n){    int r = 0;    // 32位int整型,符号位为0所以不用担心    for(int i=0; i < 32; i++)        T(r, n, i);    // 得到的r等于n*(n+1)    // 结合公式n*(n+1)/2    return r>>1

比特位操作

字符串处理

1.写一个函数,它的原型是string continumax(string outputstring)

题目:在字符串中找到连续最长的数字串

实现:

// c++实现string continumax(string inputstring){    int maxindex = 0;    int maxlen = 0;    int len = 0;    int index =0;    for(int i=0;i<inputstring.length();i++){        if(inputstring[i]<='9' & inputstring[i]>='0'){            len++;            if(i == inputstring.length()-1){                if(len>maxlen){                     maxlen = len;                     maxindex = index;                }            }        }else{            if(len>maxlen){                maxlen = len;                maxindex = index;            }            index = i+1;            len = 0;        }           }    if(maxlen == 0)        return "";    else        return inputstring.substr(maxindex, maxlen);}

2.数字字符串转化为整数
题目:
输入一个表示整数的字符串,把该字符串转换成整数并输出。

实现:

int intstring2int(string input){    int result = 0;    for(int i=0;i<input.length();i++){        result *= 10        result += input[i]-'0';    }    return result;}

3.在一个字符串中找到第一个只出现一次的字符
题目:
在一个字符串中找到第一个只出现一次的字符。如输入abaccdeff,则输出b。

实现:

char findSingle(string input){    int hashtable[255] = {0};    // 第一次扫描,统计每个字符出现的次数   for(int i=0;i<input.length();i++)       hashtable[input[i]]++;   // 第二次扫描,找出第一个只出现一次的字符   for(int i=0;i<input.length();i++)       if(hashtable[input[i]]==1) return input[i];   return '';}

4.翻转句子中的单词顺序
题目:
“I am a student.” -> “student. a am I”
要求空间复杂度O(1),时间复杂度O(n)

思路:
压栈出栈那套做不到空间复杂度O(1)
是这样的,先把原句子翻转,然后遇到每个单词再翻转(负负得正),但是要对字符串扫描2次

实现:

void swapWord(string &word, int i, int j){    // 那些越不越界什么的,我就不搞了    while(i<j){        char tmp = word[i];        word[i] = word[j];        word[j] = tmp;    } }int swapSentence(string &s){    swapWord(s, 0, s.length()-1);    int j=0;    for(int i=0;i<s.length();i++){        if(s[i] == ' '){            swapWord(s, j, i-1);            j = i + 1;        }    }}

5.最长串接字符串
题目:
有n个长为m+1的字符串,如果某个字符串的最后m个字符与某个字符串的前m个字符匹配,则两个字符串可以联接,问这n个字符串最多可以连成一个多长的字符串,如果出现循环,则返回错误。

思路:
分析一下,将各个字符串作为一个节点,首尾链接就好比是一条边,将两个节点连接起来,于是问题就变成一个有关图的路径长度的问题。链接所得的字符串最长长度即为从图的某个节点出发所能得到的最长路径问题,与最短路径类似,可以应用弗洛伊德算法求解。对于循环,即可认为各个节点通过其他节点又回到自己,反应在路径长度上,就表示某个节点到自己节点的路径大于零(注:初始化个节点到自己的长度为零)。

Floyd算法求最长路径的核心思想:
longestPath(i, j, k-1)指的是the best path from i to j that only uses vertices 1 through k-1
并有如下关系:
longestPath(i, j, k) = max(longest(i, j, k-1), longest(i, k, k-1) + longest(k, j, k-1))

实现:

#include<iostream>#include<string>using namespace std;// m = n-1void generateMatchMatrix(string *stringSet, int n, int **w){    for(int i=0;i<n;i++){        for(int j=0;j<n;j++){            if(i==j){                w[i][j] = 0;                continue;            }            w[i][j] = 1;            for(int k=0;k<n-1;k++){                if(stringSet[j][k]!=stringSet[i][k+1]){                    w[i][j] = 0;                    break;                }               }        }    }}int longestPathLength(int n, int **w){    int max = 0;    for(int k=0;k<n;k++)        for(int i=0;i<n;i++)            for(int j=0;j<n;j++){                if(w[i][k]!=0 && w[k][j]!=0){                    int tmp = w[i][k] + w[k][j];                    if(tmp > w[i][j])                        w[i][j] = tmp;                }                 // check if loop exists                 if(i == j && w[i][j] > 0) return -1;                // record longest path length                if(w[i][j] > max){                    max = w[i][j];                }            }    // max = 0 -> single string    return max + 1;}

算法复杂度:
空间复杂度:O(n2)
时间复杂度:O(n3)

其他:

  1. 比对的时候可能可以用hash值比对,这样的效率会高些。

6.一串首尾相连的珠子(m 个),有 N 种颜色(N<=10),取出其中一段,要求包含所有 N 中颜色,并使长度最短
题目:
一串首尾相连的珠子(m 个),有 N 种颜色(N<=10),
设计一个算法,取出其中一段,要求包含所有 N 中颜色,并使长度最短。
并分析时间复杂度与空间复杂度。

思路:
从前到后进行扫描,head和tail之间刚刚好保证颜色全
然后head向前移动直到颜色不全,此时tail开始向后扫描直到颜色全
在这个过程中一直追踪最短长度

实现:

#include<iostream>#include<vector>using namespace std;int shortestFullcolor(int a[], int m, int n){    int head = 0;    int tail = 0;    int color = n;    int minLen = m;    vector<int> colorCount(n);    while(tail<m){        if(colorCount[a[tail]]==0) color--;        colorCount[a[tail]]++;          if(color==0){            while(colorCount[a[head]]>1){                colorCount[a[head]]--;                head++;            }            if(tail-head+1<minLen)                minLen = tail-head+1;            colorCount[head] = 0; // delete color a[head]            color = 1; // color a[head] is left            head++;        }        tail++;    }    return minLen;}int main(){    int a[] = {0, 1, 2, 2, 2, 3, 3, 0, 1};    // n = 4 means that there are 4 different colors represented by 0, 1, 2, 3    cout<<shortestFullcolor(a, sizeof(a)/sizeof(int), 4)<<endl;    int b[] = {0};    // n = 1 means that there is only 1 color represented by 0    cout<<shortestFullcolor(b, sizeof(b)/sizeof(int), 1)<<endl;}

算法复杂度:
1. 时间复杂度:O(m)
2. 空间复杂度:O(n)

其他:
有n种颜色(N<=10)(这个大小的约束有什么意义?)

二分法

拓扑排序

有序矩阵

整数

这个迟点再搞

  1. 给出一个整型数组,按数字出现频率高低进行排序。比如说[1, 2, 3, 2, 2, 3],变为[2, 2, 2, 3, 3, 1]

  2. 假设你已经能求出数组的连续最大和子数组(提供现成函数),现在给出一个整形数组,求找出两个不相交子数组,使得它们的差最大。
    理解1:
    思路很简单:

  3. 先找出数组的连续最大和字数组
  4. 然后把数组的元素都取反,再找出变换后连续最大和字数组
  5. 然后把1,2得到数组中相同的元素去掉即可

理解2:
先从左到右扫一遍,求出从开始到每个位置的最大(最小)和,然后再从右到左扫一遍,求出从后面到每个位置的最大(最小)和,在从右到左扫的时候就可以求出结果了。