动态规划之——线性DP(入门篇)

news2025/10/31 3:23:28

文章目录

  • 概述
  • 例题(leetcode)
    • 70.爬楼梯
      • 点击这里
      • 思路
      • code1
      • code2
    • 进阶1 377. 组合总和 Ⅳ
      • 点击这里
      • 思路
      • code
    • 进阶2 2266. 统计打字方案数
      • 点击这里
      • 思路
      • code
    • 198. 打家劫舍
      • 点击这里
      • 思路
      • code1
      • code2
    • 进阶1 2320. 统计放置房子的方式数
      • 点击这里
      • 思路
      • code
    • 进阶2 3186. 施咒的最大总伤害
      • 点击这里
      • 思路
      • code
    • 53. 最大子数组和
      • 点击这里
      • 思路
      • code1
      • code2
    • 进阶1 1749. 任意子数组和的绝对值的最大值
      • 点击这里
      • 思路
      • code
    • 进阶2 1191. K 次串联后最大子数组之和
      • 点击这里
      • 思路
      • code
    • 进阶3 918. 环形子数组的最大和
      • 点击这里
      • 思路
      • code

概述

线性动态规划,是较常见的一类动态规划问题,其是在线性结构上进行状态转移,这类问题不像背包问题、区间DP等有固定的模板。
线性动态规划的目标函数为特定变量的线性函数,约束是这些变量的线性不等式或等式,目的是求目标函数的最大值或最小值。

例题(leetcode)

70.爬楼梯

点击这里

思路

线性dp入门题目, 假设当前状态为 x ,那么 x 的阶层数可以为 f ( x − 1 ) ,也可以为 f ( x − 2 ) , 那么 f ( x ) = f ( x − 1 ) + f ( x − 2 ) 假设当前状态为x,那么x的阶层数可以为f(x-1),也可以为f(x-2),那么f(x)=f(x-1)+f(x-2) 假设当前状态为x,那么x的阶层数可以为f(x1),也可以为f(x2),那么f(x)=f(x1)+f(x2),很容易就推出状态转移方程

code1

class Solution {
public:
    int climbStairs(int n) {
     int dp[50];
     dp[1]=1,dp[2]=2;
     for(int i=3;i<=n;++i){
     	dp[i]=dp[i-2]+dp[i-1]; 
	 }
	 return dp[n];
    }
};

我们通过观察可以发现, x 只跟 x − 1 和 x − 2 有关 x只跟x-1和x-2有关 x只跟x1x2有关,因此我们只需要考虑3个数就可以了,将dp进行状态压缩,转换成 f = f 1 + f 2 , f 2 = f 1 , f 1 = f f=f1+f2,f2=f1,f1=f f=f1+f2,f2=f1,f1=f
f ( x − 1 ) 的值赋值为 f ( x − 2 ) , f ( x ) 的值赋值给 f ( x − 1 ) f(x-1)的值赋值为f(x-2),f(x)的值赋值给f(x-1) f(x1)的值赋值为f(x2),f(x)的值赋值给f(x1),到此压缩完毕

code2

class Solution {
public:
    int climbStairs(int n) {
     int f1=1,f2=2;
     if(n==1) return 1;
     else if(n==2) return 2;
     for(int i=3;i<=n;++i){
     	int f=f1+f2;
        f1=f2,f2=f;
	 }
	 return f2;
    }
};

进阶1 377. 组合总和 Ⅳ

点击这里

思路

跟爬楼梯的套路一样, d p [ i ] = d p [ i − j ] ( j < = i ) dp[i]=dp[i-j](j<=i) dp[i]=dp[ij](j<=i)
怎么来的呢, 首先我们令 x 等于 4 , 那么 d p [ 4 ] 可以由 d p [ 1 ] + 3 由来, d p [ 4 ] = d p [ 2 ] + 2 由来 首先我们令x等于4,那么dp[4]可以由dp[1]+3由来,dp[4]=dp[2]+2由来 首先我们令x等于4,那么dp[4]可以由dp[1]+3由来,dp[4]=dp[2]+2由来
这里的 2 和 3 代表 j , j 是集合里面的元素 这里的2和3代表j,j是集合里面的元素 这里的23代表jj是集合里面的元素
显然只有集合里的 j j j小于 i i i, d p [ i ] dp[i] dp[i]就能加上 d p [ i − j ] dp[i-j] dp[ij]的值

code

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

进阶2 2266. 统计打字方案数

点击这里

思路

