网络知识 娱乐 【力扣刷题笔记】由简到难,模块突破, 你与AC只差一句提示

【力扣刷题笔记】由简到难,模块突破, 你与AC只差一句提示

必会基础部分👇👇👇👇👇👇, 可以收藏下来慢慢看。

在这里插入图片描述

文章目录

  • 一、易懂贪心算法
    • 分配问题
      • 455.分发饼干
      • 分发糖果
    • 区间问题
      • 435.无重叠区间
    • 练习题
      • 605.种花问题
      • 452.用最少数量的箭引爆气球
      • 763.划分字母区间
      • 122.买卖股票最佳时机Ⅱ
      • 406.根据身高重建队列
      • 665.非递减数列
  • 二、玩转双指针
    • 经典题目
      • 167.两数之和Ⅱ
      • 88.合并两个有序数组
      • 142.环形链表Ⅱ
      • 76.最小覆盖子串
    • 练习题
      • 680.验证回文字符串Ⅱ
      • 633.平方数之和
      • 524.通过删除字母匹配到字典里最长单词
  • 三、二分查找
    • 经典题目
      • 69.x的平方根
      • 34.在排序数组中查找元素的第一个和最后一个位置
      • 81.搜索旋转排序数组Ⅱ
    • 练习题目
      • 154.寻找旋转排序数组的最小值
      • 540.有序数组中的单一元素
      • 4.寻找两个正序数组的中位数
  • 四、花里胡哨排序算法
    • 快速选择
      • 215.数组中的第K个最大元素
    • 桶排序
  • 五、一切皆可搜索
    • 深度优先搜索
      • 695.岛屿的最大面积
      • 547.省份数量
      • 417.太平洋大西洋水流问题
    • 回溯
      • 46.全排列
      • 77.组合
      • 51.N皇后
    • 广度优先搜索
      • 934.最短的桥
      • 126.单词接龙II
    • 练习题
      • 130.被围绕的区域
      • 257.二叉树的所有路径
      • 47.全排列II
      • 40.组合总和II
      • 37.解数独
      • 310.最小高度树
  • 六、深入浅出动态规划
    • 动态规划基础:一维
      • 70.爬楼梯
      • 198.打家劫舍
      • 413.等差数列划分
    • 动态规划基础:二维
      • 64.最小路径和
      • 542.01矩阵
      • 221.最大正方形
    • 分割类型
      • 279.完全平方数
      • 91.解码方法
      • 139.单词拆分
    • 子序列问题
      • 300.最长递增子序列
      • 1143.最长公共子序列
    • 背包问题
      • 416.分割等和子集
      • 474.一和零
      • 322.零钱兑换
    • 字符串编辑
      • 72.编辑距离
      • 650.只有两个键的键盘
      • 10.正则表达式匹配
    • 股票交易
      • 121.买卖股票的最佳时机
      • 188.买卖股票最佳时机Ⅳ
      • 309.最佳买卖股票时机含冷冻期
    • 练习题
      • 213.打家劫舍II
      • 53.最大子数组和
      • 343.整数拆分
      • 583.两个字符串的删除操作
    • 进阶练习
      • 646.最长数对链
      • 376.摆动序列
      • 494.目标和
      • 714.买卖股票的最佳时机含手续费

一、易懂贪心算法

每次操作取局部最优,从而使得最后结果为全局最优。应注意每次操作的前提是不能扰乱之前的操作

分配问题

455.分发饼干

有一群孩子和一堆饼干,每个孩子有一个饥饿度,每个饼干都有一个大小。每个孩子只能吃一个饼干,且只有饼干的大小不小于孩子的饥饿度时,这个孩子才能吃饱。求解最多多少孩子可以吃饱

贪心问题:怎样保证每次吃的都是最优解?
贪心策略:给胃口最小的人吃最小的饼干,剩下的大饼干是最多的。所以每次都是最优得到全局最优。

分发糖果

一群孩子站成一排,每一个孩子有自己的评分。现在需要给这些孩子发糖果,规则是如果一个孩子的评分比自己身旁的一个孩子要高,那么这个孩子就必须得到比身旁孩子更多的糖果;所有孩子至少要有一个糖果。求解最少需要多少个糖果。

贪心问题:怎么避免重复更新导致需要更多糖果?
贪心策略:每次遍历只考虑并更新一侧的大小关系

