力扣算法学习贴:

力扣算法学习记录

`

文章目录


前言

记录2023年在寻找工作过程中学习过的数据结构与算法。题目均以用到的核心算法思想划为单位划分。这都是来自于大佬灵茶山艾府的总结。我也记录了一些我自己的理解。

值得一提的是,所有的算法和数据结构都是为了解决问题而服务的,思考问的方式应该由简到繁。


一、排序问题

1.冒泡

2. 选择

3. 插入

4. 归并

4.1 算法思想

4.2 算法模板

5. 快速排序

5.1 算法思想

5.2 算法模板

6. 堆排序

6.1 算法思想

6.2 算法模板

一、前缀和+哈希表

1.算法简述

核心表达式:s[right] = Function(s[left]), 最经典的条件即 s[right] = s[left]

2.练习习题

2.1 面试题 17.05. 字母与数字

题目链接:求解最长的包含相同数量字母与数字的连续数组长度。
思路:

  1. 转换:数字 → -1,字母→1。
  2. 前缀和:根据前缀和S的定义,S[j] - S[i]可以表示区间(i, j](左开右闭)的元素和。
  3. 经过前两项处理,可以得当S[j] - S[i] = 0时,区间(i, j]包含的字母与数字数量相等。
  4. 加速查询:若遍历所有的(i,j)时间复杂度为O(n^2),依然有点高,因此借用哈希表来提高效率。S[j] - S[i] = 0 → S[j] = S[i]。
    代码如下:
class Solution:
    def findLongestSubarray(self, array: List[str]) -> List[str]:
        n = len(array)
        S = [0] * (n + 1)  # 前缀和
        helper = {0: 0}  # 哈希表
        l, r, ans = 0, 0, 0
        for i in range(n):
            if len(array[i]) >= 2 or ord('0') <= ord(array[i]) <= ord('9'): # 预处理
                S[i + 1] = S[i] - 1
            else:
                    S[i + 1] = S[i] + 1
            if helper.get(S[i + 1], -1) != -1:  # 不存在相同值
                if (i + 1 - helper[S[i + 1]]) > ans:
                    ans = i + 1 - helper[S[i + 1]]
                    l, r = helper[S[i + 1]], i + 1  # 最长区间记录
            else:
                helper[S[i + 1]] = i + 1  # 只记录最左边的坐标以保证求解最长子区间
        return array[l:r]

2.2 523. 连续的子数组和

题目链接
思路同上:首先分析题目,要求区间和能被k整除,即(s[j] - s[i]) % k = 0,根据取模的性质:
( s [ j ] − s [ i ] )   m o d   k = 0 → ( ( s [ j ]   m o d   k ) − ( s [ i ]   m o d   k ) )   m o d   k = 0 → ( s [ j ]   m o d   k ) = ( s [ i ]   m o d   k ) (s[j] - s[i])\ mod\ k = 0 \\ → ((s[j]\ mod\ k) - (s[i]\ mod\ k) ) \ mod\ k = 0 \\ → (s[j]\ mod\ k) = (s[i]\ mod\ k) (s[j]s[i]) mod k=0((s[j] mod k)(s[i] mod k)) mod k=0(s[j] mod k)=(s[i] mod k)
其他同理:
代码如下:

class Solution:
    def checkSubarraySum(self, nums: List[int], k: int) -> bool:
        n = len(nums)
        s = [0] * (n + 1)
        helper = {0:0}  # 注意初始化
        for i in range(n):
            s[i + 1] = s[i] + nums[i]
            if helper.get(s[i + 1] % k, -1) != -1:
                if i + 1 - helper[s[i + 1] % k] >= 2:
                    return True
            else:
                helper[s[i + 1] % k] = i + 1
        return False

2.3 525. 连续数组

题目连接

2.4 560. 和为 K 的子数组

题目连接

2.5 974. 和可被 K 整除的子数组

题目连接

2.6 1590. 使数组和能被 P 整除

题目连接

2.7 2488. 统计中位数为 K 的子数组

题目连接

二、数位DP

1. 算法简述

一种回溯的变体,主要针对的问题都是寻找区间[1, n]内满足某些条件的数字。
针对给的模板,通常改动的地方分为n个部分:
通用模板:

def dfs(i, mask, is_num, is_limit):
	if i == len(s):
		return int(is_num)
	cnt = 0
	if not is_num:
		cnt = dfs(i + 1, mask, False, False) # 跳过高位
	up = s[i] if is_limit else 9 # 确定上限
	for i in range(1 - int(is_num), up + 1):
		dfs(i + 1, mask, True, i == up and is_limit) # 这里高位已经填充了数字 所以后面就不能跳过了
	return

2. 练习习题

2.1 2376. 统计特殊整数

2.2 1012. 至少有 1 位重复的数字

题目链接
绕个弯,就是求解(区间内数字的总数 - 每一位都不重复的数字)

2.3 233. 数字 1 的个数

2.4 面试题 17.06. 2出现的次数

2.5 600. 不含连续1的非负整数

经过这道题学习到了,应该学习的是其中蕴含的思想,强行套模板反而会限制自己。

2.6 902. 最大为 N 的数字组合

2.7 1397. 找到所有好字符串

数位DP 与 KMP 算法的结合

三、模运算相关

1.算法简述

2.练习习题

3.1 1015. 可被 K 整除的最小整数

题目连接

三、树状数组与线段树

1.算法简述

核心:区间查询与单点修改,这里面涉及到的计算不仅仅局限于求和,还可以维护最大值什么的。

2.练习习题

2.1 1626. 无矛盾的最佳球队

区间最大值的维护,目前只想清楚了查询[0, R]的最大值。

四、动态规划

1. 算法简述

  1. 最简单的最直观的思考方式是 先递 再回退,可利用修饰符@cache去减少消耗时间,@cache的本质就是记录访问过的状态,不用做大量的重复工作
  2. 然后利用数组省略递的过程,化简为推
  3. 然后再用状态压缩对将dp数组进行压缩,从而降低空间占用

2. 练习习题

2.1 1143. 最长公共子序列

题目链接

2.2 1092. 最短公共超序列

题目链接
新的思想,并不是传统的返回数字,而是需要返回构造的字符串,假如将构造的过程放在递归或者递推里面,同样会超时。因此将构造与递推的过程拆解开来,这样时间复杂度就能由O(nm(n + m)) 降为O(nm + (n + m))了

五、拓扑排序

1. 算法简述

2. 习题练习

2.1 207. 课程表

题目链接
这是最基本的拓扑排序的应用。

  1. 建立入度表
  2. 建立哈希表,记录节点的前置节点是哪些,便于入读表的更新
  3. 入度为0的节点入栈
  4. 不断出栈,更新入度表
  5. 遍历入度表,新增入度为0的节点入栈
  6. 若栈为空则结束遍历,若不为空,返回至步骤4
  7. 遍历入度表,若存在没遍历过的节点则无法修满所有课程。

六、区间dp

1.算法思想

输入i, j 确定递归的区间,然后遍历k进一步将问题拆解。

2.习题练习

2.1 1039. 多边形三角剖分的最低得分

题目链接

2.2 375. 猜数字大小 II

题目链接

  1. 运用区间dp的思想去思考这个问题
  2. 假设dfs(i, j)为给定区间[i, j]时,返回能够确保你获胜的最小现金数。
  3. 假设我们猜数字k,需要假设我们会付出最大的代价,即答案在代价更高的那个区间。则获胜的现金数为 k + max{dfs(i, k - 1), dfs(k + 1, j)}
  4. 为了使得获胜的现金数量最少,我们需要确保选择的k在当前阶段选择是代价最小的一个,因此,根据以下公式去选择k:argmin{ k + max{dfs(i, k - 1), dfs(k + 1, j)}},where k ∈ [1, n]

2.3 1000. 合并石头的最低成本

题目链接
另一种区间dp的形式,附加了额外辅助变量。又一次的敲响警钟。算法是为了解决问题而服务的。这次我虽然想到了区间dp,但是硬套模板还是让我想不明白怎么实现具体细节。
这次的问题难点在于不知道怎么处理数组中间融合后的情况,这也思考的话数组会变化,也不好用区间去处理。需要换个角度思考,从边界入手。

七、杂项

1.与字符串有关的例题

1.1 1638. 统计只差一个字符的子串数目

题目链接
优化后的代码思路:
1.枚举所有的 d = i - j
2.枚举所有的终点 i
3.维护k1, k0

1.2 795. 区间子数组个数

题目链接
自己想到的思路:利用单调栈,枚举nums[i]以为最大值的子区间数量
灵茶山艾府的思路:枚举以nums[i]为右端点的符合条件的子区间,并且维护两个关键坐标i1, i0.

1.3 2444. 统计定界子数组的数目

题目链接
同一种思想,具体的求解方式还是需要思考。对于较为复杂的情况,需要从简单的情况出发,进一步延申到复杂情况下的解。
维护三个坐标: r 0 , m a x I , m i n I r0, maxI,minI r0,maxIminI

2.前缀和+二分法

2.1 2602. 使数组元素全部相等的最少操作次数

题目连接
第一眼就能看出来暴力解法(时间复杂度为O(n^2))会超时,但是不知道怎么区别出正负的情况。
解决思路:先对数组进行排序,然后就能用二分查找找出哪些num[i]比queries[i]大,哪些比queries[i]小。然后就很好处理了,利用前缀和进一步降低时间复杂度。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值