算法【动态规划中使用观察优化枚举】

news2025/3/12 12:41:39

动态规划的问题中,已经写出了记忆化搜索的版本,还要写出严格位置依赖的版本,意义在于不仅可以进行空间压缩优化;关键还在于,很多时候通过进一步观察,可以优化枚举,让时间复杂度更好。优化枚举的技巧很多,本文讲解根据观察优化。

动态规划方法的复杂度大致可以理解为:O(状态数量 * 每个状态的枚举代价)。当每个状态的枚举代价为O(1),那么写出记忆化搜索的版本,就是时间复杂度最好的实现了。但是当每个状态的枚举代价比较高的时候,记忆化搜索的版本可能不是最优解,可能存在进一步的优化。之所以从记忆化搜索改出了严格位置依赖的版本,是为了建立空间感,让观察并优化枚举的分析变容易。

通过观察优化枚举的技巧包括:

观察并优化转移方程、观察并设计高效的查询结构

下面通过题目加深理解。注意:因为题目四属于著名的买卖股票系列问题中的一个,所以索性把这个系列全讲了,买卖股票系列请重点关注题目四。

题目一

测试链接:121. 买卖股票的最佳时机 - 力扣(LeetCode)

分析:这道题实际上是对于卖股票的一天,只要找到之前买股票时之间的差值最大就行。代码如下。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int min_price = prices[0];
        int profit = 0;
        int length = prices.size();
        for(int i = 1;i <length;++i){
            min_price = min(min_price, prices[i]);
            profit = max(profit, prices[i] - min_price);
        }
        return profit;
    }
};

题目二

测试链接:122. 买卖股票的最佳时机 II - 力扣(LeetCode)

分析:对于无限购买的情况,实际是把握住每一次价格的上升,也就是收益。代码如下。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int length = prices.size();
        int profit = 0;
        for(int i = 1;i <length;++i){
            profit += max(prices[i] - prices[i-1], 0);
        }
        return profit;
    }
};

题目三

测试链接:123. 买卖股票的最佳时机 III - 力扣(LeetCode)

分析:对于一定在第i天第二次卖掉股票的情况下,找到i之前的某一天j,只需要知道0到j时间第一笔交易的最大利润以及在第j天买入在第i天卖掉的第二笔利润,就可以知道对于在第i天第二次卖掉股票的其中一种情况。很容易知道,对于第i天第二次卖掉股票的求解过程中,有枚举过程。通过观察,可以得到对于第i天第二次卖掉股票的可能性展开方程中抽象出具有相同结构的地方形成一个数组,这样就可以通过查询数组,从而优化掉枚举过程。代码如下。

class Solution {
public:
    int dp1[100001];
    int best[100001];
    int maxProfit(vector<int>& prices) {
        int length = prices.size();
        int min_price = prices[0];
        int ans = 0;
        dp1[0] = 0;
        best[0] = -prices[0];
        for(int i = 1;i < length;++i){
            min_price = min(min_price, prices[i]);
            dp1[i] = max(dp1[i-1], prices[i] - min_price);
            best[i] = max(best[i-1], dp1[i] - prices[i]);
            ans = max(ans, best[i] + prices[i]);
        }
        return ans;
    }
};

其中,dp1数组存储从0到i之间完成一笔交易的最大利润;best数组存储抽象出来的结构。

题目四

测试链接:188. 买卖股票的最佳时机 IV - 力扣(LeetCode)

分析:可以知道,如果有n天那么最多有n/2个上升,也就是说,如果k大于等于n/2,则此题就变化成可以交易无限次求最大利润。如果k小于n/2,对于dp[i][j]代表在0到i范围上完成j笔交易的最大利润。对于dp[i][j]代表在0到j范围上完成i笔交易的最大利润可能性的展开分为是否在第j天卖出。可以观察到,如果要在第j天卖出,存在一个枚举行为,和上一题类似,通过抽象出一个best变量,从而优化掉这个枚举行为。代码如下。

class Solution {
public:
    int dp[101][1000] = {0};
    int infinite(vector<int>& prices, int length){
        int ans = 0;
        for(int i = 1;i < length;++i){
            ans += max(prices[i] - prices[i-1], 0);
        }
        return ans;
    }
    int maxProfit(int k, vector<int>& prices) {
        int length = prices.size();
        if(k >= (length/2)){
            return infinite(prices, length);
        }
        int best;
        for(int i = 1;i <= k;++i){
            best = dp[i-1][0] - prices[0];
            for(int j = 1;j < length;++j){
                best = max(best, dp[i-1][j] - prices[j]);
                dp[i][j] = max(dp[i][j-1], best + prices[j]);
            }
        }
        return dp[k][length-1];
    }
};

