目录

一、动态规划(Dynamic Programming,DP)

1.1 【编程题】斐波那契数列

1.2【编程题】青蛙跳台阶扩展问题

1.3【编程题】最小花费爬楼梯

1.4【编程题】不同路径(机器人走方格)

1.5【编程题】不同路径机器人走方格(有障碍)

1.6【编程题】走方格的方案数(同上)

1.7【编程题】拆分词句

1.8【编程题】三角形

二、背包理论

2.1  二维dp数组01背包

2.2  一维dp数组 01背包


一、动态规划(Dynamic Programming,DP)

动态规划是分治思想的延伸:将大问题化解为小问题的分治过程中,保存对这些小问题已经处理好的结果,并供后面处理更大规模的问题时直接使用这些结果。

特点:
1. 把原来的问题分解成了几个相似的子问题。
2. 所有的子问题都只需要解决一次。
3. 储存子问题的解。

本质:对问题状态的定义和状态转移方程的定义(状态以及状态之间的递推关系)。

考虑角度:状态定义(要求:定义的状态要形成递推关系)、状态间的转移方程定义、 状态的初始化、返回结果

适用场景:最大值/最小值, 可不可行, 是不是,方案个数

对于动态规划问题,拆解为如下五步骤:

  1. 确定dp数组(dp table)以及下标的含义
  2. 确定递推公式
  3. dp数组如何初始化
  4. 确定遍历顺序
  5. 举例推导dp数组(打印数组)

1.1 【编程题】斐波那契数列

斐波那契数列—牛客

题目:斐波那契数列,现在要求输入一个正整数 n ,输出斐波那契数列的第 n 项。

斐波那契数列(黄金分割数列):1、1、2、3、5、8、13、21、34、…,以递推的方法定义:

F(0)=0,F(1)=1,  F(n)=F(n-1)+F(n-2)(n ≥2,n ∈ N*)

输入描述:

一个正整数n

输出描述:

输出一个正整数

注意·!!!斐波那契数组,要单独考虑n=0和n=1的情况

1.递归方法:时间复杂度O(2^n),输入较大时,可能栈溢出,递归过程中有大量的重复计算

