递推、递归:将大目标分解为小目标的处理方法

【信息科学与工程学】【数据科学】数据科学领域 第六篇 算法设计 01 算法设计 阅读详情

一、总述

递推、递归是计算机算法设计中常用的集中基础算法。
递推是从第一步开始,根据一个特定的规律或转移方程层层推进,直到推出答案所在状态位置为止。
递归是从第一个可能性开始,枚举所有的可能性,并且根据这些可能性来找出最后的答案。

递推经常被用作 DP 中实现状态转移,递归会常常被用作 DFS 枚举所有的可能性来找到正确答案,而分治的用途十分广泛,可以使用分治来获得更高的效率。

关于时间复杂度,递推的时间复杂度通常为线型 O ( n ) O(n) O(n) ,递归的时间复杂度通常为多项式级 O ( n k ) O(n^k) O(nk) 或者指数级 O ( k n ) O(k^n) O(kn) 或者阶乘级 O ( n ! ) O(n!) O(n!) 。

二、递推:一步一步靠近正确答案的状态

递推算法要根据一个特定的情况和规律,来通过一个转移其状态的状态转移方程来靠近答案所在的状态,或者通过一个特定的规律,得出下一次要进行的变换。
这里,状态是指一个使用递推算法求值的数组,状态中的值就是我们递推过程中对于每一个状态通过一个特定的等式求得的值,这个等式就是状态转移方程,表示由一个状态转移到另一个状态时的变化规律,一般由题目中给出的已知条件通过数学计算或者逻辑推理得出。

