【算法/图论】2-SAT问题详解

一、问题引入

在了解2-SAT的定义之前,我们需要给出一些基础定义。

布尔变量(Boolean variable):只能取 1 1 1(true)或 0 0 0(false)的变量。

否定连接词 ¬ \neg ¬(negation):取布尔变量的否定。例如 ¬ 1 = 0 \neg1=0 ¬1=0 ¬ 0 = 1 \neg0=1 ¬0=1 ¬ ( ¬ a ) = a \neg(\neg a)=a ¬(¬a)=a

合取连接词 ∧ \land (conjunction):表示“且”。 a ∧ b = 1 a\land b=1 ab=1当且仅当 a , b a,b a,b同时为 1 1 1

析取连接词 ∨ \lor (disjunction):表示“或”。 a ∨ b = 1 a\lor b=1 ab=1当且仅当 a , b a,b a,b之中至少有一个为 1 1 1

蕴含连接词 → \to (implication): a → b a\to b ab等价于 ¬ a ∨ b \neg a\lor b ¬ab a → b = 1 a\to b=1 ab=1当且仅当 a = 0 a=0 a=0 b = 1 b=1 b=1。换言之,若 a = 1 a=1 a=1,则必有 b = 1 b=1 b=1,否则蕴含关系不成立;若 a = 0 a=0 a=0,则不论 b b b取何值,蕴含关系都成立。

文字(literal):变量 x x x及其否定 ¬ x \neg x ¬x称为文字。

子句/简单析取式(clause):若干个文字由析取连接词连接起来形成的布尔表达式称为简单析取式。例如 a ∨ b ∨ ¬ c a\lor b\lor\neg c ab¬c ¬ d ∨ d ∨ e ∨ ¬ f \neg d\lor d\lor e\lor\neg f ¬dde¬f

合取范式(CNF,Conjunctive Normal Form):若干个简单析取式由合取连接词连接起来形成的布尔表达式称为合取范式(即“简单析取式的合取”)。例如 ( p ∨ ¬ q ) ∧ ( ¬ p ∨ r ∨ s ) ∧ ¬ r ∧ ( s ∨ ¬ s ) (p\lor \neg q)\land(\neg p\lor r\lor s)\land \neg r\land(s\lor\neg s) (p¬q)(¬prs)¬r(s¬s),它有四个子句。

2-CNF:每个子句仅包含两个文字的合取范式。例如 ( a ∨ ¬ b ) ∧ ( b ∨ c ) ∧ ( c ∨ ¬ f ) ∨ ( ¬ d ∨ e ) (a\lor\neg b)\land(b\lor c)\land(c\lor\neg f)\lor(\neg d\lor e) (a¬b)(bc)(c¬f)(¬de)

布尔表达式的可满足性:对于一个布尔表达式,如果存在各变量的一组赋值,使得该表达式的值为 1 1 1,则称该表达式是可满足的。对于合取范式,因为各子句之间是且的关系,所以要使它取值为 1 1 1就必须使各子句取值都为 1 1 1。对于上面给出的式子 ( a ∨ ¬ b ) ∧ ( b ∨ c ) ∧ ( c ∨ ¬ f ) ∨ ( ¬ d ∨ e ) (a\lor\neg b)\land(b\lor c)\land(c\lor\neg f)\lor(\neg d\lor e) (a¬b)(bc)(c¬f)(¬de),令 a = 1 , b = 1 , c = 1 , d = 0 , e = 0 , f = 1 a=1,b=1,c=1,d=0,e=0,f=1 a=1,b=1,c=1,d=0,e=0,f=1可以使其取值为 1 1 1,所以它是可满足的;但对于式子 ( a ∨ ¬ b ) ∧ ( b ∨ c ) ∧ ( b ∨ ¬ c ) ∧ ( ¬ a ∨ ¬ b ) (a\lor\neg b)\land(b\lor c)\land(b\lor\neg c)\land(\neg a\lor\neg b) (a¬b)(bc)(b¬c)(¬a¬b),不论 a , b , c a,b,c a,b,c取何值,式子的值都是 0 0 0,因此它是不可满足的。

2-SAT:也称2-CNF-SAT,是判断2-CNF是否可满足的问题。(判断一般的合取范式是否可满足的问题是 N P \mathsf{NP} NP完全问题,至今没有发现多项式时间的算法;但2-SAT是一个特例,具有高效算法。)

