动态规划 2024.5.17 四题

279.完全平方数

279.完全平方数

 

给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。

完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,149 和 16 都是完全平方数,而 3 和 11 不是。

class Solution {
public:
    int numSquares(int n) {
        vector<int> dp(n+1,INT_MAX);
        dp[0]=0;
        for(int i=1;i*i<=n;i++){
            for(int j=i*i;j<=n;j++){//完全背包
                dp[j]=min(dp[j-i*i]+1,dp[j]);
            }
        }
        return dp[n];

    }
};

1.简单的完全背包问题

1.数字可使用无数次 完全背包

2.求的是组合数 先遍历物品 在遍历背包

3.此时物品的重量为i*i

 for(int i=1;i*i<=n;i++){
            for(int j=i*i;j<=n;j++)

所以此时  遍历物品时 I*I<=n

                 遍历背包时 j=i*i

 

 

 

139 单词拆分(动态规划,哈希表)

139单词拆分

给你一个字符串 s 和一个字符串列表 wordDict 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s 则返回 true(由此可见dp数组应该为bool型

 

注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。

 

注意:本题求的是排列数  所以外部遍历背包,内部遍历物品

class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) {
        unordered_set<string> wordSet(wordDict.begin(), wordDict.end());//哈希表//更快的检索元素
        vector<bool> dp(s.size() + 1, false);
        dp[0] = true;
        for (int i = 1; i <= s.size(); i++) {   // 遍历背包
            for (int j = 0; j < i; j++) {       // 遍历物品
                string word = s.substr(j, i - j); //substr(起始位置,截取的个数)
                if (wordSet.find(word) != wordSet.end() && dp[j]) {
                    dp[i] = true;
                }
            }
        }
        return dp[s.size()];
    }
};

1.unordered_set关联容器

1.unordered_set 是一种关联容器,用于存储唯一的元素,并且可以快速检索元素。它内部使用哈希表实现,因此查找、插入和删除操作的平均时间复杂度是 O(1)

2.wordSet 包含了 wordDict 中的所有单词,但是因为没有重复的元素,所以 wordSet 可能会比 wordDict 少一些元素。

使用 unordered_set 而不是 vector 来存储字典的好处是,可以更快地检查一个单词是否在字典中,因为 unordered_set 提供了常数时间复杂度的查找操作。

在 wordBreak 函数中,使用 wordSet 来检查一个子串是否是字典中的一个单词,这样可以提高算法的效率。

 

 

2.for (int j = 0; j < i; j++) 

 

for (int j = 0; j < i; j++) 是一个循环语句,它用于遍历字符串 s 的前 i 个字符的所有可能的分割点。

这里的 i 是当前正在考虑的子字符串的结束位置(不包括 i 本身,因为字符串索引是从 0 开始的)。j 是分割点的位置,即子字符串的起始位置。

循环的目的是检查从索引 0 到 i-1 的每一个位置 j,将字符串 s 分割成两个部分:s[0...j-1] 和 s[j...i-1]。然后,代码会检查以下几个条件:

  1. s[j...i-1] 是否是字典 wordSet 中的一个单词。
  2. s[0...j-1] 是否可以由字典中的单词组成(即 dp[j] 是否为 true)。

如果这两个条件都满足,那么 s[0...i-1] 也可以由字典中的单词组成,因此将 dp[i] 设置为 true

这个循环是动态规划算法的一部分,它帮助构建一个布尔数组 dp,其中 dp[i] 表示字符串 s 的前 i 个字符是否可以由字典中的单词组成。通过遍历所有可能的分割点,算法可以找到所有可能的单词组合,从而解决问题。

 

 

3.对象.substr 

substr 是 C++ STL 中 string 类的一个成员函数,用于获取字符串的子串。这个函数有两个参数:

  1. 第一个参数是子串的起始位置,也就是从字符串的哪个位置开始截取。
  2. 第二个参数是子串的长度,也就是要截取多少个字符。

 string word = s.substr(j, i - j); s.substr(j, i - j) 就会从位置 j 开始,截取长度为 i - j 的子串。

 

 

4.对象.find(要查找的对象)

  1. wordSet.find(word) != wordSet.end():这个条件检查子串 word 是否在字典 wordSet 中。wordSet 是一个 unordered_set,它存储了所有可以用来分割字符串的单词。

  2. find 函数是 unordered_set 的一个成员函数,用于在集合中查找一个元素。如果找到了元素,find 函数返回指向该元素的迭代器;如果没有找到,返回 end() 方法返回的迭代器。因此,          //*如果 find(word) 不等于 end(),这意味着 word 在字典中。//

 

 

5.dp[j]的作用

 

  1. dp[j]这个条件检查 s 的前 j 个字符是否可以被分割成字典中的单词dp 是一个布尔类型的向量,其中 dp[j] 的值表示 s 的前 j 个字符是否可以被分割。如果 dp[j] 是 true,那么 s 的前 j 个字符可以被分割成字典中的单词。

 

 