比如,我们有一个斐波那契数列:
1 , 1 , 2 , 3 , 5 , 8 , 13 , 21 , … 1,1,2,3,5,8,13,21,\dots 1,1,2,3,5,8,13,21,…
其中,我们设立一个状态:
令 f i f_i fi​ 表示斐波那契数列的第 i i i 项的值。由我们看到的数列规律,即第一和第二项都是 1 1 1 ,后面的每一项都是前两项的和,则我们可以得出以下的状态转移方程:
f i = { 1 ( i = 1 , 2 ) f i − 1 + f i − 2 ( i ∈ N , i ≥ 2 ) f_i= \begin{cases} 1&(i=1,2) \\ f_{i-1}+f_{i-2}&(i\in\mathbb{N},i\ge2) \end{cases} fi​={1fi−1​+fi−2​​(i=1,2)(i∈N,i≥2)​
这样,我们就完成了对于斐波那契数列第 k k k 项 ( 1 ≤ k ) (1\le k) (1≤k) 的计算。 C++ 语言实现的代码如下:

f[1] = 1; 						// 数组的初始化
for (int i = 2; i <= 1000; ++i) // 这里我们假设枚举到第 1000 项
{
	f[i] = f[i - 1] + f[i - 2];	// 状态转移方程
	// 我们这里做了一个简单的优化,将原来的 f[2] = 1 初始化条件取消了
	// 其原因是 C++ 中数组的初始化值就是 0 ,而 f[2] = f[0] + f[1] = 0 + 1 = 1 ,不必再次重新初始化
}

如上是一个简单的示例,下面我们看一道比较需要观察能力的题目(来自《算法竞赛进阶指南》),本题不需要有一个特定的状态转移方程:

费解的开关
—— 时间限制: 1 , 000  ms 1,000 \text{ ms} 1,000 ms ,空间限制: 256  MiB 256\text{ MiB} 256 MiB
题目描述
我们现在有一个 5 × 5 5\times5 5×5 的 0 / 1 0/1 0/1 矩阵,矩阵中的每个元素代表一盏灯, 0 0 0 表示亮, 1 1 1 表示灭。我们现在要在这个矩阵中进行一系列操作,使得这个矩阵所有的元素的值都变成 1 1 1 ,或者说所有灯都变为亮。定义以下的两种操作:

  • 什么都不改变,不操作任何一个数。
  • 操作一个数,改变一盏灯的亮灭状态,即 0 0 0 变成 1 1 1 , 1 1 1 变成 0 0 0 ,同时改变周围的所有灯的亮灭状态。

比如,我们定义以下的一个序列:
[ 0 1 1 1 1 0 1 0 1 1 1 0 0 0 1 1 1 0 1 0 1 1 1 1 0 ] \begin{bmatrix} 0&1&1&1&1 \\ 0&1&0&1&1 \\ 1&0&0&0&1 \\ 1&1&0&1&0 \\ 1&1&1&1&0 \\ \end{bmatrix} ​00111​11011​10001​11011​11100​ ​
改变坐标为 ( 1 , 1 ) (1,1) (1,1) 的灯的亮灭状态后,矩阵将变为:
[ 1 1 1 1 1 1 1 0 1 1 1 0 0 0 1 1 1 0 1 0 1 1 1 1 0 ] \begin{bmatrix} 1&1&1&1&1 \\ 1&1&0&1&1 \\ 1&0&0&0&1 \\ 1&1&0&1&0 \\ 1&1&1&1&0 \\ \end{bmatrix} ​11111​11011​10001​11011​11100​ ​
改变坐标为 ( 3 , 3 ) (3,3) (3,3) 的灯的亮灭状态后,矩阵将变为:
[ 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 0 ] \begin{bmatrix} 1&1&1&1&1 \\ 1&1&1&1&1 \\ 1&1&1&1&1 \\ 1&1&1&1&0 \\ 1&1&1&1&0 \\ \end{bmatrix} ​11111​11111​11111​11111​11100​ ​
改变编号为 ( 5 , 5 ) (5,5) (5,5) 的灯的亮灭状态后,矩阵将变为:
[ 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 ] \begin{bmatrix} 1&1&1&1&1 \\ 1&1&1&1&1 \\ 1&1&1&1&1 \\ 1&1&1&1&1 \\ 1&1&1&1&1 \\ \end{bmatrix} ​11111​11111​11111​11111​11111​ ​
因此,最少的变换次数为 3 3 3 次。
如果操作的数量大于 6 6 6 ,在本题中视为无解。
判断是否有一种操作数小于或等于 6 6 6 的方案,满足题意,输出这个方案的操作数量,如果没有,请输出 − 1 -1 −1 。
本题有多组测试数据。
输入格式
第 1 1 1 行是一个整数 N N N ,表示数据的组数。
接下来 N N N 组数据,每组都是一个 5 × 5 5\times5 5×5 的矩阵,同一行的数中没有空格,行末有换行符。
输出格式
对于每组数据,输出一个整数。
如果有 ≤ 6 \le6 ≤6 的解,输出当前数据的解。
如果没有,输出 − 1 -1 −1 表示无解。

观察题面,我们可以找到两个共同点:
1  无论我们以什么方式进行变换,结果总是一样的。证明如下:
假设我们的操作数的坐标序列为 A = { ( x 0 , y 0 ) , ( x 1 , y 1 ) , ( x 2 , y 2 ) , … , ( x t , y t ) } A=\{(x_0,y_0),(x_1,y_1),(x_2,y_2),\dots,(x_t,y_t)\} A={(x0​,y0​),(x1​,y1​),(x2​,y2​),…,(xt​,yt​)} 。
如果我们选择调换其中第 i i i 个坐标和第 j j j 个坐标的先后顺序,则这两个点周围的点份三种情况讨论:
a  如果这些点只被这两个点中的任意一个点修改了 1 1 1 次,则它会进行变换,即从 0 0 0 变成 1 1 1 ,从 1 1 1 变成 0 0 0 。
b 如果这些点被这两个点都修改了,则它不会进行变换。
显然,上面的性质与 i , j i,j i,j 的顺序无关,即无论如何变换 i , j i,j i,j 的顺序,最后的总次数仍旧不变。因此,结论的证。
上面的探究说明,进行操作的顺序与最终结果无关,因此我们可以随意(对程序实现有意义)地安排操作的顺序。
2  每个位置只会被点击 0 0 0 次或 1 1 1 次。证明如下:
用反证法。假设点击次数大于或等于 2 2 2 次,则相当于点击了 0 0 0 次,即没有发生任何改变。类似的,如果一个点被点击了 k ( k ∈ N , k ≥ 2 ) k(k\in\mathbb{N},k\ge2) k(k∈N,k≥2) ,则相当于被点击了 k m o d    2 k\mod2 kmod2 次。证毕。
因此,我们可以确定,在最优方案下,每个点只会被点击 0 , 1 0,1 0,1 次,否则将不是最优方案。

关于以下文段中涉及到的位运算知识,可以参考 这篇文章
利用上面的性质,我们可以确定一种解决方案:
先定义一个方位偏移数组和一个操作函数:

const int fx[5] = {0, 1, 0, -1, 0}, fy[5] = {0, 0, 1, 0, -1}; //方位偏移数组,可参考 DFS

void turn(int x, int y) 									// 操作函数
{
	for (int i = 0; i < 5; ++i)
	{
		a[x + dx[i]][y + dy[i]] ^= 1; 						// 表示将 (x+dx[i],y+dy[i])取反
	}
}

利用位运算的思想,枚举第一行的点击方案, 0 0 0 表示不点击, 1 1 1 表示点击。因为第一行一共有 5 5 5 个数,所以我们需要枚举 0 ∼ 2 5 − 1 0\sim 2^5-1 0∼25−1 的所有数,共需进行 2 5 = 32 2^5=32 25=32 次枚举。
然后,根据每一位的值,变换第一行的数。

for (int j = 0; j < 5; ++j)
{
	if (i >> j & 1) // 这个位是不是 1
	{
		turn(i, j + 1); // 注意,这里一定是 j + 1 ,因为 a 数组从 1 开始下标
	}
}

根据第一行的变换,我们可以检查第 1 ∼ 4 1\sim 4 1∼4 行每一行是否为 1 1 1 ,如果不是 1 1 1 ,则变换下一行相同列位置的数,从而变换这个位置的值。举例如下:
假设我们需要变换位置坐标为 ( 3 , 3 ) (3,3) (3,3) 位置的灯,则我们可以通过变换坐标位置为 ( 3 , 4 ) (3,4) (3,4) 位置的灯,就可以变换这个位置的灯而不影响同一行中其他位置的灯。
[ 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 0 0 0 1 1 1 0 1 1 ]    ⟹    [ 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 ] \begin{bmatrix} 1&1&1&1&1 \\ 1&1&1&1&1\\ 1&1&0&1&1\\ 1&0&0&0&1\\ 1&1& 0&1&1 \end{bmatrix} \implies \begin{bmatrix} 1&1&1&1&1 \\ 1&1&1&1&1\\ 1&1&1&1&1\\ 1&1&1&1&1\\ 1&1& 1&1&1 \end{bmatrix} ​11111​11101​11000​11101​11111​ ​⟹ ​11111​11111​11111​11111​11111​ ​
这里对一个关键的问题做一个说明:为什么开始的时候不把第一行全部初始化为 1 1 1 :
比如我们有如下的一个矩阵,如果我们将第一行初始化为 1 1 1 ,则下一行将会被第一行改变,且使用算法实现该策略难度较大,不能单纯地通过查找 0 0 0 并将其转换为 1 1 1 实现全 1 1 1 初始化。但如果我们把第一行的操作序列定为 ( 00100 ) 2 (00100)_2 (00100)2​ ,则可以实现最优解。
方案 1 1 1 :直接初始化为 1 , 1 , 1 , 1 , 1 1,1,1,1,1 1,1,1,1,1 :
[ 1 0 0 0 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 ]    ⟹    [ 0 1 1 1 0 1 0 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 ] \begin{bmatrix} 1&0&0&0&1 \\ 1&1&0&1&1\\ 1&1&1&1&1\\ 1&1&1&1&1\\ 1&1&1&1&1 \end{bmatrix} \implies \begin{bmatrix} 0&1&1&1&0 \\ 1&0&1&0&1\\ 1&1&1&1&1\\ 1&1&1&1&1\\ 1&1& 1&1&1 \end{bmatrix} ​11111​01111​00111​01111​11111​ ​⟹ ​01111​10111​11111​10111​01111​ ​
方案 2 2 2 :设置 ( 00100 ) 2 (00100)_2 (00100)2​ 的操作序列:
[ 1 0 0 0 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 ]    ⟹    [ 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 ] \begin{bmatrix} 1&0&0&0&1 \\ 1&1&0&1&1\\ 1&1&1&1&1\\ 1&1&1&1&1\\ 1&1&1&1&1 \end{bmatrix} \implies \begin{bmatrix} 1&1&1&1&1 \\ 1&1&1&1&1\\ 1&1&1&1&1\\ 1&1&1&1&1\\ 1&1& 1&1&1 \end{bmatrix} ​11111​01111​00111​01111​11111​ ​⟹ ​11111​11111​11111​11111​11111​ ​
因此,该部分的程序实现如下:

for (int j = 1; j <= 4; ++j) // 枚举行
{
	for (int k = 1; k <= 5; ++k) // 枚举列
	{
		if (!a[j][k]) // 运用上述策略
		{
			turn(j + 1, k);
		}
	}
}

最后,我们只需检查所有的操作次数和最后一行是否都为 1 1 1 即可。如果是,则这是当前的一个最优解。如果不是,则说明这不是一个最优解。
除此之外,就是细节问题。

#include <bits/stdc++.h>

using namespace std;

const int dx[5] = {0, 1, 0, -1, 0}, dy[5] = {0, 0, 1, 0, -1};

int n, a[10][10], ans, b[10][10];

void turn(int x, int y)
{
    for (int i = 0; i < 5; ++i)
    {
        a[x + dx[i]][y + dy[i]] ^= 1;
    }
}

int main()
{
    cin >> n;

    while (n--)
    {
        ans = 1e9;

        for (int i = 1; i <= 5; ++i)
        {
            for (int j = 1; j <= 5; ++j)
            {
                char c;
                cin >> c;

                a[i][j] = c - '0';
            }
        }

        memcpy(b, a, sizeof(a));

        for (int i = 0; i < 1 << 5; ++i)
        {
            memcpy(a, b, sizeof(b));

            int cnt = 0;

            for (int j = 0; j < 5; ++j)
            {
                if (i >> j & 1)
                {
                    turn(1, j + 1);
                    cnt++;
                }
            }

            for (int j = 1; j <= 4; ++j)
            {
                for (int k = 1; k <= 5; ++k)
                {
                    if (!a[j][k])
                    {
                        turn(j + 1, k);
                        cnt++;
                    }
                }
            }

            if (cnt > 6) continue;

            int f = 1;
            for (int j = 1; j <= 5; ++j)
            {
                if (!a[5][j])
                {
                    f = 0;
                    break;
                }
            }

            if (f)
            {
                ans = min(ans, cnt);
            }
        }

        if (ans > 1e8) cout << -1 << endl;
        else cout << ans << endl;
    }

    return 0;
}

三、递归:将大问题分解成若干相同的子问题求解

递归算法一般封装在一个递归函数中,递归即自己调用自己的一种函数写法,通过每次不断改变自身的参数,直到到达递归出口并得到相关信息,再返回层层带回相关答案。
递归的一个常见应用是 DFS 算法,即深度优先搜索算法。DFS 算法是基于递归实现的一种寻找最优解的 “暴力” 算法,其主要思想是每一次在不同状态下找出当前问题的解,再将最终的解与当前已发现的最优答案进行对比,最后找出最终的答案。
递归时,我们可以根据实际情况减去一些不必要的分支,比如超出给定范围,判断无法达到最优解等,从而实现对效率的最大优化。

递归的一个入门应用是用递归的写法取代递推的写法。
比如,我现在要计算以下表达式的值:
Σ i = 1 N 2 i 2 \Sigma^{N}_{i=1}2i^2 Σi=1N​2i2
我们可以考虑如下的一个递归函数:

int solve(int k)
{
	if (k > n) // 到达递归边界
	{
		return 0; // 到达出口,返回 0 ,层层带回答案
	}
	
	return 2 * k * k + solve(k + 1); // 累加最后答案
}

/*
也可以这样写:
int sum;
void solve(int k)
{
	if (k > n)
	{
		return;
	}
		
	sum += 2 * k * k;
	solve(k + 1);
}
*/

这样,我们就实现了一个从 1 1 1 累加到 N N N 的递归函数。
这个函数通过分解子任务,即到达每个地方的时候累加当前的和,来计算最后的答案。

下面,我们来看一道递归的例题:
Strange Towers of Hanoi
—— 时间限制: 1 , 000  ms 1,000 \text{ ms} 1,000 ms ,空间限制: 256  MiB 256 \text{ MiB} 256 MiB
题目描述
这里有 A , B , C , D A,B,C,D A,B,C,D 四座塔,我们的目标是将所有的圆盘(共 N N N 个)从塔 A A A 移动到塔 D D D 上。
规则如下:

  • 每次只能移动 1 1 1 个圆盘。
  • 不能将大的圆盘叠加在小的圆盘上面。
  • 可以使用 B , C B,C B,C 两座塔作为辅助将 N N N 个圆盘移动到塔 D D D 上。

问最少要经过多少个步骤,才能将所有的圆盘移动到目标。
输入格式
一行,一个数 N N N ,表示圆盘的总数。保证 N N N 一定满足:
1 ≤ N ≤ 12 1\le N\le12 1≤N≤12
输出格式
一行,一个整数,表示最少的操作数。

我们先来看一道这个问题的子问题:三个圆盘数据规模的问题,将所有圆盘从 A A A 塔移动到 C C C 塔上。
通过观察,我们可以发现,假设有 N N N 个圆盘,则最优方案是先将 N − 1 N-1 N−1 个圆盘在 3 3 3 塔模式下从 A A A 塔移动到 B B B 塔上,再将 1 1 1 个圆盘在 2 2 2 塔模式下从 A A A 塔移动到 C C C 塔上,最后将 N − 1 N-1 N−1 个圆盘在 3 3 3 塔模式下从 B B B 塔移动到 C C C 塔上。
递归函数如下:

int hanoi(char A, char B, char C, int k)
{
	if (k == 2) 
	{
		d[k] = 3;
		return d[k];
	}
	
	d[k] = hanoi(A, C, B, k - 1) + 1 + hanoi(B, A, C, k - 1);
	return d[k];
}

我们可以通过这个函数,求出 3 3 3 塔模式下最优方案的步数。

对于 4 4 4 塔模式的问题,可以采取类似的方案: 1
初始化: f 1 = 1 f_1 = 1 f1​=1 (一个盘子在4塔模式下移动到D柱需要1步)
先把i个盘子在 4 4 4 塔模式下移动到 B B B 柱,
然后把 n − i n-i n−i 个盘子在3塔模式下移动到 D D D 柱(因为不能覆盖到B柱上,就等于只剩下A、C、D柱可以用)
最后把i个盘子在 4 4 4 塔模式下移动到 D D D 柱
考虑所有可能的 i i i 取最小值,即得到递推公式:
f n = m i n i ≤ n { 2 f i + d n − i } f_n=min_{i\le n}\{2f_i+d_{n-i}\} fn​=mini≤n​{2fi​+dn−i​}

代码如下:

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

int d[20], f[20];

int hanoi(char A, char B, char C, int k)
{
	if (k == 1) 
	{
		return d[k];
	}
	
	d[k] = hanoi(A, C, B, k - 1) + 1 + hanoi(B, A, C, k - 1);
	return d[k];
}

int main() {
	d[1] = 1;
	solve(12);
	
	memset(f, 0x3f, sizeof(f));
	
    f[1] = 1;
    for (int i = 2; i <= 12; i++)
        for (int j = 1; j < i; j++)
            f[i] = min(f[i], 2 * f[j] + d[i - j]);
    
    for (int i = 1; i <= 12; i++)
        cout << f[i] << endl;
    return 0;
}

  1. 该部分引用自以下文章:
    原文链接 ↩︎

【信息科学与工程学】【物理/化学科学和工程技术】知识体系081 磁学——系列二 电磁学——电磁学理论模型01 编号模型名称核心数学描述关键参数与物理意义应用场景与说明EM1.001​麦克斯韦微分方程组∇·D=ρ, ∇·B=0, ∇×E=-∂B/∂t, ∇×H=J+∂D/∂tD-电通密度(C/m²), B-磁通密度(T), E-电场强度(V/m), H-磁场强度(A/m)所有电磁现象的基本控制方程,集成电路电磁分析基础EM1.002​麦克斯韦积分方程组∮S D·dS=Q, ∮S B·dS=0, ∮C E·dl=-dΦ_B/dt, ∮C H·dl=I+dΦ_D/dtQ-电荷(C), Φ_B-磁通(Wb), I-电流(A 阅读详情

相关推荐

5个4000比特大状态置换算法的设计与分析

下面是 5 个概念性、实验性的 4000 位可逆置换设计。它们都保证是双射,但未经过密码分析,不可直接用于生产系统。风格分别覆盖:三维 SPN、Feistel、可逆元胞自动机、ARX、宽轨迹/Keccak 式结构。 这 5 个设计分别代表了 SPN、Feistel、元胞自动机、ARX、宽轨迹 Keccak 五种不同风格,覆盖了主流大状态置换的构造思路。再次强调:它们只是实验性概念,若要用于实际协议,必须经过严格安全性分析。

HuangJinLong2的博客 350

Python高效刷题指南:从刻意练习到项目实战

编程学习中,刻意练习是提升编码能力的核心方法,其关键在于将抽象算法逻辑转化为可执行的代码。理解数据结构与算法原理,能帮助开发者优化程序性能,解决实际问题。在工程实践中,通过系统化的题目训练,可以巩固Python基础语法、掌握流程控制,并熟练运用函数与模块。例如,针对列表操作、字符串处理等高频考点进行专项练习,能够有效内化知识,提升代码的简洁性与Pythonic风格。这种从概念理解到实战应用的学习路径,最终服务于解决文件操作、数据分析等真实场景需求,将刷题所得转化为项目开发能力。

weixin_34116110的博客 336

STM32H745xI双核芯片SMPS/LDO配置误区:从复位失败到精准排查的实战复盘

本文详细分析了STM32H745xI双核芯片在SMPS/LDO配置中的常见误区,特别是复位失败问题的排查过程。通过对比参考板与故障板的波形数据,揭示了电感选型不当导致的电源稳定性问题,并提供了硬件设计检查清单和调试技巧,帮助开发者避免类似陷阱。

weixin_42531588的博客 265

视频隐私脱敏工程化:CSRT/KCF追踪算法在本地推理中的实现与边界

在短视频内容合规审查中,动态遮挡(Dynamic Masking)是高频且高耗时的环节。传统手动关键帧打码不仅效率低下,且在目标快速移动或遮挡时极易出现遮罩漂移。本文从计算机视觉工程角度,解析基于CSRT与KCF算法的目标追踪方案在视频隐私处理中的技术路径、性能权衡及落地实践。

Mysssone的博客 197

基于单目视觉的四旋翼定点降落——如何通过mavros控制pixhawk(二)

    一直拖了这么久没有更新,怪我太懒啦~~~ 今天要特别感谢舒仔仔同学,我的好队友帮我写了这篇博文。这篇文章主要讲了如何通过mavros来控制pixhawk。硬件逻辑是这样子,我们先搞一个odroid(土豪上TX2),再用usb线将odroid与pixhawk连接。所以,odroid发送控制指令来控制pixhawk飞行。pixhawk与外界的通信协议是mavlink。ROS下面有个包叫mavr...

zhengyuxin0507的博客 1万+

人工智能训练师技能等级认定四级技能考核试卷-样题1

本文为人工智能训练师四级技能考核试卷,包含六个实操考核项目,总分100分,考试时间150分钟。试卷涵盖数据处理、模型训练准备及系统维护等核心技能:1)垃圾分类数据集规范与处理(30分),要求评估数据格式并编写Python处理代码;2)学生成绩数据集分析(20分),涉及数据预处理、可视化及相关性分析;3)数据标注审核(10分),重点审核标注准确性;4)智能数据服务系统维护与优化(40分),包括安全帽数据集的项目创建、数据处理及平台协作。考核强调Python编程、数据处理平台操作及AI工作流程的实践能力。

