斐波那契数列
求斐波那契数列的第 n 项,n <= 39。
- 我对动态规划的理解是,一个问题的答案可以通过直接得出的结论来解决。和递归不同的是,递归注重子问题的解决方式。
动态规划注重这个问题如何通过子问题解决。
通常情况下,会希望不需要额外的存储空间。最好的情况下,计算一次用额外空间存储后,直接输出答案。 - 要算第三个数就是加第一个数和第二个数
- 要算第四个数就是加第二个数和第一个数
public class Solution {
private int[] fib = new int[40];
public Solution() {
fib[1] = 1;
for (int i = 2; i < fib.length; i++)
fib[i] = fib[i - 1] + fib[i - 2];
}
public int Fibonacci(int n) {
return fib[n];
}
}
https://github.com/CyC2018/CS-Notes/blob/master/notes/10.1%20%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97.md
矩形覆盖
我们可以用 21 的小矩形横着或者竖着去覆盖更大的矩形。请问用 n 个 21 的小矩形无重叠地覆盖一个 2n 的大矩形,总共有多少种方法?*
- 当n>=3的时候。第一步有两种解决思路,先覆盖一个2*1,然后再操作。或者先覆盖一个2*2,然后再操作。所以答案是
f(n-1)+f(n-2)
public int rectCover(int n) {
if (n <= 2)
return n;
int pre2 = 1, pre1 = 2;
int result = 0;
for (int i = 3; i <= n; i++) {
result = pre2 + pre1;
pre2 = pre1;
pre1 = result;
}
return result;
}
https://github.com/CyC2018/CS-Notes/blob/master/notes/10.2%20%E7%9F%A9%E5%BD%A2%E8%A6%86%E7%9B%96.md
跳台阶
一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
- 当n>=3的时候。第一步有两种解决思路,先跳一阶,然后再操作。或者跳两阶,然后再操作。
public int JumpFloor(int n) {
if (n <= 2)
return n;
int pre2 = 1, pre1 = 2;
int result = 0;
for (int i = 2; i < n; i++) {
result = pre2 + pre1;
pre2 = pre1;
pre1 = result;
}
return result;
}
https://github.com/CyC2018/CS-Notes/blob/master/notes/10.3%20%E8%B7%B3%E5%8F%B0%E9%98%B6.md
变态跳台阶
一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级... 它也可以跳上 n 级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
- 当n>=3的时候。第一步有n中解决方案,第一次跳1级,第一次跳2级....第一次跳n级。所以答案是
f(n)=f(n-1) + f(n-2) + ... + f(0)
。 - 思考一下,可以化简。
f(n-1) = f(n-2) + f(n-3) + ... + f(0)
,带入可得f(n) = 2*f(n-1)
public int JumpFloorII(int target) {
return (int) Math.pow(2, target - 1);
}
https://github.com/CyC2018/CS-Notes/blob/master/notes/10.4%20%E5%8F%98%E6%80%81%E8%B7%B3%E5%8F%B0%E9%98%B6.md
连续子数组的最大和
{6, -3, -2, 7, -15, 1, 2, 2},连续子数组的最大和为 8(从第 0 个开始,到第 3 个为止)。
- 像数学题。
- 先明确2点。第一点:答案是在遍历数组的过程中得出来的,有一个答案,然后不断的更新,最后得到正确答案。第二点:如果可以的话,只想遍历一次数组。
- 动态规划注重这个问题如何通过子问题解决。子问题是:连续子数组的最大和,是怎么做的。
- 加,遍历一个加一个,如果比答案大,答案换掉。如果比答案小,不管。
- 如果加的过程中,小于0了。加的结果换成当前的那个数,然后再比较。
public int FindGreatestSumOfSubArray(int[] nums) {
if (nums == null || nums.length == 0)
return 0;
int greatestSum = Integer.MIN_VALUE;
int sum = 0;
for (int val : nums) {
sum = sum <= 0 ? val : sum + val;
greatestSum = Math.max(greatestSum, sum);
}
return greatestSum;
}
https://github.com/CyC2018/CS-Notes/blob/master/notes/42.%20%E8%BF%9E%E7%BB%AD%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E5%92%8C.md
礼物的最大价值
在一个 mn 的棋盘的每一个格都放有一个礼物,每个礼物都有一定价值(大于 0)。从左上角开始拿礼物,每次向右或向下移动一格,直到右下角结束。给定一个棋盘,求拿到礼物的最大价值。例如,对于如下棋盘*
- 很熟悉,如果按照之前的算法肯定深度优先开始递归循环了。有一个特点,深度优先往往会有一连串的比较,甚至回溯。动态规划会找一个数。这道题里面找的是和。
- 明确两点。希望遍历一次数组,就解决这个问题。答案在遍历的过程中得出。
- 从左上角开始遍历,意味着想走到任意一个格子,只能从这个格子的上、左两个方向到达。
public int getMost(int[][] values) {
if (values == null || values.length == 0 || values[0].length == 0)
return 0;
int n = values[0].length;
int[] dp = new int[n];
for (int[] value : values) {
dp[0] += value[0];
for (int i = 1; i < n; i++)
dp[i] = Math.max(dp[i], dp[i - 1]) + value[i];
}
return dp[n - 1];
}
https://github.com/CyC2018/CS-Notes/blob/master/notes/47.%20%E7%A4%BC%E7%89%A9%E7%9A%84%E6%9C%80%E5%A4%A7%E4%BB%B7%E5%80%BC.md
丑数
把只包含因子 2、3 和 5 的数称作丑数(Ugly Number)。例如 6、8 都是丑数,但 14 不是,因为它包含因子 7。习惯上我们把 1 当做是第一个丑数。求按从小到大的顺序的第 N 个丑数。
- 这道题难点根本不在算法。难点在到底什么是丑数。
- 丑数是基础丑数1,乘数2、3、5,衍生出来的数。任意一个丑数可以继续通过乘上2、3、5,形成新的丑数。
- 接下来做的事情就是怎么把这些数按照一定顺序不重复的存储起来的问题了。
- 最小堆,牛客上面的官方答案。
import java.util.*;
public class Solution {
public int GetUglyNumber_Solution(int index) {
//排除0
if(index == 0)
return 0;
//要乘的因数
int[] factors = {2, 3, 5};
//去重
HashMap mp = new HashMap<>();
//小顶堆
PriorityQueue pq = new PriorityQueue<>();
//1先进去
mp.put(1L, 1);
pq.offer(1L);
long res = 0;
for(int i = 0; i < index; i++){
//每次取最小的
res = pq.poll();
for(int j = 0; j < 3; j++){
//乘上因数
long next = (long)res * factors[j];
//只取未出现过的
if(!mp.containsKey(next)){
mp.put(next, 1);
pq.offer(next);
}
}
}
return (int)res;
}
}
- 动态规划
- 存储起来,然后输出。
public int GetUglyNumber_Solution(int N) {
if (N <= 6)
return N;
int i2 = 0, i3 = 0, i5 = 0;
int[] dp = new int[N];
dp[0] = 1;
for (int i = 1; i < N; i++) {
int next2 = dp[i2] * 2, next3 = dp[i3] * 3, next5 = dp[i5] * 5;
dp[i] = Math.min(next2, Math.min(next3, next5));
if (dp[i] == next2)
i2++;
if (dp[i] == next3)
i3++;
if (dp[i] == next5)
i5++;
}
return dp[N - 1];
}
https://github.com/CyC2018/CS-Notes/blob/master/notes/49.%20%E4%B8%91%E6%95%B0.md
n 个骰子的点数
把 n 个骰子扔在地上,求点数和为 s 的概率。
- 一个骰子一个骰子的扔,提醒一点,就是2个骰子,骰不出1来。
- 将n-1个骰子能投出来的数字的次数进行记录,即可得到n个骰子能投出来的数字的次数。
- 对于空间上来说,得到n个骰子,需要n-1个骰子的记录,而n-1个骰子,需要n-2个骰子...之前的记录可以保存这,也可以想办法清除掉。
public List> dicesSum(int n) {
final int face = 6;
final int pointNum = face * n;
long[][] dp = new long[2][pointNum + 1];
for (int i = 1; i <= face; i++)
dp[0][i] = 1;
int flag = 1; /* 旋转标记 */
for (int i = 2; i <= n; i++, flag = 1 - flag) {
for (int j = 0; j <= pointNum; j++)
dp[flag][j] = 0; /* 旋转数组清零 */
for (int j = i; j <= pointNum; j++)
for (int k = 1; k <= face && k <= j; k++)
dp[flag][j] += dp[1 - flag][j - k];
}
final double totalNum = Math.pow(6, n);
List> ret = new ArrayList<>();
for (int i = n; i <= pointNum; i++)
ret.add(new AbstractMap.SimpleEntry<>(i, dp[1 - flag][i] / totalNum));
return ret;
}
https://github.com/CyC2018/CS-Notes/blob/master/notes/60.%20n%20%E4%B8%AA%E9%AA%B0%E5%AD%90%E7%9A%84%E7%82%B9%E6%95%B0.md
构建乘积数组
给定一个数组 A[0, 1,..., n-1],请构建一个数组 B[0, 1,..., n-1],其中 B 中的元素 B[i]=A[0]A[1]...A[i-1]A[i+1]...A[n-1]。要求不能使用除法。
- 属于是需要存储空间,但是不能在一次循环里完成,要两次循环。
- 左右两次遍历。
public int[] multiply(int[] A) {
int n = A.length;
int[] B = new int[n];
for (int i = 0, product = 1; i < n; product *= A[i], i++) /* 从左往右累乘 */
B[i] = product;
for (int i = n - 1, product = 1; i >= 0; product *= A[i], i--) /* 从右往左累乘 */
B[i] *= product;
return B;
}
https://github.com/CyC2018/CS-Notes/blob/master/notes/66.%20%E6%9E%84%E5%BB%BA%E4%B9%98%E7%A7%AF%E6%95%B0%E7%BB%84.md
引用仓库:https://github.com/CyC2018/CS-Notes/blob/master/notes/%E5%89%91%E6%8C%87%20Offer%20%E9%A2%98%E8%A7%A3%20-%20%E7%9B%AE%E5%BD%95.md