class083 动态规划中用观察优化枚举的技巧-下【算法】

简介: class083 动态规划中用观察优化枚举的技巧-下【算法】

class083 动态规划中用观察优化枚举的技巧-下【算法】

算法讲解083【必备】动态规划中用观察优化枚举的技巧-下

code1 1235. 规划兼职工作

// 规划兼职工作

// 你打算利用空闲时间来做兼职工作赚些零花钱,这里有n份兼职工作

// 每份工作预计从startTime[i]开始、endTime[i]结束,报酬为profit[i]

// 返回可以获得的最大报酬

// 注意,时间上出现重叠的 2 份工作不能同时进行

// 如果你选择的工作在时间X结束,那么你可以立刻进行在时间X开始的下一份工作

// 测试链接 : https://leetcode.cn/problems/maximum-profit-in-job-scheduling/

利用观察单调性 + 二分搜索的方式优化枚举,最优解时间复杂度O(n*logn)

dp[i]:[0…i]的最大报酬

不需要i位置的工作:dp[i-1]

需要i位置的工作:profit[i]+dp[j]:j是结束时间不超过startTime[i]的最右的

package class083;
import java.util.Arrays;
// 规划兼职工作
// 你打算利用空闲时间来做兼职工作赚些零花钱,这里有n份兼职工作
// 每份工作预计从startTime[i]开始、endTime[i]结束,报酬为profit[i]
// 返回可以获得的最大报酬
// 注意,时间上出现重叠的 2 份工作不能同时进行
// 如果你选择的工作在时间X结束,那么你可以立刻进行在时间X开始的下一份工作
// 测试链接 : https://leetcode.cn/problems/maximum-profit-in-job-scheduling/
public class Code01_MaximumProfitInJobScheduling {
  public static int MAXN = 50001;
  public static int[][] jobs = new int[MAXN][3];
  public static int[] dp = new int[MAXN];
  public static int jobScheduling(int[] startTime, int[] endTime, int[] profit) {
    int n = startTime.length;
    for (int i = 0; i < n; i++) {
      jobs[i][0] = startTime[i];
      jobs[i][1] = endTime[i];
      jobs[i][2] = profit[i];
    }
    // 工作按照结束时间从小到大排序
    Arrays.sort(jobs, 0, n, (a, b) -> a[1] - b[1]);
    dp[0] = jobs[0][2];
    for (int i = 1, start; i < n; i++) {
      start = jobs[i][0];
      dp[i] = jobs[i][2];
      if (jobs[0][1] <= start) {
        dp[i] += dp[find(i - 1, start)];
      }
      dp[i] = Math.max(dp[i], dp[i - 1]);
    }
    return dp[n - 1];
  }
  // job[0...i]范围上,找到结束时间 <= start,最右的下标
  public static int find(int i, int start) {
    int ans = 0;
    int l = 0;
    int r = i;
    int m;
    while (l <= r) {
      m = (l + r) / 2;
      if (jobs[m][1] <= start) {
        ans = m;
        l = m + 1;
      } else {
        r = m - 1;
      }
    }
    return ans;
  }
}

code2 629. K 个逆序对数组

// K个逆序对数组

// 逆序对的定义如下:

// 对于数组nums的第i个和第j个元素

// 如果满足0<=i<j<nums.length 且 nums[i]>nums[j],则为一个逆序对

// 给你两个整数n和k,找出所有包含从1到n的数字

// 且恰好拥有k个逆序对的不同的数组的个数

// 由于答案可能很大,只需要返回对10^9+7取余的结果

// 测试链接 : https://leetcode.cn/problems/k-inverse-pairs-array/

最优解 利用观察 + 构造窗口累加和,已经进入 观察并设计高效的查询结构 的范畴了

只不过这个结构就仅存在于概念,并用一个int类型的变量维护而已

