代码随想录算法训练营第46期Day37,38,39,41

这几天晚上看比赛,就把刷题耽误了。还好是开新章节,前面的题都比较简单。

然后周天做完了又忘记发了

动态规划

  1. 确定dp数组(dp table)以及下标的含义
  2. 确定递推公式
  3. dp数组如何初始化
  4. 确定遍历顺序
  5. 举例推导dp数

 Day37前两道题太简单了,我们从第三道题开始

leetcode.746.使用最小花费爬楼梯

class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        vector<int> dp(cost.size()+8);//dp数组存储当前点位的最优解
        dp[0]=0;
        dp[1]=0;
        for(int i=2;i<=cost.size();i++){
            dp[i]=min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);
            //这是dp公式,到达第i阶的最优解肯定是i-1阶的最优解+从i-1的花费和i-2阶的最有解+从i-2阶段的花费中小的那一个
        }
        return dp[cost.size()];
    }

};

leetcode.62.不同路径

class Solution {
public:
    int uniquePaths(int m, int n) {
        int dp[105][105];
        dp[0][0]=0;
        for(int i=1;i<=m;i++){
            dp[i][1]=1;
        }
        for(int i=1;i<=n;i++){
            dp[1][i]=1;
        }
        for(int i=2;i<=m;i++){
            for(int j=2;j<=n;j++){
                dp[i][j]=dp[i][j-1]+dp[i-1][j];
            }
        }
        return dp[m][n];
    }
};

leetcode.63.不同路径Ⅱ

class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
int m = obstacleGrid.size();
 int n = obstacleGrid[0].size();
        vector<vector<int>> dp(m, vector<int>(n, 0));//注意最好用vector容器。都初始化为0
     /*if(obstacleGrid[0][0]==1||obstacleGrid[m-1][n-1]==1){//如果开始或终点有障碍那直接返回障碍
        return 0;
     }*/
        for(int i=0;i<m;i++){
             if(obstacleGrid[i][0]==1){//如果在边界的两条线上有障碍,那从障碍开始一下路径数都为0
               break;
            }
            dp[i][0]=1;
           
        }
        for(int i=0;i<n;i++){ 
            if(obstacleGrid[0][i]==1){
               break;
            }
            dp[0][i]=1;
            
        }


        for(int i=1;i<m;i++){
            for(int j=1;j<n;j++){
            if(obstacleGrid[i][j]==0)dp[i][j]=dp[i][j-1]+dp[i-1][j];//如果碰不到障碍就进行dp,保证障碍处路径为0
            }
        }
        return dp[m-1][n-1];
    }
};

leetcode.416.分割等和子集

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        int sum=0;
        for(int i=0;i<nums.size();i++){
            sum+=nums[i];
        }
        vector<int> dp(11111,0);//这里数组尽量开大点,不然过不了
         if (sum % 2 == 1) return false;
         sum= sum / 2;//能够分成两个子集的元素和相等说明,有一部分元素相加的和是总和的一半
         //那么元素个数是种类,sum/2是容量的01背包问题就出现了
         for(int i=0;i<nums.size();i++){
            for(int j=sum;j>=nums[i];j--){
                dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
            }
         }
         if(dp[sum]==sum){
            return true;
         }
         return false;
    }
};

leetcode.1049.最后一块的石头重量

class Solution {
public:
//和分割等和子列类似
    int lastStoneWeightII(vector<int>& stones) {
        vector<int> dp(10010,0);
        int sum=0;
        for(int i=0;i<stones.size();i++){
            sum+=stones[i];
        }
        int sum1=sum;
        sum=sum/2;
        for(int i=0;i<stones.size();i++){
            for(int j=sum;j>=stones[i];j--){
                dp[j]=max(dp[j],dp[j-stones[i]]+stones[i]);
            }
        }
        return sum1-2*dp[sum];




    }
};

leetcode.494.目标和

