赞
踩
目录
思考题: 请输出找零钱的具体方案 (具体是用了哪些面值的硬币)
动态规划,简称DP, 是求解最优化问题的一种常用策略
通常的使用规则 (一步一步优化)
- 暴力递归 (自顶向下,出现了重叠子问题)
- 记忆化搜索 (自顶向下)
- 递推 (自底向上)
动态规划中的 “动态” 可以理解为是“会变化的状态”
来自维基百科的解释: Dynamic Programming is a method for solving a complex problem by breaking it down into a collection of simpler subproblems, solving each of those subproblems just once, and storing their solutions.
- 将复杂的原问题拆解成若干个简单的子问题
- 每个子问题仅仅解决1次,并保存它们的解
- 最后推导出原问题的解
可以用动态规划来解决的问题,通常具备2个特点:
(1) 最优子结构(最优化原理): 通过求解子问题的最优解, 可以获得原问题的最优解
(2) 无后效性
a. 某阶段的状态一旦确定,则此后过程的演变不再受此前各状态及决策的影响(未来与过去无关)
b. 在推导后面阶段的状态时,只关心前面阶段的具体状态值,不关心这个状态是怎么一步步推导出来的
问题: 从起点(0, 0)走到终点(4, 4)一共有多少种走法?只能向右、向下走
假设 dp(i, j) 是从(0, 0)走到(i, j)的走法:
dp(i, 0) = dp(0, j) = 1
dp(i, j) = dp(i, j – 1) + dp(i – 1, j)
无后效性: 推导 dp(i, j) 时只需要用到 dp(i, j – 1)、dp(i – 1, j) 的值, 不需要关心 dp(i, j – 1)、dp(i – 1, j) 的值是怎么求出来的
问题: 从起点(0, 0)走到终点(4, 4)一共有多少种走法?可以向左、向右、向上、向下走,但是同一个格子不能走 2 次
有后效性: dp(i, j) 下一步要怎么走,还要关心上一步是怎么来的, 也就是还要关心 dp(i, j – 1)、dp(i – 1, j) 是怎么来的?因为, 每个格子只能走1次, 那么在dp(i, j)后面的步骤中需要考虑dp(i, j)前面的步骤, 不能重复走同一个格子
LeetCode: leetcode_322_零钱兑换
假设有25分、20分、5分、1分的硬币,现要找给客户41分的零钱,如何办到硬币个数最少?
此前用贪心策略得到的并非是最优解 (贪心得到的解是 5 枚硬币)
假设 dp(n) 是凑到 n 分需要的最少硬币个数
所以 dp(n) = min { dp(n – 25), dp(n – 20), dp(n – 5), dp(n – 1) } + 1
- /**
- * 暴力解决
- * @param n
- * @return
- */
- static int coinChange(int n){
- if(n <= 0) return Integer.MAX_VALUE;
- if (n == 25 || n == 20 || n == 5 || n == 1) return 1;
- /**
- * 找一枚硬币, 剩下零钱需要最少的硬币数量
- * 当前找的这枚硬币可以是25, 20, 5, 1;
- */
- int min1 = Math.min(coinChange(n - 25),coinChange(n - 20));
- int min2 = Math.min(coinChange(n - 5),coinChange(n - 1));
- //确认找哪种硬币后, 总找硬币数量加一
- return Math.min(min1,min2) + 1;
- }

类似于斐波那契数列的递归版,会有大量的重复计算,时间复杂度较高
- /**
- * 记忆化搜索
- */
- static int coinChange1(int n){
- if(n < 1) return -1;
- int[] coins = new int[n + 1];
- for (int i = 0; i < coins.length; i++) {
- if(i == 25) coins[i] = 1;
- if(i == 20) coins[i] = 1;
- if(i == 5) coins[i] = 1;
- if(i == 1) coins[i] = 1;
- }
- return coinChange1(n,coins);
- }
-
- static int coinChange1(int n, int[] coins){
- if(n <= 0) return Integer.MAX_VALUE;
- //如果待找零钱的最少硬币数量已计算出来, 则直接返回
- if(coins[n] != 0) return coins[n];
- /**
- * 找一枚硬币, 剩下零钱需要最少的硬币数量
- * 当前找的这枚硬币可以是25, 20, 5, 1;
- */
- int min1 = Math.min(coinChange1(n - 25,coins),coinChange1(n - 20,coins) );
- int min2 = Math.min(coinChange1(n - 5,coins),coinChange1(n - 1,coins) );
- //当前零钱已确认找哪种硬币后, 总找硬币数量加一, 并且暂存于在数组中
- coins[n] = Math.min(min1,min2) + 1;
- return coins[n];
- }

