关于动态规划,你想知道的都在这里了!

作者 | Your DevOps Guy
翻译| 火火酱~,责编 | 晋兆雨

出品 | AI科技大本营

头图 | 付费下载于视觉中国

什么是动态规划?它又有什么重要的呢?

在本文中,我将介绍由Richard Bellman在20世纪50年代提出的动态规划(dynamic programming)概念,这是一种强大的算法设计技术——将问题分解成多个小问题,存储它们的解,通过将其结合在一起,最终得到原始问题的解决方案。

FAANG编程面试中最难的问题通常都属于这一类。你在面试的过程中也很可能会被要求解决这样的问题,因此,了解这项技术的重要性自然不言而喻。接下来,我将解释什么是动态规划,给出一个解决动态规划问题的秘诀,并且和大家一起分析几个示例,以便你能够更好地理解其应用场合和应用方法。

和我以往有关编程面试的文章一样,在本文中,我将分享自己在使用这种方法解决问题时的思考过程,这样当你在面对其中一个问题时,按照这个过程一定也能解决。不需要死记硬背,我们只需要通过了解技术和实践,将想法转化成代码技能。编程的重点不在于学习编程语言,而在于分析问题,考虑不同的解决方案,从中选出最优解,然后通过某种编程语言将其转化为现实。

动态规划

动态规划是一种解决最优化、搜索和计数问题的通用技术,这些问题都可以被分解为多个子问题。要应用动态规划,问题就必须具备以下两个属性:

  • 最优子结构(Optimal substructure)

  • 重叠子问题(Overlapping subproblems)

(1)最优子结构

如果大小为n的问题的最优解可以由大小小于n的问题的同一实例的最优解推导出,则该问题具有最优子结构。

例如,如果从巴黎到莫斯科的最短路径会经过柏林,那么可以由巴黎到柏林的最短路径和柏林到莫斯科的最短路径组成。

如果一个问题可以通过组合非重叠子问题的最优解来解决,这种策略被称为分治法。这就是归并排序和快速排序不属于动态规划问题的原因。

(2)重叠子问题

举一个大家都很熟悉的例子,斐波那契数列,即从第三项开始,每一项都等于前两项之和。斐波那契数列可以表示为

F(0) = F(1) = 1
F(n) = F(n-1) + F(n-2)

大家都说一张图片胜过千言万语,所以......(摘自《Elements of programming interviews》)             

要想解出F(n),就需要解出F(n-1)和F(n-2),但是F(n-1)又需要F(n-2)和F(n-3)。这样一来,F(n-2)是重复的,来自于同一个问题的两个不同实例——计算一个斐波那契数。

这可以用一个递归函数来表示:

  • 要想解决一个大小为n的问题,我们可以调用相同的函数来解决同一问题的一个实例,但实例规模比原始问题规模小一些。

  • 我们一直不断地调用该函数,直到到达基础用例,也就是停止条件,在此处即n = 0或n = 1。

这就引出了递归和动态规划之间的关系。

递归和动态规划

从概念上讲,动态规划涉及到递归问题。我们希望通过同一个问题的较小实例来解决原始问题,而递归是在代码中实现这一点的最佳选择。与纯递归函数的不同之处在于,我们将用空间来换取时间:我们将存储各子问题的最优解,进而高效地找到原始问题的最优解。

当然,这并不是说我们都必须使用递归来解决动态规划问题。还可以通过一种迭代方法来编写动态规划解决方案。

自下而上的动态规划

我们需要将所有子问题的解决方案填入表格(从基本用例开始),并用它来构建我们正在寻找的解决方案。这个过程是通过迭代的方式完成的,你可以从下面列别中任选其一作为存储子问题解决方案的数据结构:

  • 多维(以及一维)数组——最常用的方法;

  • 哈希表;

  • 二叉搜索树;

自上而下的动态规划

编写递归算法并添加缓存层,以避免重复的函数调用。

或许现在看起来有点糊涂,但等一会儿讲到示例后,一切都会清楚得多。

如何解决动态规划问题

如果一个问题想要通过动态规划来解决的话,就必须具备最优子结构和重叠子问题这两个属性。当直觉告诉你动态规划或许是一个可行的解决方案时,你需要验证其是否具备这两个属性。