两次遍历,先保证所有评分比左侧高的糖果高,再保证所有评分比右侧高的糖果更高。
注意第二次遍历的时候,如果当前糖果数已经比右侧高了,当前评分比它高也不用调整了

区间问题

435.无重叠区间

给一个区间集合,计算让这些区间互不重叠所需要移除区间的最少个数,起止相连不算重叠。

贪心问题:移除最少区间即保留最多区间,如果两个重叠了,移除谁?保留谁?
贪心策略:优先保留结尾小且和前面不相交的区间

结尾小,如果和前面的重叠了,前面的和自己必须移除一个才能保证不重叠,前面的结尾小的对后面影响最小,所以移除自己
如果按照头排序,那移除自己还是移除头就不确定了,因为不知道哪个对后面影响大

练习题

605.种花问题

给你一个数组表示花坛,花坛里本来有一些花,问能不能再往里种n个花,两个花不能相邻。

贪心问题:怎样可以在原有基础上种植更多的花?
贪心策略: 顺着种,能种就种

452.用最少数量的箭引爆气球

给你一堆区间,每个区间表示在x轴上的一个气球,求引爆所有气球的最少弓箭数。弓箭垂直x轴射出,可以选择不同的出发点。

每个区间都至少需要一支箭。假设选择在区间尾部射这支箭,哪些区间会爆呢?
答:区间头部在这个 点之前,尾部在这个点之后的区间都会爆。
我们每射一支箭,减掉爆了的区间,继续射下一支,直到区间为空。
遍历区间肯定是需要对其排序的,怎样排序才能更好的判断后续区间是否被上一支箭引爆 及防止重复或缺失遍历?
答:我们假设每次射区间尾部,那么自然要尾部从小到大的去排列,这样后面的尾部一定小于前面的,要么被前面的箭射爆,要么遍历到他再给它一支箭。
贪心问题: 怎么排列能保证射箭不重复或缺失?
贪心策略: 射尾部节点,所以按尾部节点大小排序,后面的区间被射爆即跳过,没被射爆就再来一支箭

763.划分字母区间

一串字母,划分区间,每个区间包含的所有不同种类的字母其他区间不能有,求最多的区间划分方式

贪心问题:找到区间所有字母最后一个出现的位置。
贪心策略:不断更新最后出现位置的最大值,等走到最大值的时候,这里就是这个区间所有字母最后一个出现的位置。

122.买卖股票最佳时机Ⅱ

给一个数组表示每天股票价格,每天可以买可以卖, 返回最大利润。

贪心问题:能赚就卖,如果后面能赚更多就相当于把之前卖的买回来重新再卖
贪心策略: 后一天比前一天多就直接赚到

406.根据身高重建队列

打乱顺序的一堆人,给个二维数组people[i] = [身高, 前面有几个比自己高的]。 重新排队让他们在正确的位置。

贪心问题:重新排队,每次插入新的人,保证之前的队伍不发生改变即可解决问题。
怎么保证已排好的队伍顺序不发生改变呢?
先处理高个的,当高个的排好了,再排矮个的不会影响到高个的相对顺序。
所以将数组先按照身高降序排序。但是身高一样怎么办呢?
如果身高一样,先排k大的,再排k小的,那k小的放到k大的前面,又会影响到之前排好的k大的。所以必须先排k小的,这样k大的后排,且其排的位置一定在k小的之后。所以将身高相同的按k升序排列。

贪心策略:身高降序,k升序。先遍历到身高高的和k小的,直接插入到k的位置,每次操作对之前的相对位置无影响

665.非递减数列

一个数组,检查改变其一个数字能否变成一个非递减数列。

碰到递减的趋势,我们只有两个方法将其改变成不递减,①改变自己和前一个一样,②改变前一个和前前一个一样。
贪心问题:改变谁对后面影响最小,从而得到最优解。
如果当前数字比前前一个大或者相等,那么前一个数字是有改变空间的。否则,只能改变当前数字使其更大。
贪心策略:能改变前一个数就改变前一个数,不能就改变当前得数。

二、玩转双指针

遍历数组时两个指针指向不同元素,从而协同完成任务。也可延申到多数组多指针

如果两个指针指向同一个数组且遍历方向相同且不会相交。也称为滑动窗口,经常用于区间搜索

两个指针指向同一数组,遍历方向相反,可以用来搜索,一般数组是排好序的

经典题目