- /**
- * 递推
- * @param n
- * @return
- */
- static int coinChange3(int n){
- if(n <= 0) return Integer.MAX_VALUE;
- int[] coins = new int[n+1];
- for (int i = 1; i <= n; i++) {
- int min = Integer.MAX_VALUE;
- if(i >= 1) min = Math.min(min,coins[i - 1]);
- if(i >= 5) min = Math.min(min,coins[i - 5]);
- if(i >= 20) min = Math.min(min,coins[i - 20]);
- if(i >= 25) min = Math.min(min,coins[i - 25]);
- coins[i] = min + 1;
- }
- return coins[n];
- }

- /**
- * 递推
- * @param n
- * @return
- */
- static int coinChange3(int n){
- if(n <= 0) return Integer.MAX_VALUE;
- int[] coins = new int[n+1];
- //faces[i]是凑够i分时最后那枚硬币的面值
- int[] faces = new int[coins.length];
- for (int i = 1; i <= n; i++) {
- int min = Integer.MAX_VALUE;
- if(i >= 1){
- min = Math.min(min,coins[i - 1]);
- faces[i] = 1;
- }
- if(i >= 5){
- min = Math.min(min,coins[i - 5]);
- faces[i] = 5;
- }
- if(i >= 20){
- min = Math.min(min,coins[i - 20]);
- faces[i] = 20;
- }
- if(i >= 25){
- min = Math.min(min,coins[i - 25]);
- faces[i] = 25;
- }
- coins[i] = min + 1;
- print(faces,i);
- }
- return coins[n];
- }
-
- static void print(int[] faces, int n){
- System.out.print("零钱:"+n+", 应找硬币:");
- while(n > 0){
- //输出凑够n分时,所需要的硬币面值
- System.out.print(faces[n] + " ");
- //计算凑够剩下零钱所需要的硬币
- n -= faces[n];
- }
- System.out.println();
- }

- static int coinChange4(int n, int[] faces){
- if(n <= 0) return Integer.MAX_VALUE;
- int[] coins = new int[n+1];
- for (int i = 1; i <= n; i++) {
- int min = Integer.MAX_VALUE;
- for (int face : faces) {
- if(i < face) continue;
- if(coins[i-face] < 0) continue; //如果使用面值为face的硬币后, 剩余的零钱无法找到合适的硬币, 那么当前零钱也无法找回
- min = Math.min(coins[i - face], min);
- }
- //如果i没有找到合适的硬币, 那么返回-1
- if (min == Integer.MAX_VALUE){
- coins[i] = -1;
- }else{
- coins[i] = min + 1;
- }
- }
- return coins[n];
- }

给定一个长度为 n 的整数序列,求它的最大连续子序列和
比如 –2、1、–3、4、–1、2、1、–5、4 的最大连续子序列和是 4 + (–1) + 2 + 1 = 6
假设 dp(i) 是以 nums[i] 结尾的最大连续子序列和(nums是整个序列)
状态转移方程
如果 dp(i – 1) ≤ 0,那么 dp(i) = nums[i]
如果 dp(i – 1) > 0,那么 dp(i) = dp(i – 1) + nums[i]
初始状态
dp(0) 的值是 nums[0]
最终的解
最大连续子序列和是所有 dp(i) 中的最大值 max { dp(i) },i ∈ [0, nums.length)
- /**
- * 最大连续子序列
- * 示例: {–2, 1, –3, 4, –1, 2, 1, –5, 4 }
- */
- static int maxSubArray(int[] nums){
- if(nums == null || nums.length == 0) return 0;
- int[] dp = new int[nums.length];
- int max = dp[0] = nums[0]; //以dp[0]结尾的最大子序列的值为nums[0], max默认为dp[0]
- for (int i = 1; i < nums.length; i++) {
- /**
- * 如果以dp[i-1]结尾的最大子序列的值大于0, 那么以dp[i]结尾的最大子序列为: 以dp[i-1]结尾的最大子序列 + nums[i];
- * 否则, 以dp[i]结尾的最大子序列为: nums[i]
- */
- if(dp[i-1] > 0){
- dp[i] = dp[i-1] + nums[i];
- }else{
- dp[i] = nums[i];
- }
- max = Math.max(max,dp[i]);
- }
- return max;
- }

