常见小问题(待更新)

14 篇文章 0 订阅

1、求一组数中第K大或第K小的数

思路:使用快速排序

#include <iostream>
#include <ctime>

using namespace std;

const int ARRAY_LEN = 100;
int iarray[ARRAY_LEN];

int divide(int low, int high)
{
	if(low>=high)return low;

	int pvoit = iarray[low];
	while (low<high)
	{
		while (iarray[high]>=pvoit&&low<high)
		{
			high--;
		}
		if(low<high)
		{
			iarray[low] = iarray[high];
			low++;
		}
		while (iarray[low]<=pvoit&&low<high)
		{
			low++;
		}
		if(low<high)
		{
			iarray[high] = iarray[low];
			high--;
		}
	}

	iarray[low] = pvoit;

	return low;
}

int myQsort(int low, int high, int k)
{
	int mid = divide(low, high);
	int res;
	if(k<mid)
		res = myQsort(low, mid-1, k);
	else if(k>mid)
		res = myQsort(mid+1, high, k);
	else
	{
		res = mid;
	}
	return res;
}

int main()
{
	srand(time(NULL));

	//for(int j=0; j<1000; ++j)
	{
		for(int i=0; i<20; ++i)
		{
			iarray[i] = rand()%100;
		}

		int k = 3;
		//int index = myQsort(0, 19,20-k);//寻找第k大的数(从1计数)
		int index = myQsort(0, 19, k-1);//寻找第K小的数(从1计数)

		cout << index << endl;
		cout << iarray[index] << endl;

	}

	system("pause");
	return 0;
}

2、求N的阶乘十进制数最后有多少个0

思路:num = N/5 + N /(5*5) + N/(5*5*5).....知道N/(5的K次方)等于0, 求1-n能分解出多少5(2*5==10而2的个数明显多于5)

 
#include <iostream>

using namespace std;

int main()
{
	int n;
	int num=0;
	
	while(cin>>n&&n)
	{
		num=0;	
		while(n)
		{
	    	n /= 5;
			num += n;
		}
		 
		cout << num << endl;
	}
	return 0;
}

3、KMP(字符串模式匹配)

KMP算法保证了在模式串和主字符串匹配时,主字符串不 回溯,其主要思想就是寻找模式字符串中当前字符前面邻近的字符与模式串开头有多少个是匹配的,如下式:


注:字符串下标从0开始

完整的代码:

#include <iostream>

using namespace std;

void getNext(char* T, int next[])
{
	int i = 0;
	int j = -1;
	int len = strlen(T);
	next[0] = -1;
	
	while (i<len-1)
	{
		if(-1==j || T[i]==T[j])
		{
			++i;
			++j;
			if(T[i]!=T[j])
			{
				next[i] = j;
			}
			else
			{
				next[i] = next[j];
			}
		}
		else
		{
			j = next[j];
		}
	}
}

int KMP(char* S, char* T, int pos, int next[])
{
	int i = pos;
	int j = -1;
	int lenS = strlen(S);
	int lenT = strlen(T);

	while (i<lenS && j <lenT)
	{
		if(-1==j || S[i]==T[j])
		{
			++i;
			++j;
		}
		else
		{
			j = next[j];
		}
	}
	if(j==lenT)
		return (i-lenT);

	return -1;
}

int main()
{
	char str1[] = "acabaabaabcacaabc";
	char str2[] = "abaabc";
	int next[100];

	getNext(str2, next);
	cout << KMP(str1, str2, 0, next) << endl;

	system("pause");
	return 0;
}


4、二叉树建立与遍历

思路:1、先序递归建立二叉树,2、先、中、后序非递归遍历二叉树 3、求其二叉树深度宽度

#include <iostream>
#include <stack>
#include <queue>

using namespace std;

typedef struct _BiTree
{
	char val;
	_BiTree* lchild;
	_BiTree* rchild;
}*BiTree, BiNode;

