面试8——手撕算法

1.链表有无环判断

//快慢指针
bool LoopList_is( ListNode *List)
{
	if(!List) return false;
	ListNode *slowPointer,*fastpointer;
	slowpointer = fastpointer = List;
	while(slowpointer != null && fastpointer == null)
	{
		slowpointer = sloepointer -> next;
		fastpointer = fastpointer -> next -> next;
		if(slowpointer == fastpointer)
		return true;
	}
	return false;
}

2.实现一个单例模式

概念:单例模式,即一个类只能被实例一次。即是一个类只能有一个实例化的对象,那么这个类就要禁止别人通过new出来,或者直接定义的出来。

class CAR
{
public:
    CAR(){}
    ~CAR(){}
};//这就不是单例模式,它可以有多个实例化对象
CAR a;
CAR *b = new CAR;

创建方法:将类的构造函数私有化,同时通过定义一个类内部的public 访问函数来调用类 ,为了保证每次返回的是同一个对象,加上static关键字。
1、饿汉式:即类产生的时候就创建好实例对象,这是一种空间换时间的方式
2、懒汉式:即在需要的时候,才创建对象,这是一种时间换空间的方式

class CSingleton
{
private:
	CSingleton()
	{
	}
	static CSingleton *p;//对象在执行getInstance之前已经生成了。饿汉式
public:
	static CSingleton *getInstance()
	{
	//static CSingleton *p;懒汉式
		if (p == NULL)
		{
			p = new CSingleton();
		}
		return p;
	}
};

CSingleton *CSingleton::p = NULL;
//CSingleton *CSingleton::p = new CSingleton();
int main()
{
	CSingleton *t = CSingleton::getInstance();
	CSingleton *tt = CSingleton::getInstance();
	cout << t << endl << tt << endl;
	return 0;
} //饿汉式

总结:利用静态变量和私有化构造函数的特性来实现单例模式。搞一个静态的自身类指针,然后把构造函数私有化,这样new的时候就只能让本类中的成员调用,然后不择手段在类内部new出这个对象,并提供一种方法供外部得到这个对象的地址。

3.给一个字符串判断单词数

无论字符串是什么格式输入,并非只有中间有空格的情况。

#include<iostream>
using namespace std;
int main()
{
	int count = 0;
	int i = 0;
	string str1;
	cout << "please input the string:" << endl;
	getline(cin, str1);
	for (i = 0; i < str1.length(); i++)
	{
		if ((str1[i] >= 'a' && str1[i] <= 'z') || (str1[i] >= 'A' && str1[i] <= 'Z'))
		{
			count++;
			while (((str1[i] >= 'a' && str1[i] <= 'z') || (str1[i] >= 'A' && str1[i] <= 'Z')) && str1[i] != '\0')
			{
				i++;
			}
		}
	}
	cout << count << endl;
	return 0;
}

4.开方算法

//二分法开方
double sqrt1(double x)
{
	if (x == 0) return 0;
	double JINGDU = 0.0001;
	double low = 0.0;
	double high = x;
	double mid = (low + high) / 2;
	while ((high - low) > JINGDU)
	{
		if (mid * mid > x)
		{
			high = mid;
		}
		else {
			low = mid;
		}
		mid = (low + high) / 2;
	}
	return mid;
}
//牛顿迭代法,推荐使用这种
double sqrt2(double n)
{
	if (n == 0) return 0;
	double last = 0.0;
	double x = 1.0;
	while (x != last)
	{
		last = x;
		x = (x + n / x) / 2;
	}
	return x;
}

5.青蛙跳台阶

青蛙跳台阶: 一次可以跳一步或者两步

int GuapiJumpFloor(int n)//斐波拉契数列
{
	int *vec = new int[n + 1];
	 vec[0] = 0;
     vec[1] = 1;
	 vec[2] = 2;
	for (int i = 3; i <= n; i++)
	{
		vec[i] = vec[i - 1] + vec[i - 2];
	}
	return vec[n];
}

6.常用排序(快排、归并、堆排序要写吐)

//快速排序 O(nlogn)
int GetIndex(vector<int> &arr, int left, int right)
{
	if (left > right)  return 0;
	int curent = arr[left];
	while (left < right)
	{
		while (left < right && curent <= arr[right])
			right--;
		arr[left] = arr[right];//挖坑 vec[right]
		while (left < right && curent >= arr[left])
			left++;
		arr[right] = arr[left];//填坑 vec[right]  挖坑 vec[left]
	}
	arr[left] = curent;//填坑 vec[left]
	return left;
}
void Quick_Sort(vector<int> &arr,int left,int right)
{
	if (left > right) return;
	if (left < right)
	{
		int index = GetIndex(arr, left, right);
		Quick_Sort(arr, 0, index - 1);
		Quick_Sort(arr, index + 1, right);
	}
}
//归并
void Merge(int *arr,int left,int mid,int right)
{
	if(left == right) return 0;
	int i = left,j = mid + 1;
	int length = right - left + 1;
	int *arr1 = new int[length];
	int index = 0;
	while(left < right)
	{
		while(i < mid && j < right) 
		   arr1[index++] = arr[i] < arr[j] ? arr[i++] : arr[j++];
		while(i < mid) arr1[index++] = arr[i++];
		while(j < right) arr1[index++] = arr[i++];
		for(int n = 0; n < length ; n++) arr[left++] = arr1[n];
	}
}
void Merge_Sort(int *arr,int left,int right)
{
	if(left == right) return 0;
	int mid = left + (right - left) / 2;
  Merge_Sort(arr,left,mid - 1);
  Merge_Sort(arr,mid + 1,right);
	Merge(arr,left,mid,right);
}