二、问题求解

1. 转化为蕴含关系

考虑2-CNF的一个子句 ( a ∨ ¬ b ) (a\lor\neg b) (a¬b),要让这个子句满足,就是让 ( a ∨ ¬ b ) = 1 (a\lor\neg b)=1 (a¬b)=1。现在令 a = 0 a=0 a=0,那么 ¬ b \neg b ¬b一定等于 1 1 1,因为如果 ¬ b \neg b ¬b也等于 0 0 0,那么 a a a ¬ b \neg b ¬b同时为 0 0 0,该子句的取值就是 0 0 0了;同理,若 ¬ b = 0 \neg b=0 ¬b=0,则可以知道 a a a一定是 1 1 1。换言之: ¬ a \neg a ¬a a = 0 a=0 a=0可以推出 ¬ b \neg b ¬b,而 b b b ¬ b = 0 \neg b=0 ¬b=0,即 ¬ ( ¬ b ) = b = 1 \neg(\neg b)=b=1 ¬(¬b)=b=1可以推出 a a a。所以, ( a ∨ ¬ b ) (a\lor\neg b) (a¬b)就等价于 ( ¬ a → ¬ b ) ∧ ( b → a ) (\neg a\to\neg b)\land(b\to a) (¬a¬b)(ba)。一般地,设 p , q p,q p,q是两个文字,则子句 ( p ∨ q ) (p\lor q) (pq)等价于两个蕴含关系 ¬ p → q \neg p\to q ¬pq ¬ q → p \neg q\to p ¬qp p = a p=a p=a,则 ¬ p = ¬ a \neg p=\neg a ¬p=¬a;若 p = ¬ a p=\neg a p=¬a,则 ¬ p = ¬ ( ¬ a ) = a \neg p=\neg(\neg a)=a ¬p=¬(¬a)=a

具体地说,我们获得蕴含关系方式由下表给出:

子句蕴含关系
a ∨ b a\lor b ab ¬ a → b \neg a\to b ¬ab ¬ b → a \neg b\to a ¬ba
a ∨ ¬ b a\lor\neg b a¬b ¬ a → ¬ b \neg a\to\neg b ¬a¬b b → a b\to a ba
¬ a ∨ b \neg a\lor b ¬ab a → b a\to b ab ¬ b → ¬ a \neg b\to\neg a ¬b¬a
¬ a ∨ ¬ b \neg a\lor\neg b ¬a¬b a → ¬ b a\to\neg b a¬b b → ¬ a b\to\neg a b¬a

2. 建图

我们把每个子句表示成蕴含关系后,就可以根据蕴含关系建图。假设有 n n n个布尔变量,那么图有 2 n 2n 2n个节点,每个变量及其否定各有一个节点(比如变量 a a a对应两个节点: a a a ¬ a \neg a ¬a)。根据蕴含关系连边,若有 ¬ a → ¬ b \neg a\to\neg b ¬a¬b就从 ¬ a \neg a ¬a ¬ b \neg b ¬b连一条边;若有 b → a b\to a ba就从 b b b a a a连一条边。注意,每个子句连 2 2 2条边:若 p p p q q q有一条边,那么 ¬ q \neg q ¬q ¬ p \neg p ¬p也有一条边(可以通过逆否命题和原命题等价来理解)。

3. 判断可满足性

我们说“ a → b a\to b ab”,指的是若 a = 1 a=1 a=1,那么 b b b一定等于 1 1 1;若 a ≠ 1 a\ne1 a=1,那么 b b b等于什么都可以。蕴含关系有传递性,例如 a → b a\to b ab b → c b\to c bc就有 a → c a\to c ac。若图中有从 a a a b b b的一条路径,那么就有 a → b a\to b ab。若 a a a b b b处于同一个强连通分量中,即 a a a b b b有路径、 b b b a a a有路径,那么就有 a → b a\to b ab b → a b\to a ba,此时,若 a = 1 a=1 a=1,则 b = 1 b=1 b=1;若 b = 1 b=1 b=1,则 a = 1 a=1 a=1;若 a = 0 a=0 a=0,则 b = 0 b=0 b=0,因为如果 b = 1 b=1 b=1的话就有 a = 1 a=1 a=1了。于是我们得出结论:若 a a a b b b处于同一个强连通分量中,则有 a → b ∧ b → a a\to b\land b\to a abba,此时 a a a b b b取值必须相同。

