Dynamic Programming
文章目录
leetcode70.Climbing Stairs
题目链接
题目描述:假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1:
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
- 1 阶 + 1 阶
- 2 阶
示例 2:
输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
3. 1 阶 + 1 阶 + 1 阶
4. 1 阶 + 2 阶
5. 2 阶 + 1 阶
思路:既然可以一次上两层台阶,,如果有n层,可以从第n-1层台阶上一层到达,也可以从第n-2层上两层到达。设第n层台阶上来的方法为F[n],则F[n] = F[n-1] + F[n-2]。
class Solution {
public int climbStairs(int n) {
if(n <= 2){
return n;
}
int dp[] = new int[n+1];
dp[1] = 1;
dp[2] = 2;
for(int i = 3; i <=n; i++){
dp[i] = dp[i-1] + dp[i-2];
}
return dp[n];
}
}
别人的代码,,,漂亮点,空间复杂度低些,,思路大致一样:
class Solution {
public int climbStairs(int n) {
int p =1, q = 1;
int temp;
for(int i = 2; i <= n; i++){
temp = q;
q += p;
p = temp;
}
return q;
}
}
leetcode62. Unique Paths
题目链接
题目描述:一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
问总共有多少条不同的路径?
说明:m 和 n 的值均不超过 100。
示例 1:
输入: m = 3, n = 2
输出: 3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
- 向右 -> 向右 -> 向下
- 向右 -> 向下 -> 向右
- 向下 -> 向右 -> 向右
示例 2:
输入: m = 7, n = 3
输出: 28
思路: 从左上角到右下角,只能向下或者向右走一步,则说明第一行和第一列中的所有位置都是1,因为只能向下或者向右走。我们可以定义个二维矩阵来代表单元格, 然后每个单元格中记录从左上角到该单元格的路径种类。然后除了第一行和第一列,则到该单元格只能是从其上方或者左方来,所以该单元格路径总数为其上方单元格路径与其左侧单元格路径之和,即dp[i][j]=dp[i-1][j]+dp[i][j-1]
class Solution {
public int uniquePaths(int m, int n) {
int dp[][] = new int[m][n];
for(int i = 0; i < m; i++){
for(int j =0; j < n; j++){
if(i == 0 || j == 0){
dp[i][j] = 1;
}else{
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
}
return dp[m-1][n-1];
}
}
leetcode63. Unique Paths II
题目链接
题目描述:一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?
说明:m 和 n 的值均不超过 100。
示例 1:
输入:
[
[0,0,0],
[0,1,0],
[0,0,0]
]
输出: 2
解释:
3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:
- 向右 -> 向右 -> 向下 -> 向下
- 向下 -> 向下 -> 向右 -> 向右
网格中的障碍物和空位置分别用 1 和 0 来表示。
思路:基本上和上道题目思路一致,只需要注意一下障碍物的位置即可。当遇到1的时候dp[][]致0就行。
class Solution {
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
int m = obstacleGrid.length;
int n = obstacleGrid[0].length;
int dp[][] = new int[m][n];
for(int i = 0; i < m; i++){
if(obstacleGrid[i][0] != 1){
dp[i][0] = 1;
}else{
while(i < m){
dp[i][0] = 0;
i++;
}
}
}
for(int j = 0; j < n; j++){
if(obstacleGrid[0][j] != 1){
dp[0][j] = 1;
}else{
while(j <n){
dp[0][j] = 0;
j++;
}
}
}
for(int i = 1; i < m; i++){
for(int j = 1; j < n; j++){
if(obstacleGrid[i][j] == 1){
dp[i][j] = 0;
}else{
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
}
return dp[m-1][n-1];
}
}
leetcode53. Maximum Subarray
题目链接
题目描述:给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
进阶:
如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。
思路1:很暴力,遍历所有,总有一个适合你 哈哈 不可取
class Solution {
public int maxSubArray(int[] nums) {
int len = nums.length;
int sum = Integer.MIN_VALUE;
int count = 0;
for(int i = 0; i < len; i++){
count = 0;
for(int j = i; j >=0; j--){
count += nums[j];
sum = Math.max(count, sum);
}
}
return sum;
}
}
·思路2:dp[i] 保存到第i个数,连续的最大值。然后可以得出 dp[i] = Math.max(nums[i], nums[i] + dp[i-1]);然后再用个变量sum来记录所有dp[]中 数最大的就好了。
class Solution {
public int maxSubArray(int[] nums) {
int len = nums.length;
int dp[] = new int[len];
dp[0] = nums[0];
int sum = dp[0];
for(int i = 1; i < len; i++){
dp[i] = Math.max(nums[i], nums[i] + dp[i-1]);
sum = Math.max(sum, dp[i]);
}
return sum;
}
}
leetcode152. Maximum Product Subarray
题目链接
题目描述:给定一个整数数组 nums ,找出一个序列中乘积最大的连续子序列(该序列至少包含一个数)。
示例 1:
输入: [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。
示例 2:
输入: [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。
思路1:很暴力,遍历所有,总有一个适合你 哈哈 不可取,,不得已而为之
class Solution {
public int maxProduct(int[] nums) {
int len = nums.length;
int sum = Integer.MIN_VALUE;
int count = 1;
for(int i = 0; i < len; i++){
count = 1;
for(int j = i; j >=0; j--){
count *= nums[j];
sum = Math.max(count, sum);
}
}
return sum;
}
}
·**思路2:因为在进行连乘操作的时候,符号很重要,一个符号可以导致数发生很大变化。所以需要记录最大值和最小值,最小值可能下一次进行乘法操作后就会变成最大值。max来记录截止到上次的最大值,min来记录截止到上次的最小值。这次里面的最大值从nums[i],nums[i]max,nums[i]min这三者中选择。
class Solution {
public int maxProduct(int[] nums) {
int len = nums.length;
int maxAns = nums[0];
int min = nums[0], max = nums[0];
for(int i = 1; i < len; i++){
int mx = max, mn = min;
max = Math.max(Math.max(nums[i], nums[i]*mx), mn * nums[i]);
min = Math.min(Math.min(nums[i], nums[i]*mx), mn * nums[i]);
maxAns = Math.max(max, maxAns);
}
return maxAns;
}
}
思路一与思路二的时间差了将近100倍,,,
Coins in a Line
题目描述:有 n 个硬币排成一条线(假设n是偶数)。两个参赛者轮流从左端或者右端拿硬币,直到没有硬币为止。拥有更多钱的玩家获胜。
你愿意先去还是后去?这有关系吗?
假设你先去,描述一个算法来计算你能赢的最大金额。
策略:
1.数一数所有奇数位置的硬币的总数。(称之为X)
2. 数一数所有偶数位置的硬币的总数。(称之为Y)
3.如果X > Y,先取最左边的硬币。在接下来的步骤中选择所有奇数的硬币。
4. 如果X < Y,先取最右边的硬币。在接下来的步骤中选择所有偶数的硬币。
5. 如果X == Y,如果你坚持只拿偶数/奇数的硬币,你一定至少会打平。
解释:先走一步,根据你选择的硬币,你实际上是在强迫你的对手只拿偶数或奇数的硬币。
策略一虽然可以保证不输掉,但并不是最优的策略。
可以作为动态规划问题来解决:
f[i][j]=max(num[i] + sum[i+1][j] - f[i+1][j] , num[j] + sum[i][j-1] - f[i][j-1])
f[i][j]表示从i到j取的最大和
sum[i][j]表示从i到j的和
上式可以进一步优化:
有个更好的解法:
const int MAX_N = 100;
void printMoves(int P[][MAX_N], int A[], int N) {
int sum1 = 0, sum2 = 0;
int m = 0, n = N-1;
bool myTurn = true;
while (m <= n) {
int P1 = P[m+1][n]; // If take A[m], opponent can get...
int P2 = P[m][n-1]; // If take A[n]
cout << (myTurn ? "I" : "You") << " take coin no. ";
if (P1 <= P2) {
cout << m+1 << " (" << A[m] << ")";
m++;
} else {
cout << n+1 << " (" << A[n] << ")";
n--;
}
cout << (myTurn ? ", " : ".\n");
myTurn = !myTurn;
}
cout << "\nThe total amount of money (maximum) I get is " << P[0][N-1] << ".\n";
}
int maxMoney(int A[], int N) {
int P[MAX_N][MAX_N] = {0};
int a, b, c;
for (int i = 0; i < N; i++) {
for (int m = 0, n = i; n < N; m++, n++) {
assert(m < N); assert(n < N);
a = ((m+2 <= N-1) ? P[m+2][n] : 0);
b = ((m+1 <= N-1 && n-1 >= 0) ? P[m+1][n-1] : 0);
c = ((n-2 >= 0) ? P[m][n-2] : 0);
P[m][n] = max(A[m] + min(a,b),
A[n] + min(b,c));
}
}
printMoves(P, A, N);
return P[0][N-1];
}