package class083;
// K个逆序对数组
// 逆序对的定义如下:
// 对于数组nums的第i个和第j个元素
// 如果满足0<=i<j<nums.length 且 nums[i]>nums[j],则为一个逆序对
// 给你两个整数n和k,找出所有包含从1到n的数字
// 且恰好拥有k个逆序对的不同的数组的个数
// 由于答案可能很大,只需要返回对10^9+7取余的结果
// 测试链接 : https://leetcode.cn/problems/k-inverse-pairs-array/
public class Code02_KInversePairsArray {
  // 最普通的动态规划
  // 不优化枚举
  public static int kInversePairs1(int n, int k) {
    int mod = 1000000007;
    // dp[i][j] : 1、2、3...i这些数字,形成的排列一定要有j个逆序对,请问这样的排列有几种
    int[][] dp = new int[n + 1][k + 1];
    dp[0][0] = 1;
    for (int i = 1; i <= n; i++) {
      dp[i][0] = 1;
      for (int j = 1; j <= k; j++) {
        if (i > j) {
          for (int p = 0; p <= j; p++) {
            dp[i][j] = (dp[i][j] + dp[i - 1][p]) % mod;
          }
        } else {
          // i <= j
          for (int p = j - i + 1; p <= j; p++) {
            dp[i][j] = (dp[i][j] + dp[i - 1][p]) % mod;
          }
        }
      }
    }
    return dp[n][k];
  }
  // 根据观察方法1优化枚举
  // 最优解
  // 其实可以进一步空间压缩
  // 有兴趣的同学自己试试吧
  public static int kInversePairs2(int n, int k) {
    int mod = 1000000007;
    int[][] dp = new int[n + 1][k + 1];
    dp[0][0] = 1;
    // window : 窗口的累加和
    for (int i = 1, window; i <= n; i++) {
      dp[i][0] = 1;
      window = 1;
      for (int j = 1; j <= k; j++) {
        if (i > j) {
          window = (window + dp[i - 1][j]) % mod;
        } else {
          // i <= j
          window = ((window + dp[i - 1][j]) % mod - dp[i - 1][j - i] + mod) % mod;
        }
        dp[i][j] = window;
      }
    }
    return dp[n][k];
  }
}

code3 514. 自由之路

// 自由之路

// 题目描述比较多,打开链接查看

// 测试链接 : https://leetcode.cn/problems/freedom-trail/

优化枚举的核心是贪心策略:

下一步做枚举时,不需要枚举所有可能性,只需要枚举 顺时针的最近、逆时针的最近 两种可能性即可

贪心当然会有专题讲述!【必备】课程的动态规划专题结束了,就会开始贪心的专题

package class083;
// 自由之路
// 题目描述比较多,打开链接查看
// 测试链接 : https://leetcode.cn/problems/freedom-trail/
public class Code03_FreedomTrail {
  // 为了让所有语言的同学都可以理解
  // 不会使用任何java语言自带的数据结构
  // 只使用最简单的数组结构
  public static int MAXN = 101;
  public static int MAXC = 26;
  public static int[] ring = new int[MAXN];
  public static int[] key = new int[MAXN];
  public static int[] size = new int[MAXC];
  public static int[][] where = new int[MAXC][MAXN];
  public static int[][] dp = new int[MAXN][MAXN];
  public static int n, m;
  public static void build(String r, String k) {
    for (int i = 0; i < MAXC; i++) {
      size[i] = 0;
    }
    n = r.length();
    m = k.length();
    for (int i = 0, v; i < n; i++) {
      v = r.charAt(i) - 'a';
      where[v][size[v]++] = i;
      ring[i] = v;
    }
    for (int i = 0; i < m; i++) {
      key[i] = k.charAt(i) - 'a';
    }
    for (int i = 0; i < n; i++) {
      for (int j = 0; j < m; j++) {
        dp[i][j] = -1;
      }
    }
  }
  public static int findRotateSteps(String r, String k) {
    build(r, k);
    return f(0, 0);
  }
  // 指针当前指着轮盘i位置的字符,要搞定key[j....]所有字符,最小代价返回
  public static int f(int i, int j) {
    if (j == m) {
      // key长度是m
      // 都搞定
      return 0;
    }
    if (dp[i][j] != -1) {
      return dp[i][j];
    }
    int ans;
    if (ring[i] == key[j]) {
      // ring b
      //      i
      // key  b
      //      j
      ans = 1 + f(i, j + 1);
    } else {
      // 轮盘处在i位置,ring[i] != key[j]
      // jump1 : 顺时针找到最近的key[j]字符在轮盘的什么位置
      // distance1 : 从i顺时针走向jump1有多远
      int jump1 = clock(i, key[j]);
      int distance1 = (jump1 > i ? (jump1 - i) : (n - i + jump1));
      // jump2 : 逆时针找到最近的key[j]字符在轮盘的什么位置
      // distance2 : 从i逆时针走向jump2有多远
      int jump2 = counterClock(i, key[j]);
      int distance2 = (i > jump2 ? (i - jump2) : (i + n - jump2));
      ans = Math.min(distance1 + f(jump1, j), distance2 + f(jump2, j));
    }
    dp[i][j] = ans;
    return ans;
  }
  // 从i开始,顺时针找到最近的v在轮盘的什么位置
  public static int clock(int i, int v) {
    int l = 0;
    // size[v] : 属于v这个字符的下标有几个
    int r = size[v] - 1, m;
    // sorted[0...size[v]-1]收集了所有的下标,并且有序
    int[] sorted = where[v];
    int find = -1;
    // 有序数组中,找>i尽量靠左的下标
    while (l <= r) {
      m = (l + r) / 2;
      if (sorted[m] > i) {
        find = m;
        r = m - 1;
      } else {
        l = m + 1;
      }
    }
    // 找到了就返回
    // 没找到,那i顺指针一定先走到最小的下标
    return find != -1 ? sorted[find] : sorted[0];
  }
  public static int counterClock(int i, int v) {
    int l = 0;
    int r = size[v] - 1, m;
    int[] sorted = where[v];
    int find = -1;
    // 有序数组中,找<i尽量靠右的下标
    while (l <= r) {
      m = (l + r) / 2;
      if (sorted[m] < i) {
        find = m;
        l = m + 1;
      } else {
        r = m - 1;
      }
    }
    // 找到了就返回
    // 没找到,那i逆指针一定先走到最大的下标
    return find != -1 ? sorted[find] : sorted[size[v] - 1];
  }
}

