【2023/1/2】每日leetcode以及effective c++

文章探讨了背包问题中的状态转移与回溯策略,解释了01背包和完全背包的区别,以及它们的遍历顺序。通过LeetCode的两道题目展示了完全背包在组合计数问题中的应用。同时,文章提到了C++中的pass-by-reference-to-const优化和多态实现,强调了返回引用的潜在问题。
摘要由CSDN通过智能技术生成

前几天阳了,同时纠结了一下到底搞开发,算法还是读博,现在继续。

一、背包问题

1、为什么可以状态转移
因为每一步的决策数是有限的,回溯也类似。

2、为什么不使用回溯而是用状态转移
因为回溯相当于把所有可能性遍历了一遍,而状态转移一般要求给出某个最值,而求某个最值不需要遍历所有的情况。
使用背包解决问题的时候要确定好背包是什么,物体是什么。

3、什么是完全背包
01背包使用一维数组的时候,为了防止重复加物品,所以是从后往前遍历的,而这里是从前往后遍历的,这样就可以把前面的结果累加,达到物品重复放的效果。

4、为什么01背包要物品先遍历,而完全背包无所谓先遍历物品还是背包?
01背包中背包容量采取的是倒序遍历,而且01背包最需要的数据其实来自左上角(如果是二维数组)所以如果背包在外层遍历,物品在内层遍历,那么左上角的数据还没有算出来的时候,就已经计算下一个物品状态了,这是不合理的。
完全背包从前向后遍历,每次都可以及时更新左上角的数据,所以没有关系。
纯完全背包是无所谓遍历嵌套先后顺序的,但是如果要求组合数和排列数,就有所谓了。
如果求组合数就是外层for循环遍历物品,内层for遍历背包。
如果求排列数就是外层for遍历背包,内层for循环遍历物品。

5、初始化如何确定?
主要看可不可以累加出来。一般求组合数排列数,就是要初始化为1。

二、leetcode*2

由组合和排序引出完全背包的两道题:

518. 零钱兑换 II

难度:中等

给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。

示例 1:

输入: amount = 5, coins = [1, 2, 5]
输出: 4
解释: 有四种方式可以凑成总金额:

5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1
示例 2:

输入: amount = 3, coins = [2]
输出: 0
解释: 只用面额2的硬币不能凑成总金额3。
示例 3:

输入: amount = 10, coins = [10]
输出: 1
注意,你可以假设:

0 <= amount (总金额) <= 5000
1 <= coin (硬币面额) <= 5000
硬币种类不超过 500 种
结果符合 32 位符号整数

这道题就是完全背包的组合体,就直接正序去做,背包容量在外遍历:

class Solution {
public:
    int change(int amount, vector<int>& coins) {
        vector<int> dp(amount+1);
        dp[0] = 1;
        for(int i = 0; i < coins.size(); i++){
            for(int j = coins[i]; j <= amount; j++){
                dp[j] += dp[j - coins[i]];
            }
        }
        return dp[amount];
    }
};

377. 组合总和 Ⅳ

难度:中等

给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数。

示例:

nums = [1, 2, 3] target = 4

所有可能的组合为: (1, 1, 1, 1) (1, 1, 2) (1, 2, 1) (1, 3) (2, 1, 1) (2, 2) (3, 1)

请注意,顺序不同的序列被视作不同的组合。

因此输出为 7。

这道题就是排列题,遍历顺序就是先遍历物品,再遍历背包。

class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        vector<int> dp(target+1);
        dp[0] = 1;
        for(int j = 0; j <= target; j++)
            for(int i = 0; i < nums.size(); i++)
                if(j >= nums[i] && dp[j] < INT_MAX - dp[j - nums[i]])
                    dp[j] += dp[j - nums[i]];
        return dp[target];
    }
};

但是要注意的是,由于排列数是可能超过INT_MAX,由于保证了答案是不超过最大范围的,所以可以先做一个条件判断。

三、effective c++

条款20 :宁以pass-by-reference-to-const替换pass-by-value

就是正常函数调用的时候,如果传值,那么就会调用拷贝构造函数(先构造父类成员,然后构造父类,构造子类成员,最后构造子类)这是很耗时的,所以可以用常引用(其实就相当于const T* const ptr

bool validateStudent (const Student& s)

还有就是用virtual实现多态的时候,要使用指针和引用才能实现动态绑定,所以如果Windows类是父类,内部有一个虚函数display,子类为WindowWithScrollBars,如果直接使用传值,调用拷贝构造函数,是无法实现多态,实现的也只会是父类的函数:

void printNameAndDisplay (Window w)

如果使用指针或者引用的话,就可以实现多态:

void printNameAndDisplay (const Windows& w)

然后其实一些小的类型传值就可以了,因为传地址还要四个字节,有些内置类型比较大,如String,就可以考虑使用传引用。

条款21:必须返回对象时,别妄想返回其reference

就是说返回引用的话,会出现各种各样的问题。
以operator重载为例。
1、如果将临时对象赋给引用,那么对象离开作用域就被销毁了,指针指向栈内存也许一开始不会变化,但是随着代码的变化,栈就会变化,这是糟糕的。
2、如果new一个对象返回的话,没有合理的办法取得operator
返回的那个指针,特别当这种情况:

w = x * y * z

两个对象几乎没办法获取。
3、返回一个static对象,
一个问题就是多线程的话,C++0x以后是保证安全的,但是之前的话应该就要上锁了。
另外一个问题就是如果出现(a*b) == (c * d),翻译过来就是:

operator==(operator*(a, b), operator*(c, d))

那么恒等于真,因为内部两个乘运算的时候,static值其实改变了。
4、返回static array更难管理了/

所以干脆就别返回引用了哈,直接传值就完事了,交给编译器公司去优化吧。

之前条款10返回引用的时候,返回的*this对象,是本来就存在的对象,所以不用考虑对象是否临时的问题,而且operator=不涉及到运算,自然也没有static的问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值