《剑指Offer》第二章

第一题:赋值运算符

class MyString
{
public:
    MyString(char *data = NULL);
    MyString(const MyString &str);
    ~MyString()
private:
    char *m_pData;
};
注意点:返回值类型、参数类型、证同处理、内存泄漏

MyString& operator=(const MyString &rhs)
{
	if (this != &rhs)
	{
		//深拷贝,析构的时候不会对rhs造成影响
		MyString strTemp(rhs);

		//交换操作,strTemp析构的时候会把原来的m_pData释放掉
		char *pTemp = strTemp.m_pData;
		strTemp.m_pData = m_pData;
		m_pData = pTemp;
	}
	return *this;
}


第二题:实现一个线程安全的单例模式类

可以借鉴一下双层检查,类似优先读的保证机制

class Singleton
{
public:
	static Singleton* Instance()
	{
		if (instance == NULL)
		{
			unique_lock<mutex> ulck(mtx);
			if (instance == NULL)
				instance = new Singleton;
		}
		return instance;
	}
private:
	Singleton() = default;
	static Singleton *instance;
	static mutex mtx;
};

Singleton *Singleton::instance = NULL;
mutex Singleton::mtx;

第三题:二维数组中的查找

bool Find(int *matrix, int rows, int columns, int number)
{
	bool found = false;
	if (matrix != NULL && rows > 0 && columns > 0)
	{
		int row = 0;
		int column = columns - 1;
		while (row < rows && column > 0)
		{
			if (matrix[row*columns + column] == number)
			{
				found = true;
				break;
			}
			else if (matrix[row*columns + column] > number)
				--column;
			else
				++row;
		}
	}
	return found;
}


第四题:替换空格

把字符串中的空格替换成%20。最直观的做法是从头到尾扫描,遇到空格就做一次替换,需要做大量的移动操作。时间复杂度O(n^2)

void ReplaceBlank(char str[], int capacity)
{
	if (capacity <= 0 || str == NULL)
		return;
	int numberOfBlank = 0;
	int size = 0;
	for (int i = 0; str[i] != '\0'; i++)
	{
		++size;
		if (str[i] == ' ')
			++numberOfBlank;
	}

	int newSize = size + 2 * numberOfBlank;

	int indexOfNewSize = newSize;
	int indexOfSize = size;

	while (indexOfSize >= 0 && indexOfNewSize > indexOfSize)
	{
		if (str[indexOfSize] == ' ')
		{
			str[indexOfNewSize--] = '0';
			str[indexOfNewSize--] = '2';
			str[indexOfNewSize--] = '%';

		}else
			str[indexOfNewSize--] = str[indexOfSize];
		--indexOfSize;
	}
}

第五题:从尾到头打印链表

这个题目较为简单,首先想到的是用栈,再者用递归也可以

void PrintListReverse(ListNode *pHead)
{
	if (pHead != NULL)
	{
		if (pHead->next != NULL)
			PrintListReverse(pHead->next);
		cout << pHead->val << endl;
	}
}
第六题:用两个栈实现队列

题目描述为:用两个栈实现队列,请实现AppendTail和DeleteHead

void appendTail(const int& node)
{
	stack1.push(node);
}

int deleteHead()
{
	int res;
	if (stack2.empty())
	{
		if (!stack1.empty())
		{
			while (!stack1.empty())
			{
				stack2.push(stack1.top());
				stack1.pop();
			}

		}
		else
			throw new exception("队列为空");
	}
	res = stack2.top();
	stack2.pop();
	return res;
}
变种,用两个队列实现一个栈:

思路是由于队列是先进先出,所以对于压栈操作,可以随意选一个队列做进队列操作,弹栈操作将压栈操作的队列进行出操作,只剩下最后一个元素,这个便是弹出的元素。这里有一个小细节,如果queue1为空,继续弹栈,则需要把queue2做相同处理,依次出队列,剩余最后一个元素,弹出。