167.两数之和Ⅱ

增序数组找到两个数的和为target, 返回两个数的索引, 有且只有一对解, 常数额外空间

双指针,小 l++, 大 r–

88.合并两个有序数组

merge(int[] nums1, int m, int[] nums2, int n), 给两个有序数组,和长度m、n,把数组合并到数组1

双指针,从后往前放。就不需要开辟新的数组了。

142.环形链表Ⅱ

给一个链表,可能有环,返回入环的第一个节点,没环返回null, 不允许修改链表

快慢指针,给两个指针一个slow,一个fast ,从起始位置,fast走两步、slow走一步,如果有环,fast可以无限走,并一定在某次和slow重合。
重合后把fast重新移到链表头部,两个指针都一步一步走,最终两个指针在环入口相遇。

76.最小覆盖子串

给你两个字符串,s、t, 找出s中包含t所有字母的最短子串。只有唯一解

滑动窗口, 先找到从0-r 包含t所有字母的一段,然后再尝试收缩左边界,刚好缩到不行为止(行的时候一直更新缩小maxLen),
再往右扩展,找到可以的了再缩短左边,中间记录最短子串的起始位置和长度

练习题

680.验证回文字符串Ⅱ

给一个非空字符串,最多删除一个字符,能否变成回文串。

双指针写个回文函数,遇到不是回文串的点,看舍弃左边这个点后面是不是回文串,或者舍弃右边那个点后面是不是回文串

633.平方数之和

给一个非负整数c,判断是否存在两个数a 2 + b 2 = c

双指针,l最左,r最右,和大了, r–, 和小了 l++

524.通过删除字母匹配到字典里最长单词

给一个字符串,和一个字典,字典里很多单词,判断字典中可以由字符串通过删减字母得到得最长单词,长度一样返回字典序小的。

遍历一遍列表,一一对比更长的或者字典序更小的能不能通过删除字符串某些字符得到这个单词

三、二分查找

二分查找时间复杂度为O(logn),折半查找。
注意考虑如果数组最后只剩一个数或者两个数,是否会陷入死循环。如果会,考虑改变左右区间的开闭

经典题目

69.x的平方根

给一个非负整数,求他的开方,向下取整

34.在排序数组中查找元素的第一个和最后一个位置

给一个增序的整数数组和一个值,查找该值的第一次和最后一次出现的位置。

二分法找左边界和右边界,左闭右开

81.搜索旋转排序数组Ⅱ

一个增序数组,从中间k处旋转了一下, 找数组中有没有target, 用二分。

对于mid 如果nums[mid]小于右端点,则说明mid右区间是排好序的,如果target比右边小比当前大,那就是右边反之是左边
如果nums[mid]大于右端点说明左区间是排好序的,如果target比当前小,大于等于左边,那就在左边,反之是右边
如果等于右端点,就不确定,就简单的将右端点左移一格

练习题目

154.寻找旋转排序数组的最小值

增序数组,从中间旋转了一下,找到数组中的最小值

二分变形,最小值一定再非排序区间,不确定所处区间就简单的移动一格。
nums[mid] < nums[r] 则右边一定是排序区间(找左边)
nums[mid] > nums[r] 则 左边一定是排序区间找右边(如果右边也是排序的,那最小值就在mid+1,所以还是找右边区间)

540.有序数组中的单一元素

有序数组,只有一个数字有是单数,其他数字都是双数。找到这个数字 O(logn)

二分查找, 看相等的两个数的奇偶位置,如果是奇偶,那就是在右边,如果是偶奇那就是在左边
优化: mid如果是奇数,就 mid-- 使其变成偶数,然后只需判断mid 和 mid + 1 处的值是否相等,相等就在右边区间 l += mid + 2,
不等要么在左边区间,要么在当前位置 r = mid

4.寻找两个正序数组的中位数

给定两个大小分别为 m 和 n 的正序数组 nums1 和 nums2。找出两个正序数组的 中位数。 时间复杂度O(logn)

二分查找,画折线,保证左边的元素和右边元素个数相等,或者左边比右边多1,保证左边元素全部小于右边(折线对角相等)

四、花里胡哨排序算法

快速选择

215.数组中的第K个最大元素

给你一个整数数组,返回数组中第K大元素

桶排序

五、一切皆可搜索

深度优先搜索

695.岛屿的最大面积