//注意指针本身是一个变量,因此这里需要加上&,
//(若不加T的值在返回被调函数时不发生变化,发生变化的仅是指针存放的那个地址里面所存放的值)
bool CreateBiTree(BiTree& T)
{
	char ch;
	scanf("%c", &ch);
	if('*' == ch)
	{
		T = NULL;
	}
	else
	{
		if(!(T=(BiNode*)malloc(sizeof(BiNode))))
		{
			exit(0);
		}
		T->val = ch;
		CreateBiTree(T->lchild);
		CreateBiTree(T->rchild);
	}

	return true;
}

//以下三个函数实现非递归先、中、后遍历二叉树
bool PreOrderTraverse(BiTree T)
{
	stack<BiNode*> S;
	BiNode* p = T;
	while (p || !S.empty())
	{
		if(p)
		{
			cout << p->val << " ";
			S.push(p);
			p = p->lchild;
		}
		else
		{
			p = S.top();
			S.pop();
			p = p->rchild;
		}
	}

	cout << endl;
	return true;
}

bool InOrderTraverse(BiTree T)
{
	stack<BiNode*> S;
	BiNode* p = T;
	while (p || !S.empty())
	{
		if(p)
		{
			S.push(p);
			p = p->lchild;
		}
		else
		{
			p = S.top();
			S.pop();
			cout << p->val << " ";
			p = p->rchild;
		}
	}
	cout << endl;
	return true;
}

int dep = 0;
bool PastOrderTraverse(BiTree T)
{
	stack<BiNode*> S;
	BiNode* p = T;
	BiNode* pre = NULL;
	while (p || !S.empty())
	{
		if(p)
		{
			S.push(p);
			p = p->lchild;
		}
		else
		{
			p = S.top();
			if(p->rchild!=NULL&&pre!=p->rchild)//第二个条件保证P的右孩子没有访问过
			{
				p = p->rchild;
			}
			else
			{
				if(dep<S.size())//可以顺便求取二叉树的深度
				{
					dep = S.size();
				}
				S.pop();
				pre = p;
				cout << p->val << " ";
				p = NULL;//已经输出了这个元素,需要再退一个栈因此p设为NULL
			}
		}
	}
	cout << endl;
	return true;
}

//递归获取二叉树的深度(非递归方法可以通过非递归后序遍历法获得)
int GetBiTreeDepth(BiTree T)
{
	if(NULL==T)
	{
		return 0;
	}

	int lDepth = GetBiTreeDepth(T->lchild);
	int rDepth = GetBiTreeDepth(T->rchild);

	return ((lDepth>rDepth) ? (lDepth+1) : (rDepth+1));
}

//求二叉树的宽度(广度遍历的过程)
int GetWidth(BiNode *pRoot)
{
	if (pRoot == NULL)
	{
		return 0;
	}

	int nCurLevelWidth = 1;   //记录当前层的宽度(初始化为根那一层为1)
	int nWidth = 1;           //二叉树的宽度(初始化为根那一层为1)
	queue<BiNode *> myQueue;
	myQueue.push(pRoot);      //将根节点入队列
	
	BiNode *pCur = NULL;

	while (!myQueue.empty())//队列不空
	{
		for(int i=0; i<nCurLevelWidth; ++i)
		{
			pCur = myQueue.front();  //取出队列头元素
			myQueue.pop();

			if (pCur->lchild!= NULL)
			{
				myQueue.push(pCur->lchild);
			}

			if (pCur->rchild != NULL)
			{
				myQueue.push(pCur->rchild);
			}

		}

		nCurLevelWidth = myQueue.size();
		nWidth = nCurLevelWidth > nWidth ? nCurLevelWidth : nWidth;
	}

	return nWidth;
}


int main()
{
	BiTree T = NULL;
	CreateBiTree(T);

	PreOrderTraverse(T);
	InOrderTraverse(T);
	PastOrderTraverse(T);
	cout << dep << endl;

	cout << "Depth = " << GetBiTreeDepth(T) << endl;
	cout << "Width = " << GetWidth(T) << endl;

	system("pause");
	return 0;
}


输入样例:ABC**DE*G**F***