和上题思路一样,当 i 大于等于 z e r o 和 o n e 时,加上他们的个数,即 d p [ i ] + = d p [ i − z e r o ] , d p [ i ] + = d p [ i − o n e ] i大于等于zero和one时,加上他们的个数,即dp[i]+=dp[i-zero],dp[i]+=dp[i-one] i大于等于zeroone时,加上他们的个数,即dp[i]+=dp[izero]dp[i]+=dp[ione]
最后记得取模即可

code

class Solution {
public:
    int dp[100010];
    int countGoodStrings(int low, int high, int zero, int one) {
        int mod=1e9+7; 
        dp[0]=1;
        int ans=0;
        for(int i=1;i<=high;++i){
           if(i>=zero) dp[i]=(dp[i]+dp[i-zero])%mod;
           if(i>=one) dp[i]=(dp[i]+dp[i-one])%mod;
           if(i>=low) ans=(ans+dp[i])%mod;
        }
        return ans;
    }
};

198. 打家劫舍

点击这里

思路

我们只会有两种选择,偷和不偷,不偷的话返回上一个状态,偷的话返回上上个状态,其他情况都不是最优
因此,它的状态转移方程为 d p [ i ] = m a x ( d p [ i − 1 ] , d p [ i − 2 ] + a [ i ] ) dp[i]=max(dp[i-1],dp[i-2]+a[i]) dp[i]=max(dp[i1],dp[i2]+a[i])

code1

class Solution {
public:
    int rob(vector<int>& nums) {
      int n=nums.size();
      int dp[105]={0};
      for(int i=0;i<n;++i){
        dp[i+2]=max(dp[i+1],dp[i]+nums[i]);//防止数组越界
      }
      return dp[n+1];
    }
};

与爬楼梯同理, f ( x ) 只跟 f ( x − 1 ) 和 f ( x − 2 ) 有关,因此我们一样可以进行状态压缩 f(x)只跟f(x-1)和f(x-2)有关,因此我们一样可以进行状态压缩 f(x)只跟f(x1)f(x2)有关,因此我们一样可以进行状态压缩

code2

class Solution {
public:
    int rob(vector<int>& nums) {
     int f1=0,f2=0;
     for(int i=0;i<nums.size();++i){
        int f=max(f1,f2+nums[i]);
        f2=f1,f1=f;
     }
     return f1;
    }
};

进阶1 2320. 统计放置房子的方式数

点击这里

思路

由于道路两侧的情况不影响,因此我们只需要考虑一侧的情况,另一侧的情况与之相同,最后进行相乘即可
考虑不放房子的情况, d p [ 0 ] = 1 , 放一个房子的情况 d p [ 1 ] = 2 , 接着就与上题一样了 考虑不放房子的情况,dp[0]=1,放一个房子的情况dp[1]=2,接着就与上题一样了 考虑不放房子的情况,dp[0]=1,放一个房子的情况dp[1]=2,接着就与上题一样了
d p [ i ] = m a x ( d p [ i − 1 ] , d p [ i − 2 ] + a [ i ] ) , 同样我们可以进行状态压缩 dp[i]=max(dp[i-1],dp[i-2]+a[i]),同样我们可以进行状态压缩 dp[i]=max(dp[i1],dp[i2]+a[i]),同样我们可以进行状态压缩
最后答案乘上另一侧的情况即可

code

class Solution {
public:
    int countHousePlacements(int n) {
        long long f2=1,f1=2;
        int mod=1e9+7;
        for(int i=2;i<=n;++i){
         int f=(f1+f2)%mod;
         f2=f1,f1=f;
        }
        return (f1*f1)%mod;
    }
};

进阶2 3186. 施咒的最大总伤害

点击这里

思路

这题是一道综合题,首先我们需要统计数列里不同数的个数,用哈希表来存
接着将它存入到数组里进行升序排序,由于 d p [ i ] 不能返回 d p [ i − 1 ] 和 d p [ i − 2 ] ,因此我们只能返回 d p [ i − 3 ] dp[i]不能返回dp[i-1]和dp[i-2],因此我们只能返回dp[i-3] dp[i]不能返回dp[i1]dp[i2],因此我们只能返回dp[i3]
这时我们可以考虑用双指针来维护新数组, 指针 j 必须满足 a [ j ] > = a [ i ] − 2 指针j必须满足a[j]>=a[i]-2 指针j必须满足a[j]>=a[i]2
那么状态转移方程就为 d p [ i + 1 ] = m a x ( d p [ i ] , d p [ j ] + ( l o n g l o n g ) x ∗ y ) ( x ∗ y 代表数值 ∗ 数量 ) 那么状态转移方程就为dp[i+1]=max(dp[i],dp[j]+(long long)x * y)(x*y代表数值*数量) 那么状态转移方程就为dp[i+1]=max(dp[i],dp[j]+(longlong)xy)(xy代表数值数量)