二维矩阵,0表示海洋,1表示陆地。相邻(上下左右)的陆地形成岛屿,求最大岛屿面积

遍历+深搜、广搜都行

547.省份数量

给定2维数组,(i,j)位置未1,表示i 和 j 是同一个省份。问有多少省份?

DFS+一层遍历,标志位为一维城市数组,而不是关系图。

417.太平洋大西洋水流问题

二维非负整数矩阵,每个位置表示海拔,左上是太平洋,右下是大西洋,求哪些位置的水可以同时流到大西洋和太平洋

DFS水往高处流,先从左上流上去,然后做标记,再从右下流上去,根据标记判断返回

回溯

46.全排列

给一个数组nums ,返回其全排列。nums 中的所有整数 互不相同

回溯,用一个boolean数组标记索引为 i 的数是否在路径里。

77.组合

给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。

DFS,每个数选或者不选,先选试试,返回结果后就不选这个位置往后走。注意剩余长度和路径不够组成新的组合的时候,需要返回,cur无限增大。

51.N皇后

n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。(不在同一行同一列同一斜线)

回溯,最好是初始化一个二维数组作为棋盘,方便表示。每次放的时候验证,能放才放。

广度优先搜索

广度优先一般求最短路很简单

934.最短的桥

在给定的二维二进制数组 A 中,存在两座岛,现在,我们可以将 0 变为 1,以使两座岛连接起来,变成一座岛。求最短路

把一个岛全部加入到队列往广搜,搜到另一个岛就返回路径。

126.单词接龙II

有点复杂 BFS过程中去每个单词的邻接节点

练习题

130.被围绕的区域

给你一个 m x n 的矩阵 board ,由若干字符 ‘X’ 和 ‘O’ ,找到所有被 ‘X’ 围绕的区域,并将这些区域里所有的 ‘O’ 用 ‘X’ 填充。

先把外侧的标记,然后剩下的‘O’都是需要用X填充的

257.二叉树的所有路径

给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。

回溯。

47.全排列II

给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。

用DFS, 难点在于怎么去重, 例如 1 1 2
索引搜索 012【112】 021【121】 102【112】 出现重复,这个是需要被舍弃的,怎么舍弃呢?
我们对所有数字是否使用有标记, 然后如果一个数字和前一个数相同,那么我们只允许其顺序出现,也就是索引0的1没被用,索引1的1也不能被使用,这样就排除了 索引为102这种情况

40.组合总和II

给定一个可包含重复数字的序列 nums 和一个目标数 target ,找出 nums 中所有可以使数字和为 target 的不重复的组合。

用DFS,对每个数选或者不选, 难点在于去重,选还是不选,和上题一样,对于相同的数我们只接受顺序选。
选或选的DFS, idx是递增的,每次都是递归相当于都是从idx-最后的一个新区间
我们在每层递归中都保存一个pre 和 cur 变量,初始化为pre 初始化为-1, cur是当前变量, 则此层递归第一个数一定可以选择,然后此层递归横向扩展放弃了前一个数,如果后一个数和前一个相同,那就跳过,继续横向扩展。

37.解数独

编写一个程序,通过填充空格来解决数独问题。题目保证数独只有唯一解

标记每行能用的数,每列能用的数,每九宫格能用的数,然后做DFS搜索,能把所有数填满说明解出了数独就返回。如果填不满就回溯

310.最小高度树

无环图, 变成一个数,求哪个节点作为根节点的树高最低

先从任意节点出发,找到距离此节点最远的节点x, 然后再从x出发找到最远的y,记录中间路径, 中点就是根节点,可能有两个或者一个

六、深入浅出动态规划

查找有很多重叠子问题的最优解,可以看做是带有状态记录的优先搜索。
即如果一个子问题在优先搜索时已经计算过,之后再遍历到该子问题时,直接返回存储结果。动态规划先解决子问题,再解决父问题。

如果题目要求最终状态,用动态规划好,如果要输出路径,用优先搜索方便。

动态规划基础:一维

70.爬楼梯

n阶楼梯, 每次可以走一步或者两步, 求走到n阶有多少种方式。

走 i 阶 的方式最后一次走一格 + 最后一次走两个 dp[ i ] = dp[ i - 1 ] + dp[ i - 2 ]
由于只需要用到 dp[i - 1] 和 dp[ i - 2 ]两个变量, 所有可以状态压缩,用两个变量代替dp数组。