5、求一个数组中连续子数组和最大或乘积最大

#include<stdio.h>
#include <string.h>
#include <Windows.h>
#include <iostream>
#include <vector>

using namespace std;

class Solution {
public:
	double maxSum(vector<double> arr)
	{
		if(0==arr.size())
			return 0;
		double subSum = arr[0];
		double tmp = arr[0];
		for(int i=1; i<arr.size(); ++i)
		{
			if(tmp>0)
			{
				tmp += arr[i];
			}
			else
			{
				tmp = arr[i];
			}
			if(tmp>subSum)
			{
				subSum = tmp;
			}
		}

		return subSum;
	}
	double maxProduct(vector<double> arr) {
		if(0==arr.size())
			return 0;

		double minPre = arr[0];
		double maxPre = arr[0];
		double maxProuct = arr[0];
		double minCur, maxCur;

		for(int i=1; i<arr.size(); ++i)
		{
			maxCur = gmax(arr[i], gmax(arr[i]*maxPre, arr[i]*minPre));
			minCur = gmin(arr[i], gmin(arr[i]*maxPre, arr[i]*minPre));

			if(maxCur>maxProuct)
			{
				maxProuct = maxCur;
			}
			maxPre = maxCur;
			minPre = minCur;
		}

		return maxProuct;
	}
private:
	double gmax(double x, double y)
	{
		if(x>y)
		{
			return x;
		}
		else
		{
			return y;
		}
	}
	double gmin(double x, double y)
	{
		if(x<y)
		{
			return x;
		}
		else
		{
			return y;
		}
	}
};

int main(void)
{
	double darr1[] = {-0.1};
	vector<double> vec;
	vec.push_back(-0.1);
	Solution sol;
	cout << sol.maxProduct(vec) << endl;
	
	system("pause");
	return 0;
}

6、约瑟夫环问题

(1)最原始约瑟夫环问题
描述:给定两个数n, m从1开始数当数到m就把当前数去掉,然后再从1开始数超过n则从头继续数,直到最后只剩一个数输出这个数是哪个。
原数列:
1, 2, 3, ... , n-1, n                                           (1)
设 k = m%n,则k是第一个去掉的数,则剩下的数为
1, 2, 3, .... k-1, k+1, .... n                               (2)
这个数列从k+1又从新排成一个数列
k+1, k+2, ... n,  1, 2, ...., k-1                          (3)
这个相当于n-1个数的约瑟夫环问题,我们把这个数列重新计数为
1,2,3, ... , n-2, n-1                                          (4)
假设我们直到第四个数列的约瑟夫环问题解为x4,那么我们就可以直到数列(3)的约瑟夫环问题解x3 = (x4+k)%n,也即是数列(1),(2)的解 x1=x2 = (x4+k)%n; = (x4+m%n)%n = (x4+m)%n,  设F(n)为n个数的约瑟夫环问题,那么F(n-1)为
n-1个数的约瑟夫环问题,那么有F(n) = (F(n-1)+m)%n, F(1) = 0; 则n个数的约瑟夫环问题解为F(n)+1.(这里F(1)=0只是因为我们需要对n求模我们可以把数列(1)看出是0到n-1哈,下文代码中有把初始值设为1的情况)。
代码实现:
#include <iostream>

using namespace std;

int main()
{
	int n, m;
	while(cin>>n>>m)
	{
		int s = 0;
		for(int i=2; i<=n; ++i)
		{
			s = (s+m)%i;
		}
		cout << s+1 << endl;
		
		s = 1; //当我们把初始值设为1时要特别注意等于0的情况,其实是最后一个元素 
		for(int i=2; i<=n; ++i)
		{
			s = (s+m)%i;
			if(s==0)s=i;
		}
		if(s==0)cout << n << endl;
		else cout << s << endl;
	}
	return 0;
}