题目五

测试链接:714. 买卖股票的最佳时机含手续费 - 力扣(LeetCode)

分析:维护一个prepare变量和一个down变量分别表示遍历到i时取得的最大利润在买入一次股票和扣掉一次手续费时的最大值,down代表来到i时所取得的最大利润。对于down的更新,当来到第i天时,如果不在第i天卖出,则此时的down不变;如果在第i天卖出,则用prepare加上第i天的价格和之前的down进行比较,取最大值。对于prepare的更新,如果不在第i天买入,则prepare不变;如果在第i天买入,则是来到第i天的最大利润减去第i天的价格和一次手续费,取最大值。代码如下。

class Solution {
public:
    int maxProfit(vector<int>& prices, int fee) {
        int length = prices.size();
        int prepare = -prices[0] - fee;
        int done = 0;
        for(int i = 1;i < length;++i){
            done = max(done, prepare + prices[i]);
            prepare = max(prepare, done - prices[i] - fee);
        }
        return done;
    }
};

题目六

测试链接:309. 买卖股票的最佳时机含冷冻期 - 力扣(LeetCode)

分析:因为存在如果要在某一天买入,则需要前两天的最大利润的情况,所以相比于上一题,此题需要维护一个prepare变量、一个来到第i天的最大利润down、来到第i-1天的最大利润pdown和来到第i-2天的最大利润ppdown。更新思路和上一题基本相同,代码如下。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int length = prices.size();
        if(length < 2){
            return 0;
        }
        int prepare = max(-prices[0], -prices[1]);
        int pdone = 0, ppdone = 0, done = max(0, prices[1] - prices[0]);
        for(int i = 2;i < length;++i){
            ppdone = pdone;
            pdone = done;
            done = max(done, prepare + prices[i]);
            prepare = max(prepare, ppdone - prices[i]);
        } 
        return done;
    }
};

题目七

测试链接:903. DI 序列的有效排列 - 力扣(LeetCode)

分析:这道题很容易想到的一个思路是,当来到了字符串的i位置,然后有一个整数是否被取的一个状态,上一个位置取的数是多少,进行可能性展开,大致思路类似于状压dp中的题。但是因为n的规模,这个思路尝试不可取。观察到这道题,对于整数的具体值并不严格要求,只严格要求大小关系,所以对于递归的参数可以用比前一个数小的数有多少个代替哪些整数被用过,这样来进行可能性展开,记忆化搜索就可行了。代码如下。

class Solution {
public:
    int MOD = 1000000007;
    int dp[201][201];
    int f(string& s, int i, int n, int less){
        if(dp[i][less] != -1){
            return dp[i][less];
        }
        int ans = 0;
        char ch = s[i-1];
        if(i == n){
            if(ch == 'D' && less == 1){
                ans = 1;
            }else if(ch == 'I' && less == 0){
                ans = 1;
            }else{
                ans = 0;
            }
        }else{
            if(ch == 'D'){
                for(int j = 0;j < less;++j){
                    ans = (int)(((long long)ans + f(s, i+1, n, j)) % MOD);
                }
            }else{
                for(int j = 0;j < (n+1-i-less);++j){
                    ans = (int)(((long long)ans + f(s, i+1, n, n-i-j)) % MOD);
                }
            }
        }
        dp[i][less] = ans;
        return ans;
    }
    int numPermsDISequence(string s) {
        int n = s.size();
        int ans = 0;
        for(int i = 0;i <= n;++i){
            for(int j = 0;j <= n;++j){
                dp[i][j] = -1;
            }
        }
        for(int i = 0;i <= n;++i){
            ans = (int)(((long long)ans + f(s, 1, n, i)) % MOD);
        }
        return ans;
    }
};

其中,f方法返回在来到i位置,比前一位置小的数有less个的情况下有效排列的个数。

题目八

测试链接:1235. 规划兼职工作 - 力扣(LeetCode)