下面让我们试着感受一下,什么样的问题可以用动态规划来解决。一切以“找到”开头的问题:

  • ... ...的前n个元素;

  • ... ...的所有方式;

  • 有多少种... ...方式;

  • 第n个... ... ;

  • ... ...的最优解决方案;

  • ... ...的最短小/最大/最短路径;

都是潜在“候选人”。

解决动态规划问题的步骤

很不幸,解决动态规划问题并没有什么通用秘诀。我们需要在经历过很多问题之后,才能逐渐掌握其诀窍。这确实不容易,毕竟这可能会是你在面试中遇到的最难的问题了。但也先不要气馁,简单来讲,就是用相对简单的工具针对问题进行建模,并不需要花哨的数据结构或算法。

我已经解决过很多此类问题了,但有时还是会觉得毫无头绪,找不到解决方法。练习得越多,就越容易。以下这几点或许能带你走近解决动态规划问题的秘诀:

  • 证明重叠子问题和次优结构特性。

  • 定义子问题。

  • 定义递归。

  • 编写自上而下或自下而上的动态规划解决方案。

复杂度分析因问题而异,但一般来说,时间复杂度可以表示为:

	时间~子问题个数*每个子问题的时间

计算自下而上解决方案的空间复杂度很简单,因为其等于存储子问题解决方案所需的空间(多维数组)。

示例

我已经根据所涉及的独立维度的数量对问题进行了分类。这一步并不是必须的,但我发现在设计解决方案时,遵循一定的心理模型是非常有用的。随着编写的代码越来越多,你会找到一些模式,而这就是其中之一。不妨试一下,如果觉得有用的话就用起来吧。

一维问题

(1)斐波那契

因为现在大家都已经对这个问题非常熟悉了,所以我就直接给出递归解决方案:

int fib(int n) {if (n == 0 || n == 1)return 1;elsereturn fib(n - 1) + fib(n - 2);}
}

从递归到自上而下的过程通常都有固定的模式:

  • 检查我们需要的值是否已经在缓存中了。如果是,就返回它。

  • 如果不是,就在返回之前先缓存的解决方案。

int fib(int n) {vector<int> cache(n + 1, -1);return fib_helper(n, cache);
}
int fib_helper(int n, vector<int> &cache) {if(-1 != cache[n])return cache[n];if (n == 0 || n == 1)cache[n] = 1;elsecache[n] = fib_helper(n - 1, cache) + fib_helper(n - 2, cache);return cache[n];
}

这里,用到自下而上的解决方案,我们通过构建一个表(从基本用例为起点),来形成要找的问题的解决方案。这个表是一个一维数组:我们只需要存储较小的问题的解,就可以推导出原始问题的解。

int fib(int n) {vector<int> f(n + 1, 0);  f[1] = 1;for(int i = 2; i <= n; i++)f[i] = f[i - 1] + f[i - 2];return f[n];
}

额外空间优化

这种方法可以进一步优化内存,但并不会优化时间(也可以通过其他技术更快地计算斐波纳契数列,但这就是另一篇文章的内容了),只需要使用3个变量,而不必使用数组,因为我们只需要跟踪两个值,即f (n - 1)和f (n - 2),就可以得到我们想要的输出——f (n)。

int fib(int n) {  if (n == 0 || n == 1)return 1;//Variables that represent f(n - 1), f(n - 2) and f(n)int n1= 1, n2 = 1, f = 0;for (int i = 2; i <= n; i++) {f= n1 + n2;n2 = n1;n1 = f;}return f;
}

这种方法更高级,也更常见。如果只需要跟踪:

  • 几个变量,或许我们就可以摆脱一维数组,将其变成几个变量。

  • 二维矩阵中的几行,或许我们可以将其减少成几个一维数组。

  • 等等... ...

通过降维,我们提高了空间的复杂度。现在,你不必记住所有的细节,但在进行过一些实践之后,要试着自己提出这些优化方案,从而增强自己分析问题并将想法转化为代码的能力。在面试中,我会选择更简单的版本,只讨论潜在的优化方案。只有在编写了自己的“标准化”动态规划解决方案,并且时间充足的时候,才动手实施这些优化。

(2)爬楼梯

假设你正在爬一段有n个台阶的楼梯,每次可以爬1或2个台阶。那么要想爬到顶端的话,一共有多少种不同的方法呢?

例1:

  • 输入:2

  • 输出:2

  • 说明:有两种方法可以爬到顶端:1阶+1阶和2阶

