1. 完全背包问题
和01背包唯一的区别是,每件物品都有无限个(也就是可以放入背包多次)
代码和01唯一的区别在于j的循环是从小到大,不是从大到小。ij谁在外谁在内层区别不大。
#include <bits/stdc++.h>
using namespace std;
int main(){
int N,V;
cin>>N>>V;
vector<int> values(N), weights(N);
for(int i =0;i<N;i++){
cin>>weights[i]>>values[i];
}
vector<int> dp(V+1);
for(int j=0;j<=V;j++ ){
for(int i =0;i<N;i++)
{
if(j-weights[i]>=0)
dp[j] = max(dp[j], dp[j-weights[i]]+values[i]);
}
}
cout<<dp[V]<<endl;
return 0;
}
2. 零钱兑换二
int change(int amount, vector<int>& coins) {
vector<int> dp(amount+1,0);
dp[0] = 1; // 当总金额为0时,有一种组合方法就是0
int n = coins.size();
for(int i=0;i<n;i++){
for(int j =coins[i];j<=amount;j++){
dp[j] += dp[j-coins[i]];
}
}
for(int i = 0;i<amount+1;i++){
cout<<dp[i]<<endl;
}
return dp[amount];
}
};
组合数:要进行初始化,并且知道每一种组合等于把所有硬币遍历后的上一种组合加起来。(不用再加一)
时间复杂度:amount*coins的数量
j从coins[i]开始!其实从0开始也行,但是就要加个判断j大于coins[i]才能做呀,不然就是负数了
注意i,j的遍历顺序与求排列数还是组合数有关:
如果求组合数就是外层for循环遍历物品,内层for遍历背包。
如果求排列数就是外层for遍历背包,内层for循环遍历物品。
3. 零钱兑换
与上一题的区别在于dp不是表示组合数个数,而是表示凑足总额为j所需钱币的最少个数为
就是从+= 变成了min?
这里就要+1了,因为每次遍历多加了一个硬币,值就要+1
int coinChange(vector<int>& coins, int amount) {
vector<int> dp(amount+1,INT_MAX);
dp[0] = 0; // 当总金额为0时,有一种组合方法就是0
int n = coins.size();
for(int i=0;i<n;i++){
for(int j =coins[i];j<=amount;j++){
if(dp[j-coins[i]] != INT_MAX)
dp[j] =min(dp[j], dp[j-coins[i]]+1);
}
}
if(dp[amount] == INT_MAX) return -1;
return dp[amount];
}
4. TOPK数的快排做法
注意这个topk指的是从大到小排序后的第k个!所以实际上并不是真正的想象中从小到大排序后的第k个!可以直接转换成第n-k+1大的数就行
int quick_sort(vector<int>& nums,int left, int right,int k){
if(left >= right) return nums[left];
int i = left-1, j = right+1;
int mid = (left+right)/2;
int x = nums[mid];
while(i <j){
do i++; while(nums[i] < x);
do j--; while(nums[j] > x);
if(i<j) swap(nums[i],nums[j]);
}
if(j-left+1 >= k) return quick_sort(nums,left,j,k);
else return quick_sort(nums,j+1,right,k-(j-left+1));
}
int findKthLargest(vector<int>& nums, int k) {
int res = quick_sort(nums,0,nums.size()-1, nums.size()-k+1);
return res;
}
代码和快排差不多,但是要想明白,每次是找哪边继续索引。如果排完序后的长度比k大,说明k在左边,就往左边回溯,如果比k小,就回溯右边,注意回溯右边的时候就不是找第k大了,而是k减去左边区间的长度
为什么时间复杂度是On啊??
还是没懂,反正就是n,快速排序的复杂度是nlogn
4. SVM的核函数和高阶是具体如何实现的?
其中,线性核适合线性可分的数据,多项式核适合非线性模型,高斯核适合那种不清楚数据分布的非线性情况。
映射到高维空间不是说把每个样本做映射,而是在计算距离的时候,用高斯核计算两个x之间的距离。
5. GBDT树
梯度提升决策树。
树的流程从 决策树-》随机森林-》梯度提升决策树->Xgboost, 随机森林是bagging,GBDT和xgb都是boosting。
梯度提升是一种集成学习的一种方法,决策树是基学习器。为啥要这两个结合呢,是因为决策树是if,else的,速度快,适合非线性关系,不需要对输入数据做标准化,特征工程少,也可以处理特征缺失的情况。决策树能自动组合多个特征。
但是,决策树的缺点就是过拟合。抑制单颗树的复杂程度,再通过梯度提升方法继承多个决策树,就能达到平衡。
抑制复杂度的方法有:(1)树的最大深度(2)限制叶子结点的最少样本数量,有的特征缺失太多了就不放了(3)限制节点分裂时的最少样本数量(4)对训练样本采样,单个决策树学习时只使用一部分训练样本,这是bagging的思想(5)随机森林的思想,学习单颗决策树时只使用一部分特征(6)在目标函数中添加正则项惩罚
使用最小化损失函数(如均方误差或对数损失)来优化模型性能。每个弱学习器的训练都是在前一轮模型的残差(即预测值与真实值之间的差)基础上进行的。
具体优化方式: 前向分布算法
GBDT 算法可以看成是由 K 棵树组成的加法模型:
使用前向分布算法,每一步只学习一个基学习器,逐步逼近优化目标函数。
在第t步的目标函数为:
注意!每一颗树只拟合上一棵树的结果与目标之间的残差,不是去拟合目标
6. 数组中有个数的数量超过一半,如何最快找出这个数?
这是一种抵消的思路,就是如果两个数不同就会抵消为0,如果这个数超过了一半,说明它与其他数不同的时候被抵消完了以后,数量还是会>0,这样On就找出来了
或者是排序求中间值,这么看似乎是可以用快速选择的?只要找到一个数,刚好在中间位置,说明他就是众数。
还有一种方法,就是利用哈希map。
7. 每日温度
好难好难好难。。实际上是一种栈的方法,如果这个数比上一个数小就压栈,大就可以取出栈中的元素,并计算坐标之差
只要大,就把小的都取出来。我这里用了两个栈,分别存索引和值,实际上只存索引就行,值从数组里取。
vector<int> dailyTemperatures(vector<int>& temperatures) {
stack<int> idxs, values;
int cur=0;
vector<int> res(temperatures.size(),0);
for(int i = 0;i<temperatures.size();i++){
while (!idxs.empty() && values.top() < temperatures[i]){
res[idxs.top()] = i-idxs.top();
values.pop();
idxs.pop();
}
idxs.push(i);
values.push(temperatures[i]);
}
return res;
}
8. 二叉树展开为链表
就是判断一下,没啥好说的
TreeNode * findr(TreeNode * cur){
while(cur->right != nullptr){
cur = cur->right;
}
return cur;
}
void flatten(TreeNode* root) {
TreeNode * cur = root;
while(cur!= nullptr){
if(cur->left != nullptr){
TreeNode * r = cur->right;
cur->right = cur->left;
TreeNode * nextr = findr(cur);
nextr->right = r;
cur->left = nullptr;
}
cur = cur->right;
}
return ;
}