分析:首先,对每个工作的结束时间从小到大排序,dp[i]代表从排好序的数组中前i个工作范围取获得的最大报酬,可能性的展开即对第i个工作做或者不做。如果要做第i个工作,则需要找到结束时间小于等于第i个工作开始时间的工作,然后得到它的dp值,遍历取最大值。这里是一个枚举过程,但是观察到dp数组是一个不递减的序列即一个有序序列,则可以利用二分查找求得离i最近的结束时间小于等于i的开始时间的dp值,从而优化掉这个枚举过程。代码如下。

class Solution {
public:
    struct job
    {
        int start;
        int end;
        int profit;
    };
    int dp[50001];
    int jobScheduling(vector<int>& startTime, vector<int>& endTime, vector<int>& profit) {
        int length = startTime.size();
        vector<job> Job;
        Job.reserve(length);
        for(int i = 0;i < length;++i){
            job temp;
            temp.start = startTime[i];
            temp.end = endTime[i];
            temp.profit = profit[i];
            Job.push_back(temp);
        }
        sort(Job.begin(), Job.end(), [](job j1, job j2){
            return j1.end < j2.end;
        });
        int l, r, m, ans;
        dp[0] = 0;
        for(int i = 1;i <= length;++i){
            if(Job[0].end > Job[i-1].start){
                ans = 0;
            }else{
                l = 0;
                r = i-2;
                while (l <= r)
                {
                    m = l + (r - l) / 2;
                    if(Job[m].end <= Job[i-1].start){
                        ans = m + 1;
                        l = m + 1;
                    }else{
                        r = m - 1;
                    }
                }
            }
            dp[i] = max(dp[i-1], Job[i-1].profit + dp[ans]);
        }
        return dp[length];
    }
};

题目九

测试链接:https://leetcode.cn/problems/k-inverse-pairs-array/

分析:dp[i][j]的含义代表在1到i范围上有j个逆序对的个数。可能性的展开即如果i大于j,则第i个数插入1到i-1的排列中,可以产生0到i-1个逆序对,这将这些可能性相加;如果i小于等于j,则第i个数插入到1到i-1的排列中,可以产生0到i-1个逆序对,将这些可能性相加。可以发现,这是一个枚举过程,并且i大于j的时候相加是从下标0开始到j,i小于等于j的时候相加是从j-i+1到j,所以可以利用一个窗口来维护这些值,进而优化掉枚举过程。第一个版本是严格位置依赖未优化枚举过程,第二个版本是优化掉枚举过程。代码如下。

class Solution {
public:
    int MOD = 1000000007;
    int dp[1001][1001] = {0};
    int kInversePairs(int n, int k) {
        dp[1][0] = 1;
        for(int i = 2;i <= n;++i){
            for(int j = 0;j <= k;++j){
                if(i > j){
                    for(int l = 0;l <= j;++l){
                        dp[i][j] = (int)(((long long)dp[i][j] + dp[i-1][l]) % MOD);
                    }
                }else{
                    for(int l = j - i + 1;l <= j;++l){
                        dp[i][j] = (int)(((long long)dp[i][j] + dp[i-1][l]) % MOD);
                    }
                }
            }
        }
        return dp[n][k];
    }
};

class Solution {
public:
    int MOD = 1000000007;
    int dp[1001][1001] = {0};
    int kInversePairs(int n, int k) {
        int window;
        dp[1][0] = 1;
        for(int i = 2;i <= n;++i){
            window = 0;
            for(int j = 0;j <= k;++j){
                if(i > j){
                    window = (int)(((long long)window + dp[i-1][j]) % MOD);
                    dp[i][j] = window;
                }else{
                    window = (int)(((long long)window + dp[i-1][j] - dp[i-1][j-i] + MOD) % MOD);
                    dp[i][j] = window;
                }
            }
        }
        return dp[n][k];
    }
};

题目十

测试链接:514. 自由之路 - 力扣(LeetCode)

分析:这道题的递归即从转盘指向i位置,关键词来到j位置时完成的最少步数。可能性的展开很好想到,就是对于关键词j位置的字符在转盘上进行遍历枚举,然后调递归。这是一个枚举过程,但是观察可知,只需要对顺时针最近的关键词字符调递归和逆时针最近的关键词字符调递归这两个可能性进行展开即可,这样就优化掉了枚举过程。代码如下。

