Leetcode《图解数据结构》刷题日志【第三周】(2022/10/31-2022/11/06)

1. 剑指Offer 59 -II.队列的最大值

1.1 题目

请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。
若队列为空,pop_front 和 max_value需要返回 -1

示例 1:
输入: 
    ["MaxQueue","push_back","push_back","max_value","pop_front","max_value"]
    [[],[1],[2],[],[],[]]
输出:
    [null,null,null,2,1,2]
示例 2:
输入: 
    ["MaxQueue","pop_front","max_value"]
    [[],[],[]]
输出:
    [null,-1,-1]
限制:
    1 <= push_back,pop_front,max_value的总操作数?<= 10000
    1 <= value <= 10^5

1.2 解题思路

因为题目里面要求的是max_value()pop_front()push_back()的平均时间复杂度都是O(1),因此在查找队列最大值的时候需要一个辅助的数据结构,以空间换时间,这一题的思路和剑指 Offer 30. 包含 min 函数的栈还有剑指 Offer 59 - I. 滑动窗口的最大值的思路类似。
我们需要维护一个辅助队列,将最大的元素存储在首位,维护过程如下

  • 加入元素:在push_back()里面,从队列尾部添加元素,如果队列尾部元素小于待插入的元素值value,将若干个小于value的元素从队列中取出
  • 删除元素:在pop_front()里面,从队列头部取出元素,如果这个头部元素是辅助队列的最大元素,则在辅助队列中也需要将头部元素删除。
    从而维护一个递减的辅助队列,原始队列中的最大元素永远在辅助队列的首位。
    PS:这个维护过程是不是和剑指 Offer 59 - I. 滑动窗口的最大值的辅助队列一样?其实感觉这两题思路类似而这题还要稍微简单一点。

1.3 数据类型功能函数总结

//LinkedList相关操作
LinkedList<> LL_Name = new LinkedList<>();//定义一个链表,可根据头尾操作模拟队列或者栈
LinkedList.add/get/remove First/Last();//添加、获取、删除 头部/尾部元素
LinkedList.size();//获得链表长度

1.4 java代码

class MaxQueue {
    LinkedList<Integer> maxQueue;
    LinkedList<Integer> subMaxQueue;
    public MaxQueue() {//初始化
        maxQueue=new LinkedList<Integer>();
        subMaxQueue=new LinkedList<Integer>();
    }

    public int max_value() {
        if(subMaxQueue.size()==0){
            return -1;
        }
        return subMaxQueue.getFirst();
    }
    
    public void push_back(int value) {
        maxQueue.addLast(value);
        while(subMaxQueue.size()!=0 && subMaxQueue.getLast()<value){
            subMaxQueue.removeLast();
        }
        subMaxQueue.addLast(value);
    }
    
    public int pop_front() {
        if(maxQueue.size()==0){
            return -1;
        }
        int ele=maxQueue.removeFirst();
        if(subMaxQueue.size()!=0 &&  subMaxQueue.getFirst()==ele){
            subMaxQueue.removeFirst();
        }
        return ele;
    }
}

/**
 * Your MaxQueue object will be instantiated and called as such:
 * MaxQueue obj = new MaxQueue();
 * int param_1 = obj.max_value();
 * obj.push_back(value);
 * int param_3 = obj.pop_front();
 */

2. 剑指Offer 67.将字符串转换成整数

2.1 题目

写一个函数 StrToInt,实现把字符串转换成整数这个功能。不能使用 atoi 或者其他类似的库函数。

首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。
当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。
该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。
注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。
在任何情况下,若函数不能进行有效的转换时,请返回 0。

说明:
假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为?[?231,? 231?? 1]。如果数值超过这个范围,请返回 ?INT_MAX (231?? 1)?INT_MIN (?231) 。

示例?1:
输入: "42"
输出: 42

示例?2:
输入: "   -42"
输出: -42
解释: 第一个非空白字符为 '-', 它是一个负号。我们尽可能将负号与后面所有连续出现的数字组合起来,最后得到 -42 。

示例?3:
输入: "4193 with words"
输出: 4193
解释: 转换截止于数字 '3' ,因为它的下一个字符不为数字。

示例?4:
输入: "words and 987"
输出: 0
解释: 第一个非空字符是 'w', 但它不是数字或正、负号。因此无法执行有效的转换。

示例?5:
输入: "-91283472332"
输出: -2147483648
解释: 数字 "-91283472332" 超过 32 位有符号整数范围。 因此返回 INT_MIN (?231)

2.2 解题思路

解题思路主要就是按照字符串的顺序进行遍历,并且进行一些缝缝补补。

  • 如果字符串是空串,直接返回0;
  • 如果字符串有若干空格,跳过这些空格;
  • 如果第一个非空格字符是正负号,存储这个字符,继续向下遍历
  • 如果第一个非空格字符是数字,开始查找连续数字,将单个字符转为整数并加入结果中
    • 由于结果可能会超过int的范围,我们将结果定义为long型,在最后再返回
    • 在结果不断累计的过程中,查看它和对应的INT_MAXINT_MIN的关系,如果超过,字符串之后的字符不必考虑,直接将结果转为特殊值并退出遍历。
  • 如果第一个非空格字符不是正负号也不是数组,跳过结果的处理,返回初值0;

2.3 数据类型功能函数总结

//string相关操作
string.length();//获得字符串长度
string.charAt(index);//获得指定下标的字符
//int型相关操作
Integer.INT_MAX;//int型最大值
Integer.INT_MIN;//int型最小值
整数类型:int/long;//注意:没有c语言里面的long long

2.4 java代码

class Solution {
    public int strToInt(String str) {
        long re=0;
        int i=0;
        if(str.length()==0){//空串
            return 0;
        }
        while(i < str.length() && str.charAt(i)==' '){//跳过空格
            i++;
        }
        char temp=' ';
        if(i<str.length() && (str.charAt(i)=='+'||str.charAt(i)=='-')){//如果是正负号,存起来,最后处理
            temp=str.charAt(i);
            i++;
        }
        while(i<str.length() && str.charAt(i)>='0'&&str.charAt(i)<='9'){//数字部分
            re=re*10+(str.charAt(i)-'0');
            if(temp=='-'){
                if(-re < Integer.MIN_VALUE){
                    re = Integer.MIN_VALUE;
                    break;
                }
            }
            else{//整数
                if(re > Integer.MAX_VALUE){
                    re = Integer.MAX_VALUE;
                    break;
                }
            }
            i++;
        }
        if(temp=='-'){
            if(re>0){
                re=-re;
            }
        }
        return (int)re;
    }
}

2.5 踩坑类型

2.5.1 “reached end of file while parsing”

错误原因:括号不匹配,仔细检查代码。

2.5.2 “java.lang.StringIndexOutOfBoundsException”

错误原因:遍历字符串的过程中超过了字符串的实际范围,访问越界。需要检查自己的代码,在遍历的时候注意不要超过范围。
比如我这里的错误点就在于:

//错误版本
if(i<str.length() && str.charAt(i)=='+'||str.charAt(i)=='-'){//如果是正负号,存起来,最后处理
    temp=str.charAt(i);
    i++;
}
//之所以错误是因为||所导致的运算逻辑。
//先判断i是不是小于字符串长度,如果越界,不判断条件2,但是还是会判断条件3“str.charAt(i)=='-'”,
//就会导致i越界的情况下依然访问str元素的操作。
//正确版本
if(i<str.length() && (str.charAt(i)=='+'||str.charAt(i)=='-')){//如果是正负号,存起来,最后处理
    temp=str.charAt(i);
    i++;
}

3. 剑指 Offer 10- I. 斐波那契数列

3.1 题目

写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:
F(0) = 0,? ?F(1)?= 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 01 开始,之后的斐波那契数就是由之前的两数相加而得出。
答案需要取模 1e9+71000000007),如计算初始结果为:1000000008,请返回 1。

示例 1:
    输入:n = 2 
    输出:1

示例 2:
    输入:n = 5 
    输出:5

提示:
    0 <= n <= 100

3.2 解题思路

我无意中写出来的竟然是“动态规划”的思路,哈哈哈确实给我学习动态规划提供了很好的自信,good start!
一开始我想要用递归法,但是递归法解决这道题还需要涉及到取模问题,并且递归实际上也存在性能问题,因此我舍弃了递归的做法。
因为题目进入n>1的时候,只需要f(n-1),f(n-2)的值就能够求出f(n)的值,然后f(n-2)=f(n-1),f(n-1)=f(n),通过循环求解出新的f(n)的值。
并且,对于递归中的取模问题,实际上仔细想想也不是什么问题,先求和最后取模和每次计算出f(n)之后取模的效果是一样的,考虑到1e9+7这个数字非常大,而且斐波那契数列也因为变态的数据增长而出名,所以为了避免数据的溢出,选择“每次计算出f(n)之后取模的效果”显然更加稳妥。

3.3 数据类型功能函数总结

//取余运算
(int)n1%(int)n2;

3.4 java代码

class Solution {
    public int fib(int n) {
        int fn_1=1,fn_2=0;
        int sum=0;
        if(n==1){
            sum=1;
        }
        for(int i=2;i<=n;i++){
            sum=fn_1+fn_2;
            sum%=1e9+7;
            fn_2=fn_1;
            fn_1=sum;
        } 
        return sum;
    }
}

3.5 动态规划学习笔记

学习官方题解的一些笔记

https://leetcode.cn/leetbook/read/illustration-of-algorithm/m5zfc1/
  1. 动态规划和递归的区别
    递归和动态规划都适用于“子问题分解”这样的问题。
    但是不同的是,递归中不会保留曾经出现的子问题的结果,而是每次求解的时候分别重新计算所有的子问题,这也是递归算法性不高的原因。
    而动态规划除了将问题分解为子问题之外,还需要将后续计算所需要的子问题结果存储起来,这样在后续的计算中不会重复计算,存储结果的数组也是一种“以空间换时间”的做法【重叠子问题】。
    动态规划求解的问题涉及到“最优子结构”问题,也就是说,该问题不仅能够分解为多个子问题,而且问题的结果也由子问题的最优解组成。【最优子结构】

  2. 动态规划的应用
    一般用在“最值”等问题中

    普遍来看,求最值的问题一般都具有「重叠子问题」和「最优子结构」特点,因此此类问题往往适合用动态规划解决

  3. 动态规划的解题思路

    1. 状态定义:问题能够分解的子问题,问题结果指的是什么,计算问题结果需要哪些变量
    2. 初始状态:最基本的子问题最优解是什么样的?(起点)
    3. 转移方程:最优解和子问题最优解的关系。子问题最优解如何组合获得问题的最优解
    4. 返回值:终止条件是什么?终止的时候应该返回的结果是什么?(一般终止条件和问题规模有关)

4. 剑指 Offer 10- II. 青蛙跳台阶问题

4.1 题目

一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n?级的台阶总共有多少种跳法。

答案需要取模 1e9+71000000007),如计算初始结果为:1000000008,请返回 1。

示例 1:
    输入:n = 2
    输出:2

示例 2:
    输入:n = 7
    输出:21

示例 3:
    输入:n = 0
    输出:1

提示:
    0 <= n <= 100

4.2 解题思路

这一题和上一题斐波那契数列很类似,只要利用动态规划将问题分解成子问题,而原始问题的答案由子问题的答案所构成。
要想跳上n个台阶,可以从n-1个台阶上跳1,也可以从n-2个台阶上跳2,因此,n的台阶的跳法f(n)由n-1个台阶的跳法f(n-1)和n-2个台阶的跳法f(n-2)组成,因此有公式f(n)=f(n-1)+f(n-2),因此n从2开始可以用这个公式,而题目也给出了初始条件的结果,f(0)=1,f(1)=1,所以我们还是设置三个变量,形成滚动数组,为了避免int型结果溢出,我们每次求出当前f(i)之后都进行取模。

4.3 数据类型功能函数总结

4.4 java代码

class Solution {
    public int numWays(int n) {
        //规律:f[n]=f[n-1]+f[n-2];
        //f[0]=1,f[1]=1,f[2]=2;
        //f[3]=3,f[4]=5,f[5]=8,f[6]=13,f[7]=21
        if(n==1||n==0){
            return 1;
        }
        else{
            int fn_2=1,fn_1=1;
            int fn=0;
            for(int i=2;i<=n;i++){
                fn=(fn_2+fn_1);
                fn%=1e9+7;
                fn_2=fn_1;
                fn_1=fn;
            }
            return fn;
        }
    }
}

忙忙的一周,周四到周日一塌糊涂,希望下周刷题能按天完成呜呜呜。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值