198.打家劫舍

你是一个小偷, 决定偷一排房子,每个房子钱财不同,不能偷相邻的,求最多可以偷多少钱。

偷 第 i 家能得到的最大金额 是 不偷前一家的能得到的最大金额 + 这家的金额
所以: dp[ i ] = max(dp[ i - 2] + nums[ i ], dp[ i - 1 ])

413.等差数列划分

给你一个数组, 求这个数组中连续且等差的子数组个数, 等差数组长度最少为3
输入:nums = [1,2,3,4]
输出:3解释:nums 中有三个子等差数组:[1, 2, 3]、[2, 3, 4] 和 [1,2,3,4] 自身。

对于一个数,如果它和前一个的差 等于 前一个数和前前个数的差相同, 说明这三个数可以组成一个新的等差子数组。
如果当前这个数是第一个满足条件的,那么等差数列的个数增加1.
如果前一个数已经满足了条件,然后当前数又满足了条件,那增加的个数就是1 + 前面满足数的个数。
例如 1 2 3 4 5 对于4增加的个数是2【234,1234】 对于5增加的个数是3【345,2345,12345】
因此需要一个动态的增量add,连续满足条件就递增,不再满足条件就重置。dp[i] = dp[i - 1] + add

当然这个也可以进行状态压缩优化。

动态规划基础:二维

64.最小路径和

给你一个二维非负矩阵,求从左上角到右下角经过的数字和最小的路径和。 每次只能向右或向下运动。

从左上出发,每次有两个选择,向右或向下, 每次选小的即可
dp[ i ][ j ] 表示到 一个位置的最小路径和 ,可以用给grid矩阵代替dp数组grid[ i ][ j ] += min(grid[i - 1][ j ], grid[ i ][j - 1])
注意需要将第0行 和 第 0 列初始化

542.01矩阵

给你一个由0、1组成的二维矩阵, 求每个位置到最近的0的距离 返回一个新矩阵。

如果所有 0 在一个点的左上方, 那么到最近0的距离就是左边数或上面数到最近0的距离 + 1
如果所有0在一个点的右下方, 那么到最近0的距离就是右边或下面数到最近0的距离 + 1
实际0的位置是不确定的,所以我们来两次遍历,两种情况都考虑,选择较小的即可。

此题还可以用BFS做, 把所有0看做一个整体,BFS找到1就给1赋值为路径长度。

221.最大正方形

给你一个 0 - 1 矩阵,求矩阵中全由 1 构成的最大正方形面积。
在这里插入图片描述

如果一个点能和左边、右边、左上角组成正方形,那这个正方形多大?
如果左边、上面、左上角能组成的正方形一样大, 那么加上这个点,能组成的正方形边长扩大了1。
如果左、上、 左上正方形不一样大,那么只能比最小的边长扩大1,不然就有缺口。
所以对于一个点,能形成更大的正方形为这个点左边的、上面的,右上角的最大正方形面积中的最小值 + 1。

分割类型

对于分割类型题目, 动态规划的转移方程,往往不是依赖于相邻的位置,而是依赖于满足分割条件的位置

279.完全平方数

给一个整数n, 求其最少可以由几个完全平方数相加构成
例: 输入13, 输出2, 解释:4 + 9

分割类型题目, 输出结果依赖于满足条件的一些点,而不是相邻的点。
dp[ i ] 表示数字 i 最少可以由几个完全平方数构成, 很明显 dp[ i ] 和 dp[ i - 1]、 dp[ i - 4 ]、dp[ i - 9 ]等相关。
可以得到,状态转移方程: dp[ i ] = 1 + min(dp[ i - 1], dp[ i - 4], dp[ i - 9 ]、 dp[ i - 16]…等)

91.解码方法

已知字母 A-Z 可以表示成数字 1-26。给定一个数字串,求有多少种不同的字符串等价于这个数字串

分为当前是0, 能不能和前面组(前面是0, 前面 >2,直接返回0)
当前不是0,能不能和前面组(前面不是0,前面小于2,或者==2 <6)

139.单词拆分

给你一个字符串和一个字符串集合, 求是否存在一种分割方式,使原字符串分割后的子串都可以在集合中找到。

dp[ i ] 表示 s[0-i] 能否分割后都在集合中找到。
如果 dp[i - j]为真, 且s[j, i]在集中在 那么dp[ i ] 为真。