class Solution {
public:
    int dp[100][100];
    int f(string& ring, int length_r, int i, string& key, int length_k, int j){
        if(j == length_k){
            return 0;
        }
        if(dp[i][j] != -1){
            return dp[i][j];
        }
        int ans;
        if(ring[i] == key[j]){
            ans = 1 + f(ring, length_r, i, key, length_k, j+1);
        }else{
            for(int k = (i+1+length_r)%length_r;;k = (k+1+length_r)%length_r){
                if(ring[k] == key[j]){
                    ans = 1 + ((k-i+length_r)%length_r) + f(ring, length_r, k, key, length_k, j+1);
                    break;
                }
            }
            for(int k = (i-1+length_r)%length_r;;k = (k-1+length_r)%length_r){
                if(ring[k] == key[j]){
                    ans = min(ans, 1 + ((i-k+length_r)%length_r) + f(ring, length_r, k, key, length_k, j+1));
                    break;
                }
            }
        }
        dp[i][j] = ans;
        return ans;
    }
    int findRotateSteps(string ring, string key) {
        int length_r = ring.size();
        int length_k = key.size();
        for(int i = 0;i < length_r;++i){
            for(int j = 0;j < length_k;++j){
                dp[i][j] = -1;
            }
        }
        return f(ring, length_r, 0, key, length_k, 0);
    }
};

题目十一

测试链接:未排序数组中累加和小于或等于给定值的最长子数组长度_牛客题霸_牛客网

分析:第一个nlog n复杂度的解法是,对于前缀和数组,如果要求以i位置结尾的子数组的值满足条件,则需要在i之前的前缀和进行枚举,符合条件的子数组进行长度的更新,这样复杂度是n²且有枚举过程。观察到,对于枚举过程中是要找到距离i最远的满足大于等于前缀和减k的下标,即如果对一个位置j之后的位置的前缀和小于j位置的前缀和,那么j之后的这个位置就没有意义,所以可以将这个前缀和数组变化成一个不递减数组,进而利用二分查找优化掉枚举过程。代码如下。

#include <iostream>
#include <cmath>
using namespace std;
int N, k;
int b_prefix[100001];
int prefix[100001];
int main(void){
    int temp;
    scanf("%d%d", &N, &k);
    b_prefix[0] = 0;
    prefix[0] = 0;
    for(int i = 1;i <= N;++i){
        scanf("%d", &temp);
        prefix[i] = prefix[i-1] + temp;
        b_prefix[i] = max(prefix[i], b_prefix[i-1]);
    }
    int ans = 0;
    int l, r, m, s;
    for(int i = 1;i <= N;++i){
        l = 0;
        r = i;
        s = prefix[i] - k;
        temp = i+1;
        while (l <= r)
        {
            m = l + (r - l) / 2;
            if(b_prefix[m] >= s){
                temp = m;
                r = m - 1; 
            }else{
                l = m + 1;
            }
        }
        ans = max(ans, i - temp);
    }
    printf("%d", ans);
    return 0;
}

第二种解法是n复杂度的解法,利用构建查询结构。构建nums数组存储数组值,numsSum数组存储从i下标往右扩的最小值,numsSumEnd数组存储从i往右扩到最小值时的下标。这样对于一个下标可以往右探查到满足条件的最远下标,更新长度后,减去此下标的值,再从刚才的最远下标看是否还能往右边扩,这样区间的左右俩下标都不回退,是n的复杂度,并且可以用一个窗口值去维护区间的和。代码如下。

#include <iostream>
#include <cmath>
using namespace std;
int N, k;
int nums[100001];
int numsSum[100001];
int numsSumEnd[100001];
int main(void){
    scanf("%d%d", &N, &k);
    for(int i = 0;i < N;++i){
        scanf("%d", &nums[i]);
    }
    numsSum[N-1] = nums[N-1];
    numsSumEnd[N-1] = N-1;
    for(int i = N-2;i >= 0;--i){
        if(numsSum[i+1] < 0){
            numsSum[i] = nums[i] + numsSum[i+1];
            numsSumEnd[i] = numsSumEnd[i+1];
        }else{
            numsSum[i] = nums[i];
            numsSumEnd[i] = i;
        }
    }
    int window;
    int p1, p2;
    int ans;
    for(p1 = 0;p1 < N;++p1){
        if(numsSum[p1] <= k){
            window = numsSum[p1];
            p2 = numsSumEnd[p1];
            break;
        }
    }
    while (p2 + 1 < N)
    {
        if(numsSum[p2+1] + window <= k){
            window += numsSum[p2+1];
            p2 = numsSumEnd[p2+1];
        }else{
            break;
        }
    }
    ans = p2 - p1 + 1;
    for(;p1 < N && p2 < N;){
        if(p2 + 1 == N){
            break;
        }
        window -= nums[p1++];
        while (p2 + 1 < N)
        {
            if(numsSum[p2+1] + window <= k){
                window += numsSum[p2+1];
                p2 = numsSumEnd[p2+1];
            }else{
                break;
            }
        }
        ans = max(ans, p2 - p1 + 1);
    }
    printf("%d", ans);
    return 0;
}

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

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

