算法小笔记

文章目录

0. 算法基础

1. 判定问题与优化问题1:

Decision problem | Optimization problem

  • 优化问题:

在计算机科学中, 优化问题是在可行解中找到最优解

优化问题分类 说明
连续优化问题 诸如连续函数的优化
组合优化问题 对于离散变量的优化
  • NPNP-Hard问题2:
  1. NP问题: NP → \rightarrow Non-deterministic, 在非确定性图灵机上可以被多项式时间内求解的决策问题. — 即多项式时间内可以验证解是否正确.

  2. NP-Hard问题: 如果一个判定问题是NP问题, 且所有NP问题都可以约化(reduce)到它. 注意NP-Hard问题不一定是NP问题.(NP-Hard比NPC问题更广)

越想越头疼, 先跳过…

1. 状态空间

状态(state)

状态转移(state transition)

状态空间(state space)

状态变量(state variable)

价值函数(cost function)

斐波那契数列:

F 1 = F 2 = 1 F n = F n − 1 + F n − 2 ( n > 2 ) F_1 = F_2 = 1 \\ F_n = F_{n-1}+F_{n-2}(n > 2) F1=F2=1Fn=Fn1+Fn2(n>2)
复杂度为 O ( n ) O(n) O(n)

如果使用矩阵快速幂:
( F n F n − 1 ) = ( 1 1 1 0 ) ( F n − 1 F n − 2 ) = ( 1 1 1 0 ) ( n − 1 ) ( F 1 F 0 ) \begin{pmatrix} F_n \\ F_{n-1} \end{pmatrix} = \begin{pmatrix} 1 & 1 \\ 1 & 0 \\ \end{pmatrix} \begin{pmatrix} F_{n-1} \\ F_{n-2} \end{pmatrix} = \begin{pmatrix} 1 & 1 \\ 1 & 0 \\ \end{pmatrix}^{(n-1)} \begin{pmatrix} F_1 \\ F_0 \end{pmatrix} (FnFn1)=(1110)(Fn1Fn2)=(1110)(n1)(F1F0)
矩阵 ( 1 1 1 0 ) \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix} (1110) 的n-1次幂使用快速幂: T ( n ) = T ( n 2 ) + O ( 1 ) T(n)=T(\frac{n}{2})+O(1) T(n)=T(2n)+O(1), 因此 T ( n ) = O ( l o g 2 n ) T(n)=O(log_2n) T(n)=O(log2n)

拨转开关问题:

有一个4$\times$4的灯阵, 每个灯上均有一个开关, 每次拨动开关会使当前灯相邻灯的开关状态改变, 判断给出某个开关图案是否可以通过操作开关使全部灯都打开?

deng_1

4$\times$4灯阵的每一种开关状态都对应一个状态, 如果用一个二维矩阵来存储一个状态, 空间开销是 2 16 × 16 2^{16}\times 16 216×16, 对于一个 4 × 4 4\times 4 4×4的灯阵来说似乎还可以接受, 但是空间复杂度是指数增长的.

  • 状态压缩: 使用16个二进制位来表示
  • 状态哈希: H : X → Y H: X \rightarrow Y H:XY

埃及分数问题:

古埃及数学的分数表示十分特殊, 不允许分子不为1的分数存在, 比如 2 3 \frac{2}{3} 32在古埃及数学中只能表示为 1 2 + 1 6 \frac{1}{2} + \frac{1}{6} 21+61, 请设计算法, 对给定的真分数$\frac{a}{b} $, 请计算出满足以下条件的埃及分数表示:

  1. 和式中分数互不相同
  2. 和式中分数个数最少
  3. 满足条件2的情况下, 保留和式中最小分数的最大情况下的解 .

例如: 19 45 = 1 5 + 1 6 + 1 18 \frac{19}{45}=\frac{1}{5} + \frac{1}{6} + \frac{1}{18} 4519=51+61+181

