[英]Need help understanding why the following code does not work
我正在尝试解决leetcode上最长增加子序列的问题,我必须在数组中找到最长增加子序列的问题。 我正在尝试使用动态编程(O(n ^ 2)复杂度)解决它。
我已经写了2个函数,这两个函数都试图分别解决问题,但是只有一个函数起作用。
我试图使用与第一个功能完全相同的逻辑来优化第二个功能,但是我很难弄清为什么它不起作用。
以下功能有效:
// WORKS
private int dp(int[] a) {
int[][] dp = new int[a.length+1][a.length+1];
for(int p = a.length-1; p >=0; p--) {
for(int i = a.length-1; i >=0; i--) {
if(i >= a.length)
dp[p][i] = 0;
else if(a[i] > a[p])
dp[p][i] = Math.max(dp[p][i+1], 1 + dp[i][i+1]);
else
dp[p][i] = dp[p][i+1];
}
}
return dp[0][0];
}
在第二个函数中,我尝试通过使用2列而不是完整矩阵来减少所需的空间,因为我们只需要i + 1列的值即可填充i列。 但是,它不起作用(col 1是0的数组),我不知道为什么
// DOES NOT WORK
private int dp_optimized(int[] a) {
int[] col1 = new int[a.length+1];
int[] col2 = new int[a.length+1];
for(int p = a.length-1; p >=0; p--) {
for(int i = a.length-1; i >=0; i--) {
if(i >= a.length)
col1[p] = 0;
else if(a[i] > a[p])
col1[p] = Math.max(col2[p], 1+col2[i]);
else
col1[p] = col2[p];
}
for(int i=0; i< col1.length; i++)
col2[i] = col1[i];
}
return col1[0];
}
这基本上是一回事吗? 为什么功能1起作用而功能2不起作用?
同样,调用这些函数的主要方法如下:
public int lengthOfLIS(int[] nums) {
int[] a = new int[nums.length+1];
int[][] dp = new int[a.length+1][a.length+1];
for(int i = 1; i<nums.length+1; i++)
a[i] = nums[i-1];
a[0] = Integer.MIN_VALUE;
// return dp(a)
return dp_optimized(a);
}
任何帮助,将不胜感激。 谢谢。
首先,请确保我们正确理解重复发生。
定义: dp [p] [i]存储以下问题的答案:从具有索引[i,a.length()-1]的元素中选择的整数中最长的递增子序列是什么,附加的约束是第一个元素必须大于a [p](我们将通过将其索引存储在变量p中来跟踪最后一个元素)
重复发生: dp [p] [i]的答案是:
现在让我们讨论代码
N ^ 2内存代码:
我对您的正确代码做了些微修改,让我们看一下。
private int dp(int[] a) {
int[][] dp = new int[a.length+1][a.length+1];
for(int p = a.length-1; p >=0; p--) {
for(int i = a.length-1; i >p; i--) {
dp[p][i] = dp[p][i+1]; // Try to leave the i-th item
if(a[i] > a[p]) // Try to pick the i-th item
dp[p][i] = Math.max(dp[p][i], 1 + dp[i][i+1]);
}
}
return dp[0][1];
}
第一个修改: if(i >= a.length) col1[p] = 0;
删除了以下部分if(i >= a.length) col1[p] = 0;
,条件将永远无法满足。
第二次修改:内部循环在[p + 1,a.length-1]之间进行迭代,因为我必须始终大于p
第三变形例:不是返回的dp[0][0]
则返回dp[0][1]
其中第一个项目是未包括在原始阵列中并保持比任何其他项目小的值的附加元件。 (dp [0] [1]找出元素[1,a.length-1]的LIS,因为对要选择的第一个元素没有限制)
减少内存:
让我们更多地考虑上述代码的dp表。 表格上的第一个维度是前一个索引,第二个维度是数组a中的起始索引(我们正在尝试从给定前一个p的i开始查找LIS)
为了减少dp解决方案的内存,您必须问自己两个问题:
1-可以缩小哪个尺寸?
要回答这个问题,您必须分别遍历每个维度,并问自己该维度的当前值是否是x我还依赖于当前维度的其他值? 这些值中最远的值使我们知道可以在此维度上进行多少缩减。
让我们将上述技术应用于我们的问题。
维度p:如果p的值为x ,则在第一个子问题dp[p][i+1]
我们不会更改x(这很好),但是在第二个子问题dp[i][i+1]
x更改为i ,并且i在范围[i + 1,a.length-1]中取任何值,因此,此维是不可约的!
维度i:如果i的值为x ,则在第一个子问题dp[p][i+1]
我们取决于存储在x + 1中的值,在第二个子问题dp[i][i+1]
我们也取决于存储在x + 1中的值,这很好,很明显,当i的值为x时,我们只需要存储x + 1中的值,我们根本就不在乎存储在x +中的值2或x + 3 ...
2-循环的顺序应该是什么?
当我们进行内存减少时,循环的顺序很重要,在要减少的维度上迭代的循环必须是最外层的!
当我们的外循环是一个在第二维i上迭代的循环时,给定i为常数,内循环负责计算dp [p] [ i ]的所有值(换句话说,计算dp表中的完整列) ,计算之后,我们准备移至i-1,因为第i列中的所有值都已存储并准备使用,在计算第(i-1)列中的所有值之后,我们可以移动到I-2和计算使用仅存储在第(i-1)列的答案,并忽略存储在第i列中的所有的值I-2的所有的答案。
因此,让我们重新排列代码循环:
private int dp(int[] a) {
int[][] dp = new int[a.length+1][a.length+1];
for(int i = a.length-1; i>0; i--) {
for(int p = i-1; p>=0; p--) {
dp[p][i] = dp[p][i+1]; // Try to leave the i-th item
if(a[i] > a[p]) // Try to pick the i-th item
dp[p][i] = Math.max(dp[p][i], 1 + dp[i][i+1]);
}
}
return dp[0][1];
}
现在,让我们重新排序和修改您在dp优化函数中编写的代码:
private int dp_optimized(int[] a) {
int[] col1 = new int[a.length+1];
int[] col2 = new int[a.length+1];
for(int i = a.length-1; i>0; i--) {
for(int p = i-1; p>=0; p--) {
col1[p] = col2[p];
if(a[i] > a[p])
col1[p] = Math.max(col1[p], 1+col2[i]);
}
for(int p=0; p< col1.length; p++){
col2[p] = col1[p];
}
}
return col1[0];
}
完成!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.