weixin_42221934的博客 440

【算法拾光】链表 哈希表|K个一组翻转链表 + 两数之和

K个一组翻转链表需分段反转,借助哑节点与前驱指针维护连接,避免断链;两数之和利用哈希表实现O(1)查找,空间换时间,高效定位目标组合。前者考察链表指针操作,后者聚焦映射关系构建,均为面试高频题。

wuqingyigexiaoba的博客 307

STM32硬件SPI驱动ADS1220实战:从零搭建高精度数据采集系统(附避坑指南)

本文详细介绍了使用STM32硬件SPI驱动24位高精度ADC芯片ADS1220的完整实战方案。内容涵盖从硬件设计、CubeMX工程配置、SPI通信底层实现到数据读取策略与性能优化的全流程,并重点分享了多通道切换、噪声抑制等关键避坑指南,帮助开发者构建稳定可靠的高精度数据采集系统。

o4p5q6r7s的博客 450

排序算法深度笔记(下):非比较类排序的线性突破与工程边界

排序算法时间复杂度额外空间复杂度稳定性是否基于比较选择排序O(N^2)(O(1))不稳定是冒泡排序O(N^2)(O(1))稳定是插入排序O(N^2)(O(1))稳定是归并排序O(N\log N)(O(N))稳定是快速排序O(N\log N)O(log N)不稳定是堆排序O(N\log N)O(1)不稳定是计数排序(O(N + M))O(M))稳定否基数排序O(N)稳定否桶排序平均O(N) / 最坏O(N^2)

