[剑指offer] JZ 5-7 做题笔记

JZ5 用两个栈实现队列

题目描述

题目描述
用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。

思路:一个栈进,一个栈出,即实现了队列

代码

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(node);
    }
    
    public int pop() {
        if(stack1.empty()&&stack2.empty()){
            throw new RuntimeException("Queue is empty!");
        }
        if(stack2.empty()){
            while(!stack1.empty()){
                stack2.push(stack1.pop());
            }
        }
        return stack2.pop();
    }
}

JZ6 旋转数组的最小

题目描述

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
示例1
输入
[3,4,5,1,2]
返回值
1

思路1:直接遍历,效率很低,但是代码能过

运行时间:198ms
超过79.15%用Java提交的代码
占用内存:29076KB
超过6.92%用Java提交的代码

代码

import java.util.ArrayList;
public class Solution {
    public int minNumberInRotateArray(int [] array) {
        int min = array[0];
        for(int i=1; i < array.length; i++){
            if(min > array[i]){
                min = array[i];
            }
        }
        return min;
    }
}

思路2:二分的思想

将右端点标识为target,中端点标示mid,array[mid]和target比较共三种情况:
array[mid] < target: 因为是非递增数组,下一个查找区间应该是在[first,mid]找
array[mid] > target:下一个查找区间为[mid+1, last]
array[mid] = target:不能判断,将last=last-1后继续查找。
但是,不能将左端点制定为target,如

  • 12345,mid=3,target=1,mid>target,但最小在mid左侧
  • 34512,mid=5,target=3,mid>target,但最小在mid右侧

代码

import java.util.ArrayList;
import java.util.Arrays;
public class Solution {   
    public int minNumberInRotateArray(int [] array) {
        if(array.length == 0) return 0;
        int first = 0, last = array.length - 1;
        while(first < last){//first = last的时候只有一个元素,停止
            if(array[first] < array[last]){//退出,说明没有旋转,就是按照递增的顺序
                return array[first];
            }
            int mid = first + ((last - first) >> 1);
            if(array[mid] > array[last])
                first = mid + 1;
            else if(array[mid] < array[last])
                last = mid;
            else
                --last;
        }
        return array[first];
    }
};

JZ 7 斐波那契数列

题目描述

大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0,第1项是1)。
n≤39n≤39
示例1
输入
4
返回值
3

思路1:直接递归,但是有大量重复计算,时间和空间复杂度都比较高

public class Solution {
    public int Fibonacci(int n) {
        if(n == 0 || n == 1) return n;
        return Fibonacci(n-1) + Fibonacci(n-2);
    }
}
时间复杂度:O(2^n)
空间复杂度:递归栈的空间

思路2:将计算过的斐波那契的值存在另外一个数组中,不再二次计算,减少时间复杂度

import java.util.Arrays;
//解决第一次提交中递归中重复计算的问题,节约时间
public class Solution {
    public int Fib(int n, int[] dp){
        if(n == 0 || n == 1) return n;
        if(dp[n] != -1) return dp[n];//dp用来存放FIbonacci(n),只要不为-1.说明计算过了,直接返回。
        return dp[n] = Fib(n-1, dp) + Fib(n-2, dp);//如果没算过,先计算,再返回
    }
    public int Fibonacci(int n) {
        int[] dp = new int[45];//测试用例长度小于45
        Arrays.fill(dp, -1);//将为计算的地方填充为-1,因为答案均大于0,方便计算
        return Fib(n, dp);
    }
}

时间复杂度:O(n), 没有重复的计算
空间复杂度:O(n)和递归栈的空间

思路3:动态规划

虽然方法二可以解决此题了,但是如果想让空间继续优化,那就用动态规划,优化掉递归栈空间。
方法二是从上往下递归的然后再从下往上回溯的,最后回溯的时候来合并子树从而求得答案。
那么动态规划不同的是,不用递归的过程,直接从子树求得答案。过程是从下往上。

public class Solution {
    public int Fibonacci(int n) {//直接 从子树开始
        int [] dp = new int[n+1];
        dp[1] = 1;
        for(int i = 2; i <= n; ++i){
            dp[i] = dp[i-1] + dp[i-2];
        }
        return dp[n];
    }
}

这段代码我本地试了下没问题,牛客网上显示数组越界,暂时不纠结了。
时间复杂度:O(n)
空间复杂度:O(n)

再进一步,计算n的时候,只会用到n-1和 n-2,所以这里只用保存2个元素

public class Solution {
    public int Fibonacci(int n) {//直接 从子树开始
        if(n == 0 || n == 1) return n;
        int a = 0 , b = 1, c = 0;
        for(int i = 2; i <= n; ++i){
            c = a + b;
            a = b;
            b = c;
        }
        return c;
    }
}

时间复杂度:O(n)
空间复杂度:O(1)

posted @ 2021-02-12 11:17  lonelyisland  阅读(97)  评论(0编辑  收藏  举报