7.反转链表

将链表头尾指针相互交换。

 ListNode *ReverseList(ListNode *phead)
 {
	 if (phead == NULL) return NULL;
	 ListNode *pCurent, *pre, *pNext;
	 pCurent = phead;
	 pre = NULL;
	 while (pCurent != NULL)
	 {
		 pNext = pCurent->next;
		 if (pNext == NULL)
		 {
			 pCurent->next = pre;
			 return pCurent;
		 }
		 pCurent->next = pre;
		 pre = pCurent;
		 pCurent = pNext;
	 }
 }

在这里插入图片描述

8.两个链表,寻找公共节点

List FindFirstCommonNode(List list1, List list2)
{
	List list_1 = list1;
	int list1_length = 0, list2_length = 0;
	while (list1 != NULL)
	{
		list1_length++;
		list1 = list1->next;
	}
	while (list2_length != NULL)
	{
		list2_length++;
		list2 = list2->next;
	}
	if (list2_length > list1_length)//如果2的长度大于1,那么交换,否则不换。
	{
		list1 = list2;
		list2 = list_1;
	}
	int sub_length = list1_length > list2_length ? (list1_length - list2_length) : 
		                                           (list2_length - list1_length);
	                                              //寻找长的数量,让长链表先走
	for (size_t i = 0; i < sub_length; i++)
	{
		list1 = list1->next;
	}
	while (list1 != list2)
	{
		list1 = list1->next;
		list2 = list2->next;
	}
	if (list1 == NULL) return 0;
	else return list1;
}

9.查找字符串中不重复的最长子串

int LengthOfLongestSubstring(string s)
{
	if (s.length() == 0) return 0;
	//找到string中最大值,分配内存空间,防止内存浪费
	int maxvalue = s[0], i = 0, j = 0;
	int length = s.length();
	for (i = 1; i < length; i++)
	{
		maxvalue = maxvalue > s[i] ? maxvalue : s[i];
	}
	bool *mark = new bool[maxvalue + 1];
	for (i = 0; i <= maxvalue; i++)
	{
		mark[i] = false;//将标志位数组所有置为false
	}
	i = 0; j = 0;
	int maxnum = 0;
	while (i < length && j < length)
	{
		if (!mark[s[j]])
		{
			mark[s[j]] = true;
			j++;
			maxnum = maxnum > (j - i) ? maxnum : (j - i);//与之前的最大值比较,找到最大的字串长度
		}
		else
		{
			mark[s[i]] = false;
			i++;
		}
	}
	delete mark;
	return maxnum;
}

10.LRU

缓存淘汰算法:LRU算法的设计原则是:如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小。也就是说,当限定的空间已存满数据时,应当把最久没有被访问到的数据淘汰。
敬请移步

11.手写求树的深度的代码

递归方式:

int GetDepthBitree(BiTree &T)
{
	int Ldepth = 0;
	int Rdepth = 0;

	if (T == NULL)
	{
		return 0;
	}
	else
	{
		Ldepth = GetDepthBitree(T->Lchild);
		Rdepth = GetDepthBitree(T->Rchild);
	}
	return Ldepth > Rdepth ? (Ldepth + 1): (Rdepth + 1);
}

12.手写生产者消费者

敬请移步,生产者消费者

13.编程实现string类

class My_String
{
public:
	My_String(const char *str);//普通构造函数	
	~My_String();//析构函数	
	My_String(const myString &s);//拷贝构造函数
	My_String &operator = (const My_String &s);
private:
	char *_str;
}

My_String::My_String(const char *str)//普通构造函数
{
	if (str == NULL)
	{
		_str = new char[1];
		_str = '\0';
	}else {
		int length = strlen(str);
		_str = new char[length + 1];
		my_strcpy(_str, str);
	}
}

My_String::~My_String()//析构函数
{
	while (_str != NULL)
	{
		delete[]_str;
	}
}

My_String::My_String(const My_String &s)//拷贝构造函数
{
	int length = strlen(s._str);
	if (length == 0)
	{
		_str = new char[1];
		_str = '\0';
	}
	else {
		_str = new char[length + 1];
		my_strcpy(_str,s._str);
	}
}

My_String &My_String::operator = (const My_String &s)//符号重载函数
{
	if(this == &s)
	{
		return *this;
	}
	delete []_str;
	int length = strlen(s);
	_str = new char[length + 1];
	strcpy(_str,s._str);
	return *this;
}