2401_87069538的博客 567

ArcMap投影变换避坑指南:为什么你的坐标转换总出错?常见问题解析与解决方案

本文深入解析ArcMap中坐标转换与投影变换的核心区别与常见陷阱。针对坐标转换总出错的问题,从混淆“定义投影”与“投影变换”、栅格数据重采样选择、矢量数据地理变换参数设置等关键环节,提供清晰的避坑指南与实战解决方案,帮助用户确保空间数据的精确匹配。

weixin_29210727的博客 356

基于MYD-YT153与AD7616的16通道高速ADC采集系统实战指南

在嵌入式系统与工业数据采集领域,模拟信号采集是连接物理世界与数字系统的关键技术。其核心原理是通过模数转换器(ADC)将连续的模拟电压信号转换为离散的数字码值,供处理器处理。这项技术的价值在于实现了对温度、压力、振动等关键物理量的精确感知与数字化,是工业自动化、设备监测、科学实验等场景的基石。本文聚焦于**多通道**、**高精度**采集这一常见需求,深入剖析如何利用全志T153处理器的LocalBus并行总线接口,高效驱动ADI的16位、16通道同步采样ADC芯片AD7616,构建一个稳定可靠的高速数据采集系

weixin_30335353的博客 443

算法日常・每日刷题--<动态规划>42