空间复杂度:O(n), 时间复杂度: O(n)
- /**
- * 最大连续子序列 -- 优化
- * 仅使用dp变量, 而不是数组; dp为以nums[i-1]结尾的最大子序列的值
- *
- * 示例: {–2, 1, –3, 4, –1, 2, 1, –5, 4 }
- */
- static int maxSubArray2(int[] nums){
- if(nums == null || nums.length == 0) return 0;
- int dp = nums[0];
- int max = dp; //以dp[0]结尾的最大子序列的值为nums[0], max默认为dp[0]
- for (int i = 1; i < nums.length; i++) {
- /**
- * 如果以nums[i-1]结尾的最大子序列(原dp)的值大于0, 那么以nums[i]结尾的最大子序列(新dp)为: 以nums[i-1]结尾的最大子序列(原dp) + nums[i];
- * 否则, 以nums[i]结尾的最大子序列(新dp)为: nums[i]
- */
- if(dp > 0){
- dp = dp + nums[i];
- }else{
- dp = nums[i];
- }
- max = Math.max(max,dp);
- }
- return max;
- }

空间复杂度: O(1), 时间复杂度: O(n)
最长上升子序列 (最长递增子序列, Longest Increasing Subsequence, LIS)
LeetCode: leetcode_300_最长上升子序列
给定一个无序的整数序列,求出它最长上升子序列的长度 (要求严格上升)
比如 [10, 2, 2, 5, 1, 7, 101, 18] 的最长上升子序列是 [2, 5, 7, 101]、[2, 5, 7, 18],长度是 4
假设数组是 nums, [10, 2, 2, 5, 1, 7, 101, 18]
dp(i) 是以 nums[i] 结尾的最长上升子序列的长度,i ∈ [0, nums.length)
最长上升子序列的长度是所有 dp(i) 中的最大值 max { dp(i) },i ∈ [0, nums.length)
遍历 j ∈ [0, i)
1. 当 nums[i] > nums[ j]
nums[i] 可以接在 nums[ j] 后面,形成一个比 dp( j) 更长的上升子序列,长度为 dp( j) + 1
dp(i) = max { dp(i), dp( j) + 1 }
2. 当 nums[i] ≤ nums[ j]
nums[i] 不能接在 nums[ j] 后面,跳过此次遍历(continue)
3. 状态的初始值
dp(0) = 1
所有的 dp(i) 默认都初始化为 1
- /**
- * 最长上升子序列 -- 动态规划
- * 示例:{10, 2, 3, 5, 1, 7, 101, 18}
- */
- static int lis(int[] nums){
- if(nums == null || nums.length == 0) return 0;
-
- int[] dp = new int[nums.length];
- int max = dp[1] = 1;
- for (int i = 0; i < nums.length; i++) {
- dp[i] = 1;
- /**
- * 每个nums[i]都需要跟前[0,i)范围内的所有以nums[j]结尾的最长上升子序列(dp[i])进行比较,
- * 如果nums[i]的值大于nums[j], 那么以nums[i]结尾的最长上升子序列为: 以nums[j]结尾的最长上升子序列(dp[i]) + 1;
- * 否则,以nums[i]结尾的最长上升子序列为:nums[i];
- *
- * 在计算以nums[i]的最长上升子序列时, 需要比较当前已计算出的dp[i]与dp[j]+1的大小, 取较大值
- */
- for (int j = 0; j < i; j++) {
- if(nums[i] > nums[j]){
- //例: Math.max(dp[5],dp[3] + 1) 注意此时dp[5] = dp[2] + 1;
- dp[i] = Math.max(dp[i],dp[j] + 1);
- }
- }
- max = Math.max(max,dp[i]);
- }
- return max;
- }

