1.16 LeetCode总结(基本算法)动态规划

编程总结

每每刷完一道题后,其思想和精妙之处没有地方记录,本篇博客用以记录刷题过程中的遇到的算法和技巧


首先想到的是递归法;
记忆化搜索,自顶向下的解决问题。
动态规划,自下而上的解决问题;

70. 爬楼梯

在这里插入图片描述
手法1. 首先容易想到的是使用递归来求解,但递归的效率很低

// 递归 
int climbStairs(int n) {
    if (n <= 2) {
        return n;
	}

    return climbStairs(n - 1) + climbStairs(n - 2);
}

其实记忆化搜索就是在递归的条件上,为减少递归次数而产生的
比如下述代码中对于 mem[n] !=0 直接return memo[n].
我们总是习惯自顶向下思考问题,而不容易自底向上思考问题

手法2:记忆化搜索 – 自顶向下

// 记忆化搜索 -- 自顶往下
int memo[64] = { 0 };
int climbStairs(int n) {
    if (n <= 2) {
        return n;
	}
	// 如果满足条件则直接返回记忆数组里的值,减少递归次数
	if (n < 64 && memo[n] != 0) {
		return memo[n];
	}
    // 不满足条件才进行递归 O(n)
	memo[n] = climbStairs(n-1) + climbStairs(n-2);
	
	return memo[n];
}

手法3:动态规划 – 自底往上

// 动态规划 -- 自底往上
int climbStairs(int n) {
    if (n <= 2) {
        return n;
	}
    int a1 = 1;
    int a2 = 2;
    int memo = 0;
	// 自下而上的进行计算,动态规划
    for (int i = 3; i <= n; i++) {
        memo = a1 + a2;
        a1  = a2;
        a2  = memo;
    }
    return memo;
}

343. 整数拆分

在这里插入图片描述

将 i 拆分成 j 和 i−j 的和,且 i−j 不再拆分成多个正整数,此时的乘积是 j * (i-j);
将 i 拆分成 j 和 i−j 的和,且 i−j 继续拆分成多个正整数,此时的乘积是 j * dp[i−j];

// 记忆化搜索 -- 自顶往下
int memo[64] = {0};          // memo[i]表示将i分割后可以获得的最大乘积
int integerBreak(int n) {
	int res = -1;
	if (n == 1 || n == 2) {  // memo[1] || memo[2] == 1
		return 1;
	}
	if (memo[n] != 0) {
		return memo[n];
	}
	// res  i*(n-i) i*integerBreak(n-i)
	//  1     1*2       1*1
	//  2     2*1       2*1
	for (int i = 1; i <= n-1; i++) { // memo[3] == 2
		res = max3(res, i * (n-i), i * integerBreak(n - i));
	}
	memo[n] = res;

	return res;
}
// 动态规划-- 自底往上
int memo[64] = {0}; // memo[i]表示将i分割后可以获得的最大乘积
int integerBreak(int n) {

    memo[1] = 1;
    memo[2] = 1;
    for(int i = 3; i <= n; i++){
        // 求解memo[i]
        for(int j = 1; j <= i - 1; j++){
            // 将i分割成 j+(i-j), memo[i-j]为i-j可以获得的最大乘积
            memo[i] = max3( memo[i], j*memo[i-j], j*(i-j) );
        }
    }

	return memo[n];
}

198. 打家劫舍

在这里插入图片描述

如果房屋数量大于两间,应该如何计算能够偷窃到的最高总金额呢?对于第 k (k>2)间房屋,有两个选项:

  1. 偷窃第 k 间房屋,那么就不能偷窃第 k−1 间房屋,偷窃总金额为前 k−2 间房屋的最高总金额与第 k 间房屋的金额之和。
  2. 不偷窃第 k 间房屋,偷窃总金额为前 k−1 间房屋的最高总金额。
    在两个选项中选择偷窃总金额较大的选项,该选项对应的偷窃总金额即为前 k 间房屋能偷窃到的最高总金额。

dp[i] 表示前 i 间房屋能偷窃到的最高总金额,那么就有如下的状态转移方程:
在这里插入图片描述

int rob(int *nums, int numsSize) {
    if (numsSize == 1) {
        return nums[0];
    }
    int dp[numsSize+1];
    dp[0] = nums[0];
    dp[1] = fmax(nums[0], nums[1]);
    for(int i = 2; i < numsSize; i++) {
        // 偷窃第k间房屋,那么就不能偷窃第k−1间房屋,总金额为前k−2间房屋的最高总金额与第k间房屋的金额之和
        // 不偷窃第k间房屋,偷窃总金额为前k−1间房屋的最高总金额
        dp[i] = fmax(dp[i-2] + nums[i], dp[i-1]);
    }

    return dp[numsSize - 1];
}

64. 最小路径和

给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
在这里插入图片描述

#define ROWS     200
#define COLUMNS  200