什么时候不可满足呢?就是出现矛盾的时候。所谓矛盾,就是存在变量 x x x,使得 x x x ¬ x \neg x ¬x相等。换言之,如果某个变量和其否定出现在了同一个强连通分量中,那么这个式子就是不可满足的。如果没有这种情况,就是可满足的。

详细地说,如果 x x x ¬ x \neg x ¬x在同一个强连通分量中,那么我们有 x → ¬ x x\to\neg x x¬x ¬ x → x \neg x\to x ¬xx。如果 x = 1 x=1 x=1,那么由 x → ¬ x x\to\neg x x¬x ¬ x = 1 \neg x=1 ¬x=1,即 x = 0 x=0 x=0,但是一个变量不能同时取 1 1 1 0 0 0,所以不成立;如果 x = 0 x=0 x=0,那么 ¬ x = 1 \neg x=1 ¬x=1,由 ¬ x → x \neg x\to x ¬xx x = 1 x=1 x=1,同样出现了矛盾。反之,如果没有一个变量,使得它本身和它的否定在同一个强连通分量中,那么我们总能构造出一个可满足赋值,这就是下一段讨论的内容。因此,“某一个变量的本身和否定在同一个强连通分量中”是输入的2-CNF不可满足的充要条件。

4. 赋值

当我们知道某个式子是可满足的时候,我们希望找到使其值为 1 1 1的一组变量的赋值。此时对于变量 x x x x x x ¬ x \neg x ¬x一定不属于同一个强连通分量。要求出强连通分量,就需要对原图进行缩点,顺便可以求出得到的有向无环图的拓扑序。令 x x x所在强连通分量的拓扑序为 t x t_x tx。我们这样进行赋值:

  • t x < t ¬ x t_x<t_{\neg x} tx<t¬x,则令 x = 0 x=0 x=0
  • t x > t ¬ x t_x>t_{\neg x} tx>t¬x,则令 x = 1 x=1 x=1

t x = t ¬ x t_x=t_{\neg x} tx=t¬x的情况是不存在的,因为 x x x ¬ x \neg x ¬x不在同一个强连通分量中。

如果有从 x x x ¬ x \neg x ¬x或从 ¬ x \neg x ¬x x x x的路径,那么这个很好理解。

  • t x < t ¬ x t_x<t_{\neg x} tx<t¬x就是说有从 x x x ¬ x \neg x ¬x的路径,这种情况下我们有 x → ¬ x x\to\neg x x¬x,只有当 x = 0 x=0 x=0的时候才可满足。
  • t x > t ¬ x t_x>t_{\neg x} tx>t¬x就是说有从 ¬ x \neg x ¬x x x x的路径,这种情况下我们有 ¬ x → x \neg x\to x ¬xx,只有当 x = 1 x=1 x=1的时候才可满足。

下面我们来证明这种赋值办法不会违反任何蕴含关系。

  • 首先,它不会违反 x x x ¬ x \neg x ¬x之间的蕴含关系,因为 x x x ¬ x \neg x ¬x属于不同的强连通分量,二者之间最多只能有一个蕴含关系,而我们赋值的方法总能满足这个蕴含关系。
  • 其次,我们考虑变量 x x x和另一个变量 y y y的蕴含关系。我们假设有 x → y x\to y xy(如果有 ¬ x → y \neg x\to y ¬xy x → ¬ y x\to\neg y x¬y ¬ x → ¬ y \neg x\to\neg y ¬x¬y同理)。我们担心的情况是: x = 1 x=1 x=1 y = 0 y=0 y=0,这样蕴含关系就被违反了。下面我们用反证法证明这种情况不存在。假设 x = 1 x=1 x=1 y = 0 y=0 y=0 x → y x\to y xy三个条件,那么我们可以得出以下结论:
    • x = 1 x=1 x=1知道 t ¬ x < t x t_{\neg x}<t_x t¬x<tx(见两个赋值规则);
    • y = 0 y=0 y=0知道 t y < t ¬ y t_y<t_{\neg y} ty<t¬y
    • x → y x\to y xy知道 t x ≤ t y t_x\le t_y txty。(如果 x x x y y y在同一个强连通分量里,则 t x = t y t_x=t_y tx=ty;如果在不同的强连通分量里,那么我们有从 x x x y y y的一条边,因此 x x x y y y先被访问。至于为什么如果有 x x x y y y的边那么 x x x y y y先被访问,这是拓扑排序的定义。)
  • 综合这三个不等式,我们得到 t ¬ x < t x ≤ t y < t ¬ y t_{\neg x}<t_x\le t_y<t_{\neg y} t¬x<txty<t¬y。但是,我们知道原命题等价于逆否命题,也就是说, x → y x\to y xy成立意味着 ¬ y → ¬ x \neg y\to\neg x ¬y¬x也一定成立,这表明 t ¬ y ≤ t ¬ x t_{\neg y}\le t_{\neg x} t¬yt¬x,与绿色不等式矛盾。因此假设不成立,违反 x → y x\to y xy的情况不存在,换言之,所有蕴含关系都被满足了,这意味着输入的2-CNF公式被满足了。综上所述,我们的赋值策略是正确的。

