1、 求逆序对
转化为归并排序,递归求解,例如7564-〉 75 64-〉 57 46 –〉4 5 6 7,复杂度是O(nlogn)
插入排序,进行了多少次swap表示有多少个逆序对。O(n^2)
int CountReverseNum(int a[], int na, int b[], int nb)
{
int sum = 0;
int indexa = 0, indexb = 0;
int* temp = new int[na+nb];
int index = 0;
while(indexa < na && indexb < nb)
{
if (a[indexa] <= b[indexb])
temp[index++] = a[indexa++];
else
{
temp[index++] = b[indexb++];
sum += na - indexa; //减少对比次数
}
}
memcpy(&temp[index], ((indexa == na)? b:a), (na+nb - index)*sizeof(int));
memcpy(a, temp, sizeof(int)*(na+nb));
return sum;
}
int CountReverseNum(int a[], int n)
{
if ( n == 1) return 0;
int sum = 0, len = n/2;
sum += CountReverseNum(a, len);
sum += CountReverseNum(a+len, len+(n&1));
sum += CountReverseNum(a, len, a+len, len+(n&1));
return sum;
}
2、 求链表的第一个公共点
一定会出现形状Y,在公共点处合并。
若为双向链表,问题容易解决,如果不是:
方法一、压栈,从链表尾查看不同点
方法二、先遍历一遍链表,得到每一链表的长度l1、l2。从长的一端开始遍历l1,遍历l1-l2+1个节点,然后两端同时开始遍历,直到相遇为止
变形:求两个叶节点的最低公共祖先
情况一、是二叉搜索树
从树根开始进行遍历,如果当前节点比两个值都小,则搜索它的右子树,如果比两个值都大,则搜索左子树,如果比期中一个小,另一个大,则该节点为所求节点
情况二、任意树,可转化为求链表的第一个公共点
3、 找出第1500个丑数(只能被2、3、5整除的数)
保存之前结果(排好序),下一个数为之前的结果*2、*3、*5的最小值,可以用三个指针分别指向*2、*3、*5的最后一个数的位置。
4、 数字在排序数组中出现的次数例如1 2 3 3 3 3 4 5 中3出现的次数
方法一、二分查找,如果中间为3,则向前面遍历,向后遍历,找到中间出现3的个数,最坏情况下是O(n)
方法二、用二分查找,找到首尾的3,然后end-first+1得出结论,O(logn)
5、 求二叉树深度
使用遍历方法,传递一个参数为树的高度,(nleft > nrigth)? nleft+1: nright+1
6、 判断平衡二叉树
条件,任何节点左右子树的深度不超过1
后序遍历,同时记录每个点的深度
7、 求数组中只出现一次的数,其它数字都出现两次
所有数进行异或,最后得到的数为所求数字
变形:如果有两个数出现1次,找到这两个数
先进行一次异或,根据异或结果中的某一位为1,将所有的数分成两组,分别作异或
for(i= 0; I < n; i++)
resultExOr ^=data[i];
8、 从递增数组中找到和为s的两个数,例如 1 2 47 11 15 找到两个数,和为15
确定两个指针分别位于首尾,和若小,小的向前移动一位,和若大,大的向后移动一位
9、 翻转单词顺序 I am a student. student. a am I
方法,翻转所有单词顺序,再翻转每一个单词顺序
变形: 左旋若干位单词, 例如abcdef变成 cdefab
->先把ab提出,所有单词向前移动
->(a’b’) -> (a’b’)’-> ba
void ReverseWords(char* words);
int main()
{
char words[3][100]; //注意,如果直接初始化为char* word=”hello“,内容是保存在常量数据区的,因此是不允许更改的!
strcpy(words[0] , "hello world!" );
strcpy(words[1] , "a" );
strcpy(words[2] , "l'm a student.");
for (int i = 0; i < 3; i++)
{
printf("%s", words[i]);
ReverseWords(words[i]);
printf(" -> %s\n", words[i]);
}
gets(words[0]);
return 0;
}
void Reverse(char* words, int start, int end)
{
if (end < start || start < 0)
return;
int i= start, j = end;
while ( i < j)
{
char temp = words[i];
words[i] = words[j];
words[j] = temp;
i++;
j--;
}
return;
}
void ReverseWords(char* words)
{
if (words == NULL) return;
int size = 0;
while (words[size] != '\0') size++;
if (0 == size)
return ;
Reverse(words, 0, size -1);
int i = 0;
int start, end;
while(i < size)
{
start = i;
while (words[i] != ' ' && i < size) i++;
end = i -1;
Reverse(words, start, end);
i++;
}
return;
}
10、 n个骰子的点数之和,所有可能的值,及出现次数
方法一、递归
方法二、循环,每一次算加上第i个骰子后,可能的值
f(n) =f(n-1)+(fn-2)+f(n-3)+…+f(n-6)
只用保存当次循环值和上一次循环的值
11、 不用加减乘除做加法
a ^ b,得到是加过以后,未进行进位的值
(a & b) << 1,得到该进的那一位
do{
sum= num1 ^ num2;
carry= (num1 & num2) <<1;
num1 = sum;
num2 = carry;
}while(num2 != 0)
12、 替换空格,实现一个函数,把字符串中每个空格替换成“%20”,例如 we arehappy替换成we%20are%20happy
方法一:从头向尾检索,一旦发现空格便进行替换,并将之后的字符向后移位,复杂度O(n^2)
方法二:遍历一遍字符串,找到需要替换的总个数,计算最后字符串长度,然后从字符串尾开始向前替换,复杂度O(n)
13、 根据前序、中序遍历结果重建二叉树
14、 用两个栈实现队列
15、 找到旋转数组中的最小数字,例如 3 4 5 1 2 中最小的数字1
方法一:从头到尾进行遍历,复杂度O(n)
方法二:使用类似与二分查找的方式,用三个指针,第一个指向第一个元素,第二个指向最后一个元素,第三个指向中间元素,复杂度O(logn)
16、 非波那契数列,f(n) =f(n-1) + f(n-2)
方法一:递归计算,但是重复计算多
方法二:将之前的结果都保存起来,for循环计算时间复杂度O(n)
方法三:矩阵乘法计算,O(logn)
方法四:求解通项公式:f(n) = f(n-1)+f(n-2),的特征方程为:x^2 = x -1, 有根 x = (1+- sqrt(5))/2,所以存在A、B使得f(n) = a * ((1 - sqrt(5))/2) ^ n + B* ((1+sqrt(5))/2)^n
将f(0) = 0, f(1) = 1带入求解,解得A = -sqrt(5) /5 B = sqrt(5)/5
应用:
一只青蛙一次可以跳上一级台阶,也可以跳上2级,求该青蛙跳上一个n级台阶总共有多少种跳法。
f(n) = f(n-1) + f(n-2)
17、 求二进制中1的个数
方法一: 考虑到如果单纯进行右移并判断最后一位是否为1,对于负数可能出现死循环,因此,将原数与1、2、4...相与,但是复杂度为32次
方法二:由于一个整数减去1再与它自己相与,刚好能使最后一个1变成0,由此循环多少次则可以计算有多少个1.
3.3 章
11、 数值的整数次方
使用递归的方法,a^4 = a^2 *a^2,同时注意,0的次幂、负数的次幂等问题。可以使用全局标识来表示是否出错。
12、打印1到最大的n位数
最重要是考虑到大数问题。使用字符串数组来表示。
方法一:使用字符串数组模拟加法
方法二:递归全排列
13、在O(1)的时间内删除链表节点
方法一:从表头遍历到该节点,同时记录下前一个节点的位置,从而进行删除
方法二:将该节点的下一个节点复制到该节点,然后删除下一个节点。!(包含假设,该节点一定在链表中!)
14、调整数组顺序使得奇数位于偶数前面
方法一:参考快排,设定两个指针,分别指向首尾。
方法二:考虑扩展性的方法,把判断的函数单写。
15、求链表中倒数第k个节点
方法一:使用两个指针,第一个指针先指向第k个节点,第二个指针指向首节点,然后两个同时向后遍历
注意:k的输入是否合法,链表是否为空
16、反转链表
方法一:从头到尾一个一个反转
注意:只有一个节点的时候,如何处理
17、合并两个排序链表
方法一: 两个指针,同时合并。
注意特殊输入
18、 输入两颗树,判断A是不是B的子结构
前序遍历,递归操作
4章
19、二叉树镜像
通过写实例分析,得出从头到尾交换左右子树即可。
20、顺时针打印矩阵
21、包含min函数的栈,栈中pop、push、min操作复杂度都为1
使用一个辅助栈保存最小值,栈顶为最小值。压入3 辅助栈中为3,再压入4,辅助栈中为3 3
22、判断是否是栈的压入弹出序列。例如:45321是,但是43512就不是
使用辅助栈,模拟入栈出栈过程,如果出现某一处无法找到出栈数,则不是。
23、从上往下打印二叉树
24、输入整数组,判断是否是二叉树的后序便历(5 7 6 9 11 10 8)
25、打印二叉树中节点值和为输入整数的所有路径
采用递归的方法,记录下到某一节点的和,使用前序遍历。使用栈来保存路径。
26、复杂链表的复制,一个节点和能包含多个指向。
先在原链表上复制一遍链表,再拆开两个链表。
27、二叉搜索树与双向链表
28、打印一个字符串所有可能的排序
递归算法
//打印字符串所有可能的排序
void GernerateString(char s[], int n, vector<char> &result, vector<int> &visit)
{
if (result.size() == n)
{
for(int i = 0; i < n; i++)
cout<<result[i];
cout<<endl;
return;
}
for (int i =0; i < n; i++)
{
if (!visit[i])
{
result.push_back(s[i]);
visit[i] = 1;
GernerateString(s, n, result, visit);
visit[i] = 0;
result.pop_back();
}
}
}
void PrintAllString(char s[], int n)
{
vector<char> result;
vector<int> visit( n, 0);
GernerateString(s, n, result, visit);
}
48、不能被继承的类
解法一:将构造函数设为私有函数,通过静态函数来调用他的构造函数。任何继承都会在编译时报错。但这样的定义,得到的变量只能位于堆上,而不能位于栈上。
class SealedClass1
{
public:
static SealedClass1 * GetInstance(){ return new Instance;}
static void DeleteInstance(){ delete pInstance;}
private:
SealedClass1() {}
~SealedClass1(){ }
};
解法二:利用虚拟继承。SealedClass2在使用上跟别的类型没有区别,当我们试图从SealedClass2中继承一个类并创建它的实例的时候,却不能通过。因为会调用MakeSealed的私有函数。这样的做法有一个问题,是它的移植性不好,GCC中不支持将模板参数类型设为友元类型。
template <typename T>
class MakeSealed
{
friend T;
private:
MakeSealed(){}
~MakeSealed(){}
};
class SealedClass2 : virtual public MakeSealed<SealedClass2>
{
public:
SealedClass2(){}
~SealedClass2(){}
}