每日一题|【每日一题】只有两个键的键盘(dp、数学), LIS问题变种(最长递增子序列的个数(dp+二分 nlogn))
【每日一题|【每日一题】只有两个键的键盘(dp、数学), LIS问题变种(最长递增子序列的个数(dp+二分 nlogn))】
文章目录
- 650 只有两个键的键盘(dp、数学)
- LIS问题变种:最长递增子序列的个数(dp+二分 nlogn)
650 只有两个键的键盘(dp、数学)
文章图片
一道dp的题目,但是美妙之处在于这道题有数学解法。
首先对于dp的思路,
dp[i]
表示 i 个字符时候的最小次数,dp[i] = dp[i/j]+j
得到转移方程。一个小优化在于,因为是找的因子,i/j * j = i 因此我们枚举到 i \sqrt{i} i ?就可以了。更为巧妙的数学解法是,进行拆分的时候肯定
a1*a2*a3 = n
一定是要找到因子的。而实际的代价是 a1+a2+a3
考虑到a1*a2>=a1+a2
。因此,我们其实找到全部质因子就可以class Solution {public int minSteps(int n) {int ans = 0;
// 分解质因数,但是这里其实不是最佳的。
for(int i = 2;
i*i<=n;
i++){while(n%i ==0){n /= i;
ans += i;
}
}
// 最后剩余了一个质数
if(n>1) ans += n;
return ans;
=================================================
int[] dp = new int[n+1];
Arrays.fill(dp, Integer.MAX_VALUE);
dp[1] = 0;
for (int i = 2;
i<=n;
i++){// 这里其实可以找到两个因子 j和i/j;
for(int j = 1;
j*j<=i;
j++){if (i%j == 0){dp[i] = Math.min(dp[i], dp[j]+i/j);
dp[i] = Math.min(dp[i], dp[i/j]+j);
}
}
}
return dp[n];
}
}
LIS问题变种:最长递增子序列的个数(dp+二分 nlogn)
文章图片
LIS问题是非常经典的问题了,如果要求解寻找最长递增子序列的长度,可以nlogn,这个不难。
这个题目是升级版本,要求得到最长子序列的个数。需要更加复杂的dp+二分了。
当然我们可以使用简单的 n 2 n^2 n2的方法,维护一个
cnt[i]
表示使用 i 得到最长序列的种类数,dp[i]
表示以 i 结尾的最长序列。class Solution {public int findNumberOfLIS(int[] nums) {int n = nums.length, maxLen = 0, ans = 0;
int[] dp = new int[n];
int[] cnt = new int[n];
for (int i = 0;
i < n;
++i) {dp[i] = 1;
cnt[i] = 1;
for (int j = 0;
j < i;
++j) {if (nums[i] > nums[j]) {if (dp[j] + 1 > dp[i]) {dp[i] = dp[j] + 1;
cnt[i] = cnt[j];
// 重置计数
} else if (dp[j] + 1 == dp[i]) {cnt[i] += cnt[j];
}
}
}
if (dp[i] > maxLen) {maxLen = dp[i];
ans = cnt[i];
// 重置计数
} else if (dp[i] == maxLen) {ans += cnt[i];
}
}
return ans;
}
}
更优的,我们考虑一个nlogn的方法 dp.get(i).get(j) 表示长度为 i 的第 j 种可能。 这里需要注意,如果是从前往后遍历的话,其实很容易发现,这个dp.get(i)一定是单调不增的,否则一定可以增加长度。
例子 我们可以假想插入 4.5 和5
[1,3,5,4,7]
1/ 1
2/ 3
3/ 5 4
4/ 7
cnt[i][j] 是前缀和的思想,表示长度为 i 的时候,前 j 个元素作为结尾的全部可能数量。当我们遍历到 nums 的某个数字的时候,查看dp每个list的最后一个数字(因为是最小的)。如果大于,说明可以这个list里面起码有小于的当前数字的。假设是k。
然后我们可以二分的找到
dp.get(k)
种的边界 p (第一个小于 nums[i] 的数字),在 p 之后的数字都是小于 numus[i] 的。因此 nums[i] 和这些数字可以组成长度为k+1的序列,而这些数字的可能数量是 newadd = cnt.get(k).get(size()-1) - cnt.get(k).get(p)
最后在
dp.get(k+1).add(nums[i]) ;
cnt.get(k+1).add(last+1)
class Solution {public int findNumberOfLIS(int[] nums) {// 如果是n2的方法,就是暴力dp
List> dp = new ArrayList<>();
List> cnt = new ArrayList<>();
int n = nums.length;
for(int i = 0;
i= 0){int p = findInList(dp.get(k), nums[i]);
int size = cnt.get(k).size();
newadd = cnt.get(k).get(size-1)-cnt.get(k).get(p);
}
if(k == dp.size()-1){dp.add(new ArrayList());
cnt.add(new ArrayList());
cnt.get(k+1).add(0);
dp.get(k+1).add(nums[i]);
cnt.get(k+1).add(newadd);
}else{dp.get(k+1).add(nums[i]);
int cntSize = cnt.get(k+1).size();
cnt.get(k+1).add(cnt.get(k+1).get(cntSize-1)+newadd);
}
}
int size = cnt.size();
int lastszie = cnt.get(size-1).size();
return cnt.get(size-1).get(lastszie-1);
}
// 返回的是最后一个元素一定小于的,且再大就一定大于等于的。
public int findList(List> dp, int target){int n = dp.size();
int l = 0;
int r = n-1;
while(l<=r){int mid = l+(r-l)/2;
int size = dp.get(mid).size();
int cur = dp.get(mid).get(size-1);
if (cur>target){r = mid-1;
}else if (cur < target){l = mid+1;
}else{return mid-1;
}
}
return r;
}
// 返回小于的第一个
public int findInList(List nums, int target){int n = nums.size();
int l = 0;
int r = n-1;
while(l<=r){int mid = l+(r-l)/2;
int cur = nums.get(mid);
if (cur>target){l = mid+1;
}else if (cur < target){r = mid-1;
}else{l = mid+1;
}
}
return l;
}
}
推荐阅读
- 宽容谁
- 我要做大厨
- 增长黑客的海盗法则
- 画画吗()
- 2019-02-13——今天谈梦想()
- 远去的风筝
- 三十年后的广场舞大爷
- 叙述作文
- 20190302|20190302 复盘翻盘
- 学无止境,人生还很长