假设加法的总和为x,那么减法对应的总和就是sum - x。

所以我们要求的是 x - (sum - x) = target

x = (target + sum) / 2

此时问题就转化为,用nums装满容量为x的背包,有几种方法

  • 不放物品i:即背包容量为j,里面不放物品i,装满有dp[i - 1][j]中方法。

  • 放物品i: 即:先空出物品i的容量,背包容量为(j - 物品i容量),放满背包有 dp[i - 1][j - 物品i容量] 种方法。

  • if (nums[i] > j) dp[i][j] = dp[i - 1][j]; //说明背包容量装不下 物品i,所以此时装满背包的方法值 等于 不放物品i的装满背包的方法,

  1. 举例推导dp数组

输入:nums: [1, 1, 1, 1, 1], target: 3

bagSize = (target + sum) / 2 = (3 + 5) / 2 = 4

dp数组状态变化如下

那么最上行dp[0][j] 如何初始化呢?

dp[0][j]:只放物品0, 把容量为j的背包填满有几种方法。

只有背包容量为 物品0 的容量的时候,方法为1,正好装满。

其他情况下,要不是装不满,要不是装不下。

所以初始化:dp[0][nums[0]] = 1 ,其他均为0 。

表格最左列也要初始化,dp[i][0] : 背包容量为0, 放物品0 到 物品i,装满有几种方法。

都是有一种方法,就是放0件物品。

即 dp[i][0] = 1

如果有两个物品,物品0为0, 物品1为0,装满背包容量为0的方法有几种。

  • 放0件物品
  • 放物品0
  • 放物品1
  • 放物品0 和 物品1

此时是有4种方法。

其实就是算数组里有t个0,然后按照组合数量求,即 2^t 

遍历顺序

在明确递推方向时,我们知道 当前值 是由上方和左上方推出。

那么我们的遍历顺序一定是 从上到下,从左到右。

因为只有这样,我们才能基于之前的数值做推导。

 for (int i = 0; i < nums.size(); i++) {
            if (nums[i] == 0) numZero++;
            dp[i][0] = (int) pow(2.0, numZero);
        }
class Solution {
public:
int count=0;
   
    int findTargetSumWays(vector<int>& nums, int target) {
     int sum=0;
     for(int i=0;i<nums.size();i++){
        sum+=nums[i];  
             }
    if (abs(target) > sum) return 0; 
    
     sum+=target;
     if(sum%2==1){
        return 0;
     }       
     sum=sum/2;
     int bagSize=sum;
   vector<vector<int>> dp(nums.size(), vector<int>(10010, 0));
        
        // 初始化最上行
        if (nums[0] <= bagSize) dp[0][nums[0]] = 1; 

        // 初始化最左列,最左列其他数值在递推公式中就完成了赋值
        dp[0][0] = 1; 

        int numZero = 0;
        for (int i = 0; i < nums.size(); i++) {
            if (nums[i] == 0) numZero++;
            dp[i][0] = (int) pow(2.0, numZero);
        }

        // 以下遍历顺序行列可以颠倒
        for (int i = 1; i < nums.size(); i++) { // 行,遍历物品
            for (int j = 0; j <= bagSize; j++) { // 列,遍历背包
                if (nums[i] > j) dp[i][j] = dp[i - 1][j]; 
                else dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i]];
            }
        }
        return dp[nums.size() - 1][bagSize];
    }
};

一维数组版

二维DP数组递推公式: dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i]];

去掉维度i 之后,递推公式:dp[j] = dp[j] + dp[j - nums[i]] ,即:dp[j] += dp[j - nums[i]]