public class Solution {public int Fibonacci(int n) {if(n <= 1) return n;int f = Fibonacci(n-1)+Fibonacci(n-2);  //如果n>2 则输出freturn f;}
}

2.动态规划:    空间复杂度为O(1)、空间复杂度为O(n)

状态:F(n)
状态递推:F(n)=F(n-1)+F(n-2)
初始值:F(1)=F(2)=1
返回结果:F(N)

public class Solution {public int Fibonacci(int n) {   //空间复杂度为O(1)if(n <= 0)  return 0;if(n == 1 || n == 2) return 1;int ret = 0;int fn1 = 1, fn2 = 1;for(int i = 3; i <= n; i++) {ret = fn1 + fn2;fn1 = fn2;fn2 = ret;}return ret;}
}//方法二:  //空间复杂度为O(n)public int Fibonacci(int n) {   int[] dp= new int[n+1];  //创建一个数组保存中间状态的解dp[0] = 0;dp[1] = 1;for(int i = 2; i < n+1; i++) {dp[i] = dp[i-1] + dp[i-2];}return dp[n];}

1.2【编程题】青蛙跳台阶扩展问题

牛客—青蛙跳台阶

题目1:青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶(n为正整数)总共有多少种跳法。

方法1:递归

1.逆向思维:若从第n个台阶进行下台阶,下一步有2中可能,一种走到第n-1个台阶,一种是走到第n-2个台阶,可得到如下关系:f[n] = f[n-1] + f[n-2]. (f[n] 表示在第n个台阶上的方法数)

2.初始条件:f[0] = f[1] = 1

3.看到此问题可以想到斐波那契数组,使用动态规划:可优化空间,优化掉递归的栈空间,动态规划直接从子树求得答案。过程是从下往上。

//递归方法
class Solution {public int jumping(int num) {if(num<=1) return n;return jumping(num-1)+jumping(num-2);}//优化1:动态规划public int jumping(int num) {   int[] arr= new int[num+1];  //创建一个数组存放每一级台阶可以的方法数arr[0] = 0;arr[1] = 1;for(int i = 2; i < num+1; i++) {arr[i] = arr[i-1] + arr[i-2];}return arr[num];  // 返回num此时对应的方法数量}//优化2:可以发现在这个过程中,计算当前台阶数只用到了前两个台阶的值,因此,只需定义三个变量即可public int jumping(int num) {   int a = 1;int b = 1;int sum = 0;for(int i=2,i <= num,i++) {sum = a + b;a = b;b = sum;}return sum;}
}

题目2:一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶(n为正整数)总共有多少种跳法。

解析:此问题是上面的延伸:可得到关系 :f(n)=f(n-1)+…f(1)---->f(n)=2*f(n-1)

public class Solution {
//方法一:排列
// 每个台阶看成一个位置,除过最后一个位置,其它位置都有两种可能性,
// 所以总的排列数为2^(n-1)*1 = 2^(n-1)public int jumpFloorII(int n) {int sum = 1;if (n == 1) return 1; //如果只有一个台阶,则只有一种方法for(int i = 1; i < n; i++) {   //从第二级台阶开始sum *= 2;}return sum;}
}

1.3【编程题】最小花费爬楼梯

746. 使用最小花费爬楼梯

给一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。计算并返回达到楼梯顶部的最低花费

输入:cost = [10,15,20]

输出:15

解释:从下标为 1 的台阶开始。 支付 15 ,向上爬两个台阶,到达楼梯顶部, 总花费 15。

输入:cost = [1,100,1,1,1,100,1,1,100,1]
输出:6
解释:从下标为 0 的台阶开始。
- 支付 1 ,向上爬两个台阶,到达下标为 2 的台阶。
- 支付 1 ,向上爬两个台阶,到达下标为 4 的台阶。
- 支付 1 ,向上爬两个台阶,到达下标为 6 的台阶。
- 支付 1 ,向上爬一个台阶,到达下标为 7 的台阶。
- 支付 1 ,向上爬两个台阶,到达下标为 9 的台阶。
- 支付 1 ,向上爬一个台阶,到达楼梯顶部。
总花费为 6 。

解析:

!!!!!!!!每当你爬上一个阶梯,都要花费对应的体力值cost[i]

1.确定dp数组以及下标的含义:

dp[i]的定义:到达第i个台阶所花费的最少体力为dp[i]。

2.确定递推公式:

可以有两个途径得到dp[i],一个是dp[i-1] 一个是dp[i-2]。

dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i];

3. dp数组如何初始化

dp[0] =cost[0] ;  dp[1]  = cost[1];

4.确定遍历顺序

从前到后遍历cost数组

5.举例推导dp数组

// 方式一:第一步支付费用
class Solution {public int minCostClimbingStairs(int[] cost) {int len = cost.length;  //求数组长度int[] dp = new int[len+1];  //动态规划初始化数组dp大小一般为n+1dp[0] = 0;dp[1] = 0;   // dp[i]为到达第i个台阶所需要支付的费用
//!!!!!!!!每当你爬上一个阶梯,都要花费对应的体力值cost[i]for(int i = 2;i < len+1; i++) {dp[i] = Math.min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2]);  //支付费用后,有两种可选方式,爬一个台阶或者两个台阶,取每一步(局部)最小值,即可求得最后所有步的最小值}return dp[len];  //返回爬上最后一个台阶需要花费的费用}
}// 方式二:第一步不支付费用
class Solution {public int minCostClimbingStairs(int[] cost) {int len = cost.length;int[] dp = new int[len];dp[0] = cost[0];dp[1] = cost[1];for (int i = 2; i < len; i++) {dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i];}//最后一步,如果是由倒数第二步爬,则最后一步的体力花费可以不用算return Math.min(dp[len - 1], dp[len - 2]);}
}

1.4【编程题】不同路径(机器人走方格)

62. 不同路径

一个机器人位于 m *n 网格的左上角 (起始点标记为 “Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(标记为 “Finish” )。问总共有多少条不同的路径?

解析:

1.根据分析画图可得到:当n或m为1时,ret = 1,只有一种方法

2.m = 2,且 n = 2时,f(2,2) = 2;

2.递归公式f(m,n) = f(m-1,n) + f(m,n-1)

1.动态规划解法:

class Solution {public int uniquePaths(int m, int n) { int[][] dp = new int[m][n];for(int i = 0; i < m; i++) {  dp[i][0] = 1;   //初始化,表示一条竖线,故只有一种方法·}for(int i = 0; i < n; i++) {   //初始化,表示一条横线dp[0][i] = 1;}for(int i = 1;i < m; i++) {for(int j = 1; j < n; j++) {dp[i][j] = dp[i-1][j] + dp[i][j-1]; //动态规划推导公式}}return dp[m-1][n-1];  //f(m,n) = f(m-1,n)+f(m,n-1)}
}

2.递归方法:

class Solution {public int uniquePaths(int m, int n) {if(m <= 0 || n <= 0) return 0 ;if(m == 1 || n == 1) return 1 ;if(m == 2 && n == 2) return 2 ;return uniquePaths(m-1,n) + uniquePaths(m,n-1);}
}

1.5【编程题】不同路径机器人走方格(有障碍)

63. 不同路径 II

机器人位于 m*n 网格的左上角 (起始点标记为 “Start” )每次只能向下或者向右移动一步。机器人到网格的右下角( “Finish”)。现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?网格中的障碍物和空位置分别用 1 和 0 来表示。

输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
输出:2
解释:3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:
1. 向右 -> 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右 -> 向右

解析:

1.确定dp数组(dp table)以及下标的含义

dp[i][j] :表示从(0 ,0)出发,到(i, j) 有 dp[i][j] 条不同的路径。

2.确定递推公式

无障碍进行递推:dp[i][j] = dp[i - 1][j] + dp[i][j - 1]。有了障碍,(i, j)如果就是障碍的话应该就保持初始状态(初始状态为0)

if (obstacleGrid[i][j] == 0) { // 当(i, j)没有障碍的时候,再推导dp[i][j]dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}

3.初始化

若无障碍 dp[i][0] = dp[0][i] = 1,有障碍则为·0

 4.确定遍历顺序

从递归公式dp[i][j] = dp[i - 1][j] + dp[i][j - 1] 中得出,一定是从左到右一层一层遍历,保证推导dp[i][j]时,dp[i - 1][j] 和 dp[i][j - 1]一定是有数值。

5.举例推导dp数组

     

完整代码:

class Solution {public int uniquePathsWithObstacles(int[][] obstacleGrid) {int m = obstacleGrid.length;int n = obstacleGrid[0].length;int[][] dp = new int[m][n];if(obstacleGrid[m-1][n-1] == 1 || obstacleGrid[0][0] == 1) {return 0;     //起点和终点若是障碍物,则不同通行}for (int i = 0; i < m && obstacleGrid[i][0] == 0; i++) {dp[i][0] = 1;}for (int j = 0; j < n && obstacleGrid[0][j] == 0; j++) {dp[0][j] = 1;}for(int i = 1; i < m; i++) {   //无障碍动规操作 ,有障碍为0for(int j = 1; j < n; j++) {dp[i][j] = (obstacleGrid[i][j] == 0) ? (dp[i-1][j] + dp[i][j-1]): 0;  //无障碍执行动规,有障碍0}}return dp[m-1][n-1];}
}

1.6【编程题】走方格的方案数(同上)

走方格

请计算n*m的棋盘格子(n为横向的格子数,m为竖向的格子数)从棋盘左上角出发沿着边缘线从左上角走到右下角,总共有多少种走法,要求不能走回头路,即:只能往右和往下走,不能往左和往上走。

注:沿棋盘格之间的边缘线行走

输入描述:

输入两个正整数n和m,用空格隔开。(1≤n,m≤8)

输出描述:

输出一行结果                如:输入:2  2---->6

解析:总路径:(n,m)=(n-1,m)+(n,m-1) ---->使用递归

2.当n==1 && m>= 1------>对应路径数n+m;

3.当m==1 && n>= 1------>对应路径数n+m

终止条件m,n = 1

4.当m,n都>1时,如下情况:每走一步有两种情况,因此用递归方法来实现

import java.util.Scanner;
public class Main {public static void main(String[] args) {Scanner in = new Scanner(System.in);while (in.hasNextInt()) {  //多组样例,因此需要循环读入int n = in.nextInt();int m = in.nextInt();System.out.println(func(n,m));}}public static int func(int n,int m) {  //处理输入的两个数据,递归函数if((n == 1 && m >=1) || (m == 1 && n >=1)) {return m+n;}return func(n-1,m)+func(n,m-1); }
}

1.7【编程题】拆分词句

给定一个字符串s和一组单词dict,判断s是否可以用空格分割成一个单词序列,使得单词序列中所有的单词都是dict中的单词(序列可以包含一个或多个单词),如:
给定s=“nowcode”;dict=["now", "code"].
返回true,因为"nowcode"可以被分割成"now code".

import java.util.Set;public class Solution {public boolean wordBreak(String s, Set<String> dict) {if(s == null || s.length() == 0) return false;boolean[] dp = new boolean[s.length()+1];    //给定一个状态数组,存放每个字符是否被分割的true,false值,判定其是否能够被分割,dp[0] = true;  //初始值for(int i = 1;i <= s.length();i++) { //遍历字符串,从1开始,因为下标0给了初始状态for (int j = i - 1; j >= 0; j--) {if (dp[j] && dict.contains(s.substring(j, i))) {  // 字典里有字符串的子串 dp[i] = true;}}}return dp[s.length()];}
}

1.8【编程题】三角形

给出一个三角形,计算从三角形顶部到底部的最小路径和,每一步都可以移动到下面一行相邻的数字,如 : 给出的三角形如下:

[[20],[30,40],[60,50,70],[40,10,80,30]]

最小的从顶部到底部的路径和是20 + 30 + 50 + 10 = 110。

问题:从顶部到底部的最小路径和

状态F(i,j):(0,0)到(i,j)的最小路径和
状态递推:F(i,j) = min(i-1,j-1), F(i-1,j) + array[i , j]
初始值:F(1)=F(2)=1
返回结果:F(N)

解析:

1. 新增一个数组来存储当前层到下一层各个节点最短的路径值

2.用这个三角形数组每一层本身来存储到达当前层的最短路径,这样就不需要额外的存储空间。。

import java.util.*;
public class Solution {public int minimumTotal(ArrayList<ArrayList<Integer>> triangle) {if (triangle.size() == 0 || triangle == null) return 0;int n = triangle.size();    //记录三角形的层数 (外层数组)      int[] temp = new int[n];  //创建一个数组,存放到达每一层的最小步数,数组大小为层数    (内层数组)for (int i = 0; i < n; i++) //  triangle.get(n-1)获取(n-1) 行的所有元素  ----->(n-1)行中i位置的元素temp[i] = triangle.get(n-1).get(i);  //获取最后一层 i位置 元素//继续由下向上运算for(int i = n-2; i >= 0; i--){  //i代表行数,j为每一行的元素for(int j = 0; j <= i; j++){temp[j] = triangle.get(i).get(j)+Math.min(temp[j],temp[j+1]);  //获取当前行i-1的最小值min + 上一层i节点}}return temp[0];}
}

二、背包理论

2.1  二维dp数组01背包

1. 确定dp数组以及下标的含义

dp[i][j] :[0,i ] 物品里任意,放进容量为 j 的背包里,价值总和的最大值。

 2.确定递推公式

  • 不放物品 i :最大价值为dp[ i - 1] [ j ],也即是物品 i 的容量 > 背包 j  的体积
  • 放物品 i :最大价值为dp[ i - 1] [ j  - weight[ i ] ]  +  value[ i ]  ---> 也即是  i-1 个物品的价值+第 i 个物品的价值  ( value[ i ] 为物品 i 的价值;weight[ i ]为物品 i 的容量)

递归公式: dp[ i ][ j ] = max(dp[i - 1][ j ], dp[i - 1][j - weight[ i ]] + value[ i ]);

3.dp数组如何初始化

1) 若背包容量 j = 0(dp[ i ][ 0 ]),背包价值总和 dp[ i ][ j ] = 0。

2) i 为 0,存放编号0的物品,各个容量的背包所能存放的最大价值 dp[ i ][ j ]。

当 j < weight [ 0 ] 时,dp[ 0 ][ j ]  = 0,因为背包容量 <  物品容量,(装不下,最大价值为0)

当j >= weight [ 0 ] 时,dp[ 0 ][ j ]  = value[ 0 ],背包容量只要大于物品容量即可存放。

先遍历 物品  还是 背包?都可以!!  先遍历物品更好理解。


二维dp数组实现01背包 完整代码:

public static void main(String[] args) {int[] goodsWeight = {1, 3, 4};  // 物品容量int[] value = {15, 20, 30};  // 物品价值int bagSize = 4;  //背包最大体积testweightbagproblem(goodsWeight, value, bagSize);     //递归}public static void testweightbagproblem(int[] goodsWeight, int[] value, int bagSize){int goodsNum= goodsWeight.length;  //物品个数int value0 = 0;  //价值为0int[][] dp = new int[goodsNum+ 1][bagSize + 1];  //dp[i][j]包容量为j,前i个物品的最大价值for (int i = 0; i <= goodsNum; i++){  //背包容量为0,价值0(不能放物品)dp[i][0] = value0;}for (int i = 1; i <= goodsNum; i++){   //先遍历物品,再遍历背包容量for (int j = 1; j <= bagSize; j++){if (j < goodsWeight[i - 1]){   //背包容量 < 物品i,则i不能放入dp[i][j] = dp[i - 1][j];}else{dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - goodsWeight[i - 1]] + value[i - 1]);}}}for (int i = 0; i <= goodsNum; i++){   //打印dp数组for (int j = 0; j <= bagSize; j++){System.out.print(dp[i][j] + " ");}System.out.print("\n");}}

2.2  一维dp数组 01背包

二维:dp[ i ][ j ] = max(dp[ i ][ j ], dp[ i ][ j - weight[i] ] + value[i]);

dp[ j ]:容量为j的背包,所背物品价值dp[ j ]

递推公式:

dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

初始化:非0下标都初始化为0

一维dp数组实现01背包 完整代码:

 public static void main(String[] args) {int[] goodsWeight = {1, 3, 4};int[] value = {15, 20, 30};int bagWight = 4;testWeightBagProblem(goodsWeight, value, bagWight);  //递归方法}public static void testWeightBagProblem(int[] goodsWeight, int[] value, int bagWeight){int doodsNum = goodsWeight.length;int[] dp = new int[bagWeight + 1]; // dp[j]背包容量为 j 的最大价值for (int i = 0; i < doodsNum; i++){   //先遍历物品,再遍历背包容量for (int j = bagWeight; j >= weight[i]; j--){dp[j] = Math.max(dp[j], dp[j - goodsWeight[i]] + value[i]);}}for (int j = 0; j <= bagWeight; j++){  //打印dp数组System.out.print(dp[j] + " ");}}

【算法01】—动态规划相关推荐

  1. Java入门算法(动态规划篇2:01背包精讲)

    本专栏已参加蓄力计划,感谢读者支持❤ 往期文章 一. Java入门算法(贪心篇)丨蓄力计划 二. Java入门算法(暴力篇)丨蓄力计划 三. Java入门算法(排序篇)丨蓄力计划 四. Java入门算 ...

  2. NOI入门级:算法之动态规划

    糖糖讲动态规划算法,找零钱完全背包问题,LeetCode 322 糖糖讲动态规划算法,找零钱完全背包问题,LeetCode 322_哔哩哔哩_bilibili 程序员面试再也不怕动态规划了,看动画,学 ...

  3. 【算法】动态规划笔记

    [算法]动态规划笔记 动态规划: 将一个复杂的问题分解成若干个子问题,通过综合子问题的最优解来得到原问题的最优解 动态规划会将每个求解过的子问题的解记录下来,这样下一次碰到同样的子问题时,就可以直接使 ...

  4. 算法:动态规划窃贼问题C语言实现

    算法:动态规划窃贼问题C语言实现 目录 算法:动态规划窃贼问题C语言实现 第一章 问题描述 1.1问题描述 第二章 算法思想及算法设计分析 2.1算法思想 2.2设计算法 2.3算法分析 2.4填表结 ...

  5. 数据结构与算法学习⑥(动态规划 题解 背包和打家劫舍问题)

    数据结构与算法学习⑥(动态规划 动态规划 1.初识动态规划 1.1.从贪心说起 1.1.1.贪心的特点 1.1.2.贪心的局限性 1.1.3.贪心失效后怎么办 1.1.4.从最优化问题到递归 1.2. ...

  6. 算法学习--动态规划与贪心算法

    动态规划与贪心算法都是一种递推算法,都是用局部最优解来推导全局最优解:是对遍历解空间的一种优化:当问题具有最优子结构时,可以用动态规划来解决,而贪心算法是动态规划的特例 动态规划 1. 动态规划的思想 ...

  7. 零起点学算法01——第一个程序Hello World!

    零起点学算法01--第一个程序Hello World! Description 题目很简单 输出"Hello World!"(不含引号),并换行. Input 没有输入 Outpu ...

  8. Bellman-Ford 算法 和 动态规划

    Floyd算法: 状态: d[k][i][j]定义:"只能使用第1号到第k号点作为中间媒介时,点i到点j之间的最短路径长度." 动态转移方程: d[k][i][j]=min(d[k ...

  9. 五大经典算法之动态规划

    一.概念起源   动态规划,又名DP算法(取自其Dynamic Programming的缩写),最初是运筹学的一个分支,是用来求解决策过程最优化的数学方法. 二.基本思想   把 多阶段过程 转化为一 ...

  10. 贪心算法和动态规划的区别

    一.动态规划  动态规划(简称DP)的思想是把一个大的问题进行拆分,细分成一个个小的子问题,且能够从这些小的子问题的解当中推导出原问题的解. 性质 1.最优子结构性:既所拆分的子问题的解是最优解. 2 ...

最新文章

  1. spring 配置文件 数据库引入
  2. python读excel乱码_Python读写excel练习_去除excel中乱码行,并添加列
  3. 线性代数:第三章 矩阵的初等变换与线性方程组(1)矩阵的初等变换 矩阵的秩
  4. 设置socket.Receive()的等待时延
  5. lol服务器不稳定补偿地址,LOL9月4日更新bug补偿地址在哪里 9月4日更新bug补偿地址分享...
  6. 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(五)
  7. linux中重定向学习总结
  8. sqlserver 查询中使用Union或Union All
  9. 论游戏项目中的左与右
  10. mysql使用裸设备_请教dd清空裸设备问题
  11. Netty工作笔记0069---Protobuf使用案例
  12. 李飞飞最新研究成果!斯坦福正在用算法判断政治倾向
  13. 团队-象棋游戏-代码设计规范
  14. 线程池原理_Java线程池实现原理
  15. 关闭计算机端口的命令行,关闭端口命令,小编教你如何关闭电脑80端口
  16. c语言数字转化为英文版,(C语言编写的英语数字转化代码数字转化为用英语表达的数字.doc...
  17. 佛理(引用别人的东西,怕忘记了不好找,拿来了,哈哈)
  18. 安全架构--8--我设计的企业安全体系架构
  19. 那年花一个钟用PS改证件照的背景色,今天用Excel我只花了60秒!
  20. Python开发【第六章】:面向对象

热门文章

  1. c语言嵌入式学习,学习嵌入式C语言的秘诀
  2. VAX 的使用快捷键
  3. xmlspy使用必备的技巧
  4. 前端使用阿里巴巴矢量图库的图标大全
  5. HTML 中获取现在时间,实时时间获取
  6. android 不限速迅雷,迅雷不限速分享(手机+PC端打包) – 长期更新
  7. linux修改u盘mbr,远景论坛U盘版 完美 4G/8G/16G WINPE+LINUXPE+MACPE+10.9正式版MBR安装版 制作超简单...
  8. ong拼音汉字_拼音ong的正确发音
  9. 好用的python工具_Python哪些工具好用?老男孩Python开发
  10. 软件项目管理第十章笔记---项目采购管理