相关文章

ML.Net二元分类

ML.Net二元分类 文章目录 ML.Net二元分类前言项目的创建机器学习模型的创建添加模型选择方案训练环境的选择训练数据的添加训练数据的选择训练数据的格式要预测列的选择模型评估模型的使用总结前言 ‌ML.NET‌是由Microsoft为.NET开发者平台创建的免费、开源、跨平台的机器学习…

visutal studio 2022使用qcustomplot基础教程

编译 下载&#xff0c;2.1.1版支持到Qt6.4 。 拷贝qcustomplot.h和qcustomplot.cpp到项目源目录&#xff08;Qt project&#xff09;。 在msvc中将它俩加入项目中。 使用Qt6.8&#xff0c;需要修改两处代码&#xff1a; L6779 # if QT_VERSION > QT_VERSION_CHECK(5, 2, …

本地搭建自己的专属客服之OneApi关联Ollama部署的大模型并创建令牌《下》

这里写目录标题 OneApi1、渠道设置2、令牌创建 配置文件修改修改配置文件docker-compose.yml修改config.json到此结束 上文讲了如何本地docker部署fastGtp&#xff0c;相信大家也都已经部署成功了&#xff01;&#xff01;&#xff01; 今天就说说怎么让他们连接在一起 创建你的…

【C】初阶数据结构4 -- 双向循环链表

之前学习的单链表相比于顺序表来说&#xff0c;就是其头插和头删的时间复杂度很低&#xff0c;仅为O(1) 且无需扩容&#xff1b;但是对于尾插和尾删来说&#xff0c;由于其需要从首节点开始遍历找到尾节点&#xff0c;所以其复杂度为O(n)。那么有没有一种结构是能使得头插和头删…

小爱音箱控制手机和电视听歌的尝试

最近买了小爱音箱pro&#xff0c;老婆让我扔了&#xff0c;吃灰多年的旧音箱。当然舍不得&#xff0c;比小爱还贵&#xff0c;刚好还有一台红米手机&#xff0c;能插音箱&#xff0c;为了让音箱更加灵活&#xff0c;买了个2元的蓝牙接收模块Type-c供电3.5接口。这就是本次尝试起…

Kotlin Lambda

Kotlin Lambda 在探索Kotlin Lambda之前&#xff0c;我们先回顾下Java中的Lambda表达式&#xff0c;Java 的 Lambda 表达式是 Java 8 引入的一项强大的功能&#xff0c;它使得函数式编程风格的代码更加简洁和易于理解。Lambda 表达式允许你以一种更简洁的方式表示实现接口&…

Java 设计模式之备忘录模式

文章目录 Java 设计模式之备忘录模式概述UML代码实现 Java 设计模式之备忘录模式 概述 备忘录(Memento)&#xff1a;在不破坏封装性的前提下&#xff0c;捕获一个对象的内部状态&#xff0c;并在该对象之外保存这个状态。方便对该对象恢复到原先保存的状态。 UML Originnato…

vue3搭建实战项目笔记二

vue3搭建实战项目笔记二 2.1.git管理项目2.2.隐藏tabBar栏2.2.1 方案一&#xff1a;在路由元信息中设置一个参数是否显示tabBar2.2.2 方案二&#xff1a;通过全局设置相对定位样式 2.3.项目里封装axios2.3.1 发送网络请求的两种做法2.3.2 封装axios并发送网络请求2.3.2.1 对axi…

【原创】解决vue-element-plus-admin无法实现下拉框动态控制表单功能,动态显隐输入框

前言 目前使用vue-element-plus-admin想要做一个系统定时任务功能&#xff0c;可以选择不同的定时任务类型&#xff0c;比如使用cron表达式、周期执行、指定时间执行等。每种类型对应不同的输入框&#xff0c;需要动态显隐输入框才行&#xff0c;但是这个vue-element-plus-adm…