空间复杂度: O(n), 时间复杂度: O(n^2)
1. 把每个数字看做是一张扑克牌,从左到右按顺序处理每一个扑克牌
2. 将它压在(从左边数过来)第一个牌顶 ≥ 它的牌堆上面
3. 如果找不到牌顶 ≥ 它的牌堆, 就在最右边新建一个牌堆, 将它放入这个新牌堆中
4. 当处理完所有牌,最终牌堆的数量就是最长上升子序列的长度
为什么最终牌堆的数量就是最长上升子序列的长度?
根据摆放规则可以推出:
第n个堆的堆顶元素A, 在第n-1个堆中, 一定存在一个元素B, 元素B大于A且在原始数组中 A 排在B的前面; 只有B大于A, 才导致摆放B时需要重新建堆, 并且, 如果在原始数组中 A 排在B的后面且B大于A, 那么A与B将放在一个堆中, 所以堆的数量就是最长上升子序列的长度
- /**
- * 最长上升子序列 -- 耐心排序优化
- * @param nums
- * @return
- */
- static int lis2(int[] nums){
- if(nums == null || nums.length == 0) return 0;
- //牌堆的数量
- int len = 0;
- //牌顶数组
- int[] top = new int[nums.length];
- for (int num : nums) {
- int j = 0;
- while(j < len){
- if(top[j] >= num){
- top[j] = num;
- break;
- }
- //牌顶 < num
- j++;
- }
- if(j == len){ //新建一个堆顶
- len++;
- top[j] = num;
- }
- }
- return len;
- }

假设数组是 nums,也就是最初的牌数组:
1. top[i] 是第 i 个牌堆的牌顶,len 是牌堆的数量,初始值为 0
2. 遍历每一张牌 num
(1) 利用二分搜索找出 num 最终要放入的牌堆位置 index
(2) num 作为第 index 个牌堆的牌顶,top[index] = num
(3) 如果 index 等于 len,相当于新建一个牌堆,牌堆数量 +1,也就是 len++
- /**
- * 最长上升子序列 -- 耐心排序优化 --二分搜索优化
- * 时间复杂度 O(nlogn)
- * @param nums
- * @return
- */
- static int lis3(int[] nums){
- if(nums == null || nums.length == 0) return 0;
- //牌堆的数量
- int len = 0;
- //牌顶数组
- int[] top = new int[nums.length];
- for (int num : nums) {
- int begin = 0;
- int end = len;
- while (begin < end) {
- int mid = (begin + end) >> 1;
- if (num <= top[mid]) {
- end = mid;
- } else {
- begin = mid + 1;
- }
- }
- // 覆盖牌顶
- top[begin] = num;
- // 检查是否要新建一个牌堆
- if (begin == len) len++;
- }
- return len;
- }

