背包问题
先循环物品
再循环体积
最后循环决策
01背包
二维动态规划
f[i][j] 表示只看前i个物品, 总体积是j的情况下,总价值最大是多少。
result = max {f[n][0-v]}
f[i][j] =
1.不选第i个物品, f[i][j] = f[i - 1][j];
2.选第i个物品, f[i][j] = f[i - 1][j - v[i]] + w[i];
f[i][j] = max{1, 2}
f[0][0] = 0;
1 |
|
滚动数组
关于背包问题的初始化。
- 恰好装满背包时的最优解。初始化时f[0] = 0, 其余初始化为-INF。
- 初始化数组f 就是在没有任何物品可以放入背包时的合法状态, 如果要求背包恰好装满,那么此时只有容量为0的背包可以在没有东西放入的情况下“恰好装满”,其它容量的背包均没有合法的解,属于未定义的状态。
- 背包不是必须装满,只希望价值尽量大。 初始化时f数组全为0
后面输出最大价值时 选择f[m]
这是因为我们把f[N]数组初始化为0了, 即得到最优解时背包不一定是满的。
如果把f[0]初始化为0, 其余为-INF, 这样得到的最优解就是背包恰好装满时的最优解了, 此时最大值就不一定是数组最后一个元素了。
初始化方式: 不超过背包容量时的最大值就是最后一个元素。
1 |
|
初始化方式:恰好装满背包时 , 这时 最大值就不是最后一个元素了。
1 |
|
完全背包
f[i] 表示 总体积是i的情况下, 最大价值是多少。
result = max{f[0 … m]} // m 是 最大体积
for (int i = 0; i < n; i++) {
for (int j = v[i]; j <= m; j++ )
f[j] = max (f[j], f[j - v[i]] + w[i]);
}
1 |
|
多重背包
1 | /* |
多重背包的二进制优化方法
数据范围
0 < N <= 1000
0 < V <= 2000
0 < vi, wi, si <= 2000
1 |
|
混合背包
1 |
|
二维费用背包
第一重循环 循环物品
第二重循环 循环体积
第三重循环 循环重量
1 | /* |
分组背包 (多重背包实际上是分组背包的特殊情况)
1 |
|
有依赖的背包
背包问题求方案数
1 |
|
背包问题求具体方案
1 |
|
排序
稳定:相同元素相对位置不变
冒泡排序
1 |
|
冒泡排序优化
加入一个标志变量, 当发生元素交换时改变flag状态, 一趟遍历完成后 检查flag , 如果flag未改变, 说明这趟遍历没有元素逆序, 也就是说序列是有序的, 此时退出循环即可。
1 |
|
选择排序
1 | void selectionSort(vector<int>& q) { |
插入排序
1 | void insertionSort (vector<int>& q) { |
自己写这个插入排序时 在第二个for 循环处 又定义了一个 j, 于是 for循环结束后, nums[j + 1] = temp;这里就会报错, j没有初始化。
归并排序
1 | void merge_sort (vector<int>& q, int l, int r) { |
快速排序
1 | void quick_sort(vector<int>& nums, int l, int r) { //r是数组最后一个元素的下标 |
1 |
|
do while 语句
1 | do { |
快速排序找数组中第k小的数字
每轮排序都有一个pivot就位, 判断一下这个pivot的下标是否为 k - 1, 如果是那么它就是第k小的数字, 否则向左/右递归
1 |
|
堆排序
完全二叉树:
大根堆 : 根节点大于lc、 rc
小根堆 : 根节点小于lc、 rc
堆中节点编号是按顺序排的, 如图
如果当前节点 编号为 u, 那么它的左儿子编号为 2u, 又儿子编号为2u + 1
如果我们改变了根节点的值, 执行push_down 操作
如果我们改变了叶节点的值, 执行push_up 操作
如果 改变了 中间几点的值, 执行push_down 和 push_up操作 (二者只会执行一个)
堆 的下标从 1 开始
1 |
|
计数排序
1 | // 从1开始是因为 上面堆排序的时候 改了输入格式。。。 |
桶排序
实现方法太多了。。。
1 |
基数排序
1 |
|
下面是自己写了一遍排序
1 |
|
基于链表的排序
1 | // 基于链表的排序 |
剑指offer
不修改数组找出重复的数字
1 | class Solution { |
从尾到头打印链表
1 | /** |
这里没有用stack, 而是使用了反向迭代器
c.rbegin() 返回一个逆序迭代器,它指向最后一个元素。
c.rend() 返回一个逆序迭代器,它指向第一个元素的前面一个位置。
重建二叉树 VLR LVR
1 | /** |
二叉树的下一个节点
找个二叉树的图, 两种情况
- 我有右子树,那么我的后继就是我的右子树最左的哪个节点;
- 我没有右子树,如果我是我father的左儿子,那么我的后继就是我father的father
1 | /** |
矩阵中的路径
在深度优先搜索中,最重要的就是考虑好搜索顺序。
我们先枚举单词的起点,然后依次枚举单词的每个字母。
过程中需要将已经使用过的字母改成一个特殊字母,以避免重复使用字符。
1 | class Solution { |
旋转数组的最小数字
1 | class Solution { |
机器人的运动范围
1 | class Solution { |
倒数第k个节点
1 | /** |
之字形打印二叉树
1 | /** |
s.end());```之后 s 中的内容就反向了 1
2
3-```return vector<int> (s.rbegin(), s.rend()); ``` //带return的时候 不起变量名 直接构造。
这个新建的vector 内容就是 s的反向了。。。
或者``` vector<int> a (s.rbegin(), s.rend());
二叉搜索树的后序遍历序列
1 | class Solution { |
与重建二叉树那道题一个思路。 这个题相当于是根据LVR和LRV构建二叉树。
二叉树中和为某一值的路径
1 | /** |
递归调用的本质是一个压栈与出栈的过程
序列化二叉树
- to_string(…) 数值类型转string
- stoi \ stof \ stod string转数值类型
- val = val * 10 + data[i] - ‘0’
1 | /** |
数字排列 (可能包含重复数字)
1 | class Solution { |
二进制判断第i位是不是1

例如 把1011的第i位(最右边是第0位, >>1 相当于倒数第二位 挪到了个位上) 挪到 个位上, 然后把这个 &1
1 | int a = 11; // 二进制1011 , |
关于DFS与backtracing
- I would say, DFS is the special form of backtracking; backtracking is the general form of DFS.
If we extend DFS to general problems, we can call it backtracking. If we use backtracking to tree/graph related problems, we can call it DFS.
They carry the same idea in algorithmic aspect.
I think this answer to another related question offers more insights.
For me, the difference between backtracking and DFS is that backtracking handles an implicit tree and DFS deals with an explicit one. This seems trivial, but it means a lot. When the search space of a problem is visited by backtracking, the implicit tree gets traversed and pruned in the middle of it. Yet for DFS, the tree/graph it deals with is explicitly constructed and unacceptable cases have already been thrown, i.e. pruned, away before any search is done.
So, backtracking is DFS for implicit tree, while DFS is backtracking without pruning.
最小的k个数
1 | class Solution { |
数据流的中位数
1 | class Solution { |
连续子数组的最大和
1 | class Solution { |
把数组排成最小的数
sort()函数 里比较方式采用自己定义的比较函数
当sort函数在类内使用,并且定义comp函数也是类成员函数时,必须要在comp函数前加static,因为sort需要传入的参数是一个普通函数指针,而不是成员函数指针,所以需要在类成员定义前加static。https://blog.csdn.net/qq_34489443/article/details/86219772
1 | class Solution { |
礼物的最大价值
这里注意dp数组是从1开始的。这样就不用考虑边界条件了。
1 | class Solution { |
两个链表的第一个公共节点
1 | /** |
数字在排序数组中出现的次数
1 | class Solution { |
如图所示, 我们要找到的就是图中的两个红点, 用二分查找找到这两个点。
对于left这个点来说, 我们要找到该点左边具有 而 右边 不具有的性质, 即 该点左边的数 小于 k。
对于right这个点来说, 它的右边的数 大于k。
0到n-1中缺失的数字
1 | class Solution { |
数组中数值和下标相等的元素
1 | class Solution { |
同样是采用二分法, 这里 nums[i] - i 单调增, 可以使用二分法。
二叉搜索树的第k个结点
1 | /** |
二叉树的深度
1 | /** |
左子树的最大深度 与 右子树的最大深度 中的最大值 加上 1 就是 整棵树的最大深度了。
平衡二叉树
1 | /** |
这个题注意 必须 每一层的节点都平衡 才是 平衡二叉树。
数组中只出现一次的两个数字
1 | class Solution { |
异或
- 相同的数异或 为 0
- 不同的数异或为 1
1^1 = 0
1^0 = 1
解法步骤 :
- 把 所有数 异或, 得到 x^y;
- x^y的第k位是1
- 对于每个数a 看他的第k位 是否为 1, 把是0的归为一个集合, 是1 的归为一个集合
反转单词顺序
首先我们看看怎么反转整个字符串, 用两个指针就可以了。
1 | string s = "Harry potter!"; |
用操作分解的角度来做这题。
1.反转整个句子
2.反转每个单词
1 | class Solution { |
这里注意:
原型: void reverse(iterator first, iterator second);
功能: 颠倒区间[first, second)的次序
左旋转字符串
1 | class Solution { |
反转整个序列 gfedcba
分别反转前后两个序列 cdefgab
滑动窗口的最大值
1 | class Solution { |
滑动窗口用一个双端队列来做。
骰子的点数
dfs
dfs记住两点
- 状态的含义是什么
- 按照什么样的顺序递归,枚举所有方案
dfs(n, s) 表示一共投了n次, 总和是s时, 方案数是多少
按照最后一次的选择,不重不漏划分为6个集合(所谓热狗法)。 按最后一次骰子点数为1、2、3、4、5、6 六种情况划分…
1 | class Solution { |
dp
- 状态表示 f[i][j] 前i次 总和是 j 的情况下 的 方案数
- 计算 这里可以用热狗划分,按照最后一次的情况划分为6个不相交的集合
- 边界
1 | class Solution { |
实际上这是一个分组背包问题 (但是这个题每组物品都要选一个,不存在不选的情况)
n组 每组有物品 1、2、3、4、5、6 (每组物品只能选一个)
我们要凑出总和是 n-6n 所有的方案数
for 循环物品组
for 循环体积
for 循环决策
加和
扑克牌的顺子
这里使用的 vector
1 | class Solution { |
圆圈中剩下的数字
用list 模拟
1 |
|
##
1 | class Solution { |
把字符串转换成整数
1 | class Solution { |
树中两个结点的最低公共祖先
1 | /** |
笔试
Leetcode 768. 京东笔试
单调栈从底至顶是单调递增的,其保存的是到目前为止的遇到的最大值。当一个新元素到达的时候,如果比栈顶大,直接进栈;如果比栈顶小,那么保存一下栈顶curMax,再对栈进行出栈操作直至栈顶元素小于当前元素,最后再把curMax入栈。这样遍历一遍所有的数字之后,得到的栈中的元素个数就是我们要求的块的个数。
思路就是,导致块数变少的原因是来自后面出现了一个较小的元素。这个较小元素的存在,导致了我们必须把它划分到前面去,所以就一路打通到前面一个比它小的元素,这些被打通的元素属于同一个块。最后把curMax进栈,curMax的含义是我们前面一个块的最大值,也就是每个块排序后的最后一个元素。所以最后栈里多少个元素就是我们有多少个块,栈里的每个元素是每个块的结尾元素(最大值)。栗子如下:
比如数组为 [1 0 3 3 2],我们先把第一个数字1压入栈,此时栈为:
stack:1
然后遍历到第二个数字0,发现小于栈顶元素,将栈顶元素1取出存入curMax,此时栈为空了,不做任何操作,将curMax压回栈,此时栈为:
stack:1
然后遍历到第三个数字3,大于栈顶元素,压入栈,此时栈为:
stack:1,3
然后遍历到第四个数字3,等于栈顶元素,压入栈,此时栈为:
stack:1,3,3
然后遍历到第五个数字2,小于栈顶元素,将栈顶元素3取出存入curMax,此时新的栈顶元素3,大于当前数字2,移除此栈顶元素3,然后新的栈顶元素1,小于当前数字2,循环结束,将curMax压回栈,此时栈为:
stack:1,3
所以最终能拆为两个块儿,即stack中数字的个数。
1 |
|
网易互娱1
给定一个数x, 判断x在二进制下(去掉前导0)是否是回文数。
1 | int main() |
网易互娱2
1 | 作者:老司机李云龙 |