遍历物品放在外循环,遍历背包在内循环,且内循环倒序(为了保证物品只使用一次)。

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int sum = 0;
        for (int i = 0; i < nums.size(); i++) sum += nums[i];
        if (abs(target) > sum) return 0; // 此时没有方案
        if ((target + sum) % 2 == 1) return 0; // 此时没有方案
        int bagSize = (target + sum) / 2;
        vector<int> dp(bagSize + 1, 0);
        dp[0] = 1;
        for (int i = 0; i < nums.size(); i++) {
            for (int j = bagSize; j >= nums[i]; j--) {
                dp[j] += dp[j - nums[i]];
            }
        }
        return dp[bagSize];
    }
};

深搜遍历做法

class Solution {
public:
int count=0;
    void backtrack(vector<int>& nums,int target,int index,int sum){
        if(index==nums.size()){
            if(sum==target){
                count++;
            }
        }else{
            backtrack(nums,target,index+1,sum+nums[index]);
            backtrack(nums,target,index+1,sum-nums[index]);
        }
        
    }
    int findTargetSumWays(vector<int>& nums, int target) {
      backtrack(nums,target,0,0);
      return count;  
    }
};

leetcode.474.一零和..

本题中strs 数组里的元素就是物品,每个物品都是一个!

而m 和 n相当于是一个背包,两个维度的背包

理解成多重背包的同学主要是把m和n混淆为物品了,感觉这是不同数量的物品,所以以为是多重背包。

但本题其实是01背包问题!

只不过这个背包有两个维度,一个是m 一个是n,而不同长度的字符串就是不同大小的待装物品。

开始动规五部曲:

  1. 确定dp数组(dp table)以及下标的含义

dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j]

  1. 确定递推公式

dp[i][j] 可以由前一个strs里的字符串推导出来,strs里的字符串有zeroNum个0,oneNum个1。

dp[i][j] 就可以是 dp[i - zeroNum][j - oneNum] + 1。

然后我们在遍历的过程中,取dp[i][j]的最大值。

所以递推公式:dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);

此时大家可以回想一下01背包的递推公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

对比一下就会发现,字符串的zeroNum和oneNum相当于物品的重量(weight[i]),字符串本身的个数相当于物品的价值(value[i])。

这就是一个典型的01背包! 只不过物品的重量有了两个维度而已。

  1. dp数组如何初始化

01背包的dp数组初始化为0就可以。

因为物品价值不会是负数,初始为0,保证递推的时候dp[i][j]不会被初始值覆盖。

  1. 确定遍历顺序

01背包一定是外层for循环遍历物品,内层for循环遍历背包容量

那么本题也是,物品就是strs里的字符串,背包容量就是题目描述中的m和n。(相当于之前的一维dp,采用了滚动数组)

倒序遍历是为了保证物品i只被放入一次!。但如果一旦正序遍历了,那么物品0就会被重复加入多次!

那么问题又来了,为什么二维dp数组遍历的时候不用倒序呢?

因为对于二维dp,dp[i][j]都是通过上一层即dp[i - 1][j]计算而来,本层的dp[i][j]并不会被覆盖!

class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        vector<vector<int>> dp(m + 1, vector<int> (n + 1, 0)); // 默认初始化0
        for (string str : strs) { // 遍历物品
            int oneNum = 0, zeroNum = 0;
            for (char c : str) {
                if (c == '0') zeroNum++;
                else oneNum++;
            }
            for (int i = m; i >= zeroNum; i--) { // 遍历背包容量且从后向前遍历!
                for (int j = n; j >= oneNum; j--) {
                    dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
                }
            }
        }
        return dp[m][n];
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/898677.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

DBeaver查看已保存连接的密码

打开Dbeaver窗口菜单-首选项-工作空间&#xff0c;找到工作空间路径 在文件管理器中打开工作空间路径\General.dbeaver&#xff0c;找到credentials-config.json。 在Linux下&#xff0c;使用如下命令对credentials-config.json文件进行解密 openssl aes-128-cbc -d -K babb4…

简历怎么写?怎么准备面试?怎么让面试官感兴趣?