空间复杂度: O(n), 时间复杂度: O(nlogn)
最长公共子序列 (Longest Common Subsequence,LCS)
LeetCode: leetcode_1143_最长公共子序列
求两个序列的最长公共子序列长度
例: [1, 3, 5, 9, 10] 和 [1, 4, 9, 10] 的最长公共子序列是 [1, 9, 10],长度为 3
ABCBDAB 和 BDCABA 的最长公共子序列长度是 4,可能是
ABCBDAB 和 BDCABA > BDAB
ABCBDAB 和 BDCABA > BDAB
ABCBDAB 和 BDCABA > BCAB
ABCBDAB 和 BDCABA > BCBA
1. 假设 2 个序列分别是 nums1、nums2 (i ∈ [1, nums1.length], j ∈ [1, nums2.length] )
2. 假设 dp(i, j) 是【nums1 前 i 个元素】与【nums2 前 j 个元素】的最长公共子序列长度
a. dp(i, 0)、dp(0, j) 初始值均为 0
b. 如果 nums1[i – 1] = nums2[ j – 1], 那么 dp(i, j) = dp(i – 1, j – 1) + 1
c. 如果 nums1[i – 1] ≠ nums2[ j – 1], 那么 dp(i, j) = max { dp(i – 1, j), dp(i, j – 1) }
- /**
- * 最长公共子序列 (递归)
- */
- static int lcs(int[] nums1, int[] nums2){
- if(nums1 == null || nums1.length == 0) return 0;
- if(nums2 == null || nums2.length == 0) return 0;
- return lcs(nums1,nums1.length,nums2,nums2.length);
- }
-
- /**
- * @param nums1 数组1
- * @param i 数组1中前i个元素: [0,i-1]
- * @param nums2 数组2
- * @param j 数组2中前j个元素:[0,j-1]
- * @return
- */
- static int lcs(int[] nums1, int i, int[] nums2, int j){
- if(i == 0 || j == 0) return 0;
- if(nums1[i-1] != nums2[j-1]){
- /**
- * 此处递归的意义在于:
- * 如果数组nums1的第i-1个元素与数组nums2的第j-1个元素不相等, 那么
- * 1. 比对数组nums1的前i-1个元素与数组nums2的前j个元素, 存在的最长公共子序列
- * 2. 比对数组nums1的前i个元素与数组nums2的前j-1个元素, 存在的最长公共子序列
- * 在到达递归基后的逻辑可以理解为, 遍历数组nums1每个元素, 去比对数组nums2的每一个元素,如果存在相同的, 则公共子序列数量加一
- */
- return Math.max(
- lcs(nums1,i-1,nums2,j),
- lcs(nums1,i,nums2,j-1));
- }
- return lcs(nums1,i-1,nums2,j-1) + 1;
- }

空间复杂度: O(k), k = min{n, m}, n、m 是2个序列的长度
时间复杂度: O(2n), 当 n = m 时
出现重复递归调用
- /**
- * 最长公共子序列 (非递归)
- * @param nums1
- * @param nums2
- * @return
- */
- static int lcs2(int[] nums1, int[] nums2){
- if(nums1 == null || nums1.length == 0) return 0;
- if(nums2 == null || nums2.length == 0) return 0;
- int[][] dp = new int[nums1.length+1][nums2.length+1];
- for (int i = 1; i <= nums1.length; i++) {
- /**
- * 遍历nums1中的每一个元素, 与nums2中进行比较
- * 如果相同, 则更新dp[i][j]的值 (dp[i][j]表示nums1数组前i个元素与nums2数组前j个元素存在的最长公共子序列长度)
- * 如果不同, 则比较 nums1数组前i个元素与nums2数组前j-1个元素存在的最长公共子序列长度
- * 和 nums1数组前i-1个元素与nums2数组前j个元素存在的最长公共子序列长度
- * 取较大的值
- */
- for (int j = 1; j <= nums2.length; j++) {
- if(nums1[i-1] == nums2[j-1]){ //i与j的含义为数组的前i个元素,前j个元素, 不包含第i,j个元素
- dp[i][j] = dp[i-1][j-1] + 1;
- }else{
- dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]);
- }
- }
- }
- return dp[nums1.length][nums2.length];
- }