(2)约瑟夫环问题变种一
现在开始数数的位置不是从1开始了,而是从第k个开始
1,2,3,... , ... n-1, n   假设k= 3
问题可以变为
3,4,... , n-1,n, 1, 2我们还可以利用映射把它映射回1,2,...,n-1,n求出这个数列的值后再映射回去(其实我们在计算时都是从0开始计数的也即在0,1,2,...,n-2,n-1中找,找到了在加一,这样做是因为在%时可以区分开起始端和结束端0,n)
代码如下:
#include <iostream>

using namespace std;

int main()
{
	int n, m, k;
	while(cin>>n>>m>>k)
	{
		int s = 0;
		for(int i=2; i<=n; ++i)
		{
			s = (s+m)%i;
		}
		s = (s+k-1)%n;
		cout << s+1 << endl;
	}
	return 0;
}


(3)约瑟夫环变种二
报数前把第k个去掉,从第k+1个开始,这个问题可以转化为变种二,假设为从某个位置开始数到第K个刚好为m
代码如下:
#include <iostream>

using namespace std;

int main()
{
	int n, m, k;
	while(cin>>n>>m>>k)
	{
		int s = 0;
		for(int i=2; i<=n; ++i)//无论哪种变种,都是在编号为0到n-1中先找到那个元素然后再变回到1到n中
		{
			s = (s+m)%i;
		}
		s = (s+(k-m+n))%n;
		cout << s+1 << endl;
	}
	return 0;
}

约瑟夫环总结:对于n个数数到m,都是先变换到0,1,..., n-2,n-1, 每次从0开始数数到m-1,得到这个数列的最终解,然后再映射到此数列的变种上,然后再+1映射回1,2,3,...,n上。

7、从n个数中等概率取m个数

代码:
#include <iostream>
#include <cstdlib>

using namespace std;

int main()
{
	int n, m;
	while(cin>>n>>m)
	{
		int radix = n;
		int tm = m;
		for(int i=0; i<n; ++i)
		{
			if(rand()%radix < tm)
			{
				cout << i << " ";
				tm--;
			}
			radix--;
		}
		cout << endl;
	}
	return 0;
}

对数组进行随机打乱顺序:
#include <iostream>
#include <cstdlib>

using namespace std;

int main()
{
	int array[10] ={0,1,2,3,4,5,6,7,8,9};
	int size = 10;
	for(int i=0; i<size; ++i)
	{
		swap(array[i], array[i+rand()%(size-i)]);
	}
	
	for(int i=0; i<size; ++i)
	{
		cout << array[i] << " ";
	}
	cout << endl;
	return 0;
}

8、快速求(n^p)%m

int GetNPowerModM(int n, int p, int m)
{
	unsigned k = 1;
	n %= m;
	while(p!=1)
	{
		if(p&1)k = (k*n)%m;
		n = (n*n)%m;
		p >>= 1;
	}

	return (n*k)%m;
}

9、数组中求出现奇数次或一次的数

(1)数组中只有一个数出现了奇数次,其他数都出现了偶数次,求这个数
思路:遍历整个数组,把所有数异或起来,得到的结果就是所要求的数。
(2)数组中只有两个数出现了奇数次,其他数都出现了偶数次,求出现奇数次的两个数
思路:假设出现奇数的这两个数为a, b,那么把整个数组异或起来得到的就是a^b,因为a不等于b那么a^b不等于0,寻找a^b二进制从右起第一个为1的位,因为a不等于b所有a,b中这一位一个为1,一个为0,根据这个特点把原数组画为两部分,这两部分各自数异或的值就是a,b
代码:
#include <iostream>

using namespace std;

int array[] = {1,1,2,3,3,4,4,5,5,6,6,7,8,8};

int lowbit(int n)  //求二进制最后一个1的位置,比如0110100的结果为0000100 
{
	return (n&(-n));
}

int main()
{
	int len = sizeof(array)/sizeof(array[0]);
	int x = 0;
	for(int i=0; i<len; ++i)
	{
		x ^= array[i];
	}
	
	int x_lowbit = lowbit(x);
	int a = 0, b = 0;
	for(int i=0; i<len; ++i)
	{
		if(x_lowbit&array[i])
		{
			a ^= array[i];
		}
		else
		{
			b ^= array[i];
		}
	}
	cout << a << " " << b << endl;
	
	return 0;
}