视频地址&#xff1a;如何写好简历打开找工作的第一道门_哔哩哔哩_bilibili项目介绍不过关&#xff0c;项目责任不清楚&#xff0c;项目技术方案有漏洞&#xff0c;项目优势不明显&#xff0c;八股文没有准备好。都是面试大忌讳。, 视频播放量 1、弹幕量 0、点赞数 0、投硬币枚…

AI 搜索战火重燃:Perplexity 企业版 VS Glean AI | LeetTalk Daily

“LeetTalk Daily”&#xff0c;每日科技前沿&#xff0c;由LeetTools AI精心筛选&#xff0c;为您带来最新鲜、最具洞察力的科技新闻。 在当今快速发展的人工智能&#xff08;AI&#xff09;领域&#xff0c;企业面临着日益增长的数据碎片化挑战。为了提高生产力和决策效率&am…

【element-tiptap】如何把分隔线改造成下拉框的形式?

当前的分隔线只有细横线这一种形式 但是咱们可以看一下wps中的分隔线&#xff0c;花里胡哨的 这些在wps里都需要使用快捷键打出来&#xff0c;真没找到菜单在哪里 那么这篇文章咱们就来看一下如何改造分隔线组件&#xff0c;改造成下拉框的形式&#xff0c;并且把咱们想要的分…

数据结构-八大排序之归并排序

归并排序 一、概念 归并排序&#xff08;Merge sort&#xff09;是建立在归并操作上的一种有效、稳定的排序算法&#xff0c;该算法是采用分治法(Divide and Conquer&#xff09;的一个非常典型的应用。将已有序的子序列合并&#xff0c;得到完全有序的序列&#xff1b;即先使…

友思特技术 | 视觉阶梯发展:传感器材料对短波红外成像技术的影响

导读 短波红外成像技术的发展受到了传感器材料种类的限制与推动&#xff0c;从硅基到铟镓砷&#xff0c;从量子点到锗基&#xff0c;丰富的材料影响着短波红外相机的分辨率、质量、成本等性能特征。 短波红外成像与传感器 短波红外光通常定义在 900 - 1700nm&#xff0c;相比…

使用 Python 处理 CSV 文件

文章目录 常见问题及解决方案使用 Python 处理 CSV 文件&#xff1a;全面指南CSV 文件的基本概念使用内置 csv 模块使用 pandas 库处理缺失值使用 DictReader 和 DictWriter案例分析最佳实践参考资源性能比较结论 常见问题及解决方案 问题&#xff1a;文件编码错误 解决方案&am…

大厂为什么要禁止使用数据库自增主键

大表为何不能用自增主键&#xff1f; 数据库自增主键&#xff0c;以mysql为例&#xff0c;设置表的ID列为自动递增&#xff0c;便可以在插入数据时&#xff0c;ID字段值自动从1开始自动增长&#xff0c;不需要人为干预。 在小公司&#xff0c;或者自己做项目时&#xff0c;设置…

Ollama 离线安装

1. 查看服务器CPU的型号 ## 查看Linux系统CPU型号命令&#xff0c;我的服务器cpu型号是x86_64 lscpu 2. 根据CPU型号下载Ollama安装包&#xff0c;并保存到/home/Ollama目录 我下载的是Ollama的v0.1.31版本&#xff0c;后面均以此版本为例说明 下载地址 https://github.…

拴柱说Mac之Mac的高效使用技巧第三期

Mac的设计有着非常多的使用技巧&#xff0c;这些技巧能够极大的提高你的使用效率&#xff0c;但是还是有许多人并不知道&#xff0c;那么今天Mac高效使用技巧分享第三期来了 Mac有一个独特的设置&#xff0c;那就触发角&#xff0c;触发角有着非常多的妙用 在 “系统偏好设置…

为什么计算机科学存在图灵机和Lambda演算两种世界观,而量子力学却存在三种世界图景?