code4 未排序数组中累加和小于或等于给定值的最长子数组长度

// 累加和不大于k的最长子数组

// 给定一个无序数组arr,长度为n,其中元素可能是正、负、0

// 给定一个整数k,求arr所有的子数组中累加和不大于k的最长子数组长度

// 要求时间复杂度为O(n)

// 测试链接 : https://www.nowcoder.com/practice/3473e545d6924077a4f7cbc850408ade

// 请同学们务必参考如下代码中关于输入、输出的处理

// 这是输入输出处理效率很高的写法

// 提交以下的code,提交时请把类名改成"Main",可以直接通过

强烈推荐先看一下讲解046

利用构造单调数组 + 二分搜索的解不是最优解,时间复杂度O(n*logn)

最优解中包含的贪心思想(窗口的加速建立、可能性的淘汰),是这个题的重点,时间复杂度O(n)

贪心当然会有专题讲述!【必备】课程的动态规划专题结束了,就会开始贪心的专题

package class083;
// 累加和不大于k的最长子数组
// 给定一个无序数组arr,长度为n,其中元素可能是正、负、0
// 给定一个整数k,求arr所有的子数组中累加和不大于k的最长子数组长度
// 要求时间复杂度为O(n)
// 测试链接 : https://www.nowcoder.com/practice/3473e545d6924077a4f7cbc850408ade
// 请同学们务必参考如下代码中关于输入、输出的处理
// 这是输入输出处理效率很高的写法
// 提交以下的code,提交时请把类名改成"Main",可以直接通过
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
// 至今的最优解,全网题解几乎都是我几年前讲的方法
public class Code04_LongestSubarraySumNoMoreK {
  public static int MAXN = 100001;
  public static int[] nums = new int[MAXN];
  public static int[] minSums = new int[MAXN];
  public static int[] minSumEnds = new int[MAXN];
  public static int n, k;
  public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    StreamTokenizer in = new StreamTokenizer(br);
    PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
    while (in.nextToken() != StreamTokenizer.TT_EOF) {
      n = (int) in.nval;
      in.nextToken();
      k = (int) in.nval;
      for (int i = 0; i < n; i++) {
        in.nextToken();
        nums[i] = (int) in.nval;
      }
      out.println(compute2());
    }
    out.flush();
    out.close();
    br.close();
  }
  public static int compute1() {
    int[] sums = new int[n + 1];
    for (int i = 0, sum = 0; i < n; i++) {
      // sum : 0...i范围上,这前i+1个数字的累加和
      sum += nums[i];
      // sums[i + 1] : 前i+1个,包括一个数字也没有的时候,所有前缀和中的最大值
      sums[i + 1] = Math.max(sum, sums[i]);
    }
    int ans = 0;
    for (int i = 0, sum = 0, pre, len; i < n; i++) {
      sum += nums[i];
      pre = find(sums, sum - k);
      len = pre == -1 ? 0 : i - pre + 1;
      ans = Math.max(ans, len);
    }
    return ans;
  }
  public static int find(int[] sums, int num) {
    int l = 0;
    int r = n;
    int m = 0;
    int ans = -1;
    while (l <= r) {
      m = (l + r) / 2;
      if (sums[m] >= num) {
        ans = m;
        r = m - 1;
      } else {
        l = m + 1;
      }
    }
    return ans;
  }
  public static int compute2() {
    minSums[n - 1] = nums[n - 1];
    minSumEnds[n - 1] = n - 1;
    for (int i = n - 2; i >= 0; i--) {
      if (minSums[i + 1] < 0) {
        minSums[i] = nums[i] + minSums[i + 1];
        minSumEnds[i] = minSumEnds[i + 1];
      } else {
        minSums[i] = nums[i];
        minSumEnds[i] = i;
      }
    }
    int ans = 0;
    for (int i = 0, sum = 0, end = 0; i < n; i++) {
      while (end < n && sum + minSums[end] <= k) {
        sum += minSums[end];
        end = minSumEnds[end] + 1;
      }
      if (end > i) {
        // 如果end > i,
        // 窗口范围:i...end-1,那么窗口有效
        ans = Math.max(ans, end - i);
        sum -= nums[i];
      } else {
        // 如果end == i,那么说明窗口根本没扩出来,代表窗口无效
        // end来到i+1位置,然后i++了
        // 继续以新的i位置做开头去扩窗口
        end = i + 1;
      }
    }
    return ans;
  }
}

