9.用两个栈实现队列

思路:维护两个栈,第一个栈支持插入操作,第二个栈支持删除操作。
在执行删除操作的时候我们首先看下第二个栈是否为空。如果为空,我们将第一个栈里的元素一个个弹出插入到第二个栈里,这样第二个栈里元素的顺序就是待删除的元素的顺序,要执行删除操作的时候我们直接弹出第二个栈的元素返回即可
1 | class CQueue { |
30.包含min函数的栈

思路:
- 建立一个ListNode结构类
- MinStack类维护一个ListNode头节点,初始为null
- push方法:头节点为null直接新建;头节点不为null时,在头结点之前插入一个节点,值为x,next指向头节点;将新的节点重新赋值给头节点,也就是每次push都将值插入头节点之前
- pop方法:直接将头节点指向头节点的下一个节点,也就是每次pop头节点,从而实现后进后出的栈操作
- top方法:直接返回头节点的值即可
- min方法:直接返回头节点的min值即可
1 | class MinStack { |
思路:获取栈的最小值需要遍历整个栈,复杂度为O(N),要将复杂度降为O(1),可通过建立辅助栈实现
数据栈A用于存储所有元素,保证入栈函数、出栈函数、获取栈顶函数的正常逻辑
辅助栈B存储栈A中所有非严格降序的元素,则栈A中的最小元素始终对应栈B的栈顶元素
1 | class MinStack { |
6.从尾到头打印链表

思路:数组倒序存储(反转链表、栈均可)
- 遍历获取链表长度
- 从后向前按照索引填数组
1 | /** |
24.反转链表

思路:迭代
在遍历链表时,将当前节点的next指针改为指向前一个节点。由于节点没有引用其前一个节点,因此必须事先存储其前一个节点。在更改引用之前,还需要存储后一个节点。最后返回新的头引用。
1 | class Solution { |
35.复杂链表的复制

思路:哈希表
利用哈希表的查询特点,考虑构建原链表节点和新链表对应节点的键值对映射关系,再遍历构建新链表各节点的next和random引用指向即可
1 | /* |
5.替换空格

思路:遍历添加
在Java/Python等语言中,字符串都被设计成不可变的类型,即无法直接修改字符串的某一位字符,需要新建一个字符串实现。
- 初始化一个
StringBuilder- 遍历列表s中的每个字符
- 当c为空格时:向res后添加字符串”%20”
- 当c不为空格时:向res后添加字符c
- 将列表res转化为字符串并返回
1 | class Solution { |
58.左旋转字符串

思路:字符串切片/列表遍历拼接/字符串遍历拼接
1 | class Solution { |
3.数组中重复的数字

思路:遍历数组
由于只需要找出数组中任意一个重复的数字,因此遍历数组,遇到重复的数字即返回。为了判断一个数字是否重复遇到,使用集合存储已经遇到的数字,如果遇到的一个数字已经在集合中,则当前数字是重复数字。
1 | class Solution { |
53.在排序数组中查找数字

思路:二分查找
直观的思路肯定是从前往后遍历一遍。用两个变量记录第一次和最后一次遇见 target 的下标,但这个方法的时间复杂度为 O(n),没有利用到数组升序排列的条件。
由于数组已经排序,因此整个数组是单调递增的,我们可以利用二分法来加速查找的过程。
要求统计数字target的出现次数,可转化为:使用二分法分别找到左边界和右边界
1 | class Solution { |
53.0~n-1中缺失的数字

思路:排序数组的搜索问题,首先想到二分法解决
根据题意,数组可以按照以下规则划分为两部分
- 左子数组:
nums[i] = i- 右子数组:
nums[i] ≠ i缺失的数字等于右子数组的首位元素对应的索引;因此考虑二分法查找右子数组的首位元素
1 | class Solution { |
4.二维数组中的查找

思路:利用从上到下递增,从左到右递增的特点
将矩阵逆时针旋转45°,并将其转化为图形式,其类似于二叉搜索树
通过从“根节点”开始搜索,遇到比
target大的元素就向左,反之向右,即可找到目标值target
1 | class Solution { |
11.旋转数组的最小数字

思路:排序数组的查找问题首先考虑使用二分法解决
寻找旋转数组的最小元素即为寻找右排序数组的首个元素
- 初始化:声明
i,j双指针分别指向nums数组左右两端- 循环二分:设
m为每次二分的中点,可分为以下三种情况
- 当
nums[m] > nums[j]时:m一定在 左排序数组 中,即旋转点x一定在[m+1, j]闭区间内,因此执行i=m+1- 当
nums[m] < nums[j]时:m一定在 右排序数组 中,即旋转点x一定在[i, m]闭区间内,因此执行j=m- 当
nums[m] = nums[j]时:无法判断m在哪个排序数组中,即无法判断旋转点x在[i,m]还是[m+1, j]闭区间内,因此缩小判断范围,执行j=j-1- 返回值:当
i=j时跳出二分循环,并返回 旋转点的值nums[i]即可

1 | class Solution { |
补充思考:为什么本题二分法不用 nums[m]和nums[i] 作比较?
二分目的是判断 mm 在哪个排序数组中,从而缩小区间。而在nums[m]>nums[i]情况下,无法判断m在哪个排序数组中。本质上是由于 j 初始值肯定在右排序数组中; i初始值无法确定在哪个排序数组中。
50.第一个只出现一次的字符

思路:哈希表
1 | class Solution { |
32_1.从上到下打印二叉树

思路:从上到下打印,即按层打印。使用二叉树的广度优先搜索
BFS通常借助队列的先入先出特性来实现
1 | /** |
32_2.从上到下打印二叉树

思路:将本层全部节点打印到一行,并将下一层全部节点加入队列,以此类推,即可分为多行打印
- BFS循环
- 新建一个临时列表,用于存储当前层打印结果
- 当前层打印循环
- 将当前层结果添加入
res
1 | class Solution { |
32_3.从上到下打印二叉树

思路:层序遍历+双端队列
利用双端队列的两端皆可添加元素的特性,设打印列表(双端队列)
tmp,并规定
- 奇数层 添加到
tmp尾部- 偶数层 添加到
tmp头部
1 | class Solution { |
26.树的子结构
思路:若树B是树A的子结构,则子结构的根节点可能为树A的任意一个节点。因此,判断树B是否是树A的子结构,需要完成以下两步工作
- 先遍历树A中每个节点a
- 判断树A中以a为根节点的子树是否包含树B
1 | class Solution { |
27.二叉树的镜像
思路:递归法
根据定义,递归遍历二叉树,交换每个节点的左右子节点,即可生成二叉树的镜像
- 终止条件:当节点
root为空时,则返回null- 递推工作
- 初始化节点
tmp,用于暂存root的左子节点- 开启递归右子节点,并将返回值作为
root的左子节点- 开启递归左子节点,并将返回值作为
root的右子节点- 返回值:返回当前节点
root
1 | class Solution { |
28.对称的二叉树
思路:从顶至底递归判断
1 | class Solution { |
10_1.斐波那契数列

思路:
- 动态规划:最佳解法
- 递归:大量重复的递归计算
1 | class Solution { |
10_2.青蛙跳台阶问题

思路:动态规划
1 | class Solution { |
63.股票的最大利润

思路:动态规划
- 状态定义:设动态规划列表
dp,dp[i]代表以price[i]为结尾的子数组的最大利润- 转移方程:由于限定“买卖该股票一次”,因此前
i日最大利润dp[i]等于前i-1日最大利润dp[i-1]和第i日卖出的最大利润中的最大值- 初始状态:
dp[0]=0,即首日利润为0- 返回值:
dp[n-1],其中n为dp列表长度
1 | class Solution { |
42.连续子数组的最大和

思路:动态规划
- 状态定义:
dp[i]代表以元素nums[i]为结尾的连续子数组最大和- 转移方程:若
dp[i-1]<=0,说明dp[i-1]对dp[i]产生负贡献,即dp[i-1]+nums[i]还不如nums[i]本身大- 初始状态:
dp[0]=nums[0],即以nums[0]结尾的连续子数组最大和为nums[0]- 返回值:返回
dp列表中的最大值,即全局最大值
1 | class Solution { |
47.礼物的最大价值

思路:动态规划
单元格只可能从上边或左边到达
- 状态定义:
dp(i,j)代表从棋盘的左上角开始,到达单元格(i,j)时能拿到礼物的最大累计价值- 转移方程:四种情况
- 初始状态:
dp[0][0]=price[0][0]- 返回值:
dp[m-1][n-1]
1 | class Solution { |
46. 把数字翻译成字符串


思路:动态规划
- 状态定义:
dp[i]代表以Xi为结尾的数字的翻译方案数量- 转移方程:若
Xi和Xi-1组成的两位数字可以被翻译,则dp[i]=dp[i-1]+dp[i-2];否则dp[i]=dp[i-1]- 初始状态:
dp[0]=dp[1]=1- 返回值:
dp[n],即此数字的翻译方案数量
1 | class Solution { |
48.最长不含重复字符的子字符串

思路:动态规划降低时间复杂度
使用暴力法时间复杂度N^3
- 状态定义:
dp[j]代表以字符s[j]为结尾的“最长不重复子字符串”的长度- 转移方程:固定右边界
j,设字符s[j]左边距离最近的相同字符为s[i],即s[i]=s[j]
- 当 i < 0,即
s[j]左边无相同字符,则dp[j] = dp[j-1] + 1;- 当
dp[j - 1] < j - i,说明字符s[i]在子字符串dp[j-1]区间之外 ,则dp[j]=dp[j-1]+1;- 当
dp[j−1]≥j−i,说明字符 s[i]在子字符串dp[j-1]区间之中 ,则dp[j]的左边界由s[i]决定,即dp[j] = j - i;- 返回值:
max(dp),即全局“最长不重复子字符串”的长度
1 | class Solution { |
思路:双指针 + 哈希表
- 哈希表统计字符
s[j]最后一次出现的索引- 根据上轮左指针
i和dic[s[j]],每轮更新左边界i,保证区间[i+1,j]内无重复字符且最大- 更新结果
1 | class Solution { |
18.删除链表的节点

思路:双指针
删除节点需要两步:定位节点、修改引用
- 定位节点:遍历链表,直到
head.val == val时跳出,即可定位目标节点- 修改引用:设节点
cur的前驱节点为pre,后继节点为cur.next;则执行pre.next=cur.next,即可实现删除cur节点
1 | class Solution { |
22.链表中倒数第k个节点

思路:双指针
使用双指针可以不用统计链表长度
- 初始化:双指针都指向头节点
- 构建双指针距离:前指针先向前走
k步- 双指针共同移动:循环中,双指针每轮都向前走一步,直至前指针走过链表尾结点时跳出循环。此时,后指针与尾结点距离
k-1,即后指针指向倒数第k个节点- 返回值:返回后指针即可
1 | class Solution { |
25.合并两个排序的链表

思路:双指针
- 初始化:伪头节点,节点cur指向
dum- 循环合并:当
l1或l2为空时跳出- 合并剩余尾部
- 返回值:合并链表在伪节点
dum之后,因此返回dum.next即可
1 | public class Solution { |
52.两个链表的第一个公共节点

思路:双指针
headA的节点数量为a,headB的节点数量为b,两链表的公共尾部的节点数量c,则有
- 头节点
headA到node前,共有a-c个节点- 头节点
headB到node前,共有b-c个节点指针A遍历完链表A再遍历链表B,指针B遍历完链表B再遍历链表A,当指针A和B重合时,若不指向
null,则为目标节点
1 | public class Solution { |
21.调整数组顺序使奇数位于偶数前面

思路:双指针
双指针分列数组左右两端,循环执行:
- 指针i从左往右寻找偶数
- 指针j从右往左寻找奇数
- 将偶数
nums[i]和奇数nums[j]交换
1 | class Solution { |
57.和为s的两个数字

思路:双指针
利用排序数组的条件,使用双指针将空间复杂度降至O(1)
- 初始化:双指针位于数组左右两端
- 循环搜索:当双指针相遇时跳出
- 返回空数组,代表无和为
target的数字组合
1 | class Solution { |
58_1.翻转单词顺序

思路:双指针
- 倒序遍历字符串
s,记录单词左右索引边界i,j- 每确定一个单词的边界,则将其添加至单词列表
res- 最终,将单词列表拼接为字符串,并返回即可
1 | class Solution { |
12.矩阵中的路径

思路:深度优先搜索+剪枝
- 递归参数:当前元素在矩阵
board中的行列索引i和j,当前目标字符在word中的索引k- 终止条件
- 返回false:行或列索引越界;当前矩阵元素与目标字符不同;当前元素已访问过
- 返回true:
k=len(word)-1,即当前字符串word已全部匹配- 递推工作
- 标记当前矩阵元素:将
board[i][j]修改为空字符,代表已经访问过- 搜索下一单元格:朝当前元素的上下左右四个方向开启下层递归,使用或连接,并记录结果至
res- 还原当前矩阵元素:将
board[i][j]元素还原至初始值- 返回值:返回布尔量
res,代表是否搜索到目标字符串
1 | class Solution { |
13.机器人的运动范围

思路:搜索与回溯-深度优先遍历
- 递归参数:当前元素在矩阵中的行列索引
i和j,两者的数位和si,sj- 终止条件:
- 行列索引越界
- 数位和超出目标值
- 当前元素已经访问过
- 递推工作:
- 标记当前单元格:将索引
(i,j)存入visited中,代表此单元格已经被访问- 搜索下一单元格:计算当前元素的下、右两个方向元素的数位和,并开启下层递归
- 回溯返回值:返回
1+右方搜索的可达解总数+下方搜索的可达解总数,代表从本单元格递归搜索的可达解总数
1 | class Solution { |
34.二叉树中和为某一值的路径

思路:先序遍历+路径记录
- 初始化:结果列表
res,路径列表path- 返回值:返回
res即可- 递推工作:
- 路径更新:将当前节点值
root.val加入路径path- 目标值更新:
tar = tar-root.val- 路径记录:当
root为叶节点且路径和等于目标值,则将此路径path加入res- 先序遍历:递归左右子节点
- 路径恢复:向上回溯前,需将当前节点从路径
path中删除
1 | class Solution { |
36.二叉搜索树与双向链表

思路:中序遍历
使用中序遍历访问树的各节点
cur;并在访问每个节点时构建cur和前驱节点pre的引用指向;中序遍历完成后,最后构建头节点和尾节点的引用指向即可
- 终止条件:当节点
cur为空,代表越过叶节点,直接返回- 递归左子树
- 构建链表:
- 当
pre为空时:代表正在访问链表头节点,记为head- 当
pre不为空时:修改双向节点引用,即pre.right=cur, cur.left=pre- 保存
cur:更新pre=cur,即节点cur是后继节点的pre- 递归右子树
1 | class Solution { |
54.二叉搜索树的第k大节点

思路:转化为求“此树的中序遍历倒序的第k个节点”
1 | class Solution { |
45.把数组排成最小的数

思路:求拼接起来的最小数字,本质上是一个排序问题。设数组
nums中任意两数字的字符串为x和y,则规定排序判断规则为
- 若拼接字符串
x+y>y+x,则x>y- 反之,则
x<y
1 | class Solution { |
61.扑克牌中的顺子

思路:
需满足如下条件
- 除大小王外,所有牌无重复
- 设此5张牌中最大的牌为max,最小的牌为min。需满足
max-mix<5
1 | class Solution { |
40.最小的k个数

思路:快速排序
1 | class Solution { |
41.数据流中的中位数

思路:建立一个 小顶堆 A 和 大顶堆 B,各保存列表的一半元素
设元素总数为 N = m + n,其中 m和 n分别为 A和 B中的元素个数
- 当m=n时,需向A添加一个元素。实现方法:将新元素 num插入至 B,再将 B堆顶元素插入至 A
- 当m!=n时,需向 B添加一个元素。实现方法:将新元素num插入至 A,再将 A堆顶元素插入至 B
1 | class MedianFinder { |
55_1.二叉树的深度

思路:后序遍历
递归实现
1 | class Solution { |
55_2.平衡二叉树

思路:后序遍历+剪枝
1 | class Solution { |
64.求1+2+…+n

思路:利用递归,逻辑运算符的短路效应
1 | class Solution { |
68_1.二叉搜索树的最近公共祖先

思路:迭代
- 循环搜索:当节点root为空时跳出
- 当
p,q都在root的右子树中,则遍历至root.right- 当p,q 都在 root的左子树中,则遍历至
root.left- 否则,说明找到了最近公共祖先,跳出
- 返回值:最近公共祖先
root
1 | class Solution { |
68_2.二叉树的最近公共祖先

思路:考虑通过递归对二叉树进行==先序遍历==,当遇到节点
p或q时返回。从底至顶回溯,当节点p,q在节点root的异侧时,节点root即为最近公共祖先,则向上返回root
- 终止条件
- 当越过叶节点,则直接返回
null- 当
root等于p,q,则直接返回root- 递推工作
- 开启递归左子节点,返回值记为
left- 开启递归右子节点,返回值记为
right- 返回值:根据
left,right,可展开为四种情况
- 当
left和right同时为空:说明root的左右子树中都不包含p,q,返回null- 当
left和right同时不为空,说明p,q分别列在root的异侧,因此root为最近公共祖先,返回root- 当left为空 ,right 不为空 :p,q都不在 root的左子树中,直接返回 right。具体可分为两种情况:
- p,q 其中一个在 root的右子树中,此时right指向 p(假设为 p);
- p,q 两节点都在 root的右子树中,此时的 right指向最近公共祖先节点
- 当left不为空 ,right为空:与情况3同理
1 | class Solution { |
7.重建二叉树

思路:
- 前序遍历的首元素为树的根节点
node的值- 在中序遍历中搜索根节点
node的索引,可将中序遍历划分为左子树|根节点|右子树- 根据中序遍历中的左右子树的节点数量,可将前序遍历划分为根节点|左子树|右子树
通过以上三步,可确定三个节点:1.树的根节点;2.左子树根节点;3.右子树根节点
根据分治算法思想,对于树的左、右子树,仍可复用以上方法划分子树的左右子树
1 | class Solution { |
16.数值的整数次方

思路:快速幂
二分法角度
- 当x=0时直接返回0
- 初始化res=1
- 当n<0时,把问题转化至n>=0的范围内
- 循环计算:当n=0时跳出
- 当n&1=1时,当前x乘入res
- 执行x=x^2
- 执行n右移一位
- 返回res
1 | class Solution { |
33.二叉搜索树的后序遍历序列

思路:递归分治
根据二叉搜索树的定义,可以通过递归,判断所有子树的正确性,若所有子树都正确,则此序列为二叉搜索树的后序遍历
- 终止条件:当i>=j,说明此子树节点数量<=1,无需判别正确性,直接返回true
- 递推工作
- 划分左右子树:遍历后序遍历的[i,j]区间元素,寻找第一个大于根节点的节点,索引记为m。
- 判断是否为二叉搜索树
- 左子树区间内所有节点都已经满足条件了,只需要判断右子树区间即可
- 右子树区间内所有节点应>postorder[j]。实现方式为遍历
- 返回值:所有子树都需要正确才可判定正确
- p=j,判断此树是否正确
- recur(i,m-1):判断此树的左子树是否正确
- recur(m, j-1):判断此树的右子树是否正确
1 | class Solution { |
15. 二进制中1的个数

思路:逐位判断
- 若n&1=0,则n二进制最后一位为0;
- 若n&1=1,则n二进制最后一位为1
根据此特点,使用循环判断
1 | public class Solution { |
65.不用加减乘除做加法

1 | class Solution { |
56_1.数组中数字出现的次数

思路:设两个只出现一次的数字x,y,由于x!=y,则x和y二进制至少有一位不同,据此可以将nums拆分为分别包含x和y的两个子数组
易知两个子数组都满足 除一个数字之外,其他数字都出现了两次 因此,仿照以上简化问题的思路,分别对两个子数组遍历执行异或操作,即可得到两个只出现一次的数字x,y
- 遍历nums执行异或
- 循环左移计算m (x⊕y的首位1)
- 拆分nums为两个子数组
- 分别遍历两个子数组执行异或
1 | class Solution { |
56_2.数组中数字出现的次数

思路:出现三次的数字,各二进制位出现的次数都是3的倍数。因此,统计所有数字的各二进制位中1出现次数,并对3求余,结果则为只出现一次的数字
1 | class Solution { |
39.数组中出现次数超过一般的数字

思路:哈希表统计&数组排序&摩尔投票法
摩尔投票法核心理念为票数正负抵消,为本题最佳解法
记数组首个元素为
n1,众数为x,遍历并统计票数。当票数和为0时,剩余数组的众数一定不变利用此特性,每轮假设发生票数和=0都可以缩小剩余数组区间。当遍历完成时,最后一轮假设的数字即为众数。
1 | class Solution { |
66.构建乘积数组

思路:根据主对角线(全为1)可将表格分为上三角和下三角两部分。分别迭代计算下三角和上三角两部分的乘积
- 初始化:数组B,其中B[0]=1,辅助变量tmp=1
- 计算B[i]的下三角各元素的乘积,直接乘入B[i]
- 计算B[i]的上三角各元素的乘积,记为tmp,并乘入B[i]
- 返回B
1 | class Solution { |
14_1.剪绳子

思路:
- 当所有绳长度相等时,乘积最大
- 最优的绳段长度为3
1 | class Solution { |
57_2.和为s的连续正数序列

思路:滑动窗口
- 初始化:左边界i=1,右边界j=2,元素和s=3,结果列表res;
- 循环:当i>=j时跳出
- 当s>target时,向右移动左边界,更新元素和
- 当s<target时,向右移动右边界,更新元素和
- 当s=target时,记录连续整数序列,并向右移动左边界
- 返回res
1 | class Solution { |
62.圆圈中最后剩下的数字

1 | class Solution { |
29.顺时针打印矩阵

思路:模拟
- 空值处理
- 初始化:四个边界,结果列表res
- 循环打印
- 根据边界打印,即将元素按顺序添加至列表res尾部
- 边界向内收缩1
- 判断是否打印完毕,若打印完毕则跳出
- 返回值:res
1 | class Solution { |
31.栈的压入、弹出序列

思路:模拟
- 初始化:辅助栈stack,弹出序列的索引
i- 遍历压栈序列:各元素记为num
- 元素num入栈
- 循环出栈:若stack的栈顶元素=弹出序列元素
popped[i],则执行出栈与i++- 返回值:若stack为空,则此弹出序列合法
1 | class Solution { |
20.表示数值的字符串

1 | class Solution { |
67.把字符串转换成整数

思路:
- 首部空格:删除即可
- 符号位:三种情况,新建一个变量保存符号位,返回前判断正负即可
- 非数字字符:遇到首个非数字的字符时,应立即返回
- 数字字符
- 字符转数字:数字的ASCII码与0的ASCII码相减即可
- 数字拼接
1 | class Solution { |
59_1.滑动窗口的最大值

思路:单调队列
- 初始化:双端队列deque,结果列表res
- 滑动窗口:左边界范围i∈[1-k, n-k],右边界范围j∈[0,n-1]
- 若i>0且队首元素deque[0]=被删元素nums[i-1];则队首元素出队
- 删除deque内所有<nums[j]的元素,以保持deque递减
- 将nums[j]添加至deque尾部
- 若已形成窗口(i>=0):将窗口最大值(即队首元素deque[0])添加至列表res
- 返回值:返回结果列表res
1 | class Solution { |
59_2.队列的最大值

1 | class MaxQueue { |
37.序列化二叉树

1 | /** |
38.字符串的排列

思路:回溯法
- 终止条件:当x=len(c)-1时,代表所有位已固定,则将当前组合c转化为字符串并加入res,并返回
- 递推参数:当前固定位x
- 递推工作:初始化一个Set,用于排除重复的字符;将第x位字符与i∈[x,len(c)]字符分别交换,并进入下一层递归
- 剪枝:若c[i]在set中,代表其实重复字符,因此剪枝
- 将c[i]加入Set,以便之后遇到重复字符时剪枝
- 固定字符:将字符c[i]和c[x]交换,即固定c[i]为当前位字符
- 开启下层递归:调用dfs(x+1),即开始固定第x+1个字符
- 还原交换:将字符c[i]与c[x]交换(还原之前的交换)
1 | class Solution { |
19.正则表达式匹配

思路:动态规划
1 | class Solution { |
49.丑数

思路:动态规划
- 状态定义:设动态规划列表dp,dp[i]代表第i+1个丑数
- 转移方程:
- 当索引a,b,c满足以下条件时,dp[i]为三种情况的最小值
- 每轮计算dp[i]后,需要更新索引a,b,c的值,使其始终满足方程条件。实现方法:分别独立判断dp[i]和
dp[a]x2,dp[b]x3,dp[c]x5的大小关系,若相等则相应索引a,b,c加1
dp[a]×2>dp[i−1]≥dp[a−1]×2 dp[b]×3>dp[i−1]≥dp[b−1]×3 dp[c]×5>dp[i−1]≥dp[c−1]×5
dp[i] = min(dp[a]x2, dp[b]x3, dp[c]x5)
- 初始状态:dp[0]=1,即第一个丑数为1
- 返回值:dp[n-1],即返回第n个丑数
1 | class Solution { |
60.n个骰子的点数

思路:动态规划
可通过子问题的解f(n-1)可递推计算出f(n),而输入一个骰子的解f(1)已知,因此可通过解f(1)依次递推出任意解f(n)
由于新增骰子的点数只可能为1至6,因此概率f(n-1,x)仅与f(n,x+1),f(n,x+2)……f(n,x+6)相关。因而,遍历f(n-1)中各点数和的概率,并将其相加至f(n)中所有相关项,即可完成f(n-1)至f(n)的递推
通常做法是声明一个二维数组dp,
dp[i][j]代表前i个骰子的点数和j的概率,并执行状态转移。而由于dp[i]仅由dp[i-1]递推得出,为降低空间复杂度,只建立两个一维数组dp和tmp交替前进即可
1 | class Solution { |
51.数组中的逆序对

思路:
- 终止条件:当l>=r时,代表子数组长度为1,此时终止划分
- 递归划分:计算数组中点m,递归划分左子数组和右子数组
- 合并与逆序对统计
- 暂存数组nums闭区间[l,r]内的元素至辅助数组tmp
- 循环合并:设置双指针i,j分别指向左右子数组的首元素
- 当i=m+1时:代表左子数组已合并完,因此添加右子数组当前元素tmp[j],并执行j=j+1
- 否则,当j=r+1时:代表右子数组已合并完,因此添加左子数组当前元素tmp[i],并执行i=i+1
- 否则,当tmp[i]<=tmp[j]:添加左子数组当前元素tmp[i,并执行i=i+1
- 否则添加右子数组当前元素tmp[j],并执行j=j+1;此时构成m-i+1个逆序对,统计添加至res
- 返回值
1 | class Solution { |
14_2.剪绳子

思路:贪心算法
- 如果 n == 2,返回1,如果 n == 3,返回2,两个可以合并成n小于4的时候返回n - 1
- 如果 n == 4,返回4
- 如果 n > 4,分成尽可能多的长度为3的小段,每次循环长度n减去3,乘积res乘以3;最后返回时乘以小于等于4的最后一小段;每次乘法操作后记得取余就行
- 以上2和3可以合并
1 | class Solution { |
