【算法】区间DP (从记忆化搜索到递推DP)⭐

文章目录

  • 前期知识
    • 516. 最长回文子序列
      • 思路1——转换问题:求 s 和反转后 s 的 LCS(最长公共子序列)
      • 思路2——区间DP:从两侧向内缩小问题规模
        • 补充:记忆化搜索代码
    • 1039. 多边形三角剖分的最低得分
      • 从记忆化搜索开始
      • 翻译成递推
  • 典型例题
  • 相关练习题目
    • 375. 猜数字大小 II https://leetcode.cn/problems/guess-number-higher-or-lower-ii/
      • 记忆化搜索
      • 递推dp
    • 1312. 让字符串成为回文串的最少插入次数 https://leetcode.cn/problems/minimum-insertion-steps-to-make-a-string-palindrome/
      • 记忆化搜索
      • 区间dp
      • 解法2:转换成最长回文子序列
    • 1771. 由子序列构造的最长回文串的长度 https://leetcode.cn/problems/maximize-palindrome-length-from-subsequences/
    • 1547. 切棍子的最小成本 https://leetcode.cn/problems/minimum-cost-to-cut-a-stick/
      • 记忆化搜索
      • 递推dp
    • 1000. 合并石头的最低成本 https://leetcode.cn/problems/minimum-cost-to-merge-stones/ ⭐⭐⭐⭐⭐
      • 前置知识——前缀和
      • 思路:寻找子问题
      • 记忆化搜索
        • 记忆化搜索的优化
      • DP递推

前期知识

在这里插入图片描述
通过本篇文章的学习,最重要的就是学会 记忆化搜索 的方法,
很多问题直接写 递推DP 会比较困难,但是寻找子问题,按照记忆化搜索的方式会比较简单。
之后还可以相对容易地将记忆化搜索的代码翻译成递推 DP。

516. 最长回文子序列

516. 最长回文子序列
在这里插入图片描述

思路1——转换问题:求 s 和反转后 s 的 LCS(最长公共子序列)

因为回文子序列从前往后和从后往前是一样的,所以可以转换成求
s = eacbba

s_rev = abbace
的最长公共子序列的长度。最长公共子序列的方法参见:【算法】最长公共子序列&编辑距离

class Solution {
    public int longestPalindromeSubseq(String s) {
        int n = s.length();
        String s2 = new StringBuilder(s).reverse().toString();
        int[][] dp = new int[n + 1][n + 1];
        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= n; ++j) {
                if (s.charAt(i - 1) == s2.charAt(j - 1)) {
                    dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - 1] + 1);
                } else dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
        return dp[n][n];
    }
}

思路2——区间DP:从两侧向内缩小问题规模

在这里插入图片描述
这道题目相对简单一些,可以直接写出递推公式。

刚开始不确定 i 和 j 的枚举顺序,这时候看看状态转移方法看看它们是从哪里转移来的就好了。

class Solution {
    public int longestPalindromeSubseq(String s) {
        int n = s.length();
        // dp[i][j]表示从i~j的区间中,最长回文子序列的长度
        int[][] dp = new int[n][n];
        for (int i = n - 1; i >= 0; --i) {
            dp[i][i] = 1;   // dp数组初始化
            for (int j = i + 1; j < n; ++j) {
                if (s.charAt(i) == s.charAt(j)) {   
                    // i和j相同,可以选
                    dp[i][j] = Math.max(dp[i + 1][j - 1] + 2, dp[i][j]);
                } else {
                    // 不选i或者不选j
                    dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]);    
                }
            }
        }
        return dp[0][n - 1];
    }
}

补充:记忆化搜索代码

class Solution {
    int[][] memo;
    public int longestPalindromeSubseq(String s) {
        int n = s.length();
        this.memo = new int[n][n];
        return dfs(s.toCharArray(), 0, n - 1);
    }

    public int dfs(char[] s, int l, int r) {
        if (l > r) return 0;
        if (memo[l][r] != 0) return memo[l][r];
        if (l == r) return memo[l][r] = 1;
        if (s[l] == s[r]) memo[l][r] = Math.max(memo[l][r], dfs(s, l + 1, r - 1) + 2);
        else memo[l][r] = Math.max(dfs(s, l, r - 1), dfs(s, l + 1, r));
        return memo[l][r];
    }
}