(3) 数组中只有三个数出现了奇数次,其他数都出现了偶数次,求出现奇数次的三个数
思路:设这三个数为a,b,c,把整个数组异或起来的结果为x = a^b^c;因为a,b,c各不相同,所以x^a,x^b,x^c都不为0,易知y = lowbit(x^a)^lowbit(x^b)^lowbit(x^c)的值二进制中至少有一个1,求y中从右起第一个1的位置,y_lowbit=lowbit(y),容易知道x^a,x^b,x^c中有三个或者一个在y_lowbit位上为1,若三个都在此位上为1,那么a,b,c在此位上相同,若a,b,c在此位上都为0那么x在这一位也为0那么y_lowbit也应该为0,矛盾;若a,b,c在此位上为1,那么x在此位上也为1,那么x^a,x^b,x^c在此位上都应该为0,矛盾;因此x^a,x^b,x^c只有一个在此位上为1,假设是x^a在此外上为1,那么根据此特点就可以把x^a求出来,先把数组每个元素与x异或然后根据y_lowbit位是否为1分为两部分,为1的部分异或的值最后再与x异或就是a的值,这样就剩下两个数了转换成了(2)中的问题。
代码:
#include <iostream>

using namespace std;

int array[] = {1,1,2,3,3,4,5,5,6,6,7,8,8};

int lowbit(int n)  //求二进制最后一个1的位置,比如0110100的结果为0000100 
{
	return (n&(-n));
}

int main()
{
	int len = sizeof(array)/sizeof(array[0]);
	int x = 0;
	for(int i=0; i<len; ++i)
	{
		x ^= array[i];
	}
	
	int y = 0;
	for(int i=0; i<len; ++i)
	{
		y ^= lowbit(x^array[i]);
	}
	
	int y_lowbit = lowbit(y);
	int xa = 0, bc = 0;
	for(int i=0; i<len; ++i)
	{
		if(y_lowbit&(x^array[i]))
		{
			xa ^= (x^array[i]);
		}
		else
		{
			bc ^= (x^array[i]);
		}
	}
	
	int a = x^xa;
	int b = 0;
	int c = 0;
	
	int bc_lowbit = lowbit(bc);
	if(a&bc_lowbit)
	{
		b ^= a;
	}
	else 
	{
		c ^= a;
	}
	
	for(int i=0; i<len; ++i)
	{
		if(bc_lowbit&array[i])
		{
			b ^= array[i];
		}
		else 
		{
			c ^= array[i];
		}
	}
	
	cout << a << " " << b << " " << c << " " << endl;
	
	return 0;
}

(4)一个数组中只有一个数出现了一次,其他数都出现了三次,求出现一次的数
思路:以32位系统为例,一个int数有32位,我们只需统计所有数每一位上数出现次数mod3的值为多少就可以知道只出现一次的那个数。利用位运算,遍历数组用ones记录到当前,各个位上出现1次数mod3为1位置,twos记录当前各个位上mod3位2位置。代码如下:
#include <iostream>

using namespace std;

int array[] = {1,1,1,3,3,3,4,5,5,5,8,8,8};

int main()
{
	int len = sizeof(array)/sizeof(array[0]);
	
	int ones = 0;
	int twos = 0;
	int mask = 0;
	
	for(int i=0; i<len; ++i)
	{
		//当前数array[i]各个二进制位上的与以往累积ones各个位上
		//若同时为1则说明此位到目前位置1的个数为2个
		twos ^= (ones&array[i]); 
		//把array[i]中新增出现一次的加到ones中  
		ones ^= array[i];
		//若ones与twos二进制某位同时1则为3的倍数清零 
		mask = ~(ones&twos);
		twos &= mask;
		ones &= mask;
	}
	cout << ones << endl;//若是求出现两次的数则输出twos
	
	return 0;
}

