4.二维数组中的查找
题目
在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
思路
思维误区:不要从右下角的对角线开始比较,这样比较无法判断目标数字实际上位于该对角线数字的左侧还是上侧。
转变思维:从右上角对角线开始比较。若对角线数字比目标值大,则实际位置必然位于左侧,若对角线数字比目标值小,则实际位置必然位于下侧。于是将初始点定在右上角开始循环,循环的终止条件为点超出边界。
测试用例
特殊:空数组,空指针
功能:数组包含查找的数字。数组不包含查找的数字。
5.替换空格
题目
实现一个函数,把字符串中的每个空格替换成"%20"。例如输入“We are happy.”,则输出“We%20are%20happy.“不允许用库函数。
思路
遍历字符串,算出所有的空格数,算出替换后的字符串长度,然后设定两个指针,分别指向新字符串和原来的字符串的尾部,从尾部开始复制。普通字符直接复制,若是空格则连续替换三个字符。
测试用例
空字符串,空指针,字符串中没有空格,字符串中全是空格,字符串第一个字符是空格,最后一个字符串是空格,连续多个空格。
6.逆序打印链表
题目
输入一个链表的头结点,从尾到头反过来打印出每个结点的值。
思路
- 最简单的思路是就是堆栈,每访问一个节点就将其入栈,最后将所有元素出栈。
- 还可以考虑将链表倒置,然后顺序遍历输出即可。
- 能用堆栈就考虑递归,递归输出每个节点即可。链表长度直接影响递归栈深度,相对来讲不如直接用栈。
代码
public void printReversly(ListNode node) {
if (node == null){
return;
}else {
if (node.next != null){
printReversly(node.next);
System.out.println(node.val);
}
}
}
测试用例
空链表,只有一个节点的链表
7.重建二叉树
题目
输入某二叉树的前序遍历和中序遍历的结果,重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1, 2, 4, 7, 3, 5, 6, 8}和中序遍历序列{4, 7, 2, 1, 5, 3, 8, 6},则重建出其二叉树并输出它的头结点。
思路
采用递归。(思路实在懒得写)
测试用例
空指针。只有一个节点的二叉树。两个序列不匹配。
核心代码
这个题实在是有点。。。(以后熟练了再写吧)
8.二叉树的下一个节点
题目
给定一棵二叉树和其中的一个结点,如何找出中序遍历顺序的下一个结点? 树中的结点除了有两个分别指向左右子结点的指针以外,还有一个指向父结点的指针。
思路
如果这个节点有子节点而且有右子节点,那么下一个节点必然是右子树的最左面的节点。
若果这个节点没有右子树,但他是父节点的左子节点,那么下一个节点是他的父节点。
如果不属于以上两种情况,那么就要顺着父指针向上找,直到找到一个节点,他是父节点的左子节点。如果找不到,那就不存在下一个节点。
测试用例
空指针,左斜树,右斜树,单个结点的树。
9两个栈实现队列
题目
思路
入队不用说,直接入右栈,并且只入右栈。考虑出队,若左栈不为空,则弹出栈顶;若为空,将右栈全部弹出压入左栈,弹出栈顶。
测试用例
空栈删除,空栈添加,非空栈删除,非空栈添加,连续删除到空
扩展:两个队列实现栈
思路
将queue1用作进栈出栈,queue2作为一个中转站
入栈时,直接压入queue1中,出栈时,先将queue1中的元素除最后一个元素外依次出队列,并压入队列queue2中,将留在queue1中的最后一个元素出队列即为出栈元素,最后还要把queue2中的元素再次压入queue1中。
10.fibonacii数列
题目
写一个函数,输入n,求斐波那契(Fibonacci)数列的第n项。
思路
递归方效率太低,重复计算太多。采用循环,从f(3)开始算,一项一项累加,效率高。
测试用例
负数,0,1,2,5,10,100,1000
扩展:青蛙跳台阶
题目
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
思路
青蛙第一次如果跳了一个台阶,那么剩余的台阶有f(n-1)种跳法,如果青蛙第一次跳了两个台阶,那么剩余的台阶有f(n-2)种跳法,即f(n) = f(n-1) + f(n-2),Fibonacci问题。f(1)=1,f(2)=2。
题目
一只青蛙一次可以跳上1级台阶,也可以跳上2级……也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
思路
青蛙第一次跳1级的话,有f(n-1)种方法;第一次跳2级的话,有f(n-2)种方法……第一次跳n-1级的话,有f(1)种方法;直接跳n级的话,有1种方法,所以:
f(1)=1
f(n) = f(n-1)+f(n-2)+…f(1)+1 (n≥2)
得出f(n)=2^(n-1)
扩展:填充图案
题目
用2 * 1的小矩形覆盖2 * 8 的大矩形,有多少种方法。
思路
用第一个2 * 1的小矩形覆盖时候,若竖着放,剩下的矩形有f(7)种方法,若横着放,那么下面的两个位置也只能横着放,剩下的矩形有f(6)种方法。即f(8)=f(7)+f(6)。
11.旋转数组的最小数字
题目
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3, 4, 5, 1, 2}为{1, 2, 3, 4, 5}的一个旋转,该数组的最小值为1。
思路
二分查找的思路,两个指针指向头和尾,然后找到中间位置的数,若这个数大于左边的指针,则最小值为与右侧,更新左侧指针。若这个数小于右边的指针,最小值位于左侧,更新右侧指针。
考虑特殊情况,若这个数组本身已经是有序的数组,未经过旋转,则一开始就判断左指针小于右指针,就直接返回左指针指向的数字。
再考虑特殊情况,若数组中有很多重复数字,有可能三个指针都相等,那么结果可能并不是你预计的样子。那么就老老实实的遍历吧。(早这样不就没那么多屁事儿了。。)
测试用例
空指针,未旋转的数组,数组中有重复的数字,数组中没有重复的数字,数组中全是重复的数字,长度为1的数组。
12.矩阵中的路径
13.机器人的活动范围
14.剪绳子
题目
一根长度为n绳子,把绳子剪成m段(m、n都是整数,n>1并且m≥1)。每段的绳子的长度记为k[0]、k[1]、……、k[m]。 k[0]* k[1]* …* k[m]的最大乘积是多少?例如当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到最大的乘积18。
思路
一开始没有思路时,可以从简单的情况开始想,试着算以下比较短的绳子是如何剪的。当n=1时,最大乘积只能为0;当n=2时,最大乘积只能为1;当n=3时,最大乘积只能为2;当n=4时,可以分为如下几种情况:1* 1* 1* 1,1* 2* 1,1* 3,2* 2,最大乘积为4;
往下推时,发现n≥4时,可以把问题变成几个小问题,即:如果把长度n绳子的最大乘积记为f(n),则有:f(n)=max(f(i)* f(n-1)),0<i<n。所以思路就很容易出来了:从下往上推,先算小的问题,再算大的问题,大的问题通过寻找小问题的最优组合得到。
https://www.cnblogs.com/yongh/p/9660802.html
代码
public class CuttingRope {
// ======动态规划======
public int maxProductAfterCutting_solution1(int length) {
if (length <= 1)
return 0;
if (length == 2)
return 1;
if (length == 3)
return 2;
int[] product = new int[length + 1]; // 用于存放最大乘积值,product[i]表示长度为i的绳子切段最大乘积。
// 下面几个不是乘积,因为其本身长度比乘积大
product[0] = 0;
product[1] = 1;
product[2] = 2;
product[3] = 3;
// 开始从下到上计算长度为i绳子的最大乘积值product[i]
for (int i = 4; i <= length; i++) {
int max = 0;
// 算不同子长度的乘积,找出最大的乘积
for (int j = 1; j <= i / 2; j++) {
if (max < product[j] * product[i - j])
max = product[j] * product[i - j];
}
product[i] = max;
}
return product[length];
}
测试用例
1,2,3,4,5,10。
总结
其实这就是动态规划法,以下是动态规划法的几个特点:
1.求一个问题的最优解
2.整体问题的最优解依赖各子问题的最优解
3.小问题之间还有相互重叠的更小的子问题
4.为了避免小问题的重复求解,采用从上往下分析和从下往上求解的方法求解问题
15.二进制中1的个数
题目
实现一个函数,输入一个整数,输出该数二进制表示中1的个数。例如把9表示成二进制是1001,有2位是1,该函数输出2。
思路
错误解法一:就像将一个十进制数转化为二进制数那样,不断地对2取余,每有一个1,count++。这个思路的错误就在于题目没有规定输入的整数是正数,负数是各位取反再加一,无法像正数一样得到答案
错误解法二:判断这个数的最右边一位是不是1,接着右移一位,再判断是不是一,直到这个数等于0。错误之处在于把负数不断地右移会在左边不断产生新的1,导致死循环。
常规解法:为了避免死循环,我们决定不右移,而是分别判断每一位是不是1。循环的次数等于int类型的位数。
while(flag){
if(n&flag==1)
count++;
flag = flag << 1;
}
高玩解法:将一个数减去一和这个数本身进行与运算,能将这个数的最右面一个1变成0,那么我们不断消去里面的1,直到这个数变成0。
while(n){
count++;
n = n & (n-1);
}
扩展
用一条语句判断一个整数是不是二的整数次方
n & (n-1)是否为0;
输入两个数m和n,计算需要改变m中的多少位才能变成n。
m和n做异或,看看结果里面有几个1。