三、两个例子

第一个例子

给定的2-CNF表达式为 ( a ∨ ¬ b ) ∧ ( ¬ a ∨ b ) ∧ ( b ∨ ¬ c ) ∧ ( a ∨ c ) (a\lor\neg b)\land(\neg a\lor b)\land(b\lor\neg c)\land(a\lor c) (a¬b)(¬ab)(b¬c)(ac)

例子1

图包含四个强连通分量: { a , b } , { c } , { ¬ a , ¬ b } , { ¬ c } \{a,b\},\{c\},\{\neg a,\neg b\},\{\neg c\} {a,b},{c},{¬a,¬b},{¬c}。没有出现某个变量及其否定在同一个强连通分量的情况,所以是可满足的。进行缩点:

例子1缩点

拓扑序为 t ¬ a , ¬ b < t c < t ¬ c < t a , b t_{\neg a,\neg b}<t_c<t_{\neg c}<t_{a,b} t¬a,¬b<tc<t¬c<ta,b。因为 t ¬ a < t a t_{\neg a}<t_a t¬a<ta,所以 a = 1 a=1 a=1。同理 b = 1 b=1 b=1。因为 t c < t ¬ c t_c<t_{\neg c} tc<t¬c,所以 c = 0 c=0 c=0

a = 1 , b = 1 , c = 0 a=1,b=1,c=0 a=1,b=1,c=0代入2-CNF中验算可得其值为 1 1 1

第二个例子

给定的2-CNF表达式为 ( a ∨ ¬ b ) ∧ ( b ∨ c ) ∧ ( b ∨ ¬ c ) ∧ ( ¬ a ∨ ¬ b ) (a\lor\neg b)\land(b\lor c)\land(b\lor\neg c)\land(\neg a\lor\neg b) (a¬b)(bc)(b¬c)(¬a¬b)

例子2

所有节点都在同一个强连通分量中,所以这个式子不可满足。

四、基于Tarjan算法的代码实现

详细思路

设有 n n n个变量, m m m个子句,则有 2 n 2n 2n个节点。编号 i i i 1 ≤ i ≤ n 1\le i\le n 1in)的节点对应变量 x i x_i xi i + n i+n i+n对应 ¬ x i \neg x_i ¬xi。根据每个子句建图,图中共 2 m 2m 2m条边。使用Tarjan算法进行缩点,co数组存储每个节点的染色,实际上就是有向无环图的逆拓扑序(因为在DFS树中越深的节点越先被染色)。对于每个变量 x i x_i xi,考察节点 i i i和节点 i + n i+n i+n的染色是否相同,若相同说明 x i x_i xi ¬ x i \neg x_i ¬xi在同一个强连通分量,出现矛盾。而后根据拓扑序进行赋值:若 i i i的拓扑序小于 i + n i+n i+n,对应co[i] > co[i + n] x i = 0 x_i=0 xi=0;反之 x i = 1 x_i=1 xi=1。可以简写为 x i = x_i= xi=co[i] < co[i + n]

洛谷P4782 【模板】2-SAT 问题

题目描述

n n n 个布尔变量 x 1 x_1 x1~ x n x_n xn,另有 m m m 个需要满足的条件,每个条件的形式都是 「 x i x_i xitrue / false x j x_j xjtrue / false」。比如 「 x 1 x_1 x1 为真或 x 3 x_3 x3 为假」、「 x 7 x_7 x7 为假或 x 2 x_2 x2 为假」。

2-SAT 问题的目标是给每个变量赋值使得所有条件得到满足。

输入格式

第一行两个整数 n n n m m m,意义如题面所述。

