每日两题
每天坚持,加油!责任感来自于对自我规则以及高要求!
1、贪心算法:我要监控二叉树!
给定一个二叉树,我们在树的节点上安装摄像头。
节点上的每个摄影头都可以监视其父对象、自身及其直接子对象。
计算监控树的所有节点所需的最小摄像头数量。
示例 1:
输入:[0,0,null,0,0]
输出:1
解释:如图所示,一台摄像头足以监控所有节点。
这道题在leetcode里面是hard级别的题目。是贪心算法的题目,也就是局部最优等于全局最优。
要局部最优,那么叶子节点不能是摄像头,因为叶子节点的父节点是摄像头就可以了,这样一定可以减少摄像头的个数,而且保证叶子节点也可以被覆盖到,所以一定从叶子节点开始遍历。所以本题可以选择后序遍历。
结点可以分为未覆盖结点,已覆盖结点以及摄像头结点。
其次就是对于空结点的处理,因为空结点不需要被覆盖,也不起摄像头作用,所以设为已覆盖结点即可。
然后就是遍历的返回。如果两个子节点都是已覆盖结点,那么其父节点为未覆盖结点就可以了,交给父节点上面的结点负责。如果父节点为根节点,那么只能设为摄像头了。
如果两个结点有任何一个是未覆盖结点,那么其父节点一定是摄像头结点。
排除掉前面两种情况,如果两个结点有任何一个是摄像头结点,那么父节点就是已覆盖结点。
按照这个逻辑遍历一遍,就可以求得最少摄像头数量,代码如下:
class Solution {
private:
int result = 0;
// 0 未覆盖 1 有摄像头 2 已覆盖
int traversal(TreeNode *cur){
if(cur == nullptr) return 2;
// 后序遍历
int left = traversal(cur->left);
int right = traversal(cur->right);
// 分三种情况
if(left == 2 && right == 2) return 0;
if(left == 0 || right == 0) {
result++;
return 1;
}
if(left == 1 || right == 1) return 2;
return -1;
}
public:
int minCameraCover(TreeNode* root) {
if(traversal(root) == 0) result++; //一种特殊情况.头结点的子节点都已被覆盖
return result;
} };
2、斐波那契数
动态规划五大步:
1、给出dp[i]以及i的定义。
2、给出状态转移公式。
3、给出初始值。
4、给出遍历顺序,从前到后还是从后到前。
5、自己推一遍看行不行。
这道题简单直接给代码:
class Solution {
public:
int fib(int n) {
if (n <= 1) return n;
int dp[2] = {0, 1};
for(int i = 0; i < (n-1); i++){
int tmp = dp[1];
dp[1] = dp[0] + dp[1];
dp[0] = tmp;
}
return dp[1];
}
};
每日effective c++
14 在管理资源类中小心copy行为
当使用RAII的时候,如果管理的对象不是单纯地new和delete堆内存,那么就要考虑自己想实现的功能。
如果是考虑禁止复制,可以考虑uncopyable类或者使用unique_ptr(使用unique_ptr还可以实现转移底部资源的拥有权),如果考虑引用计数法,可以考虑使用shared_ptr。unique_ptr和shared_ptr都可以传递独特的删除器,只要传函数指针进去即可。
比如想管理互斥锁的上锁和解锁,就可以,:
class Lock {
public:
explict Lock(Mutex* pm) : mutexPtr(pm, unlock){
lock(mutexPtr.get());
}
private:
shared_ptr<Mutex> mutexPtr;
};
这样就可以随便复制,不用担心析构问题。
这里解释一下为什么要用explict,考虑如下代码:
class Test1
{
public:
Test1(int n) { num = n; }; //普通构造函数
private:
int num;
};
class Test2
{
public:
explicit Test2(int n) { num = n; } //explicit(显示)构造函数
private:
int num;
};
int main()
{
Test1 t1 = 12; //隐式调用其构造函数,成功
Test2 t2 = 12; //编译错误,不能隐式调用其构造函数
Test2 t3(12); //显示调用成功
return 0;
}
转载自https://blog.csdn.net/qq_45662588/article/details/121328778
那么当Test2 t2 = 12;的时候,其实编译器是希望可以将12这个int类型转化成Test2类型的,然后通过拷贝赋值的方式赋给t2。但是如果我们不想这样,就要使用explict。当构造函数为单参数的时候就要额外注意了!!其实在函数调用的时候,就经常会发生拷贝赋值的情况,所以要额外注意了哈!比如如下:
class Sales_item
{
public:
std::istream& input(std::istream& in);
std::ostream& output(std::ostream& out);
inline double avg_price() const;
bool same_isbn(const Sales_item &rbs) const
{
return isbn == rbs.isbn;
}
Sales_item add(Sales_item& other);
Sales_item(const std::string &book = "7115145547"):isbn(book),units_sold(0),revenue(0.0){}
private:
std::string isbn;
unsigned units_sold;
double revenue;
};
转载自https://www.cnblogs.com/hong2016/p/6730613.html
就容易出现可以直接把string给add函数使用的情况,
Sales_item trans1;
string null_book = "9-999-99999-9";
trans1.same_isbn(null_book);
而这不是我们想要的结果。
15 在资源管理类中提供对原始资源的访问
要访问智能指针的原始资源,可以使用.get(),*ptr,ptr->mem的形式,一般的RAII资源也应该提供相应的函数。
当RAII资源提供相应函数的时候,就要思考,如何提供,如果使用显式的转换,用户调用起来会麻烦,但是隐式转换又会不安全,所以还是推荐按显式转换。
显式转换就是直接在RAII类中(比如f)定义一个函数(比如get()),return资源就可以了,就是用户使用起来会麻烦(要f.get()),觉得为啥我不直接调你这个资源,于是直接避开RAII类,就会出现问题(一般也不会怎么傻哈)。
隐式转换就是直接用operator 资源名() const返回资源,那么你就直接拿f使用就完事了,但问题就是,你可以直接把底层资源拷贝赋值给别人,即使你不想这样,如果底层资源不小心因为这种原因被删了,或者原RAII类对象被删了,那么就会造成资源管理失败问题。