练习200个基本数据机构及算法问题
解答思路:
- 分析问题的解决方案;
- 设计解决问题的方法及结构;
- 设计使用的算法及数据结构;
- coding实现;
- 考虑算法的边界及异常;
- 提供测试接口;
排序
- 快排
hint:以中间值分组, 注意收尾判断;
void Qsort(int a[], int low, int high)
{
if(low >= high)
{
return;
}
int first = low;
int last = high;
int key = a[first];/*用字表的第一个记录作为枢轴*/
while(first < last)
{
while(first < last && a[last] >= key)
{
--last;
}
a[first] = a[last];/*将比第一个小的移到低端*/
while(first < last && a[first] <= key)
{
++first;
}
a[last] = a[first];
/*将比第一个大的移到高端*/
}
a[first] = key;/*枢轴记录到位*/
Qsort(a, low, first-1);
Qsort(a, first+1, high);
}
数组
- 二分查找
使用while判断处理更简洁
while(low<=high)
{
int mid=(low+high)/2;
if(array[mid]>target)
high=mid-1;
else if(array[mid]<target)
low=mid+1;
else
return mid;
}
数组查询,判断
hint: 排序再查找,大数组查询会有效率问题,异或操作循环有序数组查找最小值位置
如(345678012),要求时间复杂度为log2(n)
hint: 数组分为两部分,一定有一部分是有序的,递归查找最大连续和
hint: 贪心算法
int maxSubsequSum(int* a) {
assert(a);
int maxSum = 0;
int curSum = 0;
for (int s = 0, e = 0; e < a.length; e++) {
curSum += a[e];
if (curSum < 0) {
s = e + 1;
curSum = 0;
} else if (curSum > maxSum) {
maxSum = curSum;
}
}
}
给一个数字的列表比如:,0,1,0,1,4,2,4,画成柱状时,会有凹下去的一部分,凹下去的部分装水,问能装多少水?
hint:木桶装水,短板效应,分为子问题,一直寻找最小的板并合并;一个有N个数的数组,求数组里每一个数转换成2进制后的1的个数
hint:num &=(num -1)位移
一个数一个数的算
查表发,将数组转换成char类型,对应的每个char的1的个数累加起来;
HAKMEM算法:
int Count(unsigned x)
{
unsigned n;
n = (x >> 1) & 033333333333;
x = x - n;
n = (n >> 1) & 033333333333;
x = x - n;
x = (x + (x >> 3)) & 030707070707;
x = modu(x, 63); return x;
}
参考:http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetNaive
- 一个长度为2N的数组,前面N个是数字,后面N个是字母,类似123abc,让转化为1a2b3c
hint: 转换位置,swap(arr[i], arr[n+i-gap]),转换一次后找规律
//gap这个有点难理解
void convert(vector<string>& ele,int gap)
{
int len = ele.size();
int n = len / 2;
if(gap < n)
{
for(int i = gap ; i < n; )
{
//gap组进行位置转换
for(int j = 0; j < gap; ++j)
{
swap(ele[i + j],ele[n + i + j - gap ]);
}
//偶数位保持不动
i += 2*gap;
}
convert(ele,2*gap);
}
}
convert(ele, 1);
整数
大整数乘积
输入两个整数,输出乘积,输出数字可能超过计算机内整数数据的存储范围;
hint:m位整数*n位整数,乘积结果为m+n-1或者m+n
参考:http://www.cnblogs.com/jason-yang/archive/2012/04/26/2472755.html数N内所有素数
hint:非素数肯定有一个因子是大大于sqrt(n)的,合数肯定能分解若干个质数,不能被素数整除就肯定是素数;
参考:http://www.tuicool.com/articles/qaaA3i
字符串
- 编辑距离 (Levenshtein距离)
找出字符串的编辑距离,即把一个字符串s1最少经过多少步操作变成编程字符串s2,操作有三种,添加一个字符,删除一个字符,修改一个字符
hint:动态规划
大数据
- 100w数据找最大100个
hint:最大树,分组求最大合并;
分组可根据数据特征选取不同分组
分成100个组每个组取出最大的前100个,然后建一个100的最小堆,下一个数据如果大于最小值,替换最小值并刷新堆,10000*log100的时间效率;
栈
- 给出一个入栈序列,再给一个出栈队列,看出栈序列是否正确
- 一个数组实现二个堆栈
最大限度利用数组
class stack {
public:
int a[maxSize];
int top1; //从头开始压栈
int top2;//从尾开始压栈
}
void push(stack &A, int x, int Tag);
void pop(stack &A, int Tag);
矩阵
- 填数
蛇形填数:
hint:逆序变换,坐标变换i-j,按照斜线写回 a[i-j][j] = a[i][j], a[i-j][j] = b[i][j-i+n-1]
http://blog.163.com/lvan100@yeah/blog/static/6811721420107176921749/
回转填数:
https://my.oschina.net/u/1584433/blog/226571
int n, x, y, tot = 0;
scanf("%d", &n);
memset(a, 0, sizeof(a));
a[x = 0][y = n - 1] = tot = 1;
while (tot < n * n) {
while (x + 1 < n && !a[x + 1][y]) a[++x][y] = ++tot;
while (y - 1 >= 0 && !a[x][y - 1]) a[x][--y] = ++tot;
while (x - 1 >= 0 && !a[x - 1][y]) a[--x][y] = ++tot;
while (y + 1 < n && !a[x][y + 1]) a[x][++y] = ++tot;
}
链表
- 有序链表合并
struct node {
int data;
struct node *next;
}
struct node *sortList(struct node *a, struct node *b)
{
struct node *result = NULL;
if (a == NULL) {
return b;
} else if (b == NULL) {
return a;
}
if (a->data <= b->data) {
{
result = a;
result->next = SortedMerge(a->next, b);
} else {
result = b;
result->next = sortList(a, b->next);
}
return result;
}
- 合并k个有序链表
思路:
1)在买一个链表中取出第一个值,然后把他们放在一个大小为K的数组里,然后把这个数组当成heap,然后把堆建成最小堆,此步骤时间复杂度为O(K);
- 取出堆中的最小值,然后把该最小值所处的链表的下一值放在数组的第一个位置;如果链表中一个已经为空,改变heap大小(从底往前调整);然后执行min-heapify操作,此步骤时间复杂度为O(lgK);
- 不断重复步骤2,直到所有链表都为空;
参考:
http://blog.csdn.net/beiyetengqing/article/details/7685593
- 两个升序合并成一个降序
树
>> 红黑树
平衡二叉树(AVL-树),左右两个子树的高度差绝对值不超过1,并且左右两个子树都是一颗平衡二叉树;
二叉查找树,任意节点的左子树上的值均小于它的根节点的值,右子树上的值均大于它的根节点的值,且左右子树也分别为二叉查找树;
红黑树是一种平衡二叉查找树,每个节点带有颜色属性;
用来构造关联数组和集合;
它再 O(logn)时间内做查找,插入和删除 ;
红黑树是每个节点都带有颜色属性的二叉查找树,颜色红色或黑色,其性质有:
1)节点是红色或黑色;
2)根节点是黑色;
3)每个叶节点(即空节点)是黑色的;
4)每个红色节点的两个子节点都是黑色(每个叶子到根的所有路径上不能有两个连续的红色节点);
5)从任一节点到其某个叶子节点的所有路径都包含相同数据的黑色节点;//这样约束的话,就必须有红色节点干预才能实现;
插入操作:插入新节点总是红色节点,因为不会破坏性质5,然后左右旋维护其他性质;
(这些特质能够保证一颗n个节点的红黑树的高度适中保持在logn
)
参考:http://blog.csdn.net/chenhuajie123/article/details/11951777
>> 堆
堆其实是一颗完全二叉树,堆可分为大顶堆与小顶堆,用于查找排序比较方便;
- 最小堆
满足 Key[i]<=key[2i+1]&&Key[i]<=key[2i+2]称为小顶堆,即父节点都比子节点小;
最小堆实现,插入新元素即调整元素位置,父节点是与子节点对应关系是parentNodePos = (childNodePos - 1) / 2,构建最小堆的时间复杂度为log(n):
void fix_up_min_heap( int arr[], int n, int len, int i )
{
int j = ( i - 1 ) / 2; // parent index
int tmp = arr[i];
while( j >= 0 && tmp < arr[j] )
{
arr[i] = arr[j]; i = j;
if( j > 0 )
j = ( j - 1 ) / 2;
else
break;
}
arr[i] = tmp;
}
void fix_down_min_heap( int arr[], int n, int len, int i ) // n 为数组容量,len为当前长度
{
int j = 2 * i + 1;
int tmp = arr[i];
while( j < len )
{
if( j < len - 1 && arr[j + 1] < arr[j] )
++j;
if( arr[j] < tmp )
{
arr[i] = arr[j];
i = j;
j = 2 * i + 1;
}
else
{
break;
}
}
arr[i] = tmp;
}
int insert_min_heap( int arr[], int n, int * len, int x )
{
if( n == *len ) return -1;
arr[(*len)++] = x;
fix_up_min_heap( arr, n, *len, *len - 1 );
return 1;
}
int top_min_heap( int arr[], int n )
{
return arr[0];
}
void pop_min_heap( int arr[], int n, int * len )
{
// assert( *len > 0 );
arr[0] = arr[--*len];
fix_down_min_heap( arr, n, *len, 0 );
}
- 堆排序(最大堆到有序堆)思想:
先构建一个最大堆或者最小堆,然后将最大或最小元素挪到最后位置,递归完成所有元素处理;
1)将待排序序列初始化;
2)将堆顶元素与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,......Rn-1)和新的有序区(Rn),且满足R[1,2...n-1]<=R[n];
- 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,......Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2....Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成;
参考:http://blog.csdn.net/cdnight/article/details/11650983
树操作
树的属性,使得数据可以左右分离,易于管理控制;
用数组的位置标注完全二叉树,可以简化很多操作,因为完全二叉树的数父子节点的对应关系是 j = (i - 1)/2;
最大最小的多少个数,可以轻易的用最大堆最小堆来解决;
- 深度遍历树
父子节点遍历,有前序后序中序 - 广度遍历树
一层一层的遍历 - 多叉树构建
左右节点遍历处理,比较适合递归 - 树的镜像
hint:自定义数据结构
分治法
将大问题分为子问题,最后合并子问题结果;
动态规划
每次决策依赖于当前状态,又随即引起状态的转移,一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划。
动态规划可以分为子问题解决,但是子问题之间是相互以来的;