空间复杂度: O(n ∗ m)
时间复杂度: O(n ∗ m)
dp 数组的计算结果如下所示:
使用滚动数组优化空间复杂度
- /**
- * 最长公共子序列 (非递归 -- 滚动数组优化)
- * @param nums1
- * @param nums2
- * @return
- */
- static int lcs3(int[] nums1, int[] nums2){
- if(nums1 == null || nums1.length == 0) return 0;
- if(nums2 == null || nums2.length == 0) return 0;
- int[][] dp = new int[2][nums2.length+1];
- for (int i = 1; i <= nums1.length; i++) {
- /**
- * 遍历nums1中的每一个元素, 与nums2中进行比较
- * 如果相同, 则更新dp[i][j]的值 (dp[i][j]表示nums1数组前i个元素与nums2数组前j个元素存在的最长公共子序列长度)
- * 如果不同, 则比较 nums1数组前i个元素与nums2数组前j-1个元素存在的最长公共子序列长度
- * 和 nums1数组前i-1个元素与nums2数组前j个元素存在的最长公共子序列长度
- * 取较大的值
- */
- int row = i & 1; // i % 2; ==取模优化==>>i & 1;
- int prevRow = (i-1) & 1;// (i-1) % 2; ==取模优化==>>(i-1) & 1;
- for (int j = 1; j <= nums2.length; j++) {
- if(nums1[i - 1] == nums2[j-1]){ //i与j的含义为数组的前i个元素,前j个元素, 不包含第i,j个元素
- dp[row][j] = dp[prevRow][j-1] + 1;
- }else{
- dp[row][j] = Math.max(dp[prevRow][j],dp[row][j-1]);
- }
- }
- }
- return dp[nums1.length & 1][nums2.length];
- }

- /**
- * 最长公共子序列 (非递归 -- 一维数组)
- * @param nums1
- * @param nums2
- * @return
- */
- static int lcs4(int[] nums1, int[] nums2){
- if(nums1 == null || nums1.length == 0) return 0;
- if(nums2 == null || nums2.length == 0) return 0;
- int[] dp = new int[nums2.length+1];
- for (int i = 1; i <= nums1.length; i++) {
- int cur = 0;
- for (int j = 1; j <= nums2.length; j++) {
- int leftTop = cur;
- /**
- * 此时dp[j]代表计算到上一行中第j个元素时,存在公共子序列, 将dp[j]暂存于cur中, 当计算下一个元素时需要使用
- */
- cur = dp[j];
- if(nums1[i - 1] == nums2[j-1]){
- dp[j] = leftTop + 1;
- }else{
- dp[j] = Math.max(dp[j],dp[j-1]);
- }
- }
- }
- return dp[nums2.length];
- }

空间复杂度优化至 O k , k = min{n, m}
- /**
- * 最长公共子序列 (非递归 -- 一维数组, 空间复杂度优化)
- * @param nums1
- * @param nums2
- * @return
- */
- static int lcs5(int[] nums1, int[] nums2){
- if(nums1 == null || nums1.length == 0) return 0;
- if(nums2 == null || nums2.length == 0) return 0;
- //优化dp数组的长度, 使用nums1和nums2中较小的长度来初始化dp数组
- int[] colsNums = nums2;
- int[] rowsNums = nums1;
- if(nums1.length < nums2.length){
- colsNums = nums1;
- rowsNums = nums2;
- }
- int[] dp = new int[colsNums.length+1];
- for (int i = 1; i <= rowsNums.length; i++) {
- int cur = 0;
- for (int j = 1; j <= colsNums.length; j++) {
- int leftTop = cur;
- /**
- * 此时dp[j]代表计算到上一行中第j个元素时,存在公共子序列, 将dp[j]暂存于cur中, 当计算下一个元素时需要使用
- */
- cur = dp[j];
- if(nums1[i - 1] == rowsNums[j-1]){
- dp[j] = leftTop + 1;
- }else{
- dp[j] = Math.max(dp[j],dp[j-1]);
- }
- }
- }
- return dp[colsNums.length];
- }
-
-

