MS100 [046]
括号排列数
四对括号可以有多少种匹配排列方式?比如两对括号可以有两种:()()和(())
思路:12种
MS100 [047]
最长递减子序列
求一个数组的最长递减子序列比如{9,4,3,2,5,4,3,2}的最长递减子序列为{9,5,4,3,2}
思路:经典动态规划,方案一,记录max[i]为以a[i]接尾的最长递减子序列长度,从0到n-1不断更新max数组即可。O(n^2)
方案二,使用数组b[t]表示长度为t的递减子序列的末尾值的最大值,显然b[t]数组单调递减(初始化为最小值)。每次二分查找b数组,找t使b[t-1] > a[i] && b[t] <= a[i],更新b[t] = a[i]。最大的有效t值即为所求。恢复结果时,先找到b[t],然后从后往前扫描数组a。O(n*logn)
MS100 [048]
部分有序数组查找
微软:
一个数组是由一个递减数列左移若干位形成的,比如{4,3,2,1,6,5}
是由{6,5,4,3,2,1}左移两位形成的,在这种数组中查找某一个数。
思路:数组的一半是有序的,那么可以通过判断key是否在有序的一半中来决定递归的子问题。
如果a[mid] <= a[0] :那么a[0...mid]是纯递减数组;可以简单比较出key是否在a[0..mid]中,如果在那么问题就是标准的二分查找了;否则,a[mid+1...n-1]就是原问题的子问题了。
MS100 [053]
字符串的排列
题目:输入一个字符串,打印出该字符串中字符的所有排列。
思路:递归实现。
MS100 [054]
调整数组顺序使奇数位于偶数前面
思路:类似与快速排序的partition过程,设置一个头指针从前往后指向第一个偶数,一个尾指针从后往前指向第一个奇数,交换他们,然后继续循环直到相遇即可。
MS100 [055]
异常时安全的赋值重载函数
类CMyString的声明如下:class CMyString
{
public:
CMyString(char* pData = NULL);
CMyString(const CMyString& str);
~CMyString(void);
CMyString& operator = (const CMyString& str);
private:
char* m_pData;
};
请实现其赋值运算符的重载函数,要求异常安全,即当对一个对象进行赋值时发生异常,对象的状态不能改变。分析:首先我们来看一般C++教科书上给出的赋值运算符的重载函数:
CMyString& CMyString::operator =(const CMyString &str)
{
if(this == &str)
return *this;
delete []m_pData;
m_pData = NULL;
m_pData = new char[strlen(str.m_pData) + 1];
strcpy(m_pData, str.m_pData);
return *this;
}
我们知道,在分配内存时有可能发生异常。当执行语句new char[strlen(str.m_pData) + 1]发生异常时,程序将从该赋值运算符的重载函数退出不再执行。注意到这个时候语句delete []m_pData已经执行了。也就是说赋值操作没有完成,但原来对象的状态已经改变。也就是说不满足题目的异常安全的要求。为了满足异常安全这个要求,一个简单的办法是掉换new、delete的顺序。先把内存new出来用一个临时指针保存起来,只有这个语句正常执行完成之后再执行delete。这样就能够保证异常安全了。
下面给出的是一个更加优雅的实现方案:
CMyString& CMyString::operator =(const CMyString &str)
{
if(this != &str)
{
CMyString strTemp(str);
char* pTemp = strTemp.m_pData;
strTemp.m_pData = m_pData; //这样在strTemp析构的时候会析构掉m_pData
m_pData = pTemp;
}
return *this;
}
该方案通过调用构造拷贝函数创建一个临时对象来分配内存。此时即使发生异常,对原来对象的状态没有影响。交换临时对象和需要赋值的对象的字符串指针之后,由于临时对象的生命周期结束,自动调用其析构函数释放需赋值对象的原来的字符串空间。整个函数不需要显式用到new、delete,内存的分配和释放都自动完成,因此代码显得比较优雅。
MS100 [056]
最长公共字串(LCS,Longest Common Subsequence)
如果字符串一的所有字符按其在字符串中的顺序出现在另外一个字符串二中,则字符串一称之为字符串二的子串。
注意,并不要求子串(字符串一)的字符必须连续出现在字符串二中。
请编写一个函数,输入两个字符串,求它们的最长公共子串,并打印出最长公共子串。
思路:对字符串X和Y,从后往前递推,
若xm==yn,则LCS等于(Xm-1,Yn-1)的LCS加上xm(=yn)。
若xm!=yn,则LCS等于(Xm-1,Yn)和(Xm,Yn-1)中LCS最大的那个。
递归太慢,用动态规划,从第一行开始逐行构造LCS值,并用一个二维数组存储每个xm,yn点所做的选择。
MS100 [057]
从尾到头输出链表
题目:输入一个链表的头结点,从尾到头反过来输出每个结点的值。链表结点定义如下:
struct ListNode
{
int m_nKey;
ListNode* m_pNext;
};
分析:用栈或者递归。注意边界。
void PrintListReversely(ListNode* pListHead)
{
if(pListHead != NULL)
{
// Print the next node first
if (pListHead->m_pNext != NULL)
{
PrintListReversely(pListHead->m_pNext);
}
// Print this node
printf("%d", pListHead->m_nKey);
}
}
扩展:该题还有两个常见的变体:
1. 从尾到头输出一个字符串;
2. 定义一个函数求字符串的长度,要求该函数体内不能声明任何变量。return fun(&a[i+1])+1;
MS100 [058]
不能被继承的类
题目:用C++设计一个不能被继承的类。
首先想到的是在C++ 中,子类的构造函数会自动调用父类的构造函数。同样,子类的析构函数也会自动调用父类的析构函数。要想一个类不能被继承,我们只要把它的构造函数和析构函数都定义为私有函数。那么当一个类试图从它那继承的时候,必然会由于试图调用构造函数、析构函数而导致编译错误。
可是这个类的构造函数和析构函数都是私有函数了,我们怎样才能得到该类的实例呢?
这难不倒我们,我们可以通过定义静态来创建和释放类的实例。
基于这个思路,我们可以写出如下的代码:
// Define a class which can't be derived from
class FinalClass1
{
public:
static FinalClass1* GetInstance()
{
return new FinalClass1;
}
static void DeleteInstance( FinalClass1* pInstance)
{
delete pInstance;
pInstance = 0;
}
private:
FinalClass1() {}
~FinalClass1() {}
};
这个类是不能被继承,但在总觉得它和一般的类有些不一样,使用起来也有点不方便。比如,我们只能得到位于堆上的实例,而得不到位于栈上实例。
能不能实现一个和一般类除了不能被继承之外其他用法都一样的类呢?办法总是有的,不过需要一些技巧。请看如下代码:
// Define a class which can't be derived from
template <typename T> class MakeFinal
{
friend T;
private:
MakeFinal() {}
~MakeFinal() {}
};
class FinalClass2 : virtual public MakeFinal<FinalClass2>
{
public:
FinalClass2() {}
~FinalClass2() {}
};
这个类使用起来和一般的类没有区别,可以在栈上、也可以在堆上创建实例。尽管类MakeFinal<FinalClass2>的构造函数和析构函数都是私有的,但由于类FinalClass2是它的友元函数,因此在FinalClass2中调用MakeFinal<FinalClass2>的构造函数和析构函数都不会造成编译错误。
但当我们试图从FinalClass2继承一个类并创建它的实例时,却不同通过编译。
class Try : public FinalClass2
{
public:
Try() {}
~Try() {}
};
Try temp;
由于类FinalClass2是从类MakeFinal<FinalClass2>虚继承过来的,在调用Try的构造函数的时候,会直接跳过FinalClass2而直接调用MakeFinal<FinalClass2>的构造函数。非常遗憾的是,Try不是MakeFinal<FinalClass2>的友元,因此不能调用其私有的构造函数。
基于上面的分析,试图从FinalClass2继承的类,一旦实例化,都会导致编译错误,因此是FinalClass2不能被继承。这就满足了我们设计要求。
MS100 [060]
在O(1)时间内删除链表结点。
题目:给定链表的头指针和一个结点指针,在O(1)时间删除该结点。
思路:删除的实质是删除它的内容。所以!可以复制下一个节点的内容到本节点,然后删除下一个节点。注意当删除尾部节点时,仍需要从头指针遍历,复杂度O(n)。但整个算法的平均复杂度是O(1)。