int minPathSum(int **grid, int gridSize, int *gridColSize) 
{
	int rows = gridSize, columns = gridColSize[0];
	int dp[ROWS][COLUMNS];

	if (rows == 0 || columns == 0) {
		return 0;
	}
	dp[0][0] = grid[0][0];

	// 1.处理第一行
	for (int i = 1; i < rows; i++) {
		dp[i][0] = dp[i - 1][0] + grid[i][0];
	}

	// 2.处理第一列
	for (int j = 1; j < columns; j++) {
		dp[0][j] = dp[0][j - 1] + grid[0][j];
	}
	// 3.处理中间部分
	for (int i = 1; i < rows; i++) {
		for (int j = 1; j < columns; j++) {
			dp[i][j] = fmin(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
		}
	}
	return dp[rows - 1][columns - 1];
}


int main(void)
{
	int m = 3;                            
	int n = 7;
	int **grid = NULL;
	int gridColSize = 4;
	int row0[4] = { 1,3,4,8 };
	int row1[4] = { 3,2,2,4 };
	int row2[4] = { 5,7,1,9 };
	int row3[4] = { 2,3,2,3 };
	grid = (int **)malloc(sizeof(int *)*4);
	for (int i = 0; i < 4; i++) {
		grid[i] = (int *)malloc(sizeof(int) * 4);
	}
	grid[0] = row0;
	grid[1] = row1;
	grid[2] = row2;
	grid[3] = row3;
	minPathSum(grid, 4, &gridColSize);
	for (int i = 0; i < 4; i++) {
		free(grid[i]);
	}
	free(grid);
}

303. 区域和检索 - 数组不可变

在这里插入图片描述

由于会进行多次检索,即多次调用 \text{sumRange}sumRange,因此为了降低检索的总时间,应该降低 sumRange 的时间复杂度,最理想的情况是时间复杂度 O(1)。为了将检索的时间复杂度降到 O(1),需要在初始化的时候进行预处理.

typedef struct {
    int *sums;
} NumArray;

NumArray *numArrayCreate(int *nums, int numsSize) 
{
    NumArray *ret = malloc(sizeof(NumArray));
    ret->sums = malloc(sizeof(int) * (numsSize + 1)); // 预处理,计算出前缀和
    ret->sums[0] = 0; // 并没有memset,这样可以省掉
    for (int i = 0; i < numsSize; i++) {
        ret->sums[i + 1] = ret->sums[i] + nums[i];
    }
    return ret;
}

// [i, j]之间value == obj->sums[j + 1] - obj->sums[i];
int numArraySumRange(NumArray* obj, int i, int j) 
{
    return obj->sums[j + 1] - obj->sums[i];
}

void numArrayFree(NumArray* obj) {
    free(obj->sums);
}

304. 二维区域和检索 - 矩阵不可变

在这里插入图片描述

typedef struct {
    int **sums;
    int sumsSize;
} NumMatrix;

NumMatrix *numMatrixCreate(int **matrix, int matrixSize, int *matrixColSize) 
{
    NumMatrix *ret = malloc(sizeof(NumMatrix));
    ret->sums = malloc(sizeof(int *) * matrixSize);
    ret->sumsSize = matrixSize;
    for (int i = 0; i < matrixSize; i++) {
        ret->sums[i] = malloc(sizeof(int) * (matrixColSize[i] + 1));
        ret->sums[i][0] = 0;
        for (int j = 0; j < matrixColSize[i]; j++) {
            // 初始化时对矩阵的每一行计算前缀和
            ret->sums[i][j + 1] = ret->sums[i][j] + matrix[i][j];
        }
    }
    return ret;
}

int numMatrixSumRegion(NumMatrix *obj, int row1, int col1, int row2, int col2) 
{
    int sum = 0;
    // sumRange[i,j] = sums[j+1]−sums[i]
    for (int i = row1; i <= row2; i++) {
        sum += obj->sums[i][col2 + 1] - obj->sums[i][col1];
    }
    return sum;
}

void numMatrixFree(NumMatrix *obj) {
    for (int i = 0; i < obj->sumsSize; i++) {
        free(obj->sums[i]);
    }
    free(obj->sums);
}

1143. 最长公共子序列

在这里插入图片描述
在这里插入图片描述

int longestCommonSubsequence(char* text1, char* text2) {
    int m = strlen(text1), n = strlen(text2);
    int dp[m + 1][n + 1];
    memset(dp, 0, sizeof(dp));
    for (int i = 1; i <= m; i++) {
        char c1 = text1[i - 1];
        for (int j = 1; j <= n; j++) {
            char c2 = text2[j - 1];
            if (c1 == c2) {
                dp[i][j] = dp[i - 1][j - 1] + 1;
            } else {
                dp[i][j] = fmax(dp[i - 1][j], dp[i][j - 1]);
            }
        }
    }
    return dp[m][n];
}

264. 丑数 II

在这里插入图片描述
这题能马上反映出是用动态规划来做,关键是找到递归方程:

在这里插入图片描述
没有同时求min的函数,我们可以两两处理min来求.

int nthUglyNumber(int n) {
	int gMemu[n + 1];

	gMemu[1] = 1;
	int p2 = 1, p3 = 1, p5 = 1;
	for (int i = 2; i <= n; i++) {
		int num2 = gMemu[p2] * 2, num3 = gMemu[p3] * 3, num5 = gMemu[p5] * 5;
		gMemu[i] = fmin(fmin(num2, num3), num5);
		if (gMemu[i] == num2) {
			p2++;
		}
	    if (gMemu[i] == num3) {
			p3++;
		}
		if (gMemu[i] == num5) {
			p5++;
		}
	}
	return gMemu[n];
}

416. 分割等和子集

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值