1039. 多边形三角剖分的最低得分

1039. 多边形三角剖分的最低得分
在这里插入图片描述

把这题当成经典例题,学习记忆化搜索的写法。

从记忆化搜索开始

在这里插入图片描述
在这里插入图片描述

所谓记忆化搜索,就是用一个数组存一下各个 dfs 的结果,让 dfs 不会再重复计算。

class Solution {
    int[][] memo;

    public int minScoreTriangulation(int[] values) {
        int n = values.length;
        this.memo = new int[n][n];
        return dfs(values, 0, n - 1);
    }

    public int dfs(int[] values, int l, int r) {
        if (memo[l][r] != 0) return memo[l][r];
        else if (r == l + 1) memo[l][r] = 0;
        else {
            int res = Integer.MAX_VALUE;
            for (int i = l + 1; i < r; ++i) {
                res = Math.min(res, dfs(values, i, r) + dfs(values, l, i) + values[l] * values[i] * values[r]);
            }
            memo[l][r] = res;
        }
        return memo[l][r];
    }
}

翻译成递推

在这里插入图片描述

上图中说了,分不清枚举的顺序,就看状态转移的时候是从哪里转移过来的。

class Solution {
    public int minScoreTriangulation(int[] values) {
        int n = values.length;
        int[][] dp = new int[n][n];
        for (int i = n - 3; i >= 0; --i) {
            for (int j = i + 2; j < n; ++j) {
                dp[i][j] = Integer.MAX_VALUE;
                for (int k = i + 1; k < j; ++k) {
                    dp[i][j] = Math.min(dp[i][j], dp[i][k] + dp[k][j] + values[i] * values[k] * values[j]);
                }
            }
        }
        return dp[0][n - 1];
    }
}

典型例题

相关练习题目

375. 猜数字大小 II https://leetcode.cn/problems/guess-number-higher-or-lower-ii/

https://leetcode.cn/problems/guess-number-higher-or-lower-ii/

在这里插入图片描述

记忆化搜索

  1. 当只有一个数字需要被选择时,消耗是 0
  2. 当有两个数字需要被选择时,消耗是大的那一个,因为要保证游戏一定胜利
    在这里插入图片描述

res = Math.min(res, Math.max(dfs(l, i - 1), dfs(i + 1, r)) + i);

从记忆化搜索开始写,好写一些。

class Solution {
    int[][] memo;

    public int getMoneyAmount(int n) {
        memo = new int[n + 1][n + 1];
        return dfs(1, n);
    }

    public int dfs(int l, int r) {
        if (l >= r) return 0;
        if (memo[l][r] != 0) return memo[l][r];
        int res = Integer.MAX_VALUE;
        for (int i = l; i <= r; ++i) {
            res = Math.min(res, Math.max(dfs(l, i - 1), dfs(i + 1, r)) + i);
        }
        memo[l][r] = res;
        return res;
    }
}

递推dp

同样看递推的方向来判断枚举 i 和 j 的顺序。

class Solution {
    public int getMoneyAmount(int n) {
        int[][] dp = new int[n + 2][n + 2];
        for (int i = n; i >= 1; --i) {
            for (int j = i + 1; j <= n; ++j) {
                dp[i][j] = Integer.MAX_VALUE;
                for (int k = i; k <= j; ++k) {
                    dp[i][j] = Math.min(dp[i][j], k + Math.max(dp[i][k - 1], dp[k + 1][j]));
                }
            }
        }
        return dp[1][n];
    }
}

1312. 让字符串成为回文串的最少插入次数 https://leetcode.cn/problems/minimum-insertion-steps-to-make-a-string-palindrome/

https://leetcode.cn/problems/minimum-insertion-steps-to-make-a-string-palindrome/
在这里插入图片描述

  1. 当字符串的长度为 1 时,答案是0。
  2. 否则,当 s[i] != s[j] 时,需要增添一个元素 s[i] 或者 s[j]。答案是 dfs(s, l + 1, r) 和 dfs(s, l, r - 1) 之间的最小值 + 1。
  3. 当 s[i] == s[j] 时,答案是 dfs(s, l + 1, r - 1) ,因为这两个元素不需要考虑了。