6.unordered_set与unordered_map的区别

 

unordered_set 和 unordered_map 都是 C++ STL(标准模板库)中的关联容器,它们都基于哈希表实现,提供快速的查找、插入和删除操作。二者的主要区别在于它们存储的数据类型和用途。

  1. unordered_set:

    • unordered_set 是一个集合容器,用于存储唯一的元素
    • 它内部通过哈希表来存储元素,这意味着它可以在平均常数时间 O(1) 内进行元素的查找、插入和删除操作。
    • unordered_set 只存储键(key),不存储值(value),每个元素都是唯一的。
    • 它通常用于检查一个元素是否存在于集合中,或者用于去除重复元素
  2. unordered_map:

    • unordered_map 是一个键值对映射容器,用于存储键和值的组合
    • 它也通过哈希表来存储键值对,提供快速的查找、插入和删除操作。
    • unordered_map 存储的是键(key)和对应的值(value),键是唯一的,而值可以重复。
    • 它通常用于根据键快速查找对应的值,或者用于实现映射关系。

总结来说,unordered_set 用于存储无重复的元素集合,而 unordered_map 用于存储键值对的映射。选择使用哪个容器取决于你的具体需求:如果你只需要存储元素并快速判断元素是否存在,使用 unordered_set;如果你需要存储键值对并快速通过键来查找值,使用 unordered_map

 

 

198.打家劫舍(动态规划)

198.打家劫舍

***求最大值时dp数组初始化为INT_MIN***

***求最小值时dp数组初始化为INT_MAN***

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

class Solution {
public:
    int rob(vector<int>& nums) {
        if (nums.size() == 0) return 0;
        if (nums.size() == 1) return nums[0];
        vector<int> dp(nums.size()+1,INT_MIN);
        dp[0]=nums[0];
        dp[1] = max(nums[0], nums[1]);
        for(int i=2;i<nums.size();i++){
            dp[i]=max(dp[i-2]+nums[i],dp[i-1]);
        }
        return dp[nums.size()-1];

    }
};

1.dp[i]=max(dp[i-2]+nums[i],dp[i-1]);的意义

 

  1. dp[i-2] + nums[i]这意味着小偷偷窃了当前房屋 i,因此不能偷窃前一个房屋 i-1,但可以加上偷窃房屋 i-2 之前的最高金额。
  2. dp[i-1]这意味着小偷选择不偷窃当前房屋 i,因此他的总金额保持为偷窃房屋 i-1 时的最高金额。

在循环中,dp 数组被逐步填充,每个 dp[i] 都是基于之前的计算结果。最后,dp[nums.size()-1] 就是小偷能够偷窃到的最高金额,因为它表示到达最后一个房屋时能够偷窃到的最高金额。

 

***注意,代码中的 dp 数组的大小被设置为 nums.size()+1***,并且初始化为 INT_MIN

 

这是为了处理边界情况,

dp[0] 被设置为 nums[0],表示如果只有一个房屋,那么偷窃它的金额就是最高金额。

dp[1] 被设置为 max(nums[0], nums[1])表示如果只有两个房屋,小偷会选择金额较多的那个房屋进行偷窃。

 

213.打家劫舍2(动态规划)

213.打家劫舍2

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。

给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。

class Solution {
public:
    int rob(vector<int>& nums) {
        if (nums.size() == 0)
            return 0;
        if (nums.size() == 1)
            return nums[0];
        int result1 = robResult(nums, 0, nums.size() - 2);
        int result2 = robResult(nums, 1, nums.size() - 1);
        return max(result1, result2);
    }
    int robResult(vector<int>& nums, int begin, int end) {
        if (begin == end)
            return nums[begin];
        vector<int> dp(nums.size(), INT_MIN);
        dp[begin] = nums[begin];
        dp[begin + 1] = max(nums[begin], nums[begin + 1]);
        for (int i = begin+2; i <= end; i++) {
            dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
        }
        return dp[end];
    }
};

 

1.首尾房屋相邻时,分情况讨论

1.int result1 = robResult(nums, 0, nums.size() - 2);  情况二

1658862c4d7f480b838bbac7543e1adc.png

   int result2 = robResult(nums, 1, nums.size() - 1);    情况三

3588cfa704664e6daa481ecbfde6d973.png

对这两种情况进行分类讨论 取最大值(最合适的)

 

 

2.分类讨论的两种情况都继续使用打家劫舍的方法进行实现

 int robResult(vector<int>& nums, int begin, int end) {
        if (begin == end)
            return nums[begin];
        vector<int> dp(nums.size(), INT_MIN);
        dp[begin] = nums[begin];
        dp[begin + 1] = max(nums[begin], nums[begin + 1]);
        for (int i = begin+2; i <= end; i++) {
            dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
        }
        return dp[end];
    }
};

注意此时 for循环内 i开始的条件变成了begin+2,   限定条件变成了i<=end

 

 

  • 11
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值