定义状态: a ′ b ′ = a b − 1 x \frac{a'}{b'}=\frac{a}{b}-\frac{1}{x} ba=bax1
{ 广 度 优 先 ? : x ∈ [ 1 , + ∞ ] 深 度 优 先 ? : x 可 以 无 穷 大 , 可 以 永 远 递 归 下 去 ! 现 在 还 是 优 化 问 题 ! → 每 一 步 都 没 有 界 , 导 致 无 法 找 到 一 个 判 定 准 则 . \begin{cases} 广度优先?: x\in [1, +\infin] \\ 深度优先?: x可以无穷大, 可以永远递归下去! 现在还是优化问题!\rightarrow 每一步都没有界, 导致无法找到一个判定准则. \end{cases} { 广?:x[1,+]?:x,!!,.

  • 迭代加深搜索(Iteratively Deepening DFS): 搜索深度也作为状态的一部分, 优化问题转化为判定问题.
    • 必须要注意, 这个深度是迭代深度

洛谷-埃及分数

埃及分数思路

这里还待优化, 有三个TLE

#include<iostream>
#include<vector>
#include<utility>

using namespace std;

vector<int> print_vec;

inline bool operator<(vector<int> a, vector<int> b) {
   
    if (a.size() != b.size()) {
   
        return a.size() > b.size();
    } else {
   
        return a[a.size() - 1] > b[b.size() - 1];
    }
}

inline bool operator>(vector<int> a, vector<int> b) {
   
    return !(move(a) < move(b));
}

inline pair<int, int> sub(int numerator, int denominator, int v) {
   
    numerator = numerator * v - denominator;
    denominator = denominator * v;
    return pair<int, int>(numerator, denominator);
}

inline int gcd(int a, int b) {
   
    if (b == 0) return a;
    return gcd(b, a % b);
}

inline void yuefen(int &numerator, int &denominator) {
   
    int GCD = gcd(numerator, denominator);
    numerator /= GCD;
    denominator /= GCD;
}

inline int dfs(int numerator, int denominator, int last_denominator, int depth, int max_depth, vector<int> result) {
   
    if (numerator == 0) {
   
        return 1;
    } else if (depth > max_depth){
   
        return 0;
    }
    yuefen(numerator, denominator);
    int bingo = 0;
    for (int i = last_denominator + 1; i <= min(denominator / numerator * (max_depth - depth + 1), 10000000); i++) {
   
        if (numerator * i - denominator >= 0) {
   
            pair<int, int> res = sub(numerator, denominator, i);
            int _numerator = res.first;
            int _denominator = res.second;
            result.push_back(i);
            int finish = dfs(_numerator, _denominator, i, depth + 1, max_depth, result);
            if (finish) {
   
                bingo = finish;
            }
            if (depth == max_depth && finish) {
   
                if (print_vec.empty() || result > print_vec) {
   
                    print_vec.swap(result);
                }
                break;
            }
            result.pop_back();
        }
    }
    return bingo;
}

int main() {
   
    int numerator, denominator;
    cin >> numerator >> denominator;
    int MAX = 1;
    int last = 1;
    vector<int> result;
    while (!dfs(numerator, denominator, last, 1, MAX, result)) {
   
        MAX++;
    }
//    for (int i = print_vec.size() - 1; i >= 0; i--) {
   
//        cout << print_vec[i] << ' ';
//    }
    for(auto item : print_vec) {
   
        cout<<item<<' ';
    }
    cout << endl;
    return 0;
}

八数码问题:

九个格子中放入了数字1~8方块, 利用空位移动方块, 问使数字恢复顺序所需最少移动次数?

shuma

如果用一个二维数组来存储状态的转移, 则共有 9 9 9^9 99种状态… 广度优先搜索内存必然不够

  • 双向广度优先搜索(Bidirectional BFS, Meet-in-the-mid):
    • 初始状态和结束状态都是已知的
    • 可以从初始状态结束状态同时进行广度优先搜索
  • Meet-in-the-middle: https://www.geeksforgeeks.org/meet-in-the-middle/

数字三角形:

如图所示,请找出一条自顶向下路径,使得该路径上所有数字之和最大

tri
  • 记忆化搜索

    • 状态变量: ( i , j ) (i,j) (i,j)表示路径自上而下走到了第 i i i行第 j j j
    • 有些路径都会重复经过同一个节点
    在搜索过程中, 对于已经搜索过、且价值函数的值不会再度更新(或者更优)的状态,可以使用记忆化搜索的方式,规避重复搜索。
    
  • 动态规划算法适用问题的必要条件:

    • 最优子结构: 最优解可以由子问题的最优解构造出来 – 分而治之
    • 重叠子问题: 一些子问题在搜索过程中会重复遇到 – 具有优化空间
    • 无后效性: 子问题最优解一旦确定就不会被更新
  • 动态规划算法设计的三个关键环节:

    • 定义状态和价值函数(建模)
    • 确定初始状态和对应价值(边界)
    • 确定状态转移中的价值更新方式(状态转移方程)
  • 动态规划的两种实现:

    • 记忆化搜索(Memorization): 自顶向下
    • 打表法(Tabulation): 自底向上

https://www.luogu.com.cn/problem/P1216

#include<iostream>

using namespace std;

int main() {
   
    int n;
    cin >> n;
    int a[n][n];
    int dp[n][n];

    for (int i = 0; i < n; i++) {
   
        for (int j = 0; j < i + 1; j++) {
   
            cin >> a[i][j];
        }
    }

    for (int i = n - 1; i >= 0; i--) {
   
        for (int j = 0; j < i + 1; j++) {
   
            if (i == n - 1) {
   
                dp[i][j] = a[i][j];
            } else {
   
                dp[i][j] = max(dp[i+1][j], dp[i+1][j+1]) + a[i][j];
            }
        }
    }

    cout<<dp[0][0]<<endl;
    return 0;
}

一维递推问题: 爬楼梯(求和形式)

一个有n级台阶的楼梯,每步可以迈1、2、3级,问爬完这段楼梯有几种不同的方式?

F ( n ) = ∑ i = 1 3 F ( n − i ) F(n)=\sum_{i=1}^3 F(n-i) F(n)=i=13F(ni)

https://www.luogu.com.cn/problem/P1255

#include<iostream>

using namespace std;

int main() {
   
    int n;
    cin>>n;

    unsigned long long dp[n+1];
    for (int i = 0; i <= n; i++) {
   
        if (i == 0 || i == 1) {
   
            dp[i] = 1ll;
        } else {
   
            dp[i] = dp[i-1] + dp[i-2];
        }
    }
    cout<<dp[n]<<endl;
    return 0;
}

一维递推问题: 最长上升子序列(最值形式)

给一个数组,请找出这个数组中的一个最长的单调递增子序列(可以不连续)

F ( i ) = m a x 1 ≤ j < i { I ( a j < a i ) F ( j ) } + 1 边 界 : F ( 0 ) = 0 答 案 : F ( n ) = m a x 1 ≤ j < i { F ( i ) } , 答 案 可 以 在 构 建 时 不 断 维 护 更 新 ! F ( i ) 表 示 以 a [ i ] 为 子 序 列 结 束 字 符 的 最 长 上 升 子 序 列 长 度 , 这 一 点 非 常 重 要 F(i)=\underset{1\le j\lt i}{max} \{\mathbb{I}(a_j < a_i)F(j)\} + 1 \\ 边界:F(0)= 0 \\ 答案: F(n)=\underset{1\le j \lt i}{max}\{F(i) \}, 答案可以在构建时不断维护更新! \\ F(i)表示以a[i]为子序列结束字符的最长上升子序列长度, 这一点非常重要 F(i)=1j<imax{ I(aj<ai)F(j)}+1:F(0)=0:F(n)=1j<imax{ F(i)},!F(i)a[i],

https://leetcode-cn.com/problems/longest-increasing-subsequence/

class Solution {
   
public:
    int lengthOfLIS(vector<int>& nums) {
   
        int len = nums.size();
        int dp[len];
        int res = 0;
        dp[0] = 1;

        for (int i = 0; i < len; i++) {
   
            int tmp = 0;
            for (int j = 0; j < i; j++) {
   
                tmp = max(tmp, (nums[i] > nums[j] ? dp[j] : 0));
            }
            dp[i] = tmp + 1;
            res = max(res, dp[i]);
        }
        return res;
    }
};

二维问题: 区间问题 - 最长回文子序列

f ( i , j ) → 区 间 [ i , j ] 内 的 最 长 回 文 子 序 列 长 度 f ( i , j ) = m a x { f ( i + 1 , j ) , f ( i , j − 1 ) , f ( i + 1 , j − 1 ) + 2 I ( a i = a j ) } 答 案 : f ( 1 , n ) 边 界 : f ( i , i ) = 1 f(i,j)\rightarrow 区间[i,j]内的最长回文子序列长度 \\ f(i,j) = max\{ f(i+1, j), f(i,j-1), f(i+1, j-1) + 2\mathbb{I}(a_i=a_j) \} \\ 答案: f(1, n) \\ 边界: f(i, i) = 1 \\ f(i,j)[i,j]f(i,j)=max{ f(i+1,j),f(i,j1),f(i+1,j1)+2I(ai=aj)}:f(1,n):f(i,i)=1

https://leetcode-cn.com/problems/longest-palindromic-subsequence/

class Solution {
   
public:
    int longestPalindromeSubseq(string s) {
   
        int len = s.size();
        int dp[len][len];
        for(int w = 0; w < len; w++) 
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

fanqiliang630

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值