小tips:如果面试要求在排序的数组中查找一个数字或者某个数字出现的次数,可以尝试用二分查找。
第七题:请实现一个排序算法,时间效率O(n)

一般是O(n)的话,会是基数排序,比如对年龄进行排序,那么一般公司的年龄最大不会超过65,所以可以用基数排序:

void SortAge(int ages[], int length)
{
	if (ages == NULL || length <= 0)
		return;
	const int oldestAge = 65;
	int timesOfAge[oldestAge + 1];
	for (int i = 0; i <= oldestAge; i++)
		timesOfAge[i] = 0;
	for (int i = 0; i < length; i++)
	{
		int age = ages[i];
		++timesOfAge[age];
	}
	int index = 0;
	for (int i = 0; i <= oldestAge; i++)
	{
		for (int j = 0; j < timesOfAge[i]; j++)
		{
			ages[index] = i;
			++index;
		}
	}
}
第八题:旋转数组的最小数字

这个题最为简单,最直观的是遍历一遍即可,时间复杂度O(n),但是我们注意到,旋转后,数组可以划分为两个排序数组,而前面的子数组元素都大于后面的子数组元素,而最小元素是分界线。我们可以尝试用二分查找来解,时间复杂度O(logn)。

int Min(vector<int> &nums)
{
	if (nums.empty())
		throw new exception("数组为空");
	int left = 0;
	int right = nums.size() - 1;
	int mid = left;

	while (nums[left] >= nums[right])
	{
		if (right - left == 1)
		{
			mid = right;
			break;
		}

		mid = (right + left) / 2;
		if (nums[mid] >= nums[left])
			left = mid;
		else if (nums[mid] <= nums[right])
			right = mid;
	}
	return nums[mid];
}
如果nuns[left]、nums[mid]和nums[right]相等,则只能用顺序查找的方式。

第九题:斐波那契数列

long long Fibonacci(unsigned int n)
{
	if (n <= 0)
		return 0;
	if (n == 1)
		return 1;
	return Fibonacci(n - 1) + Fibonacci(n - 2);
}

long long Fibonacci(unsigned int n)
{
	int result[2] = { 0,1 };
	if (n < 2)
		return result[n];
	long long fibNMinusOne = 0;
	long long fibNMinusTwo = 1;
	long long fibN = 0;
	for (unsigned int i = 2; i <= n; ++i)
	{
		fibN = fibNMinusOne + fibNMinusTwo;
		fibNMinusOne = fibNMinusTwo;
		fibNMinusTwo = fibN;
	}
	return fibN;
}
还有一种方式采用矩阵幂的形式,比较复杂。
另外,还有相似的题目:青蛙跳台阶问题:一只青蛙一次可以跳上1级台阶,或者2级,那么跳上n级,共有多少种跳法。典型的动态规划,或者说是菲波那切数列。
f(n)=f(n-1)+f(n-2),看这个式子,其实就是菲波那切数列。另外还有青蛙一次可以跳1,2....n级,那么有多少种?用数学归纳法可以证得这是一个等比数列,比值为2,另外还有一题,我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问8个2*1的小矩形无重叠的覆盖一个2*8的大矩形有多少种?
第十题:二进制中1的个数

正常的思路是判断最右边的数是不是1,接着右移一位,直到变0为止。但是如果输入的数为负数,这样的方法会造成死循环。鉴于这个,我们可以把比较的数左移,这样依次判断,循环的次数是32次(32位整数)。最为简洁的方法是整数中有几个1就循环几次:

int NumberOf1(int n)
{
	int count = 0;
	while (n)
	{
		++count;
		n = (n - 1) & n;
	}
	return count;
}
基本的思路是:把一个整数减去1,都是把最右边的1变成0。如果1的右边还有0的话,都变成1,而1的左边是不变的。如果我们把一个整数和它减去1做位与运算,相当于把它最右边的1变成0。这样依次进行计算,有多少个1就会循环多少次,高效。






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值