目录
二叉树
求和路径
题目:给定一棵二叉树,其中每个节点都含有一个整数数值(该值或正或负)。设计一个算法,打印节点数值总和等于某个给定值的所有路径的数量。注意,路径不一定非得从二叉树的根节点或叶节点开始或结束,但是其方向必须向下(只能从父节点指向子节点方向)。
思路:用dfs + 回溯法
递归基:根节点为空
从当前根节点进行搜索,找到符合条件的路径则路径数量加一。递归左孩子、右孩子进行搜索。
求根到叶子节点数字之和
题目:给定一个二叉树,它的每个结点都存放一个 0-9 的数字,每条从根到叶子节点的路径都代表一个数字。
例如,从根到叶子节点路径 1->2->3 代表数字 123。
计算从根到叶子节点生成的所有数字之和。
思路:使用DFS(或者说先序遍历),用 p r e v S u m prevSum prevSum 记录上一节点的“路径和”,当前节点和 s u m = p r e v S u m ∗ 10 + r o o t − > v a l sum = prevSum * 10 + root->val sum=prevSum∗10+root−>val。
若该节点为叶子节点,结束递归返回和。否则检索分叉路径,返回的和应该为 左子树 + 右子树
二叉树的深度
题目:输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。
思路:使用后序遍历,左右节点遍历完后再确定该节点的深度。关键是将空节点深度视为0。
也可使用层序遍历(广度优先搜索)(BFS),当遍历至最底层时即为深度。
最小高度树
题目:给定一个有序整数数组,元素各不相同且按升序排列,编写一个算法,创建一棵高度最小的二叉搜索树。
思路:递归
因为题目给了有序数组,因此直接从中间节点开始作为当前的根。然后递归构造左子树、右子树。
递归构造的时候注意传入数组的范围。
合并二叉树
题目:合并两棵二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。
思路:递归
递归基:若树1节点 t 1 t1 t1 为空,则返回树2节点 t 2 t2 t2。 t 2 t2 t2 为空则相反。
建立新节点,当前节点值为 t 1 − > v a l + t 2 − > v a l t1->val + t2->val t1−>val+t2−>val。
左孩子指向递归合并
t
1
t1
t1 和
t
2
t2
t2 的左节点
后孩子则同时处理右节点。
平衡二叉树
题目:输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。
思路:求平衡二叉树的深度。
和求二叉树的深度类似。只不过要额外设置递归出口:向上回归的过程中:
- 如果根节点的左右孩子深度差超过1,则立即返回-1(失败标志)
- 如果根节点左右孩子有深度为-1,立即返回-1。
若最后返回的结果为-1,则该树为非平衡二叉树。
对称二叉树
题目:判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。
思路:
根据对称二叉树定义:
- L . v a l = = R . v a l L.val == R.val L.val==R.val 左子树根节点等于右子树根节点
- L . l e f t . v a l = = R . r i g h t . v a l L.left.val == R.right.val L.left.val==R.right.val L的左孩子等于R的右孩子
- L . r i g h t . v a l = = R . l e f t . v a l L.right.val == R.left.val L.right.val==R.left.val L的右孩子等于R的左孩子
特例处理:root为空,返回true
递归判断树的每一对节点 (L,R):
- 这一对节点都为空,即对称
- 有一节点不为空,非对称
- 都不为空,且值不等,非对称
- 递推判断: ( L . l e f t , R . r i g h t ) (L.left, R.right) (L.left,R.right) && ( L . r i g h t , R . l e f t ) (L.right, R.left) (L.right,R.left)
重建二叉树
题目:输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
思路:递归
递归基:当传入的数组为空,则返回。
根据前序遍历序列确定根节点,在中序遍历序列中确定左右子树的数组范围。因此递归即可。
二叉树的镜像
题目:输入一个二叉树,该函数输出它的镜像。
思路:类似前序遍历
交换 根节点的左右孩子指针。
递归 获得左子树镜像,再 递归 获得右子树镜像(左右子树的递归顺序不影响)。
修剪二叉搜索树
题目:给定一个二叉搜索树,同时给定最小边界 L L L 和最大边界 R R R。通过修剪二叉搜索树,使得所有节点的值在 [ L , R ] [L, R] [L,R] 中 (R>=L) 。你可能需要改变树的根节点,所以结果应当返回修剪好的二叉搜索树的新的根节点。
思路:递归,类似先序遍历。
先处理根节点,再递归处理左子树和右子树。
递归:
- 如果根节点为空,则返回空
- 若根节点小于 L L L,则返回:递归处理右子树的根节点
- 若根节点大于 R R R,则返回:递归处理左子树的根节点
根节点符合要求后,根节点的左孩子指向递归处理后的左子树,右孩子指向递归处理后的右子树。返回当前的根节点。
二叉树剪枝
题目:给定二叉树根结点 root ,此外树的每个结点的值要么是 0,要么是 1。
返回移除了所有不包含 1 的子树的原二叉树。
题目理解:重复删除为0的叶节点
思路:由理解得,如果目前的根节点值为0,左子树,右子树为空,那么删除当前的根节点。
因此使用后序遍历,从下往上删除,使用 l e f t , r i g h t left, right left,right 接收递归的左右子树返回值。
递归基:根节点为空
返回值:当前的根节点
从上到下打印二叉树
题目:从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。
思路:广度优先搜索(BFS)
- 先判断根节点是否为空
- 将根节点入队 q q q 中
- 队列非空则循环:
- 访问首元素,若孩子非空,先左孩子入队,再右孩子入队
- 首元素出队
从上到下打印二叉树 II
题目:从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。
思路:广度优先搜索(BFS)
- 先判断根节点是否为空
- 根节点入队 q q q
- 队列非空则循环
- 先获取初始队列大小(此层的节点个数)
- 循环访问此层节点,并记录到临时数组 t m p tmp tmp 中:若孩子非空则入队。
- 将 t m p tmp tmp 加入到结果数组 r e s res res 中
- 返回 r e s res res
从上到下打印二叉树 III
题目:
请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。
思路一:双端队列
设置变量 l e v e l level level 控制层数(根节点为第一层):
- 奇数层:队首元素出队,元素的非空根节点按照从左到右入队尾
- 偶数层:队尾元素出队,元素的非空根节点按照从右到左入队头
实质上类似使用队列和栈的结合体控制元素的输入输出,只不过想法并无那么直接。
思路二:通过控制奇数层顺序,偶数层逆序输出即可。
特定深度节点链表
题目:给定一棵二叉树,设计一个算法,创建含有某一深度上所有节点的链表(比如,若一棵树的深度为 D,则会创建出 D 个链表)。返回一个包含所有深度的链表的数组。
思路:使用层序遍历,建立一个层次链表和尾插法解决遍历顺序的问题。
二叉树的最近公共祖先
题目:给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
思路:先分析公共祖先的性质:节点
p
p
p,
q
q
q 应在其左子树或右子树,即先要确定左、右子树是否分别存在节点
p
p
p、
q
q
q ,然后才能确定该祖先是否是公共祖先。
而最近公共祖先则可以使用递归,从底向上回溯时第一个找到的公共祖先则是最近的。
算法流程如下:
- 递归基:根节点 r o o t root root,或节点 p p p、 q q q 为空
- 设置返回值 l e f t left left 记录递归左子树是否查找 p p p, q q q
- 设置返回值 r i g h t right right 记录递归右子树是否查找 p p p, q q q
- 分类讨论:
- 如果左右子树都为空,即该子树不存在 p p p, q q q,返回 n u l l p t r nullptr nullptr
- 如果左子树为空,返回 r i g h t right right(说明该右子树有找到节点,但是目前还未找到公共祖先)
- 如果右子树为空,返回 l e f t left left
- 如果左右子树都非空,返回值根节点 r o o t root root 即为最近公共祖先
树的子结构
题目:输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)
B是A的子结构, 即 A中有出现和B相同的结构和节点值。
思路:递归。类似后序遍历
递归基: A = = n u l l p t r ∣ ∣ B = = n u l l p t r A == nullptr || B == nullptr A==nullptr∣∣B==nullptr 返回 f a l s e false false
使用检查函数从A节点开始和B匹配:
- 成功则返回 t r u e true true
- 否则递归检查A的左孩子、右孩子,只要有一个节点匹配成功则返回 t r u e true true
检查函数:递归
- B = = n u l l p t r B == nullptr B==nullptr 说明检查完毕,返回 t r u e true true。注意这里是检查函数,和上面 B = = n u l l p t r B == nullptr B==nullptr 的情况不一样。
- 如果 A = = n u l l p t r A == nullptr A==nullptr,说明A已经越过叶节点,匹配失败。
- 若两个节点值不同,匹配失败
- 最后一个情况即当前节点匹配成功,递归匹配左孩子和右孩子。注意左右孩子都要匹配成功才可返回 t r u e true true
二叉搜索树的第k大节点
题目:给定一棵二叉搜索树,请找出其中第k大的节点。
思路:利用二叉搜索树的性质
中序遍历得到升序序列,返回倒数 k 个数即可。
二叉搜索树的范围和
题目:给定二叉搜索树的根结点 root,返回 L 和 R(含)之间的所有结点的值的和。
二叉搜索树保证具有唯一的值。
思路一:中序遍历序列
思路二:二叉树的搜索
二叉搜索树的最近公共祖先
题目:给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
思路:(对二叉搜索树要有一定理解)
从根节点开始遍历
- 若p,q的值都小于根节点,即在根节点左侧,递归搜索根的左孩子
- 若都大于根节点,递归搜索根的右孩子
- 其余情况:该根节点为最近公共祖先
二叉搜索树与双向链表
题目:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。
思路一:中序遍历序列
中序遍历用 v e c t o r vector vector 获取升序序列,然后在序列中调整左指针指向前一个元素,调整右指针指向后一个元素。(注意取模,考虑首尾元素的链接)
思路二:中序遍历递归(官方解法,略慢于思路一)
使用 p r e , h e a d pre, head pre,head 指针记录上一个节点和头节点。
当递归到最左边的元素时 p r e = c u r pre = cur pre=cur,该指针指向当前节点,并且用头节点记录。其余条件则按照要求链接节点即可。
二叉搜索树的后序遍历序列
题目:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。
思路:递归
递归基: p o s t o r d e r . s i z e ( ) < = 1 postorder.size() <= 1 postorder.size()<=1
确定左右子树:
- 根据后序序列特征, r o o t = s i z e ( ) − 1 root = size()-1 root=size()−1。
- 从后向左遍历数组,找到第一个小于根节点的元素
l
e
f
t
left
left,即左子树的根节点。
因此右子树区间: [ l e f t + 1 , r o o t ) [left+1,root) [left+1,root)
遍历过程中,右子树确定所有元素大于根节点,因此只需要在遍历过程中确认左子树元素小于根节点。若大于,则返回 f a l s e false false
新建数组 l,r。将左右子树的元素分别复制进数组,然后递归判断左子树和右子树。
填充每个节点的下一个右侧节点
题目:给定一个完美二叉树。二叉树定义如下:
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
填充它的每个 next
指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next
指针设置为 NULL
。
初始状态下,所有 next
指针都被设置为 NULL
。
思路一:层序遍历(题目提示要常数空间,因此不是最优思路)
思路二:使用已建立的 n e x t next next 指针
哈希表
O(1) 时间插入、删除和获取随机元素 - 允许重复
题目:设计一个支持在平均 时间复杂度 O(1) 下, 执行以下操作的数据结构。
注意: 允许出现重复元素。
insert(val):向集合中插入元素 val。
remove(val):当 val 存在时,从集合中移除一个 val。
getRandom:从现有集合中随机获取一个元素。每个元素被返回的概率应该与其在集合中的数量呈线性相关。
思路:使用哈希表 i d x idx idx 和顺序表 n u m num num,对每一个元素值 h a s h hash hash 到索引集合(在顺序表中的位置)上。顺序表用于记录元素值。
插入操作:顺序表尾插入,同时更新哈希表。
删除操作:
- 判断哈希表中是否有 v a l val val
- 获取哈希表中该值的索引 i n d e x index index
- 用顺序表最后一个元素覆盖该元素
- 更新哈希表:
- 删除哈希表中 v a l val val 的索引 i n d e x index index
- 删除哈希表中原本最后一个元素的索引 n u m [ i n d e x ] . s i z e ( ) − 1 num[index].size() - 1 num[index].size()−1
- 如果删除的元素不是最后一个元素,则对哈希表中原本最后一个元素的索引值进行更新,即插入 i n d e x index index。
- 如果删除元素后索引集合为空,则在删除哈希表中的键值 v a l val val。
随机获取元素操作:随机获得一个索引,通过索引返回顺序表中的元素。
变位词组
题目:编写一种方法,对字符串数组进行排序,将所有变位词组合在一起。变位词是指字母相同,但排列不同的字符串。
思路:遍历一次数组,将每个元素排序后映射到哈希表中,记录该元素对应的数组索引。
然后遍历哈希表,将相同映射结果的索引取出,添加到结果数组即可。
堆(Heap)
最小的k个数
题目:输入整数数组 arr ,找出其中最小的 k k k 个数。例如,输入
4、5、1、6、2、7、3、8
这8个数字,则最小的4个数字是
1、2、3、4
思路一:用STL建堆或者用优先队列模拟堆
时间复杂度: O ( n l o g k ) O(nlogk) O(nlogk)
用STL建堆注意语法即可,建完堆后依次将前k个元素弹出并每次维护堆结构。
优先队列模拟:
- 初始化一个优先队列,含有k个元素(队首为最大值)
- 对数组 [ k , a r r . s i z e ( ) ) [ k, arr.size() ) [k,arr.size()) 的部分进行判断,如果小于则队首元素出队,该元素进队。
思路二:快排分区思想
使用快排划分函数将数组以分界值 p i v o t pivot pivot 分成两部分,左边比 p i v o t pivot pivot 小,右边比 p i v o t pivot pivot 大。然后递归处理其中的一个分区:令 p o s pos pos 表示 p i v o t pivot pivot 的位置,令 l , r l, r l,r 表示正在处理的区间 $[l,r]。
- 如果分区后 k = p i v i t k = pivit k=pivit,则该分界值为第k小元素
- 假设 p o s > k pos > k pos>k 则表示第k小的元素在左区间, 否则在右区间
- 特别的:若在右区间, p i v i t pivit pivit 是第 p o s − k + 1 pos - k + 1 pos−k+1 小的元素,则对右区间进行分区时,需要寻找的第k小元素是 k − ( p o s − k + 1 ) k - (pos - k + 1) k−(pos−k+1) 的元素。
图
题目:在一个小镇里,按从 1 到 N 标记了 N 个人。传言称,这些人中有一个是小镇上的秘密法官。
如果小镇的法官真的存在,那么:
小镇的法官不相信任何人。
每个人(除了小镇法官外)都信任小镇的法官。
只有一个人同时满足属性 1 和属性 2 。
给定数组 trust,该数组由信任对 trust[i] = [a, b] 组成,表示标记为 a 的人信任标记为 b 的人。
如果小镇存在秘密法官并且可以确定他的身份,请返回该法官的标记。否则,返回 -1。
思路:用出度入度思考。
出度数组
o
u
t
D
e
g
r
e
e
outDegree
outDegree,入度数组
i
n
D
e
g
r
e
e
inDegree
inDegree。
遍历所给的
t
r
u
s
t
trust
trust 数组,
t
r
u
s
t
[
i
[
[
0
]
trust[i[[0]
trust[i[[0] 即为第 i 个人的“出度”,
t
r
u
s
t
[
i
]
[
1
]
trust[i][1]
trust[i][1] 则为入度。
遍历结束后根据遍历入度数组,寻找度数为 N - 1的人,若有则判断该人的出度是否为0。
节点间通路
题目:节点间通路。给定有向图,设计一个算法,找出两个节点之间是否存在一条路径。注意:图中可能存在自环和平行边。
思路:使用 u n o r d e r e d _ s e t unordered\_set unordered_set 去重,使用 u n o r d e r e d _ m a p < i n t , u n o r d e r e d _ s e t < i n t > > unordered\_map<int, unordered\_set<int>> unordered_map<int,unordered_set<int>> 实现图的邻接表结构。然后使用 DFS 或者 BFS,用 v i s i t visit visit 数组判断该点是否已经遍历,解决图中有环的问题。
钥匙和房间
题目:有 N 个房间,开始时你位于 0 号房间。每个房间有不同的号码:0,1,2,…,N-1,并且房间里可能有一些钥匙能使你进入下一个房间。
在形式上,对于每个房间 i 都有一个钥匙列表 rooms[i],每个钥匙 rooms[i][j] 由 [0,1,…,N-1] 中的一个整数表示,其中 N = rooms.length。 钥匙 rooms[i][j] = v 可以打开编号为 v 的房间。
最初,除 0 号房间外的其余所有房间都被锁住。
你可以自由地在房间之间来回走动。
如果能进入每个房间返回 true,否则返回 false
思路:简单DFS,设置观察数组 v i s i t visit visit 该房间有无被访问过,防止死循环。
从0开始,遍历整个房间获得钥匙,然后根据钥匙递归访问房间。
动态规划
单词拆分
题目:给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
思路:若 [ 0 , j ) [0, j) [0,j) 能被拆分,那么只需观察 [ j , s i z e ) [j, size) [j,size) 能否被拆分即可。用数组 d p [ i ] dp[i] dp[i] 表示长度为 i 的字串能否被成功切割。对于一个字串需要枚举所有可能的分割点 j j j,并且记录。
状态转移方程: d p [ i ] = d p [ j ] ∧ c h e c k [ j , i ) dp[i] = dp[j] \land check[j, i) dp[i]=dp[j]∧check[j,i) 其中: 0 < j < i \text{ 0 < j < i} 0 < j < i
对于判断一个字串是否在单词列表中可以用哈希表预处理。
视频拼接
题目:你将会获得一系列视频片段,这些片段来自于一项持续时长为 T 秒的体育赛事。这些片段可能有所重叠,也可能长度不一。
视频片段 clips[i] 都用区间进行表示:开始于 clips[i][0] 并于 clips[i][1] 结束。我们甚至可以对这些片段自由地再剪辑,例如片段 [0, 7] 可以剪切成 [0, 1] + [1, 3] + [3, 7] 三部分。
我们需要将这些片段进行再剪辑,并将剪辑后的内容拼接成覆盖整个运动过程的片段 [ 0 , T ] [0, T] [0,T]。返回所需片段的最小数目,如果无法完成该任务,则返回 -1 。
题目理解:即在所给子区间内寻找能够覆盖 [ 0 , T ] [0,T] [0,T] 的区间
思路:动态规划
对时常为 T T T 秒的视频来说,若有子区间 [ a i , a j ] [a_i, a_j] [ai,aj] 满足 a i ≤ T ≤ a j a_i \leq T \leq a_j ai≤T≤aj,那么其能覆盖该视频的后半部分,那么只需考虑前半部分 [ 0 , a j ] [0, a_j] [0,aj] 即可。
因为要找最优解,所以长 T T T 的视频为 前半段的最小片段数量加一。
状态转移方程: d p [ T ] = m i n { d p [ a i ] } + 1 dp[T] = min\{dp[a_i]\} + 1 dp[T]=min{dp[ai]}+1 , a i ≤ T ≤ a j a_i \leq T \leq a_j ai≤T≤aj
礼物的最大价值
题目:在一个 m*n 的棋盘(vector<vector>& grid)的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?
边界情况:
- 第一行从第二个元素开始,每个元素加等于左一列元素的值
- 第一列从第二各元素开始,每个元素加等于上一行元素的值
状态转移方程:
g r i d [ i ] [ j ] = { d p [ i ] [ j − 1 ] i = 0, j > 0 d p [ i − 1 ] [ j ] i > 0, j = 0 m a x ( g r i d [ i − 1 ] [ j ] , g r i d [ i ] [ j − 1 ] ) i > 0, j > 0 grid[i][j]= \begin{cases} dp[i][j-1]& \text{i = 0, j > 0}\\ dp[i-1][j] & \text{i > 0, j = 0}\\ max(grid[i-1][j],grid[i][j-1]) & \text{i > 0, j > 0} \end{cases} grid[i][j]=⎩⎪⎨⎪⎧dp[i][j−1]dp[i−1][j]max(grid[i−1][j],grid[i][j−1])i = 0, j > 0i > 0, j = 0i > 0, j > 0
分割等和子集
题目:给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。(NP完全问题)
类似 0-1 背包问题,可以理解为选取的数字(重量)恰好等于元素和(容量)的一半。
寻找目标: t a r g e t = s u m / 2 target = sum / 2 target=sum/2
基础条件的判断:
- 若数组 n u m s nums nums 元素和 s u m sum sum 为奇数,则不可分割
- 若最大元素 m a x E l e > t a r g e t maxEle > target maxEle>target ,不可分割
设二维数组 n n n 行 t a r g e t + 1 target + 1 target+1 列, d p [ i ] [ j ] dp[i][j] dp[i][j] 表示是否存在从区间 [ 0 , i ] [0,i] [0,i] 内选取数字和为 j j j。
边界情况:
- j = 0 j = 0 j=0 时,不选取任何数即可, d p [ i ] [ 0 ] dp[i][0] dp[i][0] 都为真。
- i = 0 i = 0 i=0 时,只能选取第一个整数, d p [ 0 ] [ n u m s [ i ] ] dp[0][nums[i]] dp[0][nums[i]] 为真。
其余情况:
- 若
j
j
j
≥
\geq
≥
n
u
m
s
[
i
]
nums[i]
nums[i]:则对
n
u
m
s
[
i
]
nums[i]
nums[i] 可以选择是否选取。任何一种情况成立则
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 为真
- 选取: d p [ i ] [ j ] = d p [ i − 1 ] [ j − n u m s [ i ] ] dp[i][j]=dp[i-1][j-nums[i]] dp[i][j]=dp[i−1][j−nums[i]]
- 不选取: d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j]=dp[i-1][j] dp[i][j]=dp[i−1][j]
- 若 j < n u m s [ i ] j<nums[i] j<nums[i]:则不可选取当前值。 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j] = dp[i-1][j] dp[i][j]=dp[i−1][j]
状态转移方程:
d
p
[
i
]
[
j
]
=
{
d
p
[
i
−
1
]
[
j
]
j < nums[i]
d
p
[
i
−
1
]
[
j
]
∣
d
p
[
i
−
1
]
[
j
−
n
u
m
s
[
i
]
]
j
≥
nums[i]
dp[i][j]= \begin{cases} dp[i-1][j]& \text{j < nums[i]}\\ dp[i-1][j] | dp[i-1][j-nums[i]]& \text{j $\geq$ nums[i]} \end{cases}
dp[i][j]={dp[i−1][j]dp[i−1][j]∣dp[i−1][j−nums[i]]j < nums[i]j ≥ nums[i]