常见算法题

查找相关

二分查找

  • 基本思路:对于排序数组,在给定区间内,先将待查找的元素与中间的元素比较,若小,则将查找区间缩小为左半部分;若大,则将查找区间缩小为右半部分;直到找到该元素,或查找区间为空。

参考这里。

排序相关

归并排序

  • 基本思路:给定一个数组,将数组区间一分为二,分别对两个区间进行排序,最后将两个有序区间合并成一个。采用递归,递归结束条件为区间只包含一个元素,不需要进行划分。
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];
}		

参考这里。
归并排序(C++递归实现)

合并两个有序数组

// 需要用到额外数组
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,n1]之间,可能存在一个或多个重复的数,请找出任意一个重复的数。
  • 思路:用排序时间复杂度 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 nk步才能走到倒数第k个结点。第一阶段,我们可以先让一个指针从第一个结点开始走k-1步,第二阶段,再让另一个指针从第一个结点开始 和 该指针一起走,直到该指针到达最后一个结点,另一个指针指向的结点就是倒数第k个结点,(因为第二阶段走了 n − k n-k nk步)

// 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;
}

求连通域

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值