最长公共子串 (Longest Common Substring), 子串是连续的子序列
求两个字符串的最长公共子串长度, ABCBA 和 BABCA 的最长公共子串是 ABC,长度为 3
1. 假设 2 个字符串分别是 str1、str2
i ∈ [1, str1.length]
j ∈ [1, str2.length]
2. 假设 dp(i, j) 是以 str1[i – 1]、str2[ j – 1] 结尾的最长公共子串长度
dp(i, 0)、dp(0, j) 初始值均为 0
如果 str1[i – 1] = str2[ j – 1],那么 dp(i, j) = dp(i – 1, j – 1) + 1
如果 str1[i – 1] ≠ str2[ j – 1],那么 dp(i, j) = 0
3. 最长公共子串的长度是所有 dp(i, j) 中的最大值 max { dp(i, j) }
- static int lcs(String str1, String str2){
- if(str1 == null || str2 == null) return 0;
- char[] chars1 = str1.toCharArray();
- if(chars1.length == 0) return 0;
- char[] chars2 = str2.toCharArray();
- if(chars2.length == 0) return 0;
- int[][] dp = new int[chars1.length+1][chars2.length+1];
- int max = 0;
- for (int i = 1; i <= chars1.length; i++) {
- for (int j = 1; j <= chars2.length; j++) {
- /**
- * 在计算chars1数组中前i个元素与chars2数组前j个元素时:
- * 如果第i-1个元素与第j-1个元素相等, 那么公共子串的长度为chars1数组中前i-1个元素与chars2数组前j-1个元素的最长公共子串长度 + 1
- */
- if(chars1[i-1] == chars2[j-1]){
- dp[i][j] = dp[i-1][j-1] + 1;
- }
- max = Math.max(dp[i][j], max);
- }
- }
- return max;
- }

空间复杂度: O(n ∗ m)
时间复杂度: O(n ∗ m)
dp 数组的计算结果如下所示
- /**
- * 最长公共子串优化 -- 一维数组
- * @param str1
- * @param str2
- * @return
- */
- static int lcs2(String str1, String str2){
- if(str1 == null || str2 == null) return 0;
- char[] chars1 = str1.toCharArray();
- if(chars1.length == 0) return 0;
- char[] chars2 = str2.toCharArray();
- if(chars2.length == 0) return 0;
-
- int[] dp = new int[chars2.length+1];
- int max = 0;
- for (int i = 1; i <= chars1.length; i++) {
- int cur = 0;
- for (int j = 1; j <= chars2.length; j++) {
- int leftTop = cur;
- cur = dp[j];
- if(chars1[i-1] == chars2[j-1]){
- dp[j] = leftTop + 1;
- }
- max = Math.max(dp[j], max);
- }
- }
- return max;
- }

空间复杂度: O(k), k = min{n, m}, 时间复杂度: O(n ∗ m)
- /**
- * 最长公共子串优化 -- 一维数组 -- 行列优化
- * @param str1
- * @param str2
- * @return
- */
- static int lcs3(String str1, String str2){
- if(str1 == null || str2 == null) return 0;
- char[] chars1 = str1.toCharArray();
- if(chars1.length == 0) return 0;
- char[] chars2 = str2.toCharArray();
- if(chars2.length == 0) return 0;
- char[] colsChars = chars2, rowsChars = chars1;
- if(chars1.length < chars2.length){
- colsChars = chars1;
- rowsChars = chars2;
- }
- int[] dp = new int[colsChars.length+1];
- int max = 0;
- for (int i = 1; i <= rowsChars.length; i++) {
- int cur = 0;
- for (int j = 1; j <= colsChars.length; j++) {
- int leftTop = cur;
- cur = dp[j];
- if(rowsChars[i-1] == colsChars[j-1]){
- dp[j] = leftTop + 1;
- }
- max = Math.max(dp[j], max);
- }
- }
- return max;
- }

- /**
- * 最长公共子串优化 -- 一维数组 -- 行列优化 -- 逆序优化
- * @param str1
- * @param str2
- * @return
- */
- static int lcs4(String str1, String str2){
- if(str1 == null || str2 == null) return 0;
- char[] chars1 = str1.toCharArray();
- if(chars1.length == 0) return 0;
- char[] chars2 = str2.toCharArray();
- if(chars2.length == 0) return 0;
- char[] colsChars = chars2, rowsChars = chars1;
- if(chars1.length < chars2.length){
- colsChars = chars1;
- rowsChars = chars2;
- }
- int[] dp = new int[colsChars.length+1];
- int max = 0;
- for (int row = 1; row <= rowsChars.length; row++) {
- for (int col = colsChars.length; col >= 1 ; col--) {
- if(rowsChars[row-1] == colsChars[col-1]){
- dp[col] = dp[col-1] + 1;
- }
- max = Math.max(dp[col], max);
- }
- }
- return max;
- }

1. 有 n 件物品和一个最大承重为 W 的背包,每件物品的重量是
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。