2023-12-10 12:31:44

相关文章
|
12天前
|
存储 缓存 算法
Python中常用的数据结构与算法优化技巧指南
Python是一种强大而灵活的编程语言,它提供了丰富的数据结构和算法库,但是在处理大规模数据或者需要高效运行的情况下,需要考虑一些优化技巧。本文将介绍一些Python中常用的数据结构与算法优化技巧,并附带代码实例,帮助你更好地理解和运用。
|
6天前
|
存储 算法 搜索推荐
Java数据结构与算法优化
Java数据结构与算法优化
|
2天前
|
机器学习/深度学习 算法 调度
Matlab|基于改进鲸鱼优化算法的微网系统能量优化管理matlab-源码
基于改进鲸鱼优化算法的微网系统能量管理源码实现,结合LSTM预测可再生能源和负荷,优化微网运行成本与固定成本。方法应用于冷热电联供微网,结果显示经济成本平均降低4.03%,提高经济效益。代码包括数据分段、LSTM网络定义及训练,最终展示了一系列运行结果图表。
|
13天前
|
算法
基于GA遗传优化的混合发电系统优化配置算法matlab仿真
**摘要:** 该研究利用遗传算法(GA)对混合发电系统进行优化配置,旨在最小化风能、太阳能及电池储能的成本并提升系统性能。MATLAB 2022a用于实现这一算法。仿真结果展示了一系列图表,包括总成本随代数变化、最佳适应度随代数变化,以及不同数据的分布情况,如负荷、风速、太阳辐射、弃电、缺电和电池状态等。此外,代码示例展示了如何运用GA求解,并绘制了发电单元的功率输出和年变化。该系统原理基于GA的自然选择和遗传原理,通过染色体编码、初始种群生成、适应度函数、选择、交叉和变异操作来寻找最优容量配置,以平衡成本、效率和可靠性。
|
14天前
|
机器学习/深度学习 算法
基于鲸鱼优化的knn分类特征选择算法matlab仿真
**基于WOA的KNN特征选择算法摘要** 该研究提出了一种融合鲸鱼优化算法(WOA)与K近邻(KNN)分类器的特征选择方法,旨在提升KNN的分类精度。在MATLAB2022a中实现,WOA负责优化特征子集,通过模拟鲸鱼捕食行为的螺旋式和包围策略搜索最佳特征。KNN则用于评估特征子集的性能。算法流程包括WOA参数初始化、特征二进制编码、适应度函数定义(以分类准确率为基准)、WOA迭代搜索及最优解输出。该方法有效地结合了启发式搜索与机器学习,优化特征选择,提高分类性能。
|
10天前
|
机器学习/深度学习 存储 算法
基于SFLA算法的神经网络优化matlab仿真
**摘要:** 使用MATLAB2022a,基于SFLA算法优化神经网络,降低训练误差。程序创建12个神经元的前馈网络,训练后计算性能。SFLA算法寻找最优权重和偏置,更新网络并展示训练与测试集的预测效果,以及误差对比。SFLA融合蛙跳与遗传算法,通过迭代和局部全局搜索改善网络性能。通过调整算法参数和与其他优化算法结合,可进一步提升模型预测精度。
|
3天前
|
算法 调度
【重磅】“一招”解决智能算法中不满足“预期”的问题【以微电网优化调度为例】
摘要(Markdown格式): 在对微电网优化调度的模型复现中,发现智能算法(如改进粒子群优化)得出的结果有时不符合预期。例如,电网在低电价时段未满负荷购电,而高电价设备出力未相应降低,可能由于算法陷入局部最优或约束条件设置不当。为解决此问题,采用了梯级罚函数方法改进代码,以更好地满足预期的逻辑关系和优化目标。更新后的程序结果显示设备出力和电价成本的关系更符合预期,降低了运行成本。详细分析和改进后的程序结果图表可见相关链接。
|
4天前
|
算法 Java 数据安全/隐私保护
Java中的位操作与算法优化
Java中的位操作与算法优化
|
5天前
|
存储 算法 搜索推荐
Java数据结构与算法优化
Java数据结构与算法优化
|
5天前
|
缓存 算法 安全
Java中的数据结构与算法优化策略
Java中的数据结构与算法优化策略