本题为“不相交的线”,与最长公共子序列问题相似。使用动态规划求解:定义 dp[i][j] 表示 nums1[0:i] 与 nums2[0:j] 的最大不相交连线数。若元素相等,则继承左上值加1;否则取上方或左方最大值。最终返回 dp[m][n],即两数组的最大不相交连线数。时间复杂度 O(mn),空间复杂度 O(mn)。

2503_92261635的博客 20

基于FPGA的DDS信号发生器

之前的博客讲到了DDS的基本原理,现在用Verilog代码实现DDS,能够产生四种波形,方波,三角波,正弦波,锯齿波,用按键来控制频率和选择波形。其中按键消抖模块来自小梅哥的FPGA自学笔记设计与验证这本书。 module DDS_Module( Clk, Rst_n, EN, Fword, Key1, Pword, DA_Clk, DA_Data ); input Clk...

qq_43534451的博客 9949

C语言程序设计题|有序数组插入数字冒泡排序

题目要求将一个数插入有序数组末尾,并用冒泡排序实现整体升序。代码先将输入数存入数组最后位置,再通过双重循环进行冒泡排序,每轮将较大元素向后移动,最终输出11个升序元素。该方法虽时间复杂度为O(n²),但符合题目要求,逻辑清晰,适合初学者理解排序原理。

