剑指Offer系列之题6~题10
6.用两个栈实现队列
用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
考虑栈1用于存储元素,出队时,将栈1的元素压入栈2,此时栈2中元素从栈顶到底即其入队的顺序,然后出栈。若出队时栈2非空,则直接从栈2弹出元素。
1、根据栈2是否空将栈1元素全部压入:
import java.util.Stack;
public class Solution {
Stack<Integer> stack1 = new Stack<Integer>();
Stack<Integer> stack2 = new Stack<Integer>();
//栈1存储元素,然后压入栈2,栈2的元素即进入队列的顺序
public void push(int node) {
stack1.push(node);
}
public int pop() {//每次出队时,判断栈2是否空,空则将栈1元素出栈后压入栈2,然后弹出栈2的栈顶元素
if(stack2.empty()){
while(!stack1.empty()){
stack2.push(stack1.pop());
}
}
return stack2.pop();
}
}
2、根据栈1是否只剩1个元素将栈1除栈底外元素压入:
该思路相比上一步增加了复杂度,不推荐。
//判断栈1是否为空,空则从栈2弹出栈顶元素(考虑两栈都空的异常)
//栈1非空则判断栈1是否只有一个元素。是则pop,不是则将除栈底外元素全部push到栈2
7.旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
考虑数组旋转后分为左边的递增数组和右边的递增数组,通过三个指针去判断,左、中、右。
当中间的元素大于等于左边元素时,说明其在左边的递增数组,此时最小值在右边。左指针移到中间位置。
当中间的元素小于等于右边元素时,说明其在右边的递增数组,此时最小值在左边。右指针移到中间位置。
当左=中=右时无法判断,顺序查找。退出条件为左右指针相邻,此时右指针的元素为最小值。
1、暴力解:
//遍历,输出最小值
public class Solution {
public int minNumberInRotateArray(int [] array) {
if(array.length==0){//异常
return 0;
}
int min=array[0];
for(int i=0;i<array.length-1;i++){
if(array[i]>array[i+1]){
min=array[i+1];
break;
}
}
return min;
}
}
2、二分查找:
public class Solution {
public int minNumberInRotateArray(int [] array) {
if(array.length==0){//异常
return 0;
}
int len=array.length;
int left=0;//左指针
int right=len-1;//右指针
int mid=(left+right)/2;
if(array[left]<array[right]){//若小于则证明该数组是升序
return array[left];
}
//特殊情况,左、右、中三值相等,此时下面的方法无法判断。需要单独处理这种情况
if(array[left]==array[mid] && array[right]==array[mid]){
int min=array[0];
for(int i=0;i<len-1;i++){
if(array[i]>array[i+1]){
min=array[i+1];
break;
}
}
return min;
}
while(right!=left+1){
if(array[mid]<=array[right]){
//说明在右边的递增数组,最小值在该元素前面
right=mid;
mid=(left+right)/2;
}
if(array[mid]>=array[left]){
//说明在左边的递增数组,最小值在该元素后面
left=mid;
mid=(left+right)/2;
}
}
return array[right];
}
}
8.斐波那契数列
大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0,第1项为1)。n<=39
了解斐波那契数列的特性 f(n)=f(n-1)+f(n-2){n>=2};
1、递归:
public class Solution {
public int Fibonacci(int n) {
//n<=39 此处f(n)=f(n-1)+f(n-2){n>=2};
if(n==0){
return 0;
}else if(n==1){
return 1;
}else{
return Fibonacci(n-1)+Fibonacci(n-2);
}
}
}
该暴力解法存在效率方面的缺点,会重复计算大部分值。
2、利用循环:
public class Solution {
public int Fibonacci(int n) {
//n<=39 此处f(n)=f(n-1)+f(n-2){n>=2};
if(n==0){
return 0;
}else if(n==1){
return 1;
}else{
int f1=0;
int f2=1;
int fn=0;
//利用循环
for(int i=2;i<=n;i++){
fn=f1+f2;
f1=f2;//向后移一位
f2=fn;
}
return fn;
}
}
}
9. 跳台阶
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
斐波那契数列的应用
1.递归:
public class Solution {
public int JumpFloor(int target) {
//斐波那契数列的应用
if(target==1){
return 1;
}else if(target==2){
return 2;
}else{
return JumpFloor(target-1)+JumpFloor(target-2);
}
}
}
2.循环:
public class Solution {
public int JumpFloor(int target) {
//斐波那契数列的应用
if(target==1){
return 1;
}else if(target==2){
return 2;
}else{
int f1=1;
int f2=2;
int fn=0;
for(int i=3;i<=target;i++){
fn=f1+f2;
f1=f2;
f2=fn;
}
return fn;
}
}
}
10.变态跳台阶 🔺
一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
关键是求出f(n)的公式。
假设n级台阶,每一次可以跳1、2、3……n阶;f(1)=1;f(2)=2
那么跳n级台阶的跳法f(n)=f(n-1)+f(n-2)+f(n-3)+……+f(n-n)
上述公式的解释:总跳法=第一次跳一级之后的跳法+第一次跳2级之后的跳法+……第一次跳n级的跳法=f(n-1)+f(n-2)+……+f(2)+f(1)+f(0);其中f(n-1)=f(n-2)+f(n-3)+……+f(1)+f(0);
所以f(n)=2*f(n-1)=2*2*f(n-2)=2^((n-1)*f(1)) =2^(n-1)
另一解释:每个台阶可以看作一块木板,让青蛙跳上去,n个台阶就有n块木板,最后一块木板是青蛙到达的位子, 必须存在,其他 (n-1) 块木板可以任意选择是否存在,则每个木板有存在和不存在两种选择,(n-1) 块木板 就有 [2^(n-1)] 种跳法,可以直接得到结果。
1.循环:
//同上题,该题仍有递归和循环两种解法
public class Solution {
public int JumpFloorII(int target) {
//1:1; 2:2 3:4; 4:8 2^(n-1)
if(target==1 || target ==2){
return target;
}
int res=2;
for(int i=2;i<=target-1;i++){
res=res*2;
}
return res;
}
}
如有错误,欢迎指正