code

class Solution {
public:
    long long maximumTotalDamage(vector<int>& power) {
        unordered_map<int,int> m;
        for(auto i : power){
            m[i]++;
        }
        vector<pair<int,int>> a(m.begin(),m.end());
        sort(a.begin(),a.end());
        int n=a.size();
        vector<long long> dp(n+1);
        for(int i=0,j=0;i<n;++i){
            int x=a[i].first,y=a[i].second;
            while(a[j].first<x-2) j++;
            dp[i+1]=max(dp[i],dp[j]+(long long)x * y);
        }
        return dp[n];
    }
};

53. 最大子数组和

点击这里

思路

假设x为序列中的一个数,那么x需要考虑2种情况:

  • 加上前面的序列
  • 不加上前面的序列,序列更新为x
    因此,我们就可以得出状态转移方程为 d p [ i ] = m a x ( d p [ i − 1 ] + n u m s [ i ] , n u m s [ i ] ) ; dp[i]=max(dp[i-1]+nums[i],nums[i]); dp[i]=max(dp[i1]+nums[i],nums[i]);

code1

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
     int n=nums.size();
     vector<int> dp(n+1);
     int maxn=-1e9;
     for(int i=0;i<nums.size();++i){
        dp[i+1]=max(dp[i]+nums[i],nums[i]);//防止数组越界
        maxn=max(dp[i+1],maxn);
     }
     return maxn;
    }
};

同样这题也可以进行状态压缩,我们每次只需要考虑2个数:

  • 前面整段序列的值
  • 当前数组的值

因此,我们可以用一个整数来模拟整段序列的值

code2

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
     int b=nums[0];
     int maxn=b;
     for(int i=1;i<nums.size();++i){
        if(nums[i]+b>nums[i]) b=nums[i]+b;
        else b=nums[i];
        maxn=max(maxn,b);
     }
     return maxn;
    }
};

进阶1 1749. 任意子数组和的绝对值的最大值

点击这里

思路

开2个dp数组,一个存正值,一个存负值,它的状态转移方程为 m a x n = m a x ( d p 1 [ i + 1 ] , a b s ( d p 2 [ i + 1 ] ) , m a x n ) maxn=max({dp1[i+1],abs(dp2[i+1]),maxn}) maxn=max(dp1[i+1],abs(dp2[i+1]),maxn)

code

class Solution {
public:
    int maxAbsoluteSum(vector<int>& nums) {
      int n=nums.size();
      vector<int> dp(n+1);
      vector<int> v(n+1);
      int maxn=0;
      for(int i=0;i<n;++i){
          dp[i+1]=max(dp[i],0)+nums[i];
          v[i+1]=min(v[i],0)+nums[i];
          maxn=max({dp[i+1],abs(v[i+1]),maxn});
      }
      return maxn;
    }
};

进阶2 1191. K 次串联后最大子数组之和

点击这里

思路

这题需要分3种情况:

  • 当k=1时,与 53. 最大子数组和 53. 最大子数组和 53.最大子数组和 的状态转移方程是一致的
  • k>=2时,若数组总和相加<=0,那么我们只需要2倍数组长度的状态转移方程(在往后下去最大值不变)
  • 若数组总和相加大于0,那可以看成再第一段结尾第二段开头插入k-2个正数(也是进行状态转移,然后在该基础上相加数组的总和个数减去2乘以总和的值)
    为什么可以这么看呢,因为我们找出前2倍数组长度的最大值的基础上,在这后面我们可以看成(k-2)次循环,因此我们总和就加上(k-2)乘上总值即可

code

class Solution {
public:
    int kConcatenationMaxSum(vector<int>& arr, int k) {
      int mod=1e9+7;
      int maxn=0;
      if(k==1){
        int dp=0;
        for(auto i : arr){
         dp=max(dp,0)+i;
         maxn=max(maxn,dp);
        }
        return maxn;
      }
      else{
        int n=arr.size();
        int dp=0;
        for(int i=0;i<2*n;++i){
          dp=max(dp,0)+arr[i%n];
          maxn=max(maxn,dp);
        }
        long long sum=0;
        for(auto i : arr) sum+=i;
        if(sum>0){
            maxn=(maxn+(k-2)*sum)%mod;
        }
        return maxn;
      }
    }
};