2301_81429079的博客 748

手把手教你用Ubuntu Desktop搭建个人L2TP服务器,手机电脑都能连(Network Manager配置详解)

本文详细介绍了在Ubuntu Desktop上使用Network Manager图形化配置L2TP服务器的方法,实现手机、电脑等多设备安全连接。从环境准备、图形界面设置到各平台客户端配置,提供一站式解决方案,并包含常见问题排查与安全增强措施,帮助用户快速搭建个人远程访问网络。

weixin_30555125的博客 119

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

【算法拾光】模拟|外观数列 + 数青蛙

本文总结两道模拟题解法:外观数列通过迭代统计连续数字个数生成下一项,关键在于处理末尾字符;数青蛙则模拟青蛙叫声“croak”状态转移,维护各阶段青蛙数量,记录最大并发数并确保全部完成。共性在于严格遵循流程、细致处理边界与非法情况,锻炼字符串遍历与状态管理能力。

wuqingyigexiaoba的博客 367

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

牛客周赛165C题(前后缀性质,递推,排序,枚举)

题目大意:给定长度为 `n` 的数组,选择 `k ∈ [1,n]`,将前 `k` 个元素整体翻转,后面的元素不变。要求操作后的整个数组非递减,统计满足条件的 `k` 的数量。题目链接:https://ac.nowcoder.com/acm/contest/141721/C。时间复杂度:O(nlogn)

