咳咳咳,今晚开始刷剑指offer,以前做过一部分,这次认真再来一下
1.二维数组中的查找
题目描述
在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。
请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
解题思路:
第一种方法:
把每一行看成有序递增的数组,
利用二分查找,
通过遍历每一行得到答案,
时间复杂度是nlogn
第二种方法:
从左下角的元素开始比较,target大的话row++,target小的话col--
//第一种
public class Solution {
public boolean Find(int [][] array,int target) {
//对每列二分检索(每列每行都一样)
for(int i=0;i<array.length;i++){
int low=0;
int high=array[0].length-1;
while(low<=high){//别忘了这里
int mid=(low+high)/2;
if(target==array[i][mid])
{
return true;
}else{
if(target>array[i][mid]){
low=mid+1;
}else{
high=mid-1;
}
}
}
}
return false;
}
}
//第二种
public class Solution {
public boolean Find(int [][] array,int target) {
int row=0;
int col=array[0].length-1;
while(row<array.length&&col>=0){//边界这里其实有点问题
if(target==array[row][col]){
return true;
}else if(target>array[row][col]){
row++;
}else{
col--;
}
}
return false;
}
}
2.替换空格
题目描述
请实现一个函数,将一个字符串中的空格替换成“%20”。
例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
解体思路:
这种题有很多种方法,可以巧妙地用已有的方法
第一种:
(简单粗暴)正则表达式
题中给定的参数传的是StringBuffer类的,先str.toString()转化为String类【那个括号不要忘了】
然后用String类的replaceAll("\\s","%20");
\s表示空格,前面的 \ 用来转义第二个 \
!!注意:replaceAll方法返回一个String,而不是更改原来的字符串,所以要新定义一个String a=s.replaceAll("","")
第二种:
转化为String类后,在转化成char[]数组,遍历,声明一个StingBuffer类,遇到空格,加上%20,否则加原来的字符
//第一种
public class Solution {
public String replaceSpace(StringBuffer str) {
return str.toString().replaceAll("\\s", "%20");
}
}
//第二种
public class Solution {
public String replaceSpace(StringBuffer str) {
String s=str.toString();
char[] c=s.toCharArray();
StringBuffer sb=new StringBuffer();
for(int i=0;i<c.length;i++){
if(c[i]==' '){
sb.append("%20");
}else{
sb.append(c[i]);
}
}
return sb.toString();
}
}
3.从尾到头打印链表
题目描述
输入一个链表,从尾到头打印链表每个节点的值。
输入描述:
输入为链表的表头
输出描述:
输出为需要打印的“新链表”的表头
解题思路:
第一种:
利用堆栈"先进后出"
第二种:
递归【!!!二刷的时候还不熟悉】
判断当前结点是否为空,不空的话,递归调用该函数,不停地找next,到了尾节点,其next为空,此时,将尾节点添加进list中,递归开始往回了,不停地倒着加入节点
//第一种
/**
* 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) {
Stack<Integer> stack=new Stack<Integer>();
while(listNode!=null){
stack.push(listNode.val);
listNode=listNode.next;
}
ArrayList<Integer> arr=new ArrayList<Integer>();
while(!stack.isEmpty()){
arr.add(stack.pop());
}
return arr;
}
}
//第二种
/**
* public class ListNode {
* int val;
* ListNode next = null;
*
* ListNode(int val) {
* this.val = val;
* }
* }
*
*/
import java.util.ArrayList;
public class Solution {
ArrayList<Integer> list=new ArrayList<Integer>();//在函数外面声明
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
if(listNode!=null){
this.printListFromTailToHead(listNode.next);//用this调用
list.add(listNode.val);
}
return list;
}
}
4.重建二叉树
题目描述
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。
假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
解题思路:
先判断这两种遍历数组是否为空,不要用pre==null,用pre.length==0
根据题目给出的前序遍历、后序遍历数组,
首先找出根节点,然后再根据中序遍历找到左子树和右子树的长度,
分别构造出左右子树的前序遍历和中序遍历序列,
最后分别对左右子树采取递归,递归跳出的条件是序列长度为1.
先序遍历第一个位置肯定是根节点node,
中序遍历的根节点位置在中间p,这样就可以知道左子树和右子树的长度
用Arrays.copyOfRange(int[] ,int from,int to)【取不到to】
递归递归【递归递归!!二刷的时候还是不够熟练】
/**
* Definition for binary tree
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
import java.util.*;
public class Solution {
public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
if(pre.length==0||in.length==0){
return null;
}
TreeNode node=new TreeNode(pre[0]);
for(int i=0;i<in.length;i++){
if(pre[0]==in[i]){
node.left=reConstructBinaryTree(Arrays.copyOfRange(pre,1,i+1),Arrays.copyOfRange(in,0,i));
node.right=reConstructBinaryTree(Arrays.copyOfRange(pre,i+1,pre.length),Arrays.copyOfRange(in,i+1,in.length));
}
}
return node;
}
}
5.用两个栈实现队列
题目描述
用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
解题思路:
这个题之前整理过,主要注意的是,一个栈负责压入,一个栈负责弹出
弹出时,那个栈要保证里面是空的,从压入栈中压入后,弹出
压入时,也要保证压入栈里没有东西,这两点一样,【弹出压入这两点一样,只要实现一个就行,全都放在弹出那】
注意到要将int类型转为Integer类型!!!
import java.util.Stack;
public class Solution {
Stack<Integer> stack1 = new Stack<Integer>();
Stack<Integer> stack2 = new Stack<Integer>();
public void push(int node) {
stack1.push(new Integer(node));//注意到要将int类型转为Integer类型//不一定用转换,直接也行
}
public int pop() {
if(stack2.isEmpty()){//判断栈空不空,用isEmpty()!!!
while(!stack1.isEmpty()){
stack2.push(stack1.pop());
}
}
return stack2.pop().intValue();//注意到要将Integer类型转为int类型//不一定用转换,直接也行
}
}
6.旋转数组的最小数字【有点疑惑】
题目描述
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
【其实这个题就是求数组中最小元素,只是因为它自身的特性可以减小时间复杂度来求】
解题思路:
根据题意说明是一个递增数组的旋转,所以如题所示【3,4,5】,【1,2】还是局部递增的,
在这种的数组中查找,一般选择二分的方法;
基本模型有了,下面试着分析:
1.先取出中间的数值,和最后一个比较5>2 说明mid之前的某些部分旋转到了后面,
所以下次寻找 low = mid+1 开始;
2.取出的中间值要是小于high,说明mid-high之间都应为被旋转的部分,所以最小应该在mid的前面,
但是也有可能当前的mid 就是最小的值 所以下次需找的应该 从mid开始,也即high = mid 开始
3.当*mid == *high的时候,说明数组中存在着相等的数值,
可能是这样的形式 【2,2,2,2,1,2】所以应该选择的high 应该递减1 作为下次寻找的上界。
import java.util.ArrayList;
public class Solution {
public int minNumberInRotateArray(int [] array) {
int low=0;
int high=array.length-1;
while(low<high){
int mid=low+(high-low)/2;//最好用这种,不用(high+low)/2
if(array[mid]>array[high]){
low=mid+1;
}else if(array[mid]<array[high]){
high=mid;
}else {
high=high-1;
}
}
return array[low];//返回最小
}
}
7.斐波那契数列
现在要求输入一个整数n,请你输出斐波那契数列的第n项。
n<=39
解题思路:
迭代方法,用两个变量记录fn-1和fn-2
还有P.S. f(n) = f(n-1) + f(n-2),第一眼看就是递归啊,简直完美的递归环境
if(n<=1)
return n;
else return Fibonacci(n-1)+Fibonacci(n-2);
public class Solution {
public int Fibonacci(int n) {
if(n<2){
return n;
}
int numfn1=0;
int numfn2=1;
int current=0;
for(int i=2;i<=n;i++){
current=numfn1+numfn2;
numfn1=numfn2;
numfn2=current;
}
return current;
}
}
8.跳台阶
题目描述
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
解题思路:
f(n) = f(n-1) + f(n-2)同上同上
最后一次可能跳一阶,可能跳两阶,
public class Solution {
public int JumpFloor(int target) {
if(target<=2){
return target;
}
int f1=2;// 当前台阶后退一阶的台阶的跳法总数(初始值当前台阶是第3阶)
int f2=1;// 当前台阶后退二阶的台阶的跳法总数(初始值当前台阶是第3阶)
int current=0;
for(int i=3;i<=target;i++){
current=f1+f2;
f2=f1;//后退一阶在下一次迭代变为后退两阶
f1=current;// 当前台阶在下一次迭代变为后退一阶
}
return current;
}
}
9.变态跳台阶
题目描述
一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。
求该青蛙跳上一个n级的台阶总共有多少种跳法。
题目解析:
重要的是分析化简,,,
关于本题,前提是n个台阶会有一次n阶的跳法。分析如下:
f(1) = 1
f(2) = f(2-1) + f(2-2) //f(2-2) 表示2阶一次跳2阶的次数。
f(3) = f(3-1) + f(3-2) + f(3-3)
...
f(n) = f(n-1) + f(n-2) + f(n-3) + ... + f(n-(n-1)) + f(n-n)
说明:
1)这里的f(n) 代表的是n个台阶有一次1,2,...n阶的 跳法数。
2)n = 1时,只有1种跳法,f(1) = 1
3) n = 2时,会有两个跳得方式,一次1阶或者2阶,这回归到了问题(1) ,f(2) = f(2-1) + f(2-2)
4) n = 3时,会有三种跳得方式,1阶、2阶、3阶,
那么就是第一次跳出1阶后面剩下:f(3-1);第一次跳出2阶,剩下f(3-2);第一次3阶,那么剩下f(3-3)
因此结论是f(3) = f(3-1)+f(3-2)+f(3-3)
5) n = n时,会有n中跳的方式,1阶、2阶...n阶,得出结论:
f(n) = f(n-1)+f(n-2)+...+f(n-(n-1)) + f(n-n) => f(0) + f(1) + f(2) + f(3) + ... + f(n-1)
6) 由以上已经是一种结论,但是为了简单,我们可以继续简化:
f(n-1) = f(0) + f(1)+f(2)+f(3) + ... + f((n-1)-1) = f(0) + f(1) + f(2) + f(3) + ... + f(n-2)
f(n) = f(0) + f(1) + f(2) + f(3) + ... + f(n-2) + f(n-1) = f(n-1) + f(n-1)
可以得出:
f(n) = 2*f(n-1)
7) 得出最终结论,在n阶台阶,一次有1、2、...n阶的跳的方式时,总得跳法为:
| 1 ,(n=0 )
f(n) = | 1 ,(n=1 )
| 2*f(n-1),(n>=2)
【注!!】
也可以看成,最后一步走1,2,3,n阶
则f(n)=f(n-1)+f(n-2)+....+f(n-n);
而f(n-1)刚好等于f(n-2)+...f(n-n)
所以得出f(n)=2*f(n-1)
public class Solution {
public int JumpFloorII(int target) {
if (target <= 0) {
return -1;
} else if (target == 1) {
return 1;
} else {
return 2 * JumpFloorII(target - 1);
}
}
}
10. 矩形覆盖
我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。
请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?
解题思路
依旧是斐波那契数列
2*n的大矩形,和n个2*1的小矩形
其中target*2为大矩阵的大小
有以下几种情形:
1、target = 1大矩形为2*1,只有一种摆放方法,return1;
2、target = 2 大矩形为2*2,有两种摆放方法,return2;
3、target = n 分为两步考虑:
如果第一格竖着放,只占一个格,还剩n-1格 f(target-1)种方法
如果前两格横着放两个,占两个格,还剩n-2格 f(target-2)种方法
public class Solution {
public int RectCover(int target) {
if(target==0||target==1||target==2){
return target;
}else if(target<0){return -1;}
else{
return RectCover(target-1)+RectCover(target-2);
}
}
}
11.二进制中1的个数
题目描述
输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
解题思路:
第一种:
如果一个整数不为0,那么这个整数至少有一位是1。
如果我们把这个整数减1,那么原来处在整数最右边的1就会变为0,
原来在1后面的所有的0都会变成1(如果最右边的1后面还有0的话)。其余所有位将不会受到影响。
举个例子:一个二进制数1100,从右边数起第三位是处于最右边的一个1。
减去1后,第三位变成0,它后面的两位0变成了1,而前面的1保持不变,因此得到的结果是1011.
我们发现减1的结果是把最右边的一个1开始的所有位都取反了。
这个时候如果我们再把原来的整数和减去1之后的结果做与运算,
从原来整数最右边一个1那一位开始所有位都会变成0。
如1100&1011=1000.
也就是说,把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0.
那么一个整数的二进制有多少个1,就可以进行多少次这样的操作。
第二种:
Java自带的函数
Java.lang.Integer.bitCount()方法
统计参数i转成2进制后有多少个1
【要不是这道题还真不知道这个函数呢】
第三种:
把这个数逐次 右移 然后和1 与,
就得到最低位的情况,其他位都为0,
如果最低位是0和1与 之后依旧 是0,如果是1,与之后还是1。
对于32位的整数 这样移动32次 就记录了这个数二进制中1的个数了
public class Solution {
public int NumberOf1(int n) {
int count = 0;
while(n!= 0){
count++;
n = n & (n - 1);//!!与运算
}
return count;
}
}
public class Solution {
public int NumberOf1(int n) {
return Integer.bitCount(n);
}
}
//这个方法也是很6的
public class Solution {
public int NumberOf1(int n) {
return Integer.toBinaryString(n).replaceAll("0","").length(); }
}
public class Solution {
public int NumberOf1(int n) {
int count=0;
for(int i=0;i<32;i++){
if((n>>i&1)==1){//!!!注意这里用i标记,每次循环,就重新右移i位
count++;
}
}
return count;
}
}
12.数值的整数次方
题目描述
给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。
解题思路:
第一种:
咳咳,Java自带的函数~
第二种:
算法的本质就是模拟数学规律,我们可以先模拟一下幂运算就是乘法的连乘,那么用算法写出来,然后再考虑几个测试用例的极端情况,如exponent==0或者exponent<0的情况,然后按逻辑写出你的代码
public class Solution {
public double Power(double base, int exponent) {
return Math.pow(base,exponent);
}
}
public class Solution {
public double Power(double base, int exponent) {
if(exponent==0){
return 1;
}else if(exponent>0){
double num=base;
for(int i=1;i<exponent;i++){
num=num*base;
}
return num;
}else{
double num2=base;
int flag=-exponent;
for(int i=1;i<flag;i++){
num2=num2*base;
}
num2=1/num2;
return num2;
}
}
}
13.调整数组顺序使奇数位于偶数前面
题目描述
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
解题思路:
第一种:
空间换时间,创建一个数组,奇数从头放,偶数从奇数个数的末尾开始放
或者,直接两个数组
第二种:
在一个数组中,类似冒泡算法,前偶后奇数就交换
import java.util.*;
public class Solution {
public void reOrderArray(int [] array) {
ArrayList<Integer> odd=new ArrayList<Integer>();//奇数
ArrayList<Integer> even=new ArrayList<Integer>();//偶数
for(int i=0;i<array.length;i++){
if(array[i]%2==0){
even.add(array[i]);
}else{
odd.add(array[i]);
}
}
for(int i=0;i<odd.size();i++){
array[i]=odd.get(i);
}
for(int i=odd.size();i<array.length;i++){
array[i]=even.get(i-odd.size());
}
}
}
public class Solution {
public void reOrderArray(int [] array) {
for(int i=0;i<array.length;i++){
for(int j=array.length-1;j>i;j--){
if(array[j]%2==1&&array[j-1]%2==0){
int temp=array[j];
array[j]=array[j-1];
array[j-1]=temp;
}
}
}
}
}
14.链表中倒数第k个结点
题目描述
输入一个链表,输出该链表中倒数第k个结点。
解题思路:
两个指针,先让第一个指针和第二个指针都指向头结点,然后再让第一个指针走(k-1)步,到达第k个节点。然后两个指针同时往末尾移动,当第一个结点到达末尾的时候,第二个