接下来 m m m 行每行 4 4 4 个整数 i i i, a a a, j j j, b b b,表示 「 x i x_i xi a a a x j x_j xj b b b」( a , b ∈ { 0 , 1 } a, b\in \{0,1\} a,b{0,1})

输出格式

如无解,输出 IMPOSSIBLE;否则输出 POSSIBLE

下一行 n n n 个整数 x 1 x_1 x1~ x n x_n xn x i ∈ { 0 , 1 } x_i\in\{0,1\} xi{0,1}),表示构造出的解。

样例 #1

样例输入 #1
3 1
1 1 3 0
样例输出 #1
POSSIBLE
0 0 0

提示

1 ≤ n , m ≤ 1 0 6 1\leq n, m\leq 10^6 1n,m106 , 前 3 3 3 个点卡小错误,后面 5 5 5 个点卡效率。

由于数据随机生成,可能会含有(10 0 10 0)之类的坑,但按照最常规写法的写的标程没有出错,各个数据点卡什么的提示在标程里

分析

我们需要把条件“ x i x_i xi a a a x j x_j xj b b b”转化为子句。其实 a = 0 a=0 a=0表示子句中 x i x_i xi前面有否定连接词, b = 0 b=0 b=0表示子句中 x j x_j xj前面有否定连接词。转换关系如下:

a a a b b b子句第一个蕴含关系第二个蕴含关系
1 1 1 1 1 1 x i ∨ x j x_i\lor x_j xixj ¬ x i → x j \neg x_i\to x_j ¬xixj ¬ x j → x i \neg x_j\to x_i ¬xjxi
1 1 1 0 0 0 x i ∨ ¬ x j x_i\lor\neg x_j xi¬xj ¬ x i → ¬ x j \neg x_i\to\neg x_j ¬xi¬xj x j → x i x_j\to x_i xjxi
0 0 0 1 1 1 ¬ x i ∨ x j \neg x_i\lor x_j ¬xixj x i → x j x_i\to x_j xixj ¬ x j → ¬ x i \neg x_j\to\neg x_i ¬xj¬xi
0 0 0 0 0 0 ¬ x i ∨ ¬ x j \neg x_i\lor\neg x_j ¬xi¬xj x i → ¬ x j x_i\to\neg x_j xi¬xj x j → ¬ x i x_j\to\neg x_i xj¬xi

因为 ¬ y \neg y ¬y在图中对应的节点编号为 y y y的编号加 n n n,所以加入第一个蕴含关系时我们判断 a a a是否为 1 1 1,若为 1 1 1则给 x i x_i xi的编号加 n n n变成 ¬ x i \neg x_i ¬xi;若 b b b 0 0 0则给 x j x_j xj的编号加 n n n变成 ¬ x j \neg x_j ¬xj。加入第二个蕴含关系时,若 b = 1 b=1 b=1 x j x_j xj n n n,若 a = 0 a=0 a=0 x i x_i xi n n n

代码实现

#include <iostream>
#include <cstdio>
#include <stack>

using namespace std;

const int MAXN = 2e6 + 5, MAXM = 2e6 + 5;

int n, m, tot, nxt[MAXM], first[MAXN], go[MAXM],
    dfn[MAXN], low[MAXN], co[MAXN], col; 
// n为变量个数,m为条件(子句)个数,nxt、first、go是链式前向星用到的数组,
// dfn为Tarjan算法的DFS序数组,low为Tarjan算法的low数组,
// co是颜色(即节点的强连通分量编号),col为强连通分量个数
stack<int> stk;

inline void add_edge(int u, int v) // 加边
{
    nxt[++tot] = first[u];
    first[u] = tot;
    go[tot] = v;
}

void Tarjan(int u) // Tarjan算法求强连通分量
{
    low[u] = dfn[u] = ++tot;
    stk.push(u);
    for(int e = first[u]; e; e = nxt[e])
    {
        int v = go[e];
        if(!dfn[v])
        {
            Tarjan(v);
            low[u] = min(low[u], low[v]);
        }
        else if(!co[v])
        {
            low[u] = min(low[u], low[v]);
        }
    }
    if(low[u] == dfn[u])
    {
        co[u] = ++col;
        while(stk.top() != u)
            co[stk.top()] = col, stk.pop();
        stk.pop();
    }
}