2501_94316951的博客 170

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

【数据结构-3】算法美学之降复杂度

在while循环的循环体内,首先用临时变量保存最后一个位置的数据,再通过for循环把所有元素往后移动一位,最后把tmp中保存的值放到下标为0的位置上去。方法二的思路是:创建新数组,让原数组中后k个数据移动到新数组中前面的位置,让原数组中前numsSize-k个数据移动到新数组中后面的位置,这。这段代码的思路是:三次逆置,第一次逆置前numsSize-k个数据,第二次逆置后k个数据,最后整体逆置。这篇博文分享一道解法不一的算法题,求解过程的目的在于降低算法的时间复杂度,题源力扣。,属于空间换时间的操作。

weixin_44218929的博客 82

算法61,判定是否为字符重排

两字符串能互为重排 ⇔。

sheshibenniaodan的博客 207

算法日常・每日刷题--<动态规划>32

给定严格递增的正整数数组,求最长斐波那契子序列长度。使用二维DP,dp[i][j]表示以arr[i]、arr[j]为最后两项的序列长度,初始化均为2。通过哈希表快速查找前一项target = arr[j] - arr[i],若存在且下标小于i,则状态转移:dp[i][j] = dp[k][i] + 1。遍历更新全局最大长度,最终若结果小于3则返回0。时间复杂度O(n²),空间复杂度O(n²)。