子序列问题

300.最长递增子序列

给定一个未排序的整数数组,求最长的递增子序列(不连续)

两个方法:
1.动态规划: dp[ i ] 表示到 i 的最长递增子序列, dp[ i ] = max(dp[ i - j ] + 1 ) (j 是 比 i 小的数的位置)
2.贪心 + 二分 : 遇到更大的数就加到数组后面, 遇到数组最大数小的数, 就把之前较小的数替换成这个数。
(贪心思想: 每次递增都增较小的值,最后这个数组一定是最长的)

1143.最长公共子序列

给定两个字符串,求它们最长的公共子序列长度。
text1 = “abcde”, text2 = “ace”
输出:3

动态规划: dp[ i ][ j ] 表示 到s1[ i ] 、 s2[ j ] 位置后它们最长的公共自序列。
如果 s1[ i ] == s2[ j ] , 那它们最长公共子序列 + 1 即 dp[ i ][ j ] = dp[ i - 1 ][ j - 1] + 1, 否则 dp[ i ][ j ] = max(dp[ i - 1][ j ], dp[ i ][ j - 1])。

背包问题

背包问题: 有N个物品,和容量为W 的背包吗每个物品都有自己的体积 w 和 价值 v , 求拿哪些物品可以使背包所装物品总价最大。
0-1背包: 限定每个物品只能拿 0或1个
完全背包: 不限定每个物品的数量

用动态规划解决 dp[ i ][ j ] 表示 到第 i 件物品位置后, 体积不超过 j 能拿的最大价值 i = [1 , N] j = [1, W]
对于0-1背包: 到了第 i 件物品位置, 体积 j 如果小于 第 i 件物品重量, 则只能不拿, 如果大于,则选择
不拿, 价值和之前一样:dp[ i ][ j ] = dp[i - 1][ j ]
拿, 得从背包换个物品, 也就是使得预留的体积够放第 i 个物品:dp[ i ][ j ] = dp[ i - 1][j - w] + v
每次取两者较大值即可

对于完全背包问题, 随着 j 增大,我们可以拿多个 i , 所以dp[ i ][ j ] = dp[ i ][j - w] + v

416.分割等和子集

给定一个正整数数组, 求是否可以把数组分成和相等的两部分
输入:nums = [1,5,11,5]
输出:true, 解释:数组可以分割成 [1, 5, 5] 和 [11] 。
范围 nums[i] < 100 nums.length < 200

先把和算出来,和得为偶数, 求能不能有个子序列和是其一半。记为 target
dp[ i ][ j ]表示到第 i 个数字后, 存在最大和为 j 的子序列。
到第 i 个数字, 如果nums[ i ] > target , 那么不能选, 否则分两种情况
不选:dp[ i ][ j ]= dp[ i - 1 ][ j ]
选: dp[ i ][ j ]= dp[ i - 1 ][j - nums[ i ]]

状态压缩:
dp[ i ] 表示最大和为 i 的子序列是否存在。
dp[j] |= dp[j - nums[i]] 当前这个数nums[i]能配合之前的结果组成吗?
j 从大至小,防止重复利用nums[ i ]。 核心代码:

  		int n = nums.length, target = sum / 2;
        boolean[] dp = new boolean[target + 1];
        dp[0] = true;
        for(int i = 0; i < n; i++) {
            for(int j = target; j >= nums[i]; j--) {
                dp[j] |= dp[j - nums[i]];  //当前这个数nums[i]能配合之前的结果组成吗?
                //j 从大至小,不会出现重复利用的情况因为在i 没有增大之前,较小的 j 是用不到较大 j 的结果的
            }
        }

474.一和零

给定 m 个 0, n 个 1, 以及一堆由0,1组成的字符串, 求利用这些数字最多能构成多少个给定的字符串。字符串只能构成一次,0和1的个数也是用一个少一个。

输入:strs = [“10”, “0001”, “11001”, “1”, “0”], m = 5, n = 3
输出:4
解释:最多有 5 个 0 和 3 个 1 的最大子集是 {“10”,“0001”,“1”,“0”} ,因此答案是 4 。

动态规划, dp[ i ][ j ][ k ] 表示到数组 i 后, 0 的个数不超过j, 1的个数不超过k, 的构成最多字符串个数
到了第 i 个字符串, 如果他的 0 或 1 个数直接超过m ,n 了,那必不能选这个数, 如果没超过:
不选: dp[ i ][ j ][ k ] = dp[ i - 1][ j ][ k ]
选: dp[ i ][ j ][ k ] = dp[i - 1][j - cur0][ k - cur1] + 1