例2:

  • 输入:3

  • 输出:3

  • 说明:有三种方法可以爬到顶端:1阶+ 1阶+ 1阶,1阶+ 2阶,2阶+ 1阶

解决方案

先试着自己解决一下这个问题。你可能会想到一个递归解决方案。回顾一下我的说明和前面的示例,看看是否可以自行编写出自上而下的的解决方案。

提示一下:既然这个问题以“有多少种方式”开头,那就应该能想到采用动态规划的潜在可能性。

在这种情况下,如果想要到达第N阶,就要经过第N-1阶或第N-2阶,因为一次可以爬1阶或2阶。如果我们能解决这两个子问题的话,就可以找到一般问题的解。我们将f(N)称为到第N阶的方法数。

  • 要得到f(N),就要先求出f(N-1)和 f(N-2)。

  • 要得到f(N-1),就要先求出f(N-2)和 f(N-3)。

  • 要得到f(N-2),就要先求出f(N-3)和 f(N-4)。

不需要继续罗列下去了吧,你应该已经发现了:

  • 这个问题有重叠的子问题:你需要多次计算f(N-2), f(N-3), f(N-4),... ...

  • 这个问题向我们展现了最优子结构:通过f(N-1)和f(N-2)的最优解,可以得到f(N)的最优解。

这表示我们可以通过动态规划来求解该问题。

因为我已经在上一个例子中写过代码了,所以这里就不再写代码了。

大家可以在下方链接中试着编写并测试一下自己的解决方案。