进阶3 918. 环形子数组的最大和

点击这里

思路

这题考虑2种情况:

  • 不考虑环形,那么和前几题的状态转移方程是一样的
  • 考虑环形,我们可以求数组中最小数组的和,那么可能的最大值为数组总和减去最小数组的和
  • 将这两者进行大小比较
    单单考虑这还不够,若最小数组的和=数组总和,那么我们还是考虑情况1(即不考虑环形)

code

class Solution {
public:
    int maxSubarraySumCircular(vector<int>& nums) {
     int n=nums.size();
     int maxn=-1e9;
     int dp=0,f=0;
     int minn=1e9;
     int sum=0;
     for(auto i : nums){
       sum+=i;
       dp=max(dp,0)+i;
       maxn=max(maxn,dp);

       f=min(f,0)+i;
       minn=min(minn,f);
     }
     if(sum==minn){
        return maxn;
     }
     else return max(maxn,sum-minn);
    }
};

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

手动在ubuntu上搭建一个nginx,并安装证书的最简化完整过程

背景&#xff1a;由于想做个测试&#xff1a;即IP为A的服务器&#xff0c;绑定完域名X后&#xff0c;如果再绑定域名Y&#xff0c;能不能被访问到。&#xff08;假设对A不做绑定域名的设置&#xff09; 这个问题的来源&#xff0c;见上一篇文章&#xff1a;《云服务器被非法域名…

kaggle使用api下载数据集

背景 kaggle通过api并配置代理下载数据集datasets 步骤 获取api key 登录kaggle&#xff0c;点个人资料&#xff0c;获取到自己的api key 创建好的key会自动下载 将key放至家目录下的kaggle.json文件中 我这里是windows的administrator用户。 装包 我用了虚拟环境 pip …

021.自定义指纹浏览器编译-修改ClientRects指纹

一、什么是ClientRects指纹 ClientRects指纹获取的核心方法是DOM元素方法getClientRects()​ 。getClientRects()​ 可以返回一个元素的所有 CSS 边界框&#xff08;ClientRect对象数组&#xff09;&#xff0c;包括其大小、位置等信息。每个边界框由其左上角的 x, y 坐标和宽…

基于YOLOv10深度学习的商品条形码智能检测与识别系统【python源码+Pyqt5界面+数据集+训练代码】深度学习实战、目标检测

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

小程序、H5、APP中的微信支付概述和实战总结

最近开发的一个微信小程序的项目结束了&#xff0c;里面用到了支付相关的api&#xff0c;借着项目总结一下小程序各种场景支付的逻辑。 1. 微信支付概述 1.1 微信支付的重要性 微信支付作为中国领先的移动支付方式之一&#xff0c;其便捷性、安全性以及广泛的用户基础使其成为…

已解决丨怎么快速的让IP地址实现HTTPS访问?

要快速让IP地址实现HTTPS访问&#xff0c;可以遵循以下简洁步骤&#xff1a; 1. 确认公网IP地址 确保你拥有一个固定的公网IP地址&#xff0c;因为HTTPS访问需要通过互联网上的公网IP进行。 2. 选择证书颁发机构&#xff08;CA&#xff09; 选择一个受信任的证书颁发机构&a…

从PLC到云端,ZP3000系列网关助力工业数字化转型

ZP3000系列远程控制网关是一款专为满足现代工业自动化和远程监控需求而设计且功能强大的通讯模块。它的多接口设计和灵活配置能力&#xff0c;使得它能够适应多种复杂的工业通信和监控场景。以下是关于ZP3000系列远程控制网关的详细特点和应用场景&#xff1a; 产品特点 双以太…

playwright 模拟F11 全屏

直接上源代码 import multiprocessing import time from multiprocessing import Processfrom playwright.sync_api import sync_playwrightdef run(playwright):# 使用 Chromium 浏览器运行 设置 headlessFalse 以打开可视化窗口browser playwright.chromium.launch(headles…

C语言——设计TVM(地铁自动售票机)机软件。

输入站数&#xff0c;计算费用&#xff0c;计费规则&#xff0c;6站2元&#xff0c;7-10站3元&#xff0c;11站以上为4元。 输入钱数&#xff0c;计算找零(找零时优先找回面额大的钞票)&#xff0c;找零方式为各种面额张数&#xff0c;可识别面额&#xff1a; 100,50,20,10,5,1…