状态压缩成二维的。 dp[ i ][ j ] 表示 i 个 0 和 j 个 1 最多能构成的字符串个数
dp[ i ][ j ] = max(dp[i][j], dp[i - count[0]][j - count[1]] + 1)
0-1背包,压缩,最外层是给的选择,内层是条件 i 、 j,从大到小,防止重复。 核心代码

        for(String s : strs) {
            int[] count = count(s);
            for(int i = m; i >= count[0]; i--) {
                for(int j = n; j>= count[1]; j--) {
                    dp[i][j] = Math.max(dp[i][j], dp[i - count[0]][j - count[1]] + 1);
                }
            }
        }

322.零钱兑换

给定一些硬币面额,求最少多少个硬币组成给定金额。每种硬币无限

因为每种硬币无限,所有这是一到完全背包问题。
二维状态: dp[i][j] 表示 考虑过 i 之后 能换到 j 块钱的最少硬币个数 。如果 第 i 个硬币比总钱数还大, 必然不能选择, 否则有选和不选两种。
不选 dp[i][j] = dp[i - 1][j]
选了n个 dp[i][j] = dp[i - 1][j - n * coins[i]] + n
先初始化dp[i][j] 为最大值 amount + 2。 然后每次dp[i][j] 如果能刚好兑换则=j / coins[i],这样每次取最小值最后就能得到结果。如果最后还是amount + 2 说明无法兑换。

状态压缩:dp[ i ] 表示组成 i 块钱的最少硬币数。
完全背包, 最外层是条件 i , 内层是给的选择,应为可以重复选,所以放内层。核心代码:

        for(int i = 1; i <= amount; i++) {
            for(int coin : coins) {
                if(i >= coin) {
                    dp[i] = Math.min(dp[i], dp[i - coin] + 1);
                }
            }
        }

字符串编辑

72.编辑距离

给定两个字符串, 你可以删除、替换、插入任意字符串的任意字符, 求最少编辑几步可以使两个字符串相同
输入:word1 = “horse”, word2 = “ros”
输出:3

此题和1143.最长公共子序列有点类似, 最长公共子序列是 dp[ i ][ j ] 表示 s1[0 - i] 和 s2[0 - j] 拥有的最长公共子序列,如果s1 [ i ] == s2 [ j ], 则 子序列长度 + 1 即 dp[ i ][ j ] = dp[i - 1][ j - 1] + 1 。 否则 dp[ i ][ j ] = max (dp[ i - 1][ j ], dp[ i ][ j - 1])。

这道题是编辑距离, 如果两个字符串 i 、 j 位相同,则不用编辑。 dp[ i ][ j ] = dp[ i - 1 ][ j - 1]。 否则:
看是修改还是删除, 修改的结果是: dp[i - 1][j - 1] + 1。删除s1[ i ]的结果是: dp [ i ][ j - 1 ] + 1, 删除s2[ j ] 的结果是 dp [ i ][ j - 1 ] + 1. 取最小
(删除和插入是一样的)
写代码的时候记得分析,初始化dp边界

650.只有两个键的键盘

给你个字符 ‘A’ 你只能进行两种操作。 ①复制全部数量的A ②粘贴复制的A。 求得到n个 ‘A’ 的最少操作次数
输入:3 输出: 3 复制、粘贴、粘贴

dp[ i ] 表示 i 个 A 需要的最少操作次数
如果后面的 i 能整除前面的数, 那么 从 i 到 j 相当于 初始为 1 延伸到长度为 i / j 即 dp[ i / j]。
核心代码:

        for(int i = 2; i < n + 1; i++) {
            dp[i] = i;
            for(int j = 2; j <= Math.sqrt(i); j++) {
                if( i % j == 0) {
                    dp[i] = dp[j] + dp[ i / j];
                }
            }
        }

10.正则表达式匹配

给定一个字符串和一个正则表达式。求该字符串是否匹配。 正则表达式只有 ‘.’ 和 ‘*’

字符串匹配。 dp[ i ][ j ] 表示 s1[ 0 - i] 能不能和 表达式 s2[0 - j] 匹配

