《剑指Offer》
- 牛客网 前20道题
-
- 前言知识
- 面试题1:二维数组中的查找
- 面试题2:二维数组中的查找
- 面试题3:从头到尾打印链表
- 面试题4:重建二叉树 *****
- 面试题5:两个栈实现一个队列
- 面试题6:求旋转数组的最小数字
- 面试题7:编写斐波那契数列
- 面试题8:青蛙跳台阶问题
- 面试题9:变态青蛙跳台阶问题
- 面试题10:2*1小矩形覆盖2*n大矩形问题
- 面试题11:一个整数二进制存储中1的个数
- 面试题12:数值的整数次方
- 面试题13:调整数组顺序使奇数位于偶数前面
- 面试题14:输出链表的倒数第k个节点
- 面试题15:反转链表
- 面试题16:合并两个有序的链表
- 面试题17:判断A树是不是B的子树 *****
- 面试题18:求二叉树的镜像
- 面试题19:顺时针打印矩阵
- 面试题20:自己定义一个可以求出自身最小值的栈
牛客网 前20道题
前言知识
-
优秀的程序员首先要有良好的基本功,基本功在面试编程环境体现在:编程语言、数据结构和算法。
编程语言:以C语言为例,例如把const加在指针不同位置有什么区别。主要考察对编程语言掌握程度。
数据结构:要熟练掌握链表、树、栈、队列和哈希表等数据结构,对链表的插入和删除节点了如指掌,对二叉树的各种遍历方法和递归写法烂熟于心。
算法:基本的查找、排序算法,重点掌握二分查找、归并排序、快速排序。 -
要注重代码的鲁棒性,可靠性,编写的任何函数要格外关注边界条件、特殊输入等看书细枝末节实则至关重要地方。
-
要有清晰的思路,在解决复杂问题时,可以举举例子,试着用图像分析过程等。
-
要注重算法的复杂度,有提高优化效率的能力,例如求斐波那契数列数列,很多人喜欢用递归,但是递归的时间复杂度是以n的指数增加的,如果先求f(1)和f(2),复杂度是O(n)。只要熟知各种数据结构的优缺点,才能选择合适的数据结构解决问题。
剑指Offer中采用C++和C#来编程,我只能用C、java语言实现。
https://www.nowcoder.com/ta/coding-interviews
面试题1:二维数组中的查找
题目:在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
思路:由于每行从左到右递增,每列从上到下递增,所以选取数组右上角的数字,如果该数字等于要查找的数字,结束查找过程。如果大于要查找的数字,剔除这个数字所在的列。如果小于要查找的数字,剔除这个数字所在的行。
我犯的错误:
1、二维数组赋值方式未掌握:
int array[][] = new int[][]{
{
1,2,8,9},{
2,4,9,12},{
4,7,10,13},{
6,8,11,15}};
int array[][] = {
{
1,2,8,9},{
2,4,9,12},{
4,7,10,13},{
6,8,11,15}};
2、循环边界的判定
3、如何判断一个二维数组不为空,首先二维数组指针不能为空,其次若指针不为空,行长度不能为空,若行长度也不为空,那么列的长度也不能为空。array == null ||array.length == 0||(array.length == 1&&array[0].length == 0)
代码:
public class Solution {
public boolean Find(int target, int [][] array) {
boolean result = false;
if(array == null ||array.length == 0||(array.length == 1&&array[0].length == 0))
return false;
int row = array.length ;
int column = array[0].length;
int i = 0,j = column-1;
while(i<row&&j>=0)
{
if(array[i][j] >target)
{
j --;
}
else if(array[i][j] <target)
{
i ++;
}
else if(array[i][j] == target)
{
result = true;
break;
}
}
return result;
}
}
面试题2:二维数组中的查找
题目:请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。 public String replaceSpace(StringBuffer str)
思路:1、最简单的方式,创建新的字符串,在新的字符串上,遇到空格就替换成“%20”。从前到后。
2、然而,面试官往往如果说在原来的字符串上替换,并且保证原来的字符串有足够的长度,此时就要先计算原有字符串的长度和空格的长度,则可以计算扩充后的字符串长度,那么从后往前遍历,遇到空格就替换成“%20”。
遇到的问题:
1、对StringBuffer类的api不熟,传入的参数是StringBuffer str,傻傻的用str[i]来遍历,StringBuffer的类变量长度是length(),而不是像数组array.length。
2、charAt、deleteCharAt、函数不熟
3、初始化一个char[]字符数组:char[] insertChars = {’%’,‘2’,‘0’};
代码:
public class Solution {
public String replaceSpace(StringBuffer str) {
if(str.length() == 0) return "";
int length = str.length();
int i = length - 1;
char[] insertChars = {
'%','2','0'};
while(i>=0)
{
if(str.charAt(i) == ' '){
str.deleteCharAt(i);
str.insert(i, insertChars);
i --;
}
else
{
i--;
}
}
return str.toString();
}
}
面试题3:从头到尾打印链表
题目:输入一个链表,按链表值从尾到头的顺序返回一个ArrayList。
思路:如果我们遍历链表的话,只能从头到尾遍历,但是需要输出的是从尾到头。因此我们可以用栈来实现这种顺序,每经过一个结点时,把这个结点放到栈中,遍历完整个链表后,从栈顶开始输出,此时链表中的数据相当于反转了。
遇到的问题:
1、根本没用过java中的Stack类,使用时要导入java.util.Stack;
2、对特殊的测试用例没有做出很好的处理,当传入的是链表结点时,判断结点是否存在相当于该链表是否存在
3、case通过率为9.09%,用例:{67,0,24,58},对应输出应该为:[58,24,0,67],你的输出为:[24,0,67],原因在于遍历链表时用while(listNode.next !=null){stack.push(listNode.val);}会将最后一个值忘记装入栈中,所以后面要补充。
代码:
/**
* public class ListNode {
* int val;
* ListNode next = null;
*
* ListNode(int val) {
* this.val = val;
* }
* }
*
*/
import java.util.ArrayList;
import java.util.Stack;
public class Solution {
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> arrayList = new ArrayList<>();
if(listNode == null){
return arrayList;
}
Stack<Integer> stack = new Stack<>();
while(listNode.next != null)
{
stack.push(listNode.val);
listNode = listNode.next;
}
stack.push(listNode.val);
while(!stack.isEmpty())
{
arrayList.add(stack.pop());
}
return arrayList;
}
}
面试题4:重建二叉树 *****
题目:输入某二叉树的前序遍历和中序遍历的结果int [] pre,int [] in,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
思路:前序遍历的第一个数字就是根结点值,根据中序遍历的特点在根结点1前面的3个数字都是根结点左子树的值,位于1后面的都是根结点右子树的值。二叉树的构建是由很多个形如 /\ 的3个结点组成的最小二叉树从根结点开始由上往下构成,每次构建这样的最小二叉树getRootNode,关键在于根结点怎么确定?左子树结点、右子树结点如何确定?根结点为每次得到的前序遍历序列的第一个值TreeNode root= new TreeNode(preList[0])。得到根结点后开始构建左子树结点、右子树结点,显然左子树结点、右子树结点可以视为再下一层的子树新的根结点,root.left = getRootNode(左子树结点前序遍历序列,左子树中序遍历序列);root.left = getRootNode(右子树结点前序遍历序列,右子树中序遍历序列);
如何去确定root.left = getRootNode(左子树结点前序遍历序列,左子树中序遍历序列);root.left = getRootNode(右子树结点前序遍历序列,右子树中序遍历序列);中的参数是最难之处。每次都是取前序遍历的第一个值作为最小二叉树的根结点,因此每次遍历只需判断传入的前序二叉树是否有效即可。左子树结点中序遍历序列 = inList.subList(0,index); 右子树结点的中序遍历序列 = inList.subList(index+1,inList.size()); 左子树结点前序遍历序列 = preList.subList(1,左子树结点遍历序列的长度)
右子树结点前序遍历序列 = preList.subList();
遇到的问题:
1、数组如何用while遍历,原来想用while(array[i] != null)并不行,报错he operator != is undefined for the argument type(s) int, null,应该while(index<array.length) index++;用for遍历代码更简洁。
2、基本不能独立编完
/**
* Definition for binary tree
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
import java.util.ArrayList;
import java.util.List;
public class Solution {
public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
if(pre == null||in == null||pre.length == 0||in.length == 0)
return null;
ArrayList<Integer> preList = new