递归int F(int n)
{
如果(n=0)
返回0;
否则如果(n==1)
返回1;
别的
返回F(n-1) + F(n-2);
}缺点:效率很低,时间复杂度呈指数级增长。重复计算量极其巨大。
2.迭代法。
长Fib(int n)
{
整数我;
无符号长整型a=0, b=1, c;
如果(n=1){
返回n;
} 别的{
for (i=2; i=n; i++) {
c=a + b;
a=b;
b=c;
}
返回c;
}
}虽然在计算斐波那契数列第n项时使用了前两项的值,但它们仅用作临时计算的中间值,并不作为结果输出。因此,无需保留,完全可以转变为迭代方法。
青蛙跳台阶
可以一次跳转1或2步,找到n步跳转方法,伪装斐波那契数列。
青蛙跳台阶变态版本
青蛙可以一次跳一级,也可以跳两级……还可以跳n级。找出青蛙有多少种方式跳上n 层楼梯。
f(n)=2f(n-1)
插图:
更有效的解决方案:if(number=0)
返回0;
if(数字==1)
返回1;
int sum=1(number-1);//位运算代替*2 1
矩形覆盖
与青蛙跳步原理相同
二进制中1的个数
int 计数=0;
而(n!=0){
计数++;
n=n (n - 1);
}
返回计数;例子:一个二进制数1100,右数第三位是最右边的1,减1后,第三位变成0,后面的两个0变成1,前面的1不变,所以结果是1011。我们发现减1的结果是改变最右边的所有以1开头的位都被反转。此时,如果我们将原整数与减1后的结果进行与运算,则原整数从最右边的1开始的所有位都将变为0。例如11001011=1000。换句话说,一个整数减1,然后与原整数进行AND 运算,就会将该整数最右边的1 变成0。那么一个整数的二进制有多少个1,就可以进行多少次这样的操作。
数值的整数次方
给定一个double 型浮点数,基数类型的整数指数,国际。求底数的指数幂。
考虑:
当指数为0时,指数为负数——求指数的绝对值,计算幂结果,然后取倒数。倒数还决定了基数为0的情况。浮点数的比较存在错误,所以不能使用简单的a==0进行比较。一般的比较方法是,如果相减的差异在一个很小的区间内,我们认为是相等的(float1-float2 -0.0000001) (float1 -float2 0.0000001) 最好的解决方案:
可以使用1 代替/2
if(指数==0)
返回1;
if(指数==1)
返回基地;
if(指数==-1)
返回1.0/碱基;
if((指数1)==1){
//奇数
双结果=幂(基数,(指数-1)/2);
结果*=结果;
结果*=基础;
返回结果;
}别的{
//甚至
双结果=幂(底数,指数/2);
结果*=结果;
返回结果;
}
从尾到头打印链表
栈用于暂时存放遍历到的节点值,遍历完成后栈输出。
替换空格
将长度1的空格替换为长度3的“%20”,字符差异长度变长。如果允许我们开一个新的数组来存储替换空格后的字符串,那么这个问题就很简单了。但如果面试官要求对原始字符串进行操作,并保证原始字符串有足够的空间来存储替换后的字符串,那么我们就得另想办法了。
如果从前往后替换字符串,空格后面保存的字符串肯定会被覆盖,所以我们考虑从后往前替换。
首先遍历原始字符串,求字符串的长度和其中的空格数,
根据原字符串的长度和空格的数量,我们可以求出最终新字符串的长度。
设置两个指针point1和point2分别指向原字符串和新字符串的结束位置。
如果point1指向的内容不是空格,则将该内容赋给point2指向的位置。如果point1指向的内容是空格,则从point2开始赋值“02%”。
直到point1==point2,说明字符串中的空格全部被替换掉。
二维数组中的查找
数组的每一行从左到右按升序排序,每一列从上到下按升序排序。
一张图说明,以找7为例:
旋转数组的最小数字
示例:{3, 4, 5, 1, 2} 是{1, 2, 3, 4, 5} 的旋转数组,求最小数。
可以采用二分查找的方法。
注意:在这种情况下,{1, 2, 3, 4, 5} 仍然是一个数组选择。
特殊情况:{1, 0, 1, 1} (a[start]==a[end]==a[mid]) 此时只能使用顺序查找。
求链表中倒数第k个数
首先要先判断k和链表的长度。如果k=链表的长度,
那么指针1先移动k步,指针2和指针1一起移动。当指针1指向NULL时,指针2指向倒数第k个数。
从上往下打印二叉树
使用队列作为临时存储容器
二叉树深度
int TreeDepth(TreeNode* pRoot)
{
如果(pRoot==NULL)
返回0;
返回max(TreeDepth(pRoot-left),TreeDepth(pRoot-right))+1;
}
连续子数组的和
例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个到第3个)个子向量的长度至少为1。【动态规划思想】
公共int FindGreatestSumOfSubArray(int[] array) {
int sum=数组[0];
int max=数组[0];
for(int i=1;i
两个栈实现队列
stack1 只是压入
当stack2为空时,将stack1的内容一一弹出到stack2栈中。 stack2栈顶就是要出栈的元素。如果stack2不为空,则直接弹出。
包含min函数的栈
示例存储在stack中,栈minStack用于辅助存储min。当入栈的值较小时,minStack 存储当前入栈的值。如果压入的值大于minStack的栈顶值,则将minStack再次压入栈顶。价值。当堆栈弹出时,minStack 会比较大小。如果小于pop的值,则minStack跟随pop。
数字在排序数组中出现的次数
二分查找找到头部和尾部。尾部下标-头部下标+1就是出现的次数。
想法很简单,但要小心越线的陷阱!
公共int GetNumberOfK(int[] 数组, int k) {
int end=findEnd(数组, 0, array.length-1, k);
int start=findStart(数组, 0, array.length-1, k);
if (结束==-1 || 开始==-1)
返回0;
返回结束-开始+1;
}
公共int findStart(int[] array, int start, int end, int k) {
if (开始结束||结束=array.length||start0)
返回-1;
int mid=(开始+ 结束)/2;
if (数组[mid]==k) {
如果(中- 1 0)
中途返回;
if (数组[mid - 1] !=k)
中途返回;
别的
return findStart(数组, 开始, mid - 1, k);
} else if (数组[mid] k)
return findStart(数组, 开始, mid - 1, k);
别的
返回findStart(数组, mid + 1, end, k);
}
公共int findEnd(int[] array, int start, int end, int k) {
if (开始结束||结束=array.length||start0)
返回-1;
int mid=(开始+ 结束)/2;
if (数组[mid]==k) {
if (mid + 1=数组.长度)
中途返回;
if (数组[mid + 1] !=k)
中途返回;
别的
返回findEnd(数组, mid + 1, end, k);
} else if (数组[mid] k)
return findEnd(数组, 开始, mid - 1, k);
别的
返回findEnd(数组, mid + 1, end, k);
}
数组中出现次数超一半的数字
没什么好说的。如果出现的次数超过一半,那么排序后这个数字肯定会在数组的中线。
另一种思考方式:如果一个数字满足条件,那么它出现的次数比所有其他数字的总和还要多。遍历数组时,保存两个值:一个是数组中的数字,另一个是次数。遍历下一个数字时,如果与之前保存的数字相同,则该数字加1,否则该数字减1;如果数字为0,则保存下一个数字,并将该数字设置为1。遍历完成后,记得对数组进行计数,看看保存的数字在数组中出现的次数是否超过长度的一半数组的,并且不直接超过0。否则,返回保存的号码。
反转链表
初始化时三个指针pPrev、p、pNext、tmp分别指向NULL、head、head-next、head-next-next。重复p-next=pPrev 操作。然后一点一点地移动到下一点。
如果(pHead==NULL)
返回空值;
ListNode * pPrev=NULL;
ListNode* p=pHead;
ListNode* pNext=p-下一个;
而(p!=NULL)
{
列表节点* tmp;
if(pNext!=NULL)
tmp=pNext-下一个;
p-下一个=p上一个;
pPrev=p;
p=p下一个;
pNext=tmp;
}
返回pPrev;
合并两个排序的链表
从link1和link2的头开始比较,较小的一个将作为新链表的下一个点。
注意事项:
两个链表都为NULL,其中一个链表为NULL。两个链表的长度不一致。第一次比较时newLink头为NULL,并被分配给头。 next比较赋值给newLink的next,需要一个临时变量来存储newLink的头指针。 (最后返回一个临时变量),因为newLink是在循环中constant newLink=newLink-next之后返回的。此时newLink指向链表的中间而不是链表头。
调整数组顺序使奇数位于偶数前面
算法不稳定(不强调相对顺序):有两个指针,head 和tail。当头遇到偶数时停止,当尾部遇到奇数时停止,交换直到头指针下标大于尾指针下标。算法稳定(调整后奇数与奇数、偶数与偶数相对位置不变):
采用插入排序的思想。当找到奇数时,继续在0~奇数范围内寻找偶数,交换位置。
丑数
仅包含因子2、3 和5 的数字称为丑数。习惯上将1视为第一个丑数。
//1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, 16, 18.
分析:
//用数组存储计算出来的丑数,只需返回a[index-1](数组从0开始)
(1)索引=1,num=a[index-1]=a[0]=1
(2)index=2,num=min(a[index-2]*2,a[index-2]*3,a[index-2]*5);
//前面的数字乘以3个因子中的最小值,即与1*2和1*3和1*5相比,min为2。
(3)index=3,num=min(a[index-3]*2,a[index-3]*3,a[index-3]*5,a[index-2]*2,a[index -2]*3,a[索引-2]*5);
//前2个数字乘以3个因数中的最小值。
也就是说,比较1*2和1*3和1*5和2*2和2*3和2*5,min是2。
但此时的丑数比之前的丑数还要大!因此取下一个最小值3。
最好的方法是循环计算得到大于下一个目标数的第一个丑数*2的索引。
第一个*3是大于下一个目标数的丑数的索引
第一个*5是大于下一个目标数的丑数的索引
分别保存,然后求这三个数的最小值。如果(索引=0)
返回0;
如果(索引==1)
返回1;
int a[索引];
a[0]=1;
int 索引1=0,索引2=0,索引3=0;
for (int i=1; i 索引; i++) {
int min=getMin(a[index1] * 2, a[index2] * 3, a[index3] * 5);
a[i]=最小值;
while (a[索引1] * 2=a[i])
索引1++;
while (a[index2] * 3=a[i])
索引2++;
while (a[index3] * 5=a[i])
索引3++;
}
返回a[索引-1];
第一次只出现一次的字符
Java使用哈希表,扫描一次,统计每个字符出现的次数。
第二次扫描,去哈希表中获取对应的数字。一旦等于1,跳出循环并返回下标。
c 使用数组而不是哈希表。总共只有256个字符,数组长度为256。
二叉树的镜像
没什么好说的,这里是关键代码。
void Mirror(TreeNode* root)
{
如果(根==NULL)
返回;
如果(根!=NULL)
{
TreeNode* tmp=左根;
左根=右根;
根权=tmp;
if(左根)
镜像(根左);
if(右根)
镜像(根右);
}
}
栈的压入弹出序列
输入两个整数序列。第一个序列表示堆栈的压入序列。请判断第二个序列是否为栈的出栈序列。假设压入堆栈的所有数字不相等。比如序列1,2,3,4,5是某个栈的入栈序列,序列4,5,3,2,1是该入栈序列对应的出栈序列,但是4,3, 5, 1, 2 不能是push序列的pop序列。 (注:这两个序列的长度相等)
主意:
模拟栈操作:将原来的数字序列按顺序压入栈,将栈顶元素与给定的栈队列进行比较,如果相同则将其出栈,然后比较栈顶元素的下一位弹出队列。如果不同,则继续压入堆栈,直到原始序列中的所有数字都压入堆栈。
检查栈是否为空。如果为空,则表示可以通过对原始序列进行出栈操作得到弹出队列。否则,说明无法对原序列进行出栈操作得到出栈队列。
两个链表的第一个公共结点
两个单向链表有一个公共点,类似于“Y”
方法一:遍历两个链表,存入栈。逐个出栈,最后一个相同的节点就是第一个公共节点。方法二:遍历两个链表获取长度。长链表首先执行len1-len2步,然后将两个链表一起遍历。第一个相同节点是第一个公共节点。
不用加减乘除做加法
编写一个函数来求两个整数之和。要求函数体中不允许使用+、-、*、/这四个算术符号。
int 添加(int num1, int num2)
{
而(num2!=0)
{
int n=num1^num2;
num2=(num1num2)1;
数1=n;
}
返回num1;
}
根据先序和中序重建二叉树
递归思维
假设已知的二叉树如下:
7/
10 2
//
4 3 8
/
1 11
那么它的前序遍历和中序遍历的结果如下:
预购={7,10,4,3,1,2,8,11}
中序={4,10,3,1,7,11,8,2}
有几个要点需要注意:
1)先序遍历的第一个节点始终是根节点。在前序遍历中,父节点总是先于子节点被遍历。
2)可以观察到,在中序遍历中,7是第4个值(从0开始计数)。由于中序遍历的顺序是:左子树、根节点、右子树。因此,7左边的四个节点{4,10,3,1}属于左子树,根节点7右边的{11,8,2}属于右子树。
3) 由上述结论可以得到递推公式。构造完根节点7后,我们可以根据中序遍历{4,10,3,1}和{11,8,2}分别构造其左子树和右子树。我们还需要相应的前序遍历结果来发现模式。从前序遍历可知,左右子树的前序遍历分别为{10,4,3,1}和{2,8,11}。左右子树也分别是二叉树,因此可以递归地解决问题。
公共TreeNode reConstructBinaryTree(int [] pre,int [] in) {
return build(pre,0,pre.length-1,in,0,in.length-1);
}
public TreeNode build(int [] pre,int prestart,int preend,int [] in,int instart,int inend) {
if(prestartpreend||instartinend)
返回空值;
TreeNode root=new TreeNode(pre[prestart]);
for(int i=instart;i=inend;i++){
if(in[i]==pre[预启动]){
root.left=build(pre,prestart+1,prestart+i-instart,in,instart,i-1);
root.right=build(pre,prestart+i-instart+1,preend,in,i+1,inend);
休息;
}
}
返回根;
}
二叉搜索树的后序遍历序列
判断一个序列是否是二叉搜索树的后序遍历序列。二叉搜索树的后序遍历满足以下条件,例如:{1,3,2,5,7,6,4}。最后一位4是根节点,{1,3,2}是左子树,{5,7,6}是右子树。继续递归,2为根节点,1为左子树,3为右子树。 6是根节点,5是左子树,7是右子树。
公共布尔VerifySquenceOfBST(int []序列){
if (序列.长度=0)
返回假;
return build(序列, 0, 序列.length - 1);
}
公共布尔构建(int []序列,int开始,int结束){
if (开始结束|| 结束0 || 开始=序列.长度) {
返回真;
}
int root=序列[结束];
int j=结束- 1;
while (j=开始序列[j]根) {
j--;//查找小于根节点的第一个下标
}
for (int i=开始; i=j; i++) {
if (序列[i]根)
返回假;
//判断左子树序列是否小于根节点。如果不小于根节点,则不满足。
}
//检查左子树和右子树
返回构建(序列,开始,j)构建(序列,j + 1,结束- 1);
}
树的子结构
递归比较
bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
{
if(pRoot1==NULL||pRoot2==NULL)
返回假;
返回find(pRoot1,pRoot2)||HasSubtree(pRoot1-left,pRoot2)||HasSubtree(pRoot1-right,pRoot2);
//将标志替换为短路或
}
bool find(TreeNode* pRoot1, TreeNode* pRoot2)
{
如果(pRoot2==NULL)
返回真;
如果(pRoot1==NULL)
返回假;
if(pRoot1-val==pRoot2-val)
return find(pRoot1-左, pRoot2-左)find(pRoot1-右, pRoot2-右);
返回假;
}
顺时针打印矩阵
如果觉得关注边界条件太麻烦,可以用魔方逆时针旋转的方法,一直取出第一行。
例如
1 2 3
4 5 6
7 8 9
输出并删除第一行后,再逆时针旋转,就变成:
6 9
5 8
4 7
只要继续重复以上操作即可。
最小的k个数
输出快速排序后的前k项。
平衡二叉树
空树或其左右子树的高度差的绝对值不超过1,且左右子树均为平衡二叉树。
bool IsBalanced_Solution(TreeNode* pRoot) {
如果(pRoot==NULL)
返回真;
int height=getHeight(pRoot-left)-getHeight(pRoot-right);
if(高度1||高度-1)
返回假;
返回IsBalanced_Solution(pRoot-left)IsBalanced_Solution(pRoot-right);
}
int getHeight(TreeNode* pRoot){
如果(pRoot==NULL)
返回0;
返回max(getHeight(pRoot-left),getHeight(pRoot-right))+1;
}本文持续更新。
END,本文到此结束,如果可以帮助到大家,还望关注本站哦!
【数据结构与算法题解攻略:剑指Offer核心突破总结】相关文章:
2.米颠拜石
3.王羲之临池学书
8.郑板桥轶事十则
用户评论
感觉剑指Offer总是让我头疼,想找一些资料看看怎么应对数据结构的问题。
有19位网友表示赞同!
刷题的时候经常卡在数据结构上,不知道该从哪下手学习好呢。
有10位网友表示赞同!
这个标题听着就很靠谱,希望总结能详细讲清楚要掌握哪些重点。
有17位网友表示赞同!
数据结构是算法的基础,搞懂了它才能更高效地解决问题吧?
有18位网友表示赞同!
一直想突破剑指Offer上的数据结构难题,这篇文章正好来了!
有11位网友表示赞同!
以前学过一些基础的数据结构,希望这篇总结能帮我巩固一下。
有5位网友表示赞同!
刷题不是光靠套路,要想精通算法还是要从数据结构开始学起。
有16位网友表示赞同!
期待能让我能把数据结构学的更透彻!
有10位网友表示赞同!
想找一些实战经验,看看剑指Offer中哪些数据结构知识点最常考。
有19位网友表示赞同!
感觉数据结构是算法学习中的关键门槛,这篇总结希望能给我一些启示。
有10位网友表示赞同!
学习算法的时候总是觉得数据结构是最难的模块,希望这篇文章能帮助我攻克它!
有10位网友表示赞同!
准备报名考研了,剑指Offer的数据结构题应该很有用吧?
有14位网友表示赞同!
这个总结应该是很实用的,适合想提高编程能力的人参考。
有6位网友表示赞同!
希望这篇总结能帮我理清数据结构的学习思路!
有19位网友表示赞同!
数据结构学习的方法有很多,也想听听别的同学是如何突破的?
有8位网友表示赞同!
总感觉自己对数据结构理解还不够深入,需要多加练习了。
有7位网友表示赞同!
剑指Offer上的题都挺难的,数据结构也是其中比较重要的部分。
有10位网友表示赞同!
算法和数据结构这对孪生兄弟,学好这两种知识点才能真正提升自己的编程能力!
有8位网友表示赞同!
学习数据结构要掌握哪些核心概念呢?这篇总结希望能给我一些答案。
有10位网友表示赞同!
刷题的时候遇到没解过的数据结构问题经常会让人头疼。
有8位网友表示赞同!