根据不同的情况分类讨论。
字符,只能匹配一个字符
’ . ’ , 能匹配任意字符
‘ * ’, 能匹配任意数量的前一个字符

        boolean[][] dp = new boolean[m + 1][n + 1];
        //把所有*取0个,初始化dp
        dp[0][0] = true;
        for(int i = 1; i < n + 1; i++) {
            if(p.charAt(i - 1) == '*') {
                dp[0][i] = dp[0][i - 2];
            }
        }
        for(int i = 1; i <= m; i++) {
            for(int j = 1; j <= n; j++) {
                if(p.charAt( j - 1) == '.') {
                    //任意字符
                    dp[i][j] = dp[i - 1][j - 1];
                }else if(p.charAt(j - 1) != '*') {
                    //字母
                    dp[i][j] = dp[i - 1][j - 1] && s.charAt(i - 1) == p.charAt(j - 1) ;
                }else if(p.charAt(j - 2) != '.' && p.charAt(j - 2) != s.charAt(i - 1)) {
                    // 当前是 * , 且前一个不符合, *只能是取0个了
                    dp[i][j] = dp[i][j - 2];
                }else {
                    // 当前是 * , 且前一个符合, 取0个(删掉表达式*和前一个字母), 取1个(删掉表达式*), 取n个(删掉字母)
                    dp[i][j] = dp[i][j - 2] || dp[i][j- 1] || dp[i - 1][j];
                }
            }
        }

股票交易

121.买卖股票的最佳时机

给定一段时间每天的股票价格,可以买卖一次,求最大的收益。

从前面最低点买,后面最高点卖

188.买卖股票最佳时机Ⅳ

给定一段时间每天的股票价格,可以买卖 K 次, 求最大收益

两个动态规划数组, buy[ j ] 表示 在第 j 次买入的最大收益, sell[ j ] 表示 在第 j 次卖出的最大收益。 核心代码:

        //初始化
        for(int i = 0; i <= k; i++) {
            buy[i] = -prices[0];
        }

        for(int i = 0; i < n; i++) {
            for(int j = 1; j <= k; j++) {
                buy[j] = Math.max(buy[j], sell[j - 1] - prices[i]);
                sell[j] = Math.max(sell[j], buy[j] + prices[i]);
            }
        }

309.最佳买卖股票时机含冷冻期

给定一段时间每天的股票价格,卖完有一天冷冻期不能立马买, 可以多次交易,求最大收益

状态机转移矩阵
在这里插入图片描述
四个状态,分别是第 i 天 卖出、 冷冻、 买入、不操作
从买入开始

        buy[0] = -prices[0]; //初始化
        s1[0] = -prices[0]; // 初始化
        for(int i = 1; i < n; i++) {
            buy[i] = s2[i - 1] - prices[i];
            s1[i] = Math.max(s1[i - 1], buy[i - 1]);
            sell[i] = Math.max(s1[i - 1], buy[i - 1]) + prices[i];
            s2[i] = Math.max(s2[i - 1], sell[i - 1]);
        }

练习题

213.打家劫舍II

环形数组,分别舍弃首尾两次考虑

53.最大子数组和

和大于0就维护,小于零舍弃重新开。 再来个数维护最大值

343.整数拆分

给定一个正整数 n ,将其拆分为 k 个 正整数 的和( k >= 2 ),并使这些整数的乘积最大化。

dp[ i ] 表示 数字 i 拆分后最大乘积。

dp[i] = max(dp[i], j * (i - j), j * dp[i - j])

583.两个字符串的删除操作

给定两个单词 word1 和 word2 ,返回使得 word1 和 word2 相同所需的最小步数。每步可以删除一个字符

最长公共子序列、 编辑距离 变种题

进阶练习

646.最长数对链

给你一堆数对[a, b] , 每个数对都是 a < b 的, 现在把数对连成一个链,只有前一个数对尾 < 后一个数对头 两个数对才能连一起,求最长数对链

最长递增子序列变种题。 两种解法:
① 动态规划,dp[ i ] 表示以 数对 i 为结尾的最长数对链。 需要先对数组进行排序,使尾小的一定先得到结果。
②贪心。 贪心思想:每次递增最小,最后得到最长递增。

376.摆动序列

连续数字之间的差,正负交替。叫做摆动序列,仅有一个元素,或者两个不等元素也叫摆动序列。给你一个数组,返回数组中是摆动序