(链接地址:https://leetcode.com/problems/climbing-stairs/?ref=hackernoon.com)

(3)最长递增子序列

给定一个未排序的整数数组,求最长递增子序列的长度。

例如,对于数组[10,9,2,5,3,7,101,18]而言,其输出为4,即序列[2,3,7,101]

解决方案

我们需要找到大小为n的数组的最长递增子序列的长度。这听起来像是一个可以通过动态规划来解决的优化问题,那么让我们来试一下。假设我们已经有了大小为N的问题的解,称其为s(n),然后我们在数组中增加了一个额外元素,称为Y。那么,你能重复使用X的解决方案来解决这个新问题么?这个问题通常会为我们带来一些启发。

在这里,我们需要知道新元素是否可以扩展任一现有序列:

  • 迭代数组中的每一个元素,我们称其为X。

  • 如果新元素Y大于X,那么序列可以扩展一个元素。

  • 如果我们已经存储了所有子问题的解,那么获取新长度是非常简单的——只需在数组中进行查找即可。我们可以根据子问题的最优解得出新问题的解。

  • 返回新的最长递增子序列的长度。

我们似乎得到了某种算法。继续我们的分析:

  • 最优子结构:我们已经证明了大小为n的问题的最优解可以由子问题的最优解计算出来。

  • 重叠子问题:要想计算s(n),则需要s(0), s(1),... ...,s(n-1)。同样,要计算s(n-1),则需要s(0), s(1),... ...,s(n-2)。同样的问题需要进行多次计算。

以下是该自下而上解决方案的代码。

int lengthOfLIS(const vector<int>& nums) {
if(nums.empty())
return 0;
vector<int> dp(nums.size(), 1);
int maxSol = 1;
for(int i = 0; i < nums.size(); ++i){
for(int j = 0; j < i; ++j){
if(nums[i] > nums[j]){
dp[i] = max(dp[i], dp[j] + 1);
}
}
maxSol = max(maxSol, dp[i]);
}
return maxSol;   
}

大家可以在下方链接中试着编写并测试一下自己的解决方案。

(链接地址:https://leetcode.com/problems/longest-increasing-subsequence/?ref=hackernoon.com)

(4)有多少BST

对于给定的n,有多少结构唯一的存储值为1... ...n的BST(二叉搜索树)?

例子:

  • 输入:5

  • 输出:42

  • 说明:给定n = 5, 总共有42个唯一的BST

解决方案

我们一起来看看这个例子。假设我们有数字1、2、3、4、5,如何定义BST?

我只需要选择其中一个数作为根,先假设其为数字3,则:

  • 3是根

  • 3的左边是数字1和2

  • 3的右边是数字4和5

我们可以解决(1,2)和(4,5)的相同子问题(暂且称其为解决方案L和R),数一数以3为根可以形成多少个BST,即L*R。如果我们对每一个可能的根都这样做,并且把所有的结果相加的话,就可以得到我们所需的解决方案C(n)。如你所见,有条不紊地从几个例子出发,可以帮助我们更好地设计算法。

其实,我们只需要:

  • 选一个元素作为BST的根;

  • 解决(1到根-1)和(根+1到n)两个数字的相同问题;

  • 将每个子问题的两个结果相乘;

  • 将其加到我们的运行总计上;

  • 继续下一个根;

实际上,我们并不关心数组两边的数字是什么。我们只需要子树的大小,即根的左右两边的元素个数。这个问题中的每个实例都会产生相同的结果。在之前的例子中,L和R都是C(2)的解。我们只需要计算一次C(2),缓存,然后重复使用即可。

int numTrees(int n) {
vector<int> dp(n + 1, 0);
dp[0] = 1;
dp[1] = 1;
for(int i = 2; i <= n; ++i){
for(int j = 0; j < i; ++j){
dp[i] += dp[j] * dp[i - 1 - j];
}
}
return dp.back();
}

大家可以在下方链接中试着编写并测试一下自己的解决方案。

(链接地址:https://leetcode.com/problems/unique-binary-search-trees/?ref=hackernoon.com)

二维问题

这些问题通常比较难建模,因为它涉及两个维度。常见的例子是,在两个字符串中迭代,或移动映射。

  • 自上而下的解决方案和之前没有太大的区别:找到递归并使用缓存。

  • 对于自下而上的解决方案,一个2D数组就足以存储结果了。像我之前提到的,可能会减少一个或几个一维数组,但是没有必要太在意。之所以提到这一点只是以防你在解决问题时看到会有点摸不着头脑。

我曾在另一篇文章中说过,学习是迭代的。首先,要把注意力集中在理解基础知识上,然后再一点一点地增加更多的细节。

(1)最小路径和

给定m×n的非负数网格,找出一条从左上到右下的路径,使路径上所有数字之和最小。

注意:你只能选择向下移动或向右移动。

例子:

  • 输入:[ [1,3,1], [1,5,1], [4,2,1] ]

  • 输出:7

  • 说明:因为路径1→3→1→1→1总和最小

解决方案

最小化问题应该会让你想到动态规划。进一步分析,路径可以经过任意单元格C(i,j)(即不在上边框或左边框),单元格A = (i-1, j)和B=(i,j-1)。由此,我们发现,有些问题需要进行多次计算。此外,我们如果知道A和B的最优解,就可以计算出当前单元格的最优解为min(sol (A),sol(B)) + 1,因为我们只能通过当前单元格来表示A或B,要想移动到当前单元格就需要多走一步。换句话说,这是一个最优子结构和重叠问题。我们可以使用动态规划。

下面是由下而上的解决方案。

int minPathSum(const vector<vector<int>>& grid) {
const int nrow = grid.size();
if(nrow == 0)
return 0;
const int ncol = grid[0].size();
vector<vector<int>> minSum(nrow, vector<int>(ncol, 0));
minSum[0][0] = grid[0][0];
for(int col = 1; col < ncol; ++col)
minSum[0][col] = minSum[0][col - 1] + grid[0][col];
for(int row = 1; row < nrow; ++row)
minSum[row][0] = minSum[row - 1][0] + grid[row][0];
for(int col = 1; col < ncol; ++col){
for(int row = 1; row < nrow; ++row){
minSum[row][col] = min(minSum[row - 1][col], minSum[row][col - 1]) + grid[row][col];
}
}
return minSum[nrow - 1][ncol - 1];
}

边界条件定义在矩阵的边界上。只有一种方法可以获得边界上的点:从上一个点向右或向下移动一个格子。

大家可以在下方链接中试着编写并测试一下自己的解决方案。

(链接地址:https://leetcode.com/problems/minimum-path-sum/?ref=hackernoon.com)

(2)背包问题

给定两个整数数组val[0..n - 1]和wt [0 . .n-1],分别表示与n个物品相关的值和权重。同时,给定一个代表背包容量的整数W,求val [ ]的最大值子集,保证这个子集的权重之和小于或等于W。背包里的物品都必须保持完整,要么选择完整的物品,要么不选(0 - 1属性)。

解决方案

试着想出一个递归解决方案。在此基础上,添加一个缓存层,我们就会得到一个自上而下的动态规划解决方案了!

意思是,对于每一样物品,我们都有两个选择:

  • 我们可以把这样物品放到背包里(如果合适的话),背包的总价值增加,容量减少。

  • 我们可以放弃这样物品,背包里的价值和容量不变。

在试过所有组合之后,我们只选择最大值。这个过程极其缓慢,但却是迈向最终解决方案的第一步。

我们必须在两个选项之间做出决定(向集合中添加一个元素或跳过它),在许多问题中都面临这样的选择,所以我们一定要了解,并理解其应用场合和方式。

// Recursive. Try to turn this into a piece of top-down DP code.int knapSack(int W, int wt[], int val[], int n) {if (n == 0 || W == 0)return 0;if (wt[n - 1] > W)return knapSack(W, wt, val, n - 1);elsereturn max(val[n - 1] + knapSack(W - wt[n - 1],  wt, val, n - 1), knapSack(W, wt, val, n - 1));
}
以下是由下而上的解决方案:
// C style, in case you are not familiar with C++ vectorsint knapSack(int W, int wt[], int val[], int n) {int i, w;int K[n + 1][W + 1];for (i = 0; i <= n; i++) {for (w = 0; w <= W; w++) {if (i == 0 || w == 0)K[i][w] = 0;else if (wt[i - 1] <= w)K[i][w] = max( val[i - 1] + K[i - 1][w - wt[i - 1]], K[i - 1][w]);elseK[i][w] = K[i - 1][w];}}return K[n][W];
}

(3)最长公共子序列

给定两个字符串text 1和text 2,返回它们最长公共子序列的长度。

字符串的子序列是在不改变其余字符相对顺序的情况下,从原字符串中删除一些字符(也可以不删除)后生成的新字符串,例如,“ace”是“abcde”的子序列,但“aec”不是。两个字符串共同的子序列就被称为其公共子序列。

如果没有公共子序列的话,则返回0。

例子:

  • 输入:text 1 = “abcde”, text 2 = “ace”

  • 输出:3

  • 说明:最长公共子序列是 “ace” ,且其长度为3

解决方案

同样,计算最长X的问题,动态规划应该可以帮得上忙。

鉴于大家已经有了一些动态规划的经验了,我就直接从示例中的两个属性说起。我们将字符串称为A和B,这个问题的解为f(A, B),解题思路是看最后两个字符是否相等:

  • 如果相等,那么LCS的长度至少为1。我们需要调用f(A[0:n-1], B[0:n-1])来查找该索引前的LCS,并加1,因为A[n]和B[n]是相同的。

  • 如果不相等,我们就删除两个字符串的最后一个字符——一次删一个,并查找生成LCS的路径。换句话说,我们取f(A[0: n-1], B)和f(A, B[0:n-1])的最大值。

  • 重叠子问题:我们来看看可能会出现的调用:(“abcde”, “ace”)产生x1 = (“abcd”, “ace”)和y1 = (“abcde”, “ac”);x1将产生x12 = (“abc”, “ace”) 和y12= (“abcd”, “ac”);y1将产生(“abcd”, “ac”)和(“abcde”, “a”)。如你所见,同样的问题需要计算很多次。

  • 最优子结构:与最长递增子序列非常类似。如果我们在其中一个字符串A’中添加一个额外的字符,就可以从所有的缓存结果中快速计算出解决方案,而这些结果是我们解A和B得到的。

虽然用例子来证明理论并不是开始数学证明的好方法,但是对于应付编程面试来说已经绰绰有余了。

int longestCommonSubsequence(const string &text1, const string &text2) {
const int n = text1.length();
const int m = text2.length();
vector<vector<int>> dp(n + 1, vector<int>(m + 1,0));
for(int i = 1; i <= n; i++){for(int j = 1; j <= m; j++){if(text1[i-1] == text2[j-1])dp[i][j] = dp[i-1][j-1]+1;else dp[i][j] = max(dp[i-1][j], dp[i][j-1]);}
}
return dp[n][m];
}

大家可以在下方链接中试着编写并测试一下自己的解决方案。

(链接地址:https://leetcode.com/problems/longest-common-subsequence/?ref=hackernoon.com)

总结

我们一定要了解这些问题,因为很多其他问题都只是在此基础上的变种而已,但是不要死记硬背。主动去了解动态规划的使用场景和应用方式,并坚持练习,直到可以轻松地将自己的想法转换成代码。如你所见,这是需要讲究方法的。我们不一定要用到高级的算法或数据结构知识才能解决问题,数组就足够了。

我没有完成时间/空间复杂度分析,大家可以把它作为课后练习。欢迎大家随时在评论中留言,提出问题或分享观点。

原文链接:

https://hackernoon.com/all-you-need-to-know-about-dynamic-programming-0tj3e5l

本文由AI科技大本营翻译,转载请注明出处

未来智能实验室的主要工作包括:建立AI智能系统智商评测体系,开展世界人工智能智商评测;开展互联网(城市)云脑研究计划,构建互联网(城市)云脑技术和企业图谱,为提升企业,行业与城市的智能水平服务。

  如果您对实验室的研究感兴趣,欢迎加入未来智能实验室线上平台。扫描以下二维码或点击本文左下角“阅读原文”

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

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

相关文章

linux修改永久ip地址,centos设置IP地址,永久修改ipv4

# ifconfig #查看下本机的IP地址。eth0Link encap:Ethernet HWaddr 00:50:56:0A:0B:0Cinet addr:192.168.0.3 Bcast:192.168.0.255 Mask:255.255.255.0inet6 addr: fe80::250:56ff:fe0a:b0c/64 Scope:LinkUP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1RX packets:172220…

详细介绍MySQL/MariaDB的锁

官方手册&#xff1a;https://dev.mysql.com/doc/refman/5.7/en/innodb-locking-transaction-model.html 1.事务提交的方式 在MariaDB/MySQL中有3种事务提交的方式。 1.显式开启和提交。 使用begin或者start transaction来显式开启一个事务&#xff0c;显式开启的事务必须使用c…

美国专利商标局发布人工智能专利扩散分析报告

以下文章来源&#xff1a;中科院知识产权信息&#xff0c;2020-11-23报告显示&#xff0c;从2002到2018年&#xff0c;美国人工智能专利的年申请量增长超过100%&#xff0c;从每年3万件增加到6万多件&#xff0c;含人工智能的专利申请所占份额从9%上升到近16%。同时&#xff0c…

java 开发环境的搭建

这里主要说的是在windows 环境下怎么配置环境。 1.首先安装JDK java的sdk简称JDK &#xff0c;去其官方网站下载最近的JDK即可。。http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html点击下载好的exe文件安装即可。 2.接下来我们需要配置环…

linux内核等价多路径路由,Linux内核分析 - 网络[四]:路由表

路由表的创建inet_init() -> ip_init() -> ip_fib_init() -> fib_net_init() -> ip_fib_net_init()[net\ipv4\fib_frontend.c]首先为路由表分配空间&#xff0c;这里的每个表项hlist_head实际都会链接一个单独的路由表&#xff0c;FIB_TABLE_HASHSZ表示了分配多少个…

2017级面向对象程序设计 作业二

以下均以扫描方式为例&#xff0c;即电梯只会在最底层和最高层选择掉头&#xff0c;路途中遇到路径方向相同的乘客将他带上电梯。 文字描述面向过程实现的步骤&#xff1a; 一. 定义有关电梯的变量&#xff0c;如&#xff1a;1.电梯当前所在楼层.&#xff0c;2. 电梯内的人数&a…

新型支架状电极允许人类思想操作计算机

Illustration: Synchron来源&#xff1a;IEEE电气电子工程师据悉&#xff0c;两名患有神经肌肉疾病的澳大利亚人在他们的大脑中植入了支架状的电极&#xff0c;使他们能够利用自己的思想操作电脑&#xff0c;从而恢复了一些个人独立性。据发明者介绍&#xff0c;这是这种被称为…

java中的foreach

foreach 并不是java中的关键字&#xff0c;是for语句的特殊简化版&#xff0c;在比那里数组&#xff0c;集合时&#xff0c;foreach更加简单快捷&#xff0c;从字面上的意思理解 foreach 也就是 “ for每一个 ”的意思&#xff0c;那么到底怎么使用 foreach语句呢&#xff1f; …

ACM数论-素数

ACM数论——素数 素数定义&#xff1a; 质数&#xff08;prime number&#xff09;又称素数&#xff0c;有无限个。质数定义为在大于1的自然数中&#xff0c;除了1和它本身以外不再有其他因数&#xff0c;这样的数称为质数。例 子&#xff1a;2、3、5、7、11、13、17、19。&am…

机器视觉中彩色成像必须考虑的十个问题

来源&#xff1a;Imagination Tech在为你的产品开发最适合的机器视觉系统时&#xff0c;需要考虑很多因素&#xff0c;以下列出开发过程中需要考虑的一些问题&#xff1a;颜色准确性/差异化首先要考虑的是应用程序所需的颜色精度和差异程度。在某些应用中&#xff0c;机器视觉相…

嫦娥“挖土”归来有多难?看看中国首颗返回式卫星的故事

本文转载自“科技日报&#xff08;kjrbwx&#xff09;”&#xff0c;原标题《嫦娥“挖土”归来有多难&#xff1f;看看中国首颗返回式卫星的故事》&#xff0c;作者 | 吕炳宏 付毅飞2020年11月30日&#xff0c;嫦娥五号探测器在环月轨道上&#xff0c;成功实施着陆器上升器组合…

重磅,2020年度第十届吴文俊人工智能科学技术奖获奖名单公示

来源&#xff1a;科奖圈根据《吴文俊人工智能科学技术奖励条例》和《吴文俊人工智能科学技术奖励实施细则》相关规定&#xff0c;经全国各地方人工智能学会、协会及联盟&#xff0c;各高校及科研&#xff08;院&#xff09;所&#xff0c;学会各专业委员会及工作委会&#xff0…

理解 %IOWAIT (%WIO)

%iowait 是 “sar -u” 等工具检查CPU使用率时显示的一个指标&#xff0c;在Linux上显示为 %iowait&#xff0c;在有的Unix版本上显示为 %wio&#xff0c;含义都是一样的。这个指标常常被误读&#xff0c;很多人把它当作I/O问题的征兆&#xff0c;我自己每隔一段时间就会遇到对…

自由意志不存在?神经科学能证明不?

来源&#xff1a; 神经现实本文经授权摘自《认知科学对当代哲学的挑战》作者&#xff1a;李恒威神经科学能说明自由意志不存在吗?里贝特是人类意识和自由意志的实验研究领域的先驱性神经科学家&#xff0c;但驱使他开展意识的实证研究的根本动因是回应意识科学研究中的本体论问…

MySQL数据库order by 奇慢无比

今天遇到个奇葩的问题&#xff0c; sql 数据量很大 有where 和order by&#xff0c;不加order by 速度很快&#xff0c;加了就很慢 一、首先我们对这条sql执行查询计划&#xff1a; explain select t.order_id from book_order t ORDER BY t.order_id desc explain select t.…

PNAS “深度学习的科学”论文合集导读

来源&#xff1a;混沌巡洋舰今天的科学家对于机器可以学习做什么的想法与我们10年前完全不同。在图像处理、语音和视频处理、机器视觉、自然语言处理和经典的双人游戏中&#xff0c;特别是在过去的十年中&#xff0c;随着在一系列公共组织的挑战问题&#xff08;例如围棋&#…

s3c6410 jpeg编码 linux,S3C6410 裸机硬件JPEG解码

主函数的部分代码/**************************************************************************************************************************函数 : static PIC_ERROR OpenPictureFile(const char *FileName,u8 *buff,u32 FileMaxSize)*功能 : 打开一张…

注解原理

学习spring时&#xff0c;大量使用了注解&#xff0c;但一直对其底层实现机制不得其解&#xff1a; ref&#xff1a;http://www.cnblogs.com/Johness/archive/2013/04/17/3026689.html ref&#xff1a;https://www.jianshu.com/p/28edf5352b63 ref&#xff1a;http://www.cnblo…

C语言中关于字符数组输入,scanf没执行

问题&#xff1a;有时C语言中关于字符数组输入&#xff0c;有的scanf语句没执行。 未执行scanf的代码&#xff1a; #include<stdio.h> int main() {int n;int t0;scanf("%d",&n);char arr[n];int i;for(i0;i<n;i){scanf("%c",&arr[i]);…

清华微电子副所长尹首一:中国AI芯片的技术路线最全面

大数据文摘出品整理&#xff1a;牛婉杨12月1日&#xff0c;GTIC 2020 AI芯片创新峰会在京举办&#xff0c;本次峰会聚集了AI芯片以及各个细分赛道的产、学、研精英人士&#xff0c;共议AI芯片在中国半导体黄金时代的创新与未来。2020年对于AI芯片来说&#xff0c;是充满坎坷的一…