大疆无人机需要的kml文件如何制作kml导出(大疆KML文件)

大疆无人机需要的轨迹kml文件&#xff0c;是一种专门的格式&#xff0c;这个kml里面只有轨迹点&#xff0c;其它的属性信息都不需要。 BigemapPro提供了专门的大疆格式输出&#xff0c; 软件这里下载 www.bigemap.com 安装后&#xff0c;kml导入如下图&#xff1a; 然后选择…

免费deepseek的API获取教程及将API接入word或WPS中

免费deepseek的API获取教程: 1 https://cloud.siliconflow.cn/中注册时填写邀请码&#xff1a;GAejkK6X即可获取2000 万 Tokens; 2 按照图中步骤进行操作 将API接入word或WPS中 1 打开一个word&#xff0c;文件-选项-自定义功能区-勾选开发工具-左侧的信任中心-信任中心设置…

(三)Axure制作转动的唱片

效果图 属性&#xff1a; 图标库&#xff1a;iconfont-阿里巴巴矢量图标库 方形图片转为圆角图片&#xff0c;裁剪&#xff0c;然后加圆角&#xff0c; 唱片和底图是两个图片&#xff0c;点击播放&#xff0c;唱片在旋转。 主要是播放按钮和停止按钮&#xff0c;两个动态面板…

ASP.NET Core SixLabors.ImageSharp 位图图像创建和下载

从 MVC 控制器内部创建位图图像并将其发送到浏览器&#xff1b;用 C# 编写并与 Linux 和 Windows 服务器兼容。 使用从 ASP.NET MVC 中的控制器下载任何文件类型File。 此示例创建一个位图 (jpeg) 并将其发送到浏览器。它需要 NuGet 包SixLabors.ImageSharp v1.0.4。 另请参…

机器学习所需要的数学知识【01】

总览 导数 行列式 偏导数 概理论 凸优化-梯度下降 kkt条件

【D2】神经网络初步学习

总结&#xff1a;学习了 PyTorch 中的基本概念和常用功能&#xff0c;张量&#xff08;Tensor&#xff09;的操作、自动微分&#xff08;Autograd&#xff09;、正向传播、反向传播。通过了解认识LeNet 模型&#xff0c;定义神经网络类&#xff0c;熟悉卷积神经网络的基本结构和…

变相提高大模型上下文长度-RAG文档压缩-3.优化map-reduce(reranker过滤+社区聚类)

我遇到的业务问题实际上是RAG需要处理很多同一对象的日常报告&#xff0c;不像常识类问题&#xff0c;它的相关Document更多而且更分散&#xff0c;日常报告代表数据库里有很多它的内容&#xff0c;而且对象可能只在段落中的几句话提及到。top-k数量受限于大模型长度&#xff0…

电解电容的参数指标

容量 这个值通常是室温25℃&#xff0c;在一定频率和幅度的交流信号下测得的容量。容量会随着温度、直流电压、交流电压值的变化而改变。 额定电压 施加在电容上的最大直流电压&#xff0c;通常要求降额使用。 例如额定电压是4V&#xff0c;降额到70%使用&#xff0c;最高施…

计时器任务实现(保存视频和图像)

下面是一个简单的计时器任务实现&#xff0c;可持续地每秒保存一幅图像&#xff0c;也可持续地每60秒保存一个视频&#xff0c;图像和视频均以当前时间命名&#xff1a; TimerTask类的实现如下&#xff1a; class TimerTask { public:TimerTask(const std::string& path):…

Django 美化使用ModelForm的输入框

在初次使用ModelForm时&#xff0c;我的html文件代码如下&#xff0c;主要内容是显示一个卡片式表单&#xff0c;通过循环遍历 form 对象动态生成表单字段 {% extends layout.html %}{% block content %} <div class"container"><div class"c1"&g…

应用层优秀的共享民宿物联网框架该怎么选?

有一说一&#xff0c;应用层优秀的物联网框架通常能帮助提升用户体验、提高运营效率、节能减排等等优势&#xff0c;很多老板也很注重这个层面的设计和打磨&#xff0c;那么对于选择应用层优秀的共享民宿物联网框架时&#xff0c;大家可以从哪几个关键因素进行考量呢&#xff1…