在给一个例子:数组总所有数出现了5次,只有一个数出现了4次
#include <iostream>

using namespace std;

int array[] = {1,1,1,1,1,4,4,4,4,5,5,5,5,5};

int main()
{
	int len = sizeof(array)/sizeof(array[0]);
	
	int ones = 0;
	int twos = 0;
	int threes = 0;
	int mask = 0;
	
	for(int i=0; i<len; ++i)
	{
		threes ^= (twos&ones&array[i]); 
		twos ^= (ones&array[i]);
		ones ^= array[i];
		mask = ~(ones&~twos&threes);
		threes &= mask;
		twos &= mask;
		ones &= mask;
	}
	cout << threes << endl; //4的二进制位0100它有1的为是在右起第三个上因此输出threes
	
	return 0;
}
扩展:
给定一个数组,只有一个元素出现了p(p>=1)次,其他元素都出现了k(k>1)次,且p%k!=0,求出现p次的那个数。

a. 若k为偶数,p为奇数那么整个数组异或即可得到结果
b. 考虑k为奇数的情况,设k的二进制位共有m位,表示为kmkm-1...k2k1,xm,xm-1,...,x2,x1记录各个位上到目前位置出现1的次数(注意这里相当于对k求过余后的次数),那么掩码mask = ~(x1' & x2' &....&xm'),其中当ki=1时xi'=xi,当ki=0时xi'=~xi;

则算法如下:
for(int i=0; i<len; ++i)
{
xm ^= (xm-1&xm-2&...&array[i]);
xm-1 ^= (xm-2&xm-3&...&array[i]);
...
x1 ^= array[i];
mask = ~(x1' & x2' & ... xm');
xm &= mask;
...
x1 &= mask;
}

最后那个值是结果要看p的值,比如p=3二进制位011那么返回x1或x2(即是1的位置),若p=4,二进制位100返回x3
注意需要几个xm看K至少需要几位二进制可以表示。

10、求2000000以内的素数

代码如下:
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;

const int MAX_SIZE = 2000001;
int primes[15000];
bool total[MAX_SIZE];

void compute()
{
	total[0] = total[1] = true;
	int t = sqrt(MAX_SIZE);
	int k = 0;
	for(int i=2; i<t; ++i)
	{
		for(int j=i*i; j<MAX_SIZE; j+=i)
		{
			total[j] = true;
		}
		
	}
	
	for(int i=2; i<MAX_SIZE; ++i)
	{
		if(!total[i])
		{
			primes[k++] = i;
		}	
	}
}

int main()
{
	int N;
	compute();
	while(scanf("%d", &N) && N)
	{
		if(primes[0]<=N)
		{
			printf("%d", primes[0]);
		}
		for(int i=1; primes[i]<=N; ++i)
		{
			printf(" %d", primes[i]);
		}
		printf("\n");
	}
	return 0;
}
 
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <vector>
#include <climits>
#include <ctype.h>
#include <cmath>
#include <algorithm>
#define LL long long
using namespace std;
bool nPrime[2000001];
int prime[148940],tot = 0;
void getPrime(){
    int i,j;
    LL temp;
    for(i = 2; i < 2000001; i++){
        if(!nPrime[i]) prime[tot++] = i;
        //若i是素数则j会增加到tot,也即让i与已经求出的每一个素数相乘
		//若i是合数,那么只需找到i%prime[j]即可,因为其后的可以通过是prime[j+1]的倍数求 
        for(j = 0; j < tot && (temp = (LL)i*prime[j]) < 2000001; j++){
            nPrime[temp] = true;
            if(i%prime[j] == 0) break; //prime[j]是i的最小素因子时结束循环
        }
    }
}
int main(){
    getPrime();
    int kase,x,i;
    while(scanf("%d",&x),x){
        for(i = 0; prime[i] <= x && i < tot; i++)
            printf("%d ",prime[i]);
        putchar('\n');
    }
    return 0;
}
        

以上两种方法运行速率差不多。

注:以上代码均是在Win7+VS2012环境中运行

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值