int main()
{
    cin >> n >> m;
    for(int t = 1; t <= m; ++t) // 连边
    {
        int i, a, j, b;
        cin >> i >> a >> j >> b;
        int u = i;
        if(a) u += n;
        int v = j;
        if(!b) v += n;
        add_edge(u, v);
        u = j;
        if(b) u += n;
        v = i;
        if(!a) v += n;
        add_edge(u, v);
    }
    for(int i = 1; i <= 2 * n; ++i)
        if(!dfn[i]) Tarjan(i); // Tarjan缩点
    for(int i = 1; i <= n; ++i)
        if(co[i] == co[i + n]) // 判断冲突
        {
            puts("IMPOSSIBLE");
            return 0;
        }
    puts("POSSIBLE");
    for(int i = 1; i <= n; ++i) // 赋值
        cout << (co[i] < co[i + n]) << ' ';
    cout << endl;
    return 0;
}
  • 28
    点赞
  • 81
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: 好的,我来用中文回复这个链接:https://leetcode-cn.com/tag/dynamic-programming/ 这个链接是 LeetCode 上关于动态规划的题目集合。动态规划是一种常用的算法思想,可以用来解决很多实际问题,比如最长公共子序列、背包问题、最短路径等等。在 LeetCode 上,动态规划也是一个非常重要的题型,很多题目都需要用到动态规划的思想来解决。 这个链接里包含了很多关于动态规划的题目,按照难度从简单到困难排列。每个题目都有详细的题目描述、输入输出样例、题目解析和代码实现等内容,非常适合想要学习动态规划算法的人来练习和提高自己的能力。 总之,这个链接是一个非常好的学习动态规划算法的资源,建议大家多多利用。 ### 回答2: 动态规划是一种算法思想,通常用于优化具有重叠子问题和最优子结构性质的问题。由于其成熟的数学理论和强大的实用效果,动态规划在计算机科学、数学、经济学、管理学等领域均有重要应用。 在计算机科学领域,动态规划常用于解决最优化问题,如背包问题、图像处理、语音识别、自然语言处理等。同时,在计算机网络和分布式系统中,动态规划也广泛应用于各种优化算法中,如链路优化、路由算法、网络流量控制等。 对于算法领域的程序员而言,动态规划是一种必要的技能和知识点。在LeetCode这样的程序员平台上,题目分类和标签设置十分细致和方便,方便程序员查找并深入学习不同类型的算法。 LeetCode的动态规划标签下的题目涵盖了各种难度级别和场景的问题。从简单的斐波那契数列、迷宫问题到可以用于实际应用的背包问题、最长公共子序列等,难度不断递进且话题丰富,有助于开发人员掌握动态规划的实际应用技能和抽象思维模式。 因此,深入LeetCode动态规划分类下的题目学习和练习,对于程序员的职业发展和技能提升有着重要的意义。 ### 回答3: 动态规划是一种常见的算法思想,它通过将问题拆分成子问题的方式进行求解。在LeetCode中,动态规划标签涵盖了众多经典和优美的算法问题,例如斐波那契数列、矩阵链乘法、背包问题等。 动态规划的核心思想是“记忆化搜索”,即将中间状态保存下来,避免重复计算。通常情况下,我们会使用一张二维表来记录状态转移过程中的中间值,例如动态规划求解斐波那契数列问题时,就可以定义一个二维数组f[i][j],代表第i项斐波那契数列中,第j个元素的值。 在LeetCode中,动态规划标签下有众多难度不同的问题。例如,经典的“爬楼梯”问题,要求我们计算到n级楼梯的方案数。这个问题的解法非常简单,只需要维护一个长度为n的数组,记录到达每一级楼梯的方案数即可。类似的问题还有“零钱兑换”、“乘积最大子数组”、“通配符匹配”等,它们都采用了类似的动态规划思想,通过拆分问题、保存中间状态来求解问题。 需要注意的是,动态规划算法并不是万能的,它虽然可以处理众多经典问题,但在某些场景下并不适用。例如,某些问题的状态转移过程比较复杂,或者状态转移方程中存在多个参数,这些情况下使用动态规划算法可能会变得比较麻烦。此外,动态规划算法也存在一些常见误区,例如错用贪心思想、未考虑边界情况等。 总之,掌握动态规划算法对于LeetCode的学习和解题都非常重要。除了刷题以外,我们还可以通过阅读经典的动态规划书籍,例如《算法竞赛进阶指南》、《算法与数据结构基础》等,来深入理解这种算法思想。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值