[读书笔记]编程之美(三)
3.1字符串移位包含的问题
- 问题:给定两个字符串s1和s2,要求判定s2是否能够被s1做循环移位(rotate)得到的字符串包含。例如,给定s1=AABCD和s2=CDAA,返回true;给定s1=ABCD和s2=ACBD,返回false。
- 思路:str = s1 + s1,判断s2是否在str中。这种要申请新的空间,或者用指针做一个循环两次,这样就不用申请新的空间了。
3.2电话号码对应英语单词
- 问题:(1)尽可能快地从这些字母组合中找到一个有意义的单词来表述一个电话号码呢?如:可以用单词“computer”来描述号码26678837。
(2)对于一个电话号码,是否可以用一个单词来代表呢?怎样才是最快的方法呢? - 思路:问题一:直接循环法,遍历出所有的单词
while(true)
{
for(int i = 0; i < TelLength; i++)
printf("%c", c[number[i]][answer[i]]);
printf("\n");
int k = n - 1;
while(k >= 0)
{
if(answer[k] < total[numbeer[k]] - 1)
{
answer[k]++;
break;
}
else
{
answer[k] = 0;
k--;
}
}
if(k < 0)
break;
}
问题二:
解法一:全部计算出来,然后去匹配字典。适用于查询次数较少的情况。
解法二:如果次数较多,直接把字典里面的所有单词都按照这种转换规则转换为数字,并存在文件中,使之成为另一本数字字典。
3.3计算字符串的相似度
- 问题:对于不同的字符串。对于不同的字符串,我们希望能够有办法判断其相似程度,操作方法使得不同的字符串变得相同,
1.修改一个字符。2.增加一个字符。3.删除一个字符。计算他们之间的距离。 - 思路:1、一步操作之后,再将A[2,…,lenA]和B[1,…,lenB]变成相同字符串;
2、一步操作之后,再将A[1,…,lenA]和B[2,…,lenB]变成相同字符串;
3、一步操作之后,再将A[2,…,lenA]和B[2,…,lenB]变成相同字符串; - 核心代码:
int CalculateStringDistance(string strA, int pABegin, int pAEnd, string strB, int pBBegin, int pBEnd)
{
if(pABegin > pAEnd)
{
if(pBBegin > pAEnd)
return 0;
else
return pBEnd - pBBegin + 1;
}
if(pBBegin > pBEnd)
{
if(pABegin > pAEnd)
return 0;
else
return pABegin - pABegin + 1;
}
if(strA[pABegin] == strB[pBBegin])
{
return CalculateStringDistance(strA,pABegin+1,pAEnd,strB,pBBegin+1,pBEnd);
}
else
{
int t1 = CalculateStringDistance(strA,pABegin,pAEnd,strB,pBBegin+1,pBEnd);
int t2 = CalculateStringDistance(strA,pABegin+1,pAEnd,strB,pBBegin,pBEnd);
int t3 = CalculateStringDistance(strA,pABegin+1,pAEnd,strB,pBBegin+1,pBEnd);
return minValue(t1,t2,t3) + 1;
}
}
需要注意的是在递归的过程中,有些数据被重复计算了。需要一些保存一些中间变量。
3.4从无头单链表中删除节点
- 思路:狸喵换太子
3.5最短摘要的生成
- 问题:假定给定的已经是经过网页分词之后的结果,词语序列数组为W。其中W[0],W[1],…,W[N]为一些已经分好的词语。
假定用户输入的搜索关键词为数组Q。其中Q[0],Q[1],…,Q[m]为所有输入的搜索关键词。
这要,生成的最短摘要实际上就是一串相互联系的分词序列。比如从W[i]到W[j],其中,0< i < j <= N。包含了所有的关键字。 - 分析:两个指针依次相互扫描,依次扫描下去,在w中找到所有包含q的序列,并且维护并找出其中最小的值。
- 核心代码:
while(true)
{
//假设包含所有的关键词,并且后面的指针没有越界,往后移动指针
while(!isAllExisted() && pEnd < nLen)
pEnd++;
//假设找到一段包含所有关键词信息的字符串
while(isAllExisted())
{
if(pEnd - pBegin < nTargetLen)
{
nTargetLen = pEnd - pBegin;
nAbstractBegin = pBegin;
nAbstractEnd = pEnd - 1;
}
pBegin++;
}
if(pEnd >= N)
Break;
}
3.6编程判断两个链表是否相交
问题:给两个单向链表的头指针,判断两个链表是否相交,两个链表不带环,要是带环又如何?
1.判断链表1是否有环,并记录其长度L1,尾节点b1(无环),环入口点a1(有环)
- 2.判断链表2是否有环,并记录其长度L2,尾节点b2(无环),环入口点a2(有环)
- 3.若一个有环,另一个没环,则判断不相交,结束
- 4.若两个都没环,则两个尾节点一点是一样的,所以比较两个尾节点是否相同。若不相同则判断不相交,结束;若相同,则到6。
- 5.若两个都有环,有如下图三种情况:a1与a2不相等,且不在一个环中,则不相交,结束;a1等于a2,跳到6;a1与a2不相等,但在一个环中,对于链表1来说a1是它的相交首节点,对于链表2来说a2是它的相交首节点。
- 6.对于两个链表重头开始遍历,长链表节点先出发前进(Max(L1,L2)-Min(L1,L2))步,之后两个链表同时前进,每次一步,相遇的第一点即为两个链表相交的第一个点。
3.7队列中去最大值操作问题
- 问题:假设有这样一个拥有3个操作的队列:
1.EnQueue(v):将v加入队列中
2.DeQueue:使队列中的队首元素删除并返回此元素
3.MaxElement:返回队列中的最大元素(O(1)) - 思路:
队列是遵守“先入先出”原则的一种复杂的数据结构。底层的数据结构可以使用数组或者栈来实现。
解法一:数组或队列,Max时间复杂度O(N)。
解法二:最大堆来维护队列中的元素,max(O(1)),入队出队O(lgN)
解法三:这里我们自定义了一个栈,并在其中维护了一个最大值序列,来保证Max操作的时间复杂度为O(1)。然后用这两个栈,形成一个队列。
3.8求二叉树节点的最大距离
- 问题:如果我们把二叉树看成一个图,父子节点之间的连线看成是双向的,我们姑且定义“距离”为两个节点之间边的个数。写一个程序求一颗二叉树中相距最远的两个节点之间的距离。
- 思路:相据最远的两个节点一定是叶子节点。
- 核心代码:
struct NODE{
NODE* pLeft;
NODE* pRight;
int nMaxLeft;
int nMaxRight;
char chValue;
};
int nMaxLen = 0;
void FindMaxLen(NODE* pRoot)
{
//遍历到叶子节点,返回
if(pRoot == nullptr)
return;
//如果右子树为空,那么该节点的右边最长距离为0
if(pRoot->pRight == nullptr)
pRoot->nMaxRight = 0;
if(pRoot->pLeft == nullptr)
pRoot->nMaxLeft = 0;
//如果左子树不为空,递归寻找左子树最长距离
if(pRoot->pLeft != nullptr)
FindMaxLen(pRoot->pLeft);
if(pRoot->pRight != nullptr)
FindMaxLen(pRoot->pRight);
//计算左子树最长节点距离
if(pRoot -> pLeft != nullptr)
{
int nTempMax = 0;
if(pRoot -> pLeft -> nMaxLeft > pRoot -> pLeft -> nMaxRight)
nTempMax = pRoot -> pLeft -> nMaxLeft;
else
nTempMax = pRoot -> pRight -> nMaxRight;
pRoot -> nMaxLeft = nTempMax + 1;
}
//计算右子树最长节点距离
if(pRoot -> pRight != nullptr)
{
int nTempMax = 0;
if(pRoot -> pRight -> nMaxLeft > pRoot -> pRight -> nMaxRight)
nTempMax = pRoot -> pRight -> nMaxLeft;
else
nTempMax = pRoot -> pRight -> nMaxRight;
pRoot -> nMaxRight = nTempMax + 1;
}
//更新最长距离
if(pRoot -> nMaxLeft + pRoot -> nMaxRight > nMaxLen)
nMaxLen = pRoot -> nMaxLeft + pRoot -> nMaxRight;
}
3.9重建二叉树
- 问题:已经有了前序遍历和中序遍历的结果,希望通过一个算法重建这棵树。
- 核心代码:
#include <bits/stdc++.h>
using namespace std;
#define TREELEN 6
//树节点
struct NODE{
NODE* pLeft;
NODE* pRight;
char chValue;
};
void ReBuild(char* pPreOrder, char* pInOrder, int nTreeLen, NODE** pRoot)
{
//检查边界条件
if(pPreOrder == NULL || pInOrder == NULL)
{
return;
}
//获得前序遍历的第一个节点
NODE* pTemp = new NODE;
pTemp -> chValue = *pPreOrder;
pTemp -> pLeft = NULL;
pTemp -> pRight = NULL;
//如果节点为空,把当前节点复制到根节点
if(*pRoot == NULL)
*pRoot = pTemp;
//如果当前树长度为1,那么已经是最后一个节点
if(nTreeLen == 1)
return;
//寻找子树长度
char* pOrgInorder = pInOrder;
char* pLeftEnd = pInOrder;
int nTempLen = 0;
//找到左子树的结尾
while(*pPreOrder != *pLeftEnd)
{
if(pPreOrder == NULL || pLeftEnd == NULL)
return;
nTempLen++;
//记录临时长度,以免溢出
if(nTempLen > nTreeLen)
break;
pLeftEnd++;
}
//寻找左子树长度
int nLeftLen = 0;
nLeftLen = (int)(pLeftEnd - pOrgInorder);
//寻找右子树长度
int nRightLen = 0;
nRightLen = nTreeLen - nLeftLen - 1;
//重建左子树
if(nLeftLen > 0)
ReBuild(pPreOrder + 1, pInOrder, nLeftLen, &((*pRoot) -> pLeft));
if(nRightLen > 0)
ReBuild(pPreOrder + nLeftLen + 1, pInOrder + nLeftLen + 1, nRightLen, &((*pRoot) -> pRight));
}
void InOrder(NODE* root)
{
if(root == NULL)
return;
InOrder(root->pLeft);
cout<<root->chValue<<endl;
InOrder(root->pRight);
}
int main()
{
char pre[TREELEN] = {'a','b','d','c','e','f'};
char in[TREELEN] = {'d','b','a','e','c','f'};
NODE* pRoot = NULL;
ReBuild(pre,in,TREELEN, &pRoot);
InOrder(pRoot);
}
3.10分层遍历二叉树
- 问题:给定一颗二叉树,要求按分层遍历该二叉树,即从上到下按层次访问该二叉树(每一层将单独输出一行),每一层要求访问的顺序为从左到右。
- 思路:用一个vector存储每一层的节点,从头出一个从尾部加两个。
3.11程序改错
- 问题:二分查找源码
- 核心代码:
int bisearch(char** arr, int b, int e, char* v)
{
int minIndex = b, maxIndex = e, midIndex;
while(minIndex < maxIndex - 1)
{
midIndex =minIndex + (maxIndex - minIndex) / 2;
if(strcmp(arr[midIndex], v) <= 0)
minIndex = midIndex;
else
maxIndex = midIndex;
}
if(!strcmp(arr[maxIndex] , v))
return maxIndex;
else if(!strcmp(arr[minIndex] , v))
return minIndex;
else
return -1;
}
4.1金刚坐飞机
- 问题:金刚插队随意找了一个座位坐了下来(1)其他乘客也随意的坐下,并坚决不让座给其他乘客。
(2)如果自己的位置已经被别人占了,就随机选择另一个位置坐下。 - 思路:(1)F(i) = 1/N
(2)全概率公式
4.2瓷砖覆盖地板
- 问题:能否用1 * 2的瓷砖去覆盖N * M的地板呢?
- 思路:斐波那契数列加强版?
4.3买票找零
- 问题:每张球票为50元,现在有2n人排队,n个人手持50,n个人100,有多少种正确的排队方式?
- 思路:卡特兰数h(n)=C(2n,n)/(n+1) (n=0,1,2,…)
- 核心代码:
int validPermutation(int n)
{
long long lDemonitator = 1;
for(int i = 1 ; i <= n; i++)
{
lDemonitator *= i;
}
long long lMolecule = 1;
for(int i = 2*n ; i > n ; i--)
{
lMolecule *= i;
}
return (int) (lMolecule/((n+1) * lDemonitator));//合法序列的总个数 = 1/(n+1) Cn 2n
}
- 相似问题:
- 1.括号化问题。
矩阵链乘: P=a1×a2×a3×……×an,依据乘法结合律,不改变其顺序,只用括号表示成对的乘积,试问有几种括号化的方案?(h(n)种) - 2.出栈次序问题。
一个栈(无穷大)的进栈序列为1,2,3,..n,有多少个不同的出栈序列?
类似:有2n个人排成一行进入剧场。入场费5元。其中只有n个人有一张5元钞票,另外n人只有10元钞票,剧院无其它钞票,问有多少中方法使得只要有10元的人买票,售票处就有5元的钞票找零?(将持5元者到达视作将5元入栈,持10元者到达视作使栈中某5元出栈) - 3.将多边行划分为三角形问题。
将一个凸多边形区域分成三角形区域的方法数?
类似:一位大城市的律师在她住所以北n个街区和以东n个街区处工作。每天她走2n个街区去上班。如果她
从不穿越(但可以碰到)从家到办公室的对角线,那么有多少条可能的道路?
类似:在圆上选择2n个点,将这些点成对连接起来使得所得到的n条线段不相交的方法数? - 4.给顶节点组成二叉树的问题。
给定N个节点,能构成多少种形状不同的二叉树?
4.4点是否在三角形内
- 问题:二维坐标系,一直三角形定点的坐标,对于坐标系任意一点,判断该点是否在三角形内。
- 思路:(1)直观的转化为比较三角形的面积。可以利用海伦公式,已知三角形边长为a,b,c,p=(a+b+c)/2。三角形面积为(p-a) * (p-b) * (p-c) * p
(2)由于三角形是凸的,所以如果有一个点D在三角形ABC内,那么沿着三角形的边界逆时针走,点D一定保持在边界的左边,也就是说点D在边AB、BC、CA左边。 - 核心代码:
struct point
{
double x,y;
};
double Product(point A, point B, point C)
{
return (B.x - A.x) * (C.y - A.y) - (C.x - A.x) * (B.y - A.y);
}
//A,B,C在逆时针方向 如果D在ABC之外,返回false,否则返回true
bool isInTriangle(point A, point B, point C, point D)
{
if(Product(A, B, D) >= 0 && Product(B,C,D) >= 0 && Product(C,A,D) >= 0)
return true;
return false;
}
4.5磁带文件存放优化
- 问题:假设其中一盘磁带上有n份文件,他们的长度分别为L[0],L[1],…,L[n-1],且访问的概率为P[0],P[1],…,P[n-1]。请问怎样安排它们在磁带上的存储顺序最好?
- 思路:判断出P[i]/L[i]的值从大到小排列即为最佳存储顺序。
4.6桶中取黑白球
- 问题:有一个桶,里面有白球、黑球各100个,人们必须按照以下规则把求取出来:
1、每次从桶中里面拿两个球;
2、如果是两个同色的球,就再放一个一个黑球; 1 ^ 1 = 0 0 ^ 0 = 0
3、如果是两个异色的球,就再放一个白球;1 ^ 0 = 1
问:最后桶里面只剩下一个黑球的概率是多少? - 思路:由异或运算满足结合律可知,取球的过程相当于把里面所有的球 进行抑或操作。
4.7蚂蚁爬杆
- 问题:有一根木杆27厘米,在3、7、23厘米这几个位置各有一只蚂蚁。木杆很细,不能同时通过两只蚂蚁。开始时,蚂蚁的头朝左、右是任意的,他们只会朝前走或掉头,不会后退,。当两只蚂蚁碰头的时候,他们会同时掉头朝相反的方向走。
- 思路:题目有陷阱,两只蚂蚁碰头,不会发生任何事。这样,程序只需遍历所有蚂蚁,把每个蚂蚁走出木杆的最长时间,最短时间分别求出来。
4.8三角形测试用例
略
4.9数独
- 问题:一共有多少种不同的数独解答呢?其中有多少种是独立的解答呢?如果我们要用一个简单的字符串来表示各种数独。如何在保证一一对应的基础上,让字符串的长度最短?
- 思路:这种题,明确问题是什么,不要着急一开始就做题。“独立“是什么意思,也不一定要求出最精确的答案,重要的是分析的过程。
4.10数字哑谜和回文
- 问题:(1)神奇的9位数。能不能找出符合如下条件的9位数:
这个数字包括了1~9这9个数字;这9位数的前n位都能被n整除,若这个数表示为abcdefghi,则ab可以被2整除,abc可以被3整除…abcdefghi可以被9整除。
(2)有这样一个乘法算式:人过大佛寺 * 我 = 寺佛大过人 - 思路:举例,有规律的举例。对于问题1,我们把”整除“的条件列出来。对于问题二、我、不会超过5
4.11扫雷游戏的概率
null | A | null |
---|---|---|
null | 1 | null |
null | B | null |
null | 2 | null |
null | C | null |
– 问题一:当这个游戏有40个地雷没有被发现的时候,A、B、C三个方块有地雷的概率(P(A),P(B),P(C))各是多少?
– 问题二:这个游戏局面一共有16*16 = 256个方块,P(A),P(B),P(C)的相互大小关系和当前局面中地雷的总数有联系么?比如,当地雷总数从10个逐渐变化到240个,P(A),P(B),P(C)的三条曲线是如何变化的?他们会不会相交。
– 思路:为什么这一次的概率会和总雷数相关呢?问题就在于中间的方块中的雷数不确定。按照这个思路,题目中的问题就迎刃而解了。
此题适合用MATLAB解答是因为要求作出概率曲线. 如果地雷总数一定, 其实手算也很容易. 根据数字1和2的提示, 图示3×5方格中至少2个地雷, 至多3个地雷. 记为扫雷游戏中格子总数, 为地雷总数. 分两种情况考虑.
图中共2个地雷时可能的情况总数: .
A处为地雷的情况总数: .
B处为地雷的情况总数: .
C处为地雷的情况总数: .
图中共3个地雷时可能的情况总数: .
A处为地雷的情况总数: .
B处为地雷的情况总数: .
C处为地雷的情况总数: .
要求的概率可以简单地相除得到: