Leetcode:学习记录(二)

按照https://leetcode.cn/circle/discuss/RvFUtj/顺序刷题

零、经验记录

1. 学会画图分析

2. 学会找终止条件

3. 做一道就高质量完成

一、二分算法

0. 总结

a. 大于某个数的第一个数的位置有固定模板,其中要讨论最后一个数小于等于目标数的情况

b. 当查找范围x确定,关注的目标f(x)是单调的,就可以使用二分法

1. 二分查找

在升序数组中查找某数,如果存在则返回下标,不存在则返回-1

a. 自己的想法:left、mid、right三个指针迭代,注意终止条件,一是mid对应的元素为该数,二是到了只剩两个元素时,那么此时mid=left,如果right对应元素为该数,则返回下表right,如果不是,则代表数组中没有该数,返回-1

b. 存在的问题:判断条件复杂,原因是边界不好,每次判断完大小后,实际上mid对应的元素就可以被排除了。如果nums[mid] > target: right = mid-1。如果nums[mid]<target:left=mid+1。

c. 一点点的疏忽会带来极大的麻烦,要求逻辑足够严谨,充分利用已有信息。

2. 寻找比目标字母大的最小字母

找到一个非递减字符数组中比目标字母大的最小字母

a. 自己的想法:三指针迭代,迭代方式为: if letters[mid] <= target: left= mid+1; if letters[mid] > target: right = mid

b. 存在的问题:没考虑到字母之间可以直接比较(上面是改正后的),没有搞清终止条件。如下图,首先排除target不在该字符数组的两种情况,然后递推即可。

3. 正整数和负整数的最大计数

统计非递减数组的正整数数量和负整数数量中的最大值

a. 自己的想法:需要计算pos和neg的数量,首先如果第一个数大于0或者最后一个数小于0,那么该数组全正或全负,返回数组长度即可。然后分别找到大于0的第一个数的位置和小于0的最后一个数的位置。这里需要注意避免陷入死循环,需要考虑遍历尽头。

b.存在的问题:小于0的最后一个数容易陷入死循环,可以转换为大于等于-1的第一个数,然后这个数的前一个数就必然是最后一个负整数。

4. 两个数组间的距离值

数组1中的元素与数组2中的所有元素距离大于d的数量

a. 自己的想法:

     遍历arr1,其中每个数字作为target,去寻找arr2中大于等于和小于target的最后一个数

     最小的那个如果距离满足大于d,那么该数字满足距离要求

b.存在的问题:arr1不需要排序,找到了大于等于target的第一个数之后,前一个数即为小于target的最后一个数

c. 时间复杂度分为两部分,arr2排序为n2logn2,后面的二分查找为n1logn2,空间复杂度为n1

5. 两个数组的配对成功数

数组1中的元素与数组2中的元素相乘大于等于给定整数的数量

a. 自己的想法:遍历数组1,在数组1的每个元素下,找到与该元素相乘大于等于success的第一个数。

b. 没有问题

6. 使结果不超过阈值的最小除数

给定nums和threshold,找到最小的正整数,使得nums中的每个元素向上取整除以该整数后的和小于等于threshold

a. 自己的想法:除数存在范围,1到nums的最大值(题目中限定了threshold的最小值)。f(x)是单调的,因此就可以在该范围内进行二分查找搜索。

b. 存在的问题:向上取整方法有问题,不是a//b+1,而是(a+b-1)//b。

7. 完成旅途的最少时间

数组time,为每辆车一趟需要的时间,所有车可以并行运行,完成一趟就能继续该车的第二趟,找到totalTrips所需最短时间。

a. 自己的想法:

        目的是找时间,时间是一个序列[0,...,totalTrips*min];

        条件是每个时间对应trips,找到刚好大于等于totalTrips的第一个时间

        可以二分,因为f(trips)关于x(time)单调递增

b. 存在的问题:时间序列没必要创建,因为它和索引没区别,空占内存。另外等于totalTrips的时候的时间不一定是目标时间,因为求解的是最短时间。

二、动态规划

0.总结

a. 动态规划是一种算法,与之相对的有分治、贪婪等

b. 递归(自顶向下)和循环(自底向上)是实现动态规划的两种方式,递归需要记忆中间结果,即记忆化递归

c. 特点

  • 重复子问题:子问题是相同类型的,能够递推
  • 最优子结构:母问题的最优解可以由子问题的最优解构建
  • 无后效性:子问题的最优解不受后面的问题的影响

d. 简单判断:暴力求解子问题的最优解复杂度高、可能性多,因此转而求递推关系

e. 递推关系是多样的,可以与前面的所有最优解都有关系

1. 爬楼梯

一次可以爬一级或者两级台阶,问:n级台阶有几种方案?

a. 自己的想法:递归,n=1,返回1,n=2,返回2,其他则返回递推公式:fuc(n-1)+fuc(n-2)

b. 存在的问题:由递归树可知,节点数O(2^n),时间复杂度:O(2^n),空间复杂度为递归树的深度:O(n)。这里时间复杂度太大,原因是重复计算。

c. 解决办法:

记忆化递归:这里可以建立一个递归外的数组,用于记录每个节点的方法数,同时在每次递归时判断是否有现成的,有就直接返回,没有就计算并保存到这个数组中,时间复杂度为O(n)。

如何降低空间复杂度呢?可以使用循环,自底向上循环计算目标。每次计算只需要前两个变量即可,因此可以用滚动数组计算。

2.  使用最小花费爬楼梯

整数数组cost,cost[i]代表从第i级台阶向上爬需要的费用,可以选择爬一级,也可以选择爬两级,计算爬到顶部的最小花费。可以选择从下标为0或者1的台阶开始爬。

a. 自己的想法:循环,从底向上,计算不同级台阶下的最小花费,递推公式是dp[i] = min(dp[i-1]+cost[i-1], dp[i-2]+cost[i-2])

b. 存在的问题:最后一个条件,可以选择从哪里开始爬,得到的信息是:dp[0]=dp[1]=0

3. 01背包问题

给定几个物品的体积和价值,给定一个背包的容积,找到放置物品的最大价值。

a. 思路:

首先,我们考虑i个物品和j容量的背包的最优解为dp[i][j];

其次,这个最优解可以向前递推,得到等效的结果;

先判断volume[i]和背包容量j的大小,如果放不进去,那就必然没有物品i,那就等于dp[i-1][j];

如果放得进去,就要考虑有物品i和没有物品i的时候的最大价值,即:

max(i-1个物品和j容量的背包的最大价值,i-1个物品和j-volume[i]的背包的最优解+value[i])

4. 最长递增子序列

给一个整数数组,找出最长严格递增子序列的长度

a. 思路

为什么用动态规划?单个子问题最优解难求

要实现递推关系,需要将最后一个元素作为递推的判别条件,因此dp[i]不是前i个元素的最长子序列,而是包含nums[i]的最长子序列。递推关系是dp[i]之前的所有子问题中,nums[i]大于nums[j]则在该子问题基础上加1,并取这些中的最大值。

最终返回dp的最大值。

b. 代码

class Solution:

    def lengthOfLIS(self, nums: List[int]) -> int:

        n = len(nums)

        dp = [1]*n

        for i in range(1,n):

            for j in range(i):

                if nums[i] > nums[j]:

                    dp[i] = max(dp[i],dp[j]+1)

        return max(dp)

三、回溯

0. 总结

回溯的关键在于撤销处理操作,即在没有递归函数的情况下,上下的操作是抵消的。

1. 电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

a. 思路:每个位置都有多个选择,并且有多位,此时用回溯。回溯的关键在于撤销处理操作,即在没有递归函数的情况下,上下的操作是抵消的。如下:

                    s += dic[num][i]
                    dfs(l+1,s)
                    s = s[:-1]

没有dfs,那么上下操作是抵消的

代码

class Solution:
    def letterCombinations(self, digits: str) -> List[str]:
        if not digits:
            return []
        res = []
        dic = {"2":"abc","3":"def","4":"ghi","5":"jkl","6":"mno","7":"pqrs","8":"tuv","9":"wxyz"}
        def dfs(l,s):
            if l == len(digits):
                res.append(s)
            else:
                num = digits[l]
                for i in range(len(dic[num])):
                    s += dic[num][i]
                    dfs(l+1,s)
                    s = s[:-1]
        dfs(0,"")
        return res
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值