2503_92261635的博客 177

算法札记:几何分布在算法题UVA1262的运用

集卡问题求解核心:将集齐n种优惠券的过程分为n个阶段,每个阶段期望购买次数为 $ \frac{n}{k} $(k为剩余种类数),总期望为 $ n \cdot H_n $(调和数)。通过自洽方程推导单阶段期望,无需几何分布或无穷级数,逻辑清晰且适用于大一水平。例如,n=5时期望约11.42,非5。题目要求以带分数形式输出精确结果,算法上需累加分数并化简。

Keven_11的博客 326

不同的二叉搜索树

互不相同的二叉搜索树的种数,以 j (1<=j<=i)为头节点,左子树是 j-1 个数,右子树就是 i - (j - 1) -1 = i - j个数。所以dp[3] = dp[0] * dp[2] + dp[1] * dp[1] + dp[2] * dp[0]头1 = 左子树0节点 * 右子树2节点,二叉搜索树就是排序树,左子树<根节点<右子树,左右子树也满足左子树<根节点<右子树。头2 = 左子树1节点 * 右子树1节点。所以dp[i] += dp[j-1] * dp[i-j]个节点组成且节点值从。

qq_44721738的博客 151

考拉兹猜想 - 7

本文通过引入虚数单位重构Collatz猜想的迭代逻辑,指出偶数减半与奇数“乘3加1”的过程在虚数坐标下等价于归结操作。关键在于:乘3加1使奇数跃升至偶数,随后持续减半,而虚数单位代表“无限减1”,实现无限向有限的转化。无论初始值如何,减半始终主导,最终归结为4-2-1循环。该视角消解了正向思维中的速度竞争疑虑,证明归结是必然结果。

杨铸人的博客 42

SSL证书选RSA还是ECDSA:公钥算法、CA签名与兼容性怎么核对

申请SSL证书时,RSA和ECDSA该怎么选?先分清站点公钥算法、CA签名算法和TLS实际兼容性,再用隔离OpenSSL实验读取两类材料,核对申请、续期和部署交接,避免只看Signature Algorithm就选错。

谁呀!的博客 399

贪心题目:可以到达的最远建筑

为了到达尽可能远的建筑物,在将砖块替换成梯子时,应将高度差最大位置的砖块替换成梯子,此时剩余的砖块数量最多,如果将高度差较小位置的砖块替换成梯子,则剩余的砖块数量较少,能到达的最远建筑物下标不可能更大。开始向后面的建筑物移动,移动过程中维护优先队列存储正高度差,优先队列的队首元素是最大元素,每次遇到正高度差时优先考虑使用砖块,如果砖块数量不够则使用梯子替换最大高度差位置的砖块,直到砖块和梯子都使用完毕时,到达的建筑物即为最远建筑物。如果以最佳方式使用给定的梯子和砖块,返回可以到达的最远建筑物的下标。

stormsunshine的博客 171

从曲线到储层:Techlog/KAPPA 全流程算法、UltraLAB 选型与本地 AI 部署实战

以高频 CPU 保证交互,以充足内存承载多井与多任务,以 NVMe 打通项目和知识库 I/O,再按本地模型、上下文与并发配置 GPU。对 UltraLAB 用户而言,更合理的采购方式是带着真实项目做选型:一组典型井、一套完整解释模板、一个目标大模型、一次多人并发测试。用实测结果确定配置,才能把预算转化为解释效率,而不是闲置算力。软件模块、操作系统与许可条件以厂商当前正式说明为准;硬件建议需结合项目实测。AI 输出只能作为辅助证据,不能替代岩石物理与油藏工程专业审核。

wdf8088的博客 180
上一篇: 扩展欧几里得算法的使用及简单证明
下一篇: 基于 Dijkstra 最短路算法实现的次短路算法
Aobing_peterJr
博客等级 码龄2年 128粉丝 18原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值