常见算法题
查找相关
二分查找
- 基本思路:对于排序数组,在给定区间内,先将待查找的元素与中间的元素比较,若小,则将查找区间缩小为左半部分;若大,则将查找区间缩小为右半部分;直到找到该元素,或查找区间为空。
排序相关
归并排序
- 基本思路:给定一个数组,将数组区间一分为二,分别对两个区间进行排序,最后将两个有序区间合并成一个。采用递归,递归结束条件为区间只包含一个元素,不需要进行划分。
void mergeSort(int arr[], int tmp[], int start, int end) { // 调用 merge(arr, tmp, 0, len-1);
if(start>=end) return; // 当只有一个元素时,停止递归
int mid=(start+end)/2; // 将区间一分为二
int s1=start, e1=mid;
int s2=mid+1, e2=end;
mergeSort(arr, tmp, s1, e1);
mergeSort(arr, tmp, s2, e2);
int k=start;
while(s1<=e1 && s2<=e2)
tmp[k++]=arr[s1]<=arr[s2]?arr[s1++]:arr[s2++];
while(s1<=e1) // 该组数据有剩余
tmp[k++]=arr[s1++];
while(s2<=e2) // 第二组数据有剩余
tmp[k++]=arr[s2++];
for(k=start; k<=end; ++k)
arr[k]=tmp[k];
}
合并两个有序数组
// 需要用到额外数组
void merge( int a[], int b[], int n1, int n2, int res[]){
for(int i=0; i<n1; ++i)
L[i]=a[i];
for(int j=0; j<n2; ++j)
R[i]=b[j];
L[n1]=INT_MAX; R[n2]=INT_MAX; // 设置终点,防止数组越界
int i=0, j=0;
for(int k=0; k<n1+n2;++k){
if ( L[i] <= R[j] ) res[k]=L[i++];
else res[k]=R[i++];
}
}
// 两个有序数组A,B,将B插入A仍保持有序
// 思路:可以从后往前依次比较再插入,可以避免复制或重复移动数组A
快速排序
- 主要思想:每次选一个主元,将区间分为两部分,左边小于该值,右边大于该值。
动态规划
01背包问题
- 描述:给定N个物体和最大载重量为C的背包,求装入最大价值(物体只能选择装入或者不装入)
- 思路:如果是部分背包问题,即可以将物体的部分装入,可用贪心法,计算单位物体价值,然后排序,按单位价值从大到小依次装入;对于01背包问题,用动态规划法,result[i][j]表示前i件物体装入承重为j的容器中,求result[N][C]。得到状态转移方程:
j<w[i]时,第i件物体选择不装入,result[i][j]=result[i-1][j]
j>=w[i]时,若不装为result[i-1][j],若装为result[i-1][j-w[i]]+v[i],取两者间的最大值result[i][j]=max(result[i-1][j], result[i-1][j-w[i]]+v[i])
int getLargestValue(vector<int>& v, vector<int>& w, int c){
vector<int, vector<int> > result(v.size(), vector<int>(c+1, 0));
for(int i=1; i<=v.size(); ++i){
for(int j=1; j<=c; ++j){
if(j<w[i-1]){
result[i][j]=result[i-1][j];
}
else{
result[i][j]=max(result[i-1][j], result[i-1][j-w[i-1]]+v[i-1]);
}
}
}
return result[v.size()][c];
}
数组
找出数组中任意一个重复的数(剑指面试题3)
- 描述:含 n n n个数的数组,元素值在 [ 0 , n − 1 ] [0,n-1] [0,n−1]之间,可能存在一个或多个重复的数,请找出任意一个重复的数。
- 思路:用排序时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn);用哈希表时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( n ) O(n) O(n);依次扫描数组,若不在正确位置,则通过交换将其放入正确位置,以此对数组进行重排,直到排序过程中发现重复的数,时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( 1 ) O(1) O(1)。
bool isDuplication=true;
int getDuplication(int arr[],int n){
if(arr==nullptr || n<=1){
isDuplication=false;
return 0;
}
for(int i=0;i<n;++i){
while(arr[i]!=i){
int tmp=arr[i];
if(arr[tmp]==tmp){
isDuplication=true;
return tmp;
}
else{
arr[i]=arr[tmp];
arr[tmp]=tmp;
}
}
}
isDuplication=false;
return 0;
}
二维数组中的查找
- 描述:在一个二维数组中查找某个数,已知二维数组按行递增,按列递增
- 思路:从右上角的元素开始比较,若大于该元素,则去掉该行;若小于该元素,则去掉当前列。
bool findNumberInMatrix(int arr[], int key, int rows, int cols){
bool find=false;
if(arr!=nullptr && rows>0 && cols>0 ){
int row=0;
int col=cols-1;
while(row<rows && col>=0){
if(key<arr[row][col]){
--col;
}
else if(key>arr[row][col]){
++row;
}
else{
find=true;
break;
}
}
}
return find;
}
把数组排成最小的数(剑指面试题45)
- 描述:给定一个整数数组例如{3,32,321},用数组元素拼接成一个数,求拼接成的最小数(321323)。
- 思路:主要需要决定谁排前谁排后的问题,定义一个比较函数,对于两个数m和n,若mn<nm,则应该m排在n的前面。由于没说整数的位数,因此是隐形的大数问题,使用字符串。
const int g_MaxNumberLength = 10; // 假设数组元素的最大位数为10位
char* g_StrCombine1 = new char[g_MaxNumberLength * 2 + 1]; // 两个数合并后需要的位数
char* g_StrCombine2 = new char[g_MaxNumberLength * 2 + 1];
int compare(const void* strNumber1, const void* strNumber2)
{
// 自定义一个排序规则
// 返回值<0表示strNumber1排在strNumber2的前面,返回值>0表示strNumber1排在strNumber2的后面,=0表示二者相等
strcpy(g_StrCombine1, *(const char**)strNumber1);
strcat(g_StrCombine1, *(const char**)strNumber2);
strcpy(g_StrCombine2, *(const char**)strNumber2);
strcat(g_StrCombine2, *(const char**)strNumber1);
// strcmp(str1,str2):若str1<str2,返回负数;若str1=str2,返回0;若str1>str2,返回正数
return strcmp(g_StrCombine1, g_StrCombine2);
}
void PrintMinNumber(int* numbers, int length)
{
if (numbers == nullptr || length < 0)
return;
char** strNumbers = new char*[length]; // 创建一个二维字符数组,每一行以字符形式存储一个数
for (int i = 0; i < length; ++i) {
strNumbers[i] = new char[g_MaxNumberLength + 1];
sprintf(strNumbers[i], "%d", numbers[i]);
}
// 指针类型在32位编译器下占4个字节,在64位编译器下占8个字节。
//调用库函数来排序
//因为字符串数组里面保存的都是指针
//所以size在传参的时候要传指针的大小
qsort(strNumbers, length, sizeof(char*), compare); // 数组排序函数:qsort(数组首地址,数组元素个数,每个元素大小,比较函数)
for (int i = 0; i < length; ++i)
printf("%s", strNumbers[i]); // 依次输出
printf("\n");
for (int i = 0; i < length; ++i)
delete[] strNumbers[i];
delete[] strNumbers;
}
字符串
字符串的排列
- 基本思路:首先求出所有可能出现在第一个位置的字符,然后逐个与后面的字符交换;固定第一个字符,然后求后面的全排列。(采用递归,将字符串分为两部分,第一个字符与后面的字符)
void permutation(char* str, char* pBegin){
if(str==nullptr || pBegin==nullptr) return;
if(*pBegin=='\0') {
cout << *str << endl;
}
else {
for(char* ch=*pBegin; *ch != '\0' ; ++ch){
char tmp = *pBegin; // 将第一个字符与后面的字符交换
*pBegin = *ch;
*ch = tmp;
permutation(str, pBegin+1);
tmp = *pBegin; // 将前面的交换再换回来,以便继续将第一个字符与其他字符交换
*pBegin = *ch;
*ch = tmp;
}
}
}
将字符串转换为整数
// 如何处理非法输入?
// 如何处理正负号
// 如何处理溢出
bool g_isNumber = true;
int strToInt(char* string){
if(string==nullptr){
g_isNumber = false;
return 0;
}
int sign=1;
if(*string=='-'){
sign=-1;
++string;
}
else if(*string=='+'){
sign=1;
++string;
}
int num=0;
while(*string!='\0'){
char c=*string;
if(c>='0' && c<='9'){
num=num*10+(*string)-'0';
++string;
}
else{
g_isNumber=false;
return 0;
}
}
g_isNumber=true;
return num*sign;
}
替换空格(剑指面试题5)
- 描述:给定一个字符串A,将A中的空格替换为"%20",要求空间复杂度为O(1),时间复杂度为O(n)。
- 思路:先扫描字符串,统计空格的个数,然后计算出替换后的总长度;依次从后往前替换。
// length为字符数组的总长度
void replaceBlank(char* string, int length){
if(string==nullptr || length<=0) return;
int originalLength=0;
int numberOfBlank=0;
int i=0;
while(string[i]!='\0'){
++originalLength;
if(string[i]==' ') ++numberOfBlank;
++i;
}
int newLength=originalLength+numberOfBlank*2;
if(newLength>=length)return;
int indexOfOriginal=originalLength;
int indexOfNew=newLength;
while(indexOfOriginal>=0 && indexOfNew>indexOfOriginal){
if(string[indexOfOriginal]==' '){
string[indexOfNew--]='0';
string[indexOfNew--]='2';
string[indexOfNew--]='%';
}
else{
string[indexOfNew--]=string[indexOfOrigin];
}
--indexOfOriginal;
}
}
链表
求链表倒数第k个结点
基本思路:倒数第k个结点到最后一个结点需要走k-1步,第一个结点到最后一个结点需要走n-1步,因此需要从第一个结点走 n − k n-k n−k步才能走到倒数第k个结点。第一阶段,我们可以先让一个指针从第一个结点开始走k-1步,第二阶段,再让另一个指针从第一个结点开始 和 该指针一起走,直到该指针到达最后一个结点,另一个指针指向的结点就是倒数第k个结点,(因为第二阶段走了 n − k n-k n−k步)
// k=0如何处理?
// k>n如何处理?
ListNode* findKthToTail(ListNode* pHead, int k){
if(pHead==nullptr || k<=0) return nullptr;
int length=0;
ListNode* pA=pHead;
ListNode* pB=pHead;
while(pA!=nullptr){ // 计算链表结点总个数
++length;
pA=pA->next;
}
if(k>length) return length;
pA=pHead;
for(int i=0;i<k-1;++i){
pA=pA->next;
}
while(pA->next != nullptr){
pA=pA->next;
pB=pB->next;
}
return pB;
}
树
重建二叉树(剑指面试题7)
- 描述:给定先序遍历和中序遍历结果,重建二叉树
- 思路:先序遍历的第一个数为根结点,然后在中序遍历中找到该结点,该节点的左半部分为其左子树,设结点数为l1,右半部分为其右子树,设结点数为l2;先序遍历根节点后跟着的l1个数为左子树,接着的l2个数为右子树;再通过递归重建左子树和右子树。
BiNode* construct(int* preOrder, int* inOrder, int length){
if(preOrder==nullptr || inOrder==nullptr || length<=0)
return nullptr;
return constructCore(preOrder, preOrder+length-1, inOrder, inOrder+length-1);
}
BiNode* constructCore(int* startPreOrder, int* endPreOrder, int* startInOrder, int* endInOrder){
int rootValue=startPreOrder[0]; // 先序遍历的第一个数为根结点
BiNode* root=new BiNode();
root->value=rootValue;
root->left=root->right=nullptr;
if(startPreOrder==endPreOrder){ // 递归出口:先序遍历集合中只有一个元素
if(startInOrder==endInOrder && *startPreOrder=*startInOrder){
return root;
}
else{
throw std::exception("Invalid Input.");
}
}
int* rootInOrder=startInOrder;
while(rootInOrder<=endInOrder && *rootInOrder!=rootValue){
++rootInOrder; // 在中序遍历中找到根节点
}
if(rootInOrder>endInOrder){
throw std::exception("Invalid Input.");
}
int leftLength=rootInOrder-startInOrder; // 计算左子树结点个数
int* leftPreOrderEnd=startPreOrder+leftLength; // 计算左子树区间
if(leftLength>0){
root->left=constructCore(startPreOrder+1, leftPreOrderEnd, startInOrder, rootInOrder-1); // 重建左子树
}
if(leftLength<endPreOrder-startPreOrder){
root->right=constructCore(leftPreOrderEnd+1, endPreOrder, rootInOrder+1, endInOrder); // 重建右子树
}
return root;
}
中序遍历中的下一个结点(剑指面试题8)
- 描述:给定二叉树的某个结点,求其在中序遍历中的下一个结点。
- 思路:结点结构需要保存父结点。若该结点存在右子树,则找到右子树的最左结点;否则,找到其祖先结点中为父结点的左子树的结点,则下一个结点为这个结点的父结点。
BiNode* getNext(BiNode* pNode){
if(pNode==nullptr) return nullptr;
BiNode* pNext=nullptr;
if(pNode->right!=nullptr){
BiNode* pRight=pNode->right;
while(pRight->left!=nullptr)
pRight=pRight->left;
pNext=pRight;
}
else if(pNode->parent!=nullptr){
BiNode* pCurrent=pNode;
BiNode* pParent=pNode->parent;
while(pParent!=nullptr && pParent->right==pCurrent){
pCurrent=pParent;
pParent=pParent->parent;
}
pNext=pParent;
}
return pNext;
}