linux中mysql的安装使用(普通版版本+docker版本)

linux中mysql的安装使用 一、普通安装1.下载安装包2.流程 二、用docker安装1.拉取mysql镜像2.启动镜像3.开启权限第一种情况第二种情况 三、用Navicat连接 一、普通安装 1.下载安装包 挑选个你喜欢的目录&#xff0c;用wget下载并且解压 wget http://dev.mysql.com/get/Down…

指针!!C语言 字符串篇(第四篇)

目录 一. sizeof和strlen的对比 二. 数组和指针笔试题解析 2.1 一维数组 2.2 字符数组 2.3 二维数组 一. sizeof和strlen的对比 在C语言中有两个比较相似的知识点&#xff0c;就是sizeof和strlen&#xff0c;下面我们来讲一下它们两者之间有什么不同之处&#xff1f; &a…

python脚本制作循环执行命令行

python import subprocess import sysif __name____main__:ret 1while ret!0:ret subprocess.call(sys.argv[1:], textTrue)pack pip install pyinstaller pyinstaller --onefile loop.py pyinstaller -i *.ico loop.py #指定ico图标 使用场景 使用上面生成的loop.exe调用c…

前端开发者必备:揭秘谷歌F12调试的隐藏技巧!

前言 使用断点&#xff08;breakpoint&#xff09;是调试 JavaScript 代码的一种非常有效的方式。通过在代码的关键位置设置断点&#xff0c;可以阻止页面的状态变化&#xff0c;从而方便地检查和修改页面的当前状态。 1. 使用 setTimeout 配合 debugger 和 console.log setTi…

调用百度的大模型API接口实现AI对话!手把手教程!

本文介绍如何使用百度的大模型API接口实现一个AI对话项目 1 注册百度云 2 获取API接口 3 配置环境 4 代码编写与运行 5 chat models 1 注册百度云 搜索百度云&#xff0c;打开官网注册&#xff0c;充值一点点大米&#xff08;收费很低&#xff0c;大概生成几个句子花费一毛…

立仪光谱共焦传感器应用测量之:汽车连接器高度差测量

01 检测要求&#xff0c;要求测量汽车连接器的高度差 02 检测方式 根据观察&#xff0c;我们采用立仪科技光谱共焦H4UC控制器搭配D65A52系列镜头&#xff0c;角度最大&#xff0c;外径最大&#xff0c;量程大&#xff0c;可以有效应用于测量弧面&#xff0c;大角度面等零件。 0…

【嵌入式之RTOS】什么是著名的食客问题

目录 一、问题描述 二、四个条件 三、实际应用中的意义 著名的“食客问题”&#xff08;Dining Philosophers Problem&#xff09;是一个经典的计算机科学问题&#xff0c;用来说明并发编程中的资源竞争和死锁问题。这个问题最初是由荷兰计算机科学家Edsger Dijkstra提出的…

秘密实验室开服教程(SCP: Secret Laboratory)

1、购买后登录服务器&#xff08;百度莱卡云&#xff09; 购买服务器后&#xff0c;如下图&#x1f447;&#xff0c;面板信息都在产品详情页面内 注意&#xff1a;请不要用你的莱卡云账号信息去登陆服务器面板 进入控制面板后会出现正在安装的界面&#xff0c;安装时长约5分…

【排序算法(二)】——冒泡排序、快速排序和归并排序—>深层解析

前言&#xff1a; 接上篇&#xff0c;排序算法除了选择排序&#xff08;希尔排序&#xff09;和插入排序&#xff08;堆排序&#xff09;之外&#xff0c;还用交换排序&#xff08;冒泡排序、快速排序&#xff09;和归并排序已经非比较排序&#xff0c;本篇来深层解析这些排序算…

2 卷积神经网络CNN

文章目录 LeNet-5AlexNetGoogLeNetResNet 本章代码均在kaggle上运行成功 LeNet-5 import torch import torch.nn as nn from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pyplot as plt from matplotlib_inline impo…

木马后门实验

实验拓扑 实验步骤 防火墙 配置防火墙—充当边界NAT路由器 边界防火墙实现内部 DHCP 分配和边界NAT需求&#xff0c;其配置如下 登录网页 编辑接口 配置e0/0 配置e0/1 编辑策略 测试&#xff1a;内部主机能获得IP&#xff0c;且能与外部kali通信 kali 接下来开启 kali 虚…