记忆化搜索

class Solution {
    int[][] memo;

    public int minInsertions(String s) {
        int n = s.length();
        this.memo = new int[n][n];
        return dfs(s, 0, n - 1);
    }

    public int dfs(String s, int l, int r) {
        if (l >= r) return 0;
        if (memo[l][r] != 0) return memo[l][r];
        
        if (s.charAt(l) == s.charAt(r)) memo[l][r] = dfs(s, l + 1, r - 1);
        else memo[l][r] = 1 + Math.min(dfs(s, l + 1, r), dfs(s, l, r - 1));
        return memo[l][r];
    }
}

dfs 中第一句写的 if (l >= r) return 0; ,其实我也不知道在 dfs 的过程中 l 会不会 大于 r,但是无所谓,因为只有当 l < r 时我才想让函数接着往下走。
所以,何必写成 l == r 呢?不如宽松一点条件,省得万一 l 可能会大于 r。

区间dp

class Solution {
    public int minInsertions(String s) {
        int n = s.length();
        int[][] dp = new int[n][n];
        for (int i = n - 1; i >= 0; --i) {
            for (int j = i + 1; j < n; ++j) {
                if (s.charAt(i) == s.charAt(j)) dp[i][j] = dp[i + 1][j - 1];
                else dp[i][j] = 1 + Math.min(dp[i + 1][j], dp[i][j - 1]);
            }
        }
        return dp[0][n - 1];
    }
}

解法2:转换成最长回文子序列

考虑增加后的回文字符串,对称的两个字符不可能都是新增加的,要么其中一个是新增加的,要么就是都属于原字符串。如果只把两个都属于原字符串的那些字符作为子序列提出来(奇数长度的情况包含中间那个),他是一个回文子序列,并且是原字符串的回文子序列。并且有insertion step的数量就是原字符串长度 - 回文子序列的长度(要为那些本来只有一个的字符手动增加对称的字符)。

因此,一个增加字符使得原字符串变成回文的方案,对应着一个原字符串的回文子序列。并且,回文子序列越长,需要增加的字符越少。

从而变成找原字符串的最长回文子序列的问题。

class Solution {
    public int minInsertions(String s) {
        int n = s.length();
        // dp[i][j]表示从i~j之间的最长回文子序列的长度
        int[][] dp = new int[n][n];
        for (int i = n - 1; i >= 0; --i) {
            dp[i][i] = 1;
            for (int j = i + 1; j < n; ++j) {
                if (s.charAt(i) == s.charAt(j)) dp[i][j] = dp[i + 1][j - 1] + 2;
                else dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]);
            }
        }
        return n - dp[0][n - 1];
    }
}

1771. 由子序列构造的最长回文串的长度 https://leetcode.cn/problems/maximize-palindrome-length-from-subsequences/

https://leetcode.cn/problems/maximize-palindrome-length-from-subsequences/

在这里插入图片描述
这道题目本质上就是将 s1 和 s2 合并之后,求最长回文子序列,同时要求这个最长回文子序列的首元素在 s1 中,末尾元素在 s2 中

class Solution {
    public int longestPalindrome(String word1, String word2) {
        String s = word1 + word2;
        int n = s.length(), n1 = word1.length(), ans = 0;
        int[][] dp = new int[n][n];
        for (int i = n - 1; i >= 0; --i) {
            dp[i][i] = 1;
            for (int j = i + 1; j < n; ++j) {
                if (s.charAt(i) == s.charAt(j)) {
                    dp[i][j] = dp[i + 1][j - 1] + 2;
                    if (i < n1 && j >= n1) ans = Math.max(ans, dp[i][j]);   // 需要在两个字符串上都选择
                }
                else dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]);
            } 
        }
        return ans;
    }
}

1547. 切棍子的最小成本 https://leetcode.cn/problems/minimum-cost-to-cut-a-stick/

https://leetcode.cn/problems/minimum-cost-to-cut-a-stick/

在这里插入图片描述
示例:
在这里插入图片描述

记忆化搜索

通过 dfs 求从 i ~ j 之间切割的最小花费。

class Solution {
    int[][] memo;   // 记忆数组