计算机科学存在两种基本的世界观&#xff1a;图灵机和Lambda演算&#xff0c;它们指出了到达图灵完备的两条技术路线。但是量子力学中却存在着三种世界图景&#xff1a;薛定谔图景&#xff0c;海森堡图景和狄拉克图景。为什么计算机科学有两种基本世界观&#xff0c;但是量子力…

【Python数据可视化】利用Matplotlib绘制美丽图表!

【Python数据可视化】利用Matplotlib绘制美丽图表&#xff01; 数据可视化是数据分析过程中的重要步骤&#xff0c;它能直观地展示数据的趋势、分布和相关性&#xff0c;帮助我们做出明智的决策。在 Python 中&#xff0c;Matplotlib 是最常用的可视化库之一&#xff0c;它功能…

Netty-TCP服务端粘包、拆包问题(两种格式)

前言 最近公司搞了个小业务&#xff0c;需要使用TCP协议&#xff0c;我这边负责服务端。客户端是某个设备&#xff0c;客户端传参格式、包头包尾等都是固定的&#xff0c;不可改变&#xff0c;而且还有个蓝牙传感器&#xff0c;透传数据到这个设备&#xff0c;然后通过这个设备…

使用ORDER BY排序

在一个不明确的查询结果中排序返回的行。ORDER BY子句用于排序。如果使用了ORDER BY子句&#xff0c;它必须位于SQL语句的最后。 SELECT语句的执行顺序如下&#xff1a; 1.FROM子句 2.WHERE子句 3.SELECT子句 4.ORDER BY子句 示例一&#xff1a;查询employees表中的所有雇…

通俗易懂的入门 Axure RP文章 ,速学

目录 1. Axure RP简介&#xff1f; 2. Axure RP基本操作 &#xff08;1&#xff09;入门理解 &#xff08;2&#xff09;插入形状 &#xff08;3&#xff09;位置对齐、 &#xff08;4&#xff09;资源库 3. Axure RP基本交互 &#xff08;1&#xff09;切换不同的页面 …

进程间通信大总结Linux

目录 进程间通信介绍 进程间通信目的 进程间通信发展 进程间通信分类 管道 System V IPC POSIX IPC 管道 什么是管道 匿名管道 用fork来共享管道原理 站在文件描述符角度-深度理解管道 管道读写规则 管道特点 命名管道 创建一个命名管道 匿名管道与命名管道的区…

《云原生安全攻防》-- K8s攻击案例:权限维持的攻击手法

在本节课程中&#xff0c;我们将一起深入了解K8s权限维持的攻击手法&#xff0c;通过研究这些攻击手法的技术细节&#xff0c;来更好地认识K8s权限维持所带来的安全风险。 在这个课程中&#xff0c;我们将学习以下内容&#xff1a; K8s权限维持&#xff1a;简单介绍K8s权限维持…

UG2312软件安装教程+Siemens NX三维建模中文安装包下载

一、软件下载 【软件名称】&#xff1a;UG 2312 【支持系统】&#xff1a;win10/win11 【百度网盘】&#xff1a; https://pan.baidu.com/s/1oF-X29m1f5pDhElwi0rK8A?pwd70zi 二、UG NX软件 UG&#xff08;Unigraphics NX&#xff09;是一款集 CAD、CAM、CAE 于一体的高效…

大范围实景三维智能调色 | 模方自动化匀色解决方案

《实景三维中国建设总体实施方案&#xff08;2023—2025年&#xff09;》、《实景三维中国建设技术大纲》等相关文件中指出&#xff0c;倾斜Mesh三维模型修饰要求模型整体色彩真实&#xff0c;无明显色差。9月&#xff0c;自然资源部在国务院新闻发布会上表示&#xff0c;实景三…

Linux:线程及其控制

我们已经学了线程的创建&#xff0c;现在要学习线程的控制 线程等待 我们来先写一个没有线程等待的代码&#xff1a; pthcon.c: #include<stdio.h> #include<pthread.h> void* gopthread(void* arg){while(1){printf("pthread is running\n");sleep(1…