14.两个数组A,B,A有的B都有,求B-A;

15.输入一个字符串,输出它的全排列

16.统计完全二叉树多少个节点

17.strcpy、strlen、strcat、memcpy实现

char* my_strcpy(char *deststr, const char *str)//char*类型是为了方便后续的函数调用  链式连接
{
	assert(str != NULL);
	assert(deststr != NULL);//判断输入参数的合法性,如果条件为假,即终止程序。
	char *res = deststr;
	int length = strlen(str);
	for (size_t i = 0; i <= length; i++)
	{
		deststr[i] = str[i];
	}
	//while ((*deststr++ = *str++)!='\0')
	return res;
}

int my_strlen(const char *str)
{
	if (str == NULL)  return 0;
	int i = 0;
	while (str != '\0')
	{
		i++;
	}
	return i;
}

char* my_strcat(char *deststr, const char *str)//字符串连接函数,将字符串str连接到字符串deststr后面
{
	assert(deststr != NULL && str !=NULL);
	char *res = deststr;
	while (deststr)
	{
		*deststr++;
	}
	while (str)
	{
		*deststr++ = *str;
	}
	*deststr = '\0';
	return res;
}

void* my_memcpy(void *deststr, const void *str,size_t count)//从存储区str复制n个字符到deststr中去。
{
	assert((deststr != NULL) && (str != NULL));
	void *res = deststr;
	while (count--)
	{
		*(char*)deststr = *(char*)str;

		deststr = (char*)deststr + 1; 
		str = (char*)str + 1;
	}
	return res;
}

18 二分查找及其变种

常见的二分查找就是二叉树的性质,每次都查找剩余的一半。所以它的事件复杂度也即是O(logN)。虽然二分查找算法的效率很高(这也是二分查找算法被广泛应用的原因),但是前提就是说查找的序列:有序。在需要频繁进行插入或者删除操作的数据记录中使用二分查找算法效率不是很高,因为还要维持数据的有序还需要额外的排序开销。

int ErfenSearch(int *arr,int value)
{
	if(arr == NULL) return 0;
	length = sizeof(arr)/sizeof(int);
	int low = 0;
	int high = length-1;
	while(low <= high)
	{
		mid = low + (low + high)/2;
		if(arr[mid] == value) return mid;
		if(arr[mid] < value) low = mid + 1;
		if(arr[mid] > value) high = mid - 1;
	}
}

变种
1.插值查找算法
变种的地方主要在于mid值的计算方法。在有序序列当中,当我们所要查找的value偏小或者偏大,如果再用二分就会造成开销的过大。所以插值查找算法当中mid值的计算方法就是将其更偏向查找值value,已达到更高的效率。
二分查找中mid值计算:
在这里插入图片描述
插值查找算法中mid值计算:
在这里插入图片描述

int InsertionSearch(string str, char value)
{
	int low, high, mid;
	low = 0;
	high = str.length()-1;
	while (low <= high)
	{
		mid = low + (value -str[low])/(str[high]-str[low])*(high - low);//自适应的中值寻找,让中值跟家靠近顺序表中查找值的位置,相比二分更有效率
		if (str[mid] == value)
			return mid;
		if (str[mid] > value)
			high = mid - 1;
		if (str[mid] < value)
			low = mid + 1;
	}
}

2.斐波拉契数列查找,根据黄金分割法来查找关键值,得到的是算法时间复杂度O(logN),但是效率比二分法还是要高一些。在该算法中的mid值接为:
在这里插入图片描述

//斐波那契查找 
void Fibonacci(int * F)
{
	F[0] = 0; F[1] = 1;
	for (int i = 2; i < 20; ++i)
		F[i] = F[i - 1] + F[i - 2];
}/*定义斐波那契数列*/
const int max_size = 20;//斐波那契数组的长度 /*构造一个斐波那契数组*/
int FibonacciSearch(int *arr, int key, int length)//a为要查找的数组,n为要查找的数组长度,key为要查找的关键字 
{
	int low = 0; int high = length - 1;
	int F[max_size];
	Fibonacci(F);//构造一个斐波那契数组F 
	int k = 0;
	while (length > F[k])//计算n位于斐波那契数列的位置 
	{
		k++;
	}
	int * temp;//将数组a扩展到F[k]-1的长度
	temp = new int[F[k] - 1];
	memcpy(temp, arr, length * sizeof(int));
	for (int i = length; i < F[k] - 1; i++)
	{
		temp[i] = arr[length - 1];
	}//将新构造的数组补充完整
	while (low <= high)
	{
		int mid = low + F[k - 1] - 1;
		if (key == temp[mid]) return mid;
		if (key < temp[mid])
		{
			high = mid - 1;
			k -= 1;
		}
		else if (key > temp[mid])
		{
			low = mid + 1;
			k -= 2;
		}	
	}
	delete temp;
	return -1;
}

猜你喜欢

转载自blog.csdn.net/GJQJFJ/article/details/105974047
今日推荐