    public int minCost(int n, int[] cuts) {
        Arrays.sort(cuts);
        this.memo = new int[cuts.length][cuts.length];
        return dfs(cuts, 0, cuts.length - 1, 0, n);
    }

    public int dfs(int[] cuts, int l, int r, int start, int end) {
        if (l > r) return 0;
        if (memo[l][r] != 0) return memo[l][r];
        int res = Integer.MAX_VALUE;
        for (int i = l; i <= r; ++i) {
            res = Math.min(res, end - start + dfs(cuts, l, i - 1, start, cuts[i]) + dfs(cuts, i + 1, r, cuts[i], end));
        }
        memo[l][r] = res;
        return res;
    }
}

递推dp

先留给读者思考了。(实际上是自己不太会改
思考不出来可以看 https://leetcode.cn/problems/minimum-cost-to-cut-a-stick/solution/qie-gun-zi-de-zui-xiao-cheng-ben-by-leetcode-solut/

在这里插入代码片

1000. 合并石头的最低成本 https://leetcode.cn/problems/minimum-cost-to-merge-stones/ ⭐⭐⭐⭐⭐

https://leetcode.cn/problems/minimum-cost-to-merge-stones/

在这里插入图片描述

这道题目的难度还是很大的,但是学会之后收益颇丰。

前置知识——前缀和

在这里插入图片描述

思路:寻找子问题

在这里插入图片描述

Q:什么时候输出-1呢?
A:从 n 堆变成 1 堆,需要减少 n - 1 堆,而每次合并都会减少 k - 1 堆,所以 n - 1 必须是 k - 1 的倍数。

记忆化搜索

将上面的思路转换成记忆化搜索。

class Solution {
    int[][][] memo;     // 记忆数组
    int[] s;            // 前缀和数组
    int k;

    public int mergeStones(int[] stones, int k) {
        int n = stones.length;
        if ((n - 1) % (k - 1) != 0) return -1;  // 返回-1
        this.s = new int[n + 1];
        // 计算前缀和数组
        for (int i = 0; i < n; ++i) {
            s[i + 1] = s[i] + stones[i];
        }
        this.k = k;
        this.memo = new int[n][n][k + 1];   // 表示从i~j合并成p堆的最低成本
        return dfs(0, n - 1, 1);    // 最后返回的是从0~n-1合并成1堆的最低成本
    }

    public int dfs(int l, int r, int p) {
        if (p == 1) return memo[l][r][p] = l == r? 0: dfs(l, r, k) + s[r + 1] - s[l];
        if (memo[l][r][p] != 0) return memo[l][r][p];   // 如果已经计算过了
        int res = Integer.MAX_VALUE;
        for (int i = l; i < r; i += k - 1) {
            res = Math.min(res, dfs(l, i, 1) + dfs(i + 1, r, p - 1));
        }
        return memo[l][r][p] = res;
    }
}

动态规划的时间复杂度 = 状态个数 × 单个状态的计算时间

时间复杂度: O ( N 3 ) O(N^3) O(N3)
空间复杂度: O ( N 2 K ) O(N^2K) O(N2K)

记忆化搜索的优化

在这里插入图片描述

class Solution {
    int[][] memo;     // 记忆数组
    int[] s;            // 前缀和数组
    int k;

    public int mergeStones(int[] stones, int k) {
        int n = stones.length;
        if ((n - 1) % (k - 1) != 0) return -1;  // 返回-1
        this.s = new int[n + 1];
        // 计算前缀和数组
        for (int i = 0; i < n; ++i) {
            s[i + 1] = s[i] + stones[i];
        }
        this.k = k;
        this.memo = new int[n][n];   // 表示从i~j合并成1堆的最低成本
        return dfs(0, n - 1);    // 最后返回的是从0~n-1合并成1堆的最低成本
    }

    public int dfs(int l, int r) {
        if (l == r) return 0;                   // 如果已经是一堆了
        if (memo[l][r] != 0) return memo[l][r];   // 如果已经计算过了
        int res = Integer.MAX_VALUE;
        for (int i = l; i < r; i += k - 1) {
            res = Math.min(res, dfs(l, i) + dfs(i + 1, r));
        }
        if ((r - l) % (k - 1) == 0) {           // 如果可以合并成一堆
            res += s[r + 1] - s[l];
        }
        return memo[l][r] = res;
    }
}

DP递推

直接写出来递推还是挺难的,但是可以从记忆化搜索的代码 1:1 翻译成递推 DP。

class Solution {
    public int mergeStones(int[] stones, int k) {
        int n = stones.length;
        if ((n - 1) % (k - 1) != 0) return -1;  // 返回-1
        // 计算前缀和数组
        int[] s = new int[n + 1];
        for (int i = 0; i < n; ++i) {
            s[i + 1] = s[i] + stones[i];
        }

        int[][] dp = new int[n][n];
        for (int i = n - 1; i >= 0; --i) {
            for (int j = i + 1; j < n; ++j) {
                dp[i][j] = Integer.MAX_VALUE / 2;
                for (int m = i; m < j; m += k - 1) {
                    dp[i][j] = Math.min(dp[i][j], dp[i][m] + dp[m + 1][j]);
                }
                if ((j - i) % (k - 1) == 0) dp[i][j] += s[j + 1] - s[i];
            }
        }
        return dp[0][n - 1];
    }
}

在这里插入图片描述
还是经典的那句话,不知道枚举的顺序,就看状态从哪里转移过来。

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

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

相关文章

LLM - Hugging Face 工程 BERT base model (uncased) 配置

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://blog.csdn.net/caroline_wendy/article/details/131400428 BERT是一个在大量英文数据上以自监督的方式预训练的变换器模型。这意味着它只是在原始文本上进行预训练&#xff0c;没有人以…

el-table多级表头处理方法,了解lebel和prop的真实含义,template的意义,减少全局定义变量。

Element - The worlds most popular Vue UI framework 官网地址 其原理只需要在 el-table-column 里面嵌套 el-table-column&#xff0c;就可以实现多级表头。 要实现的效果如下图所示&#xff1a; <div class"c-table" id"tablePrint"><el-tabl…

信号链噪声分析15

文章目录 概要整体架构流程技术名词解释技术细节小结 概要 提示&#xff1a;这里可以添加技术概要 模数转换器(ADC)将模拟量——现实世界中绝大部分现象的特征——转换为数字语言&#xff0c; 以便用于信息处理、计算、数据传输和控制系统。数模转换器(DAC)则用于将发送或存 储…

【学习笔记】Unity基础(九)【cinemachine基础(body、aim参数详解)】(多fig动图示范)

目录 一 速览1.1 cinemachine下载1.2 官方示例速览1.3 cinemachine定义 二 cinemachine详解2.1 Virtual Camera2.1.1 virtual camera参数通览2.1.2 Status2.1.3 有关Dutch angle2.1.4 Standby Update2.1.5 Transitions 2.2 virtual cameral关键参数详解2.2.1 Body2.2.1.1 Do No…

主辅助服务市场出清模型研究【旋转备用】(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

vue 3.0 + vite + flv 视频流播放

官方提供的 demo 地址&#xff0c;大家可以用自己的流地址&#xff0c;先试试是否符合需求&#xff1b; http://bilibili.github.io/flv.js/demo/ Flv.js API https://gitee.com/mirrors/flv.js/blob/master/docs/api.md 安装 Flv.js npm install --save flv.js更改 tscon…

软件测试工程师的工作内容?告诉你们什么是真正的测试工程师

目录 前言 1.何为软件测试工程师&#xff1f; 2.软件测试工程师的职责&#xff1f; 3.为什么要做软件测试&#xff1f; 4.软件测试的前途如何&#xff1f; 5.工具和思维谁更重要&#xff1f; 6.测试和开发相差大吗&#xff1f; 7.成为测试工程师的必备条件 8.测试的分…

局域网远程连接

一根网线连接两台电脑 前言步骤1 设置B“允许远程连接”2 A和B必须在同一个网段下面3 “winr”&#xff0c;输入“mstsc”中4 弹出“远程桌面连接”窗口&#xff0c;输入B的ip地址和B电脑的用户名及密码&#xff08;winL键锁屏&#xff0c;看看B的用户名和密码是什么&#xff0…

【正点原子STM32连载】 第四十五章 FLASH模拟EEPROM实验 摘自【正点原子】STM32F103 战舰开发指南V1.2

第四十五章 FLASH模拟EEPROM实验 STM32本身没有自带EEPROM&#xff0c;但是STM32具有IAP&#xff08;在应用编程&#xff09;功能&#xff0c;所以我们可以把它的FLASH当成EEPROM来使用。本章&#xff0c;我们将利用STM32内部的FLASH来实现第三十六章实验类似的效果&#xff0…

MVTEC 3D dataset

官网&#xff1a;https://www.mvtec.com/company/research/datasets/mvtec-3d-ad/downloads https://www.mvtec.com/company/research/datasets/mvtec-3d-adhttps://www.mvtec.com/company/research/datasets/mvtec-3d-ad 数据大小&#xff1a;13个G 1. 介绍 MVTec 3D异常检测…

OpenCV 学习笔记(C++)(1.4W字)

一切图像皆Mat OpenCV中图像对象的创建与复制 Mat基本结构 Mat对象数据组成&#xff1a;头部和数据部分&#xff0c;头部存储图像的属性&#xff08;大小、宽高、图像类型&#xff1a;浮点数类型、字节类型、16位整型、32位整型、双精度浮点型&#xff0c;通道数量和获取途径…

【Soft-prompt Tuning for Large Language Models to Evaluate Bias 论文略读】

Soft-prompt Tuning for Large Language Models to Evaluate Bias 论文略读 INFORMATIONAbstract1 Introduction2 Related work3 Methodology3.1 Experimental setup 4 Results5 Discussion & Conclusion总结A Fairness metricsB Hyperparmeter DetailsC DatasetsD Prompt …

【CSS3系列】第八章 · 伸缩盒模型

写在前面 Hello大家好&#xff0c; 我是【麟-小白】&#xff0c;一位软件工程专业的学生&#xff0c;喜好计算机知识。希望大家能够一起学习进步呀&#xff01;本人是一名在读大学生&#xff0c;专业水平有限&#xff0c;如发现错误或不足之处&#xff0c;请多多指正&#xff0…

服务器配置与操作

服务器配置与操作 一、连接远程服务器 推荐用xshell 或者 finalshell 或者 winSCP 或者 FileZilla xshell下载地址&#xff1a;https://xshell.en.softonic.com/ 二、服务器配置 2.1 安装JDK 2.1 方法一&#xff1a;在线安装 yum list java* yum -y install java-1.8.0-ope…

利用jmeter测试java请求

jmeter和loadrunner一样包含了测试脚本开发、测试执行、以及测试结果统计三个部分。只是jmeter没有脚本开发工具&#xff0c;因此测试java请求的脚本选择在eclipse中进行。 首先介绍如何用eclipse编写接口性能测试脚本。 针对"Java请求"类型的测试&#xff0c;需要…

系列五、NotePad++下载安装

一、下载 链接&#xff1a;https://pan.baidu.com/s/1U2f74vfBJIds7W2wJYnBxg?pwdyyds 提取码&#xff1a;yyds 二、安装 2.1、安装NotePad 解压NotePad-x64.zip至指定目录即可&#xff0c;例如 2.2、安装NppFTP 2.2.1、查看NotePad对应的位数&#xff08;32位or64位&a…

文本分析-使用jieba库实现TF-IDF算法提取关键词

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

程序员找工作难!拿到外包公司的 offer 我应该去么?

引言 前一阵子有一个帖子引起了非常广泛的讨论&#xff0c;描述的就是一个公司的外包工作人员&#xff0c;加班的时候因为吃了公司给员工准备的零食,被公司的HR当场批评&#xff01;这个帖子一发出来&#xff0c;让现在测试行业日益新增的外包公司备受关注。那么外包公司和非外…

驱动开发:内核读写内存多级偏移

让我们继续在《内核读写内存浮点数》的基础之上做一个简单的延申&#xff0c;如何实现多级偏移读写&#xff0c;其实很简单&#xff0c;读写函数无需改变&#xff0c;只是在读写之前提前做好计算工作&#xff0c;以此来得到一个内存偏移值&#xff0c;并通过调用内存写入原函数…

【RF-SSA-LSTM】随机森林-麻雀优化算法优化时间序列预测研究(Python代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…