简单DP
lc.509 斐波那契
- 滑动窗口
class Solution {
public:
int fib(int n) {
int a = 0 , b = 1 , c ;
if(n == 0) return 0;
if(n == 1) return 1;
for(int i = 1 ; i < n ; i ++){
c = a + b ;
a = b;
b = c;
}
return c;
}
};
lc.70 爬楼梯
只能走一步或两步。
class Solution {
public:
int climbStairs(int n) {
int a1 = 1 , a2 = 2 , ans;
if(n == 1) return 1;
if(n == 2) return 2;
for(int i = 3 ; i <= n ; i ++){
ans = a1 + a2;
a1 = a2;
a2 = ans;
}
return ans;
}
};
lc.746 使用最小花费爬楼梯
每一步有所花费。
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
int n = cost.size();
for(int i = n - 3 ; i >= 0 ; i --){
cost[i] = min(cost[i] + cost[i + 1] , cost[i] + cost[i + 2]);
}
int ans;
ans = min(cost[0] , cost[1]);
return ans;
}
};
线性DP
lc.53 最大子数组合
给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。子数组 是数组中的一个连续部分。
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int a = nums[0] , Max = nums[0];
for(int i = 1 ; i < nums.size() ; i ++){
a = max (nums[i] , a + nums[i]);
Max = max (a , Max);
}
return Max;
}
};
lc.198 打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
class Solution {
public:
int rob(vector<int>& nums) {
int Max = 0 ;
if(nums.size() == 1) return nums[0];
if(nums.size() == 2) return max(nums[1],nums[0]);
for(int i = 2 ; i < nums.size() ; i ++){
Max = max(Max , nums[i-2]);
nums[i] = max (nums[i-1] , nums[i] + Max);
}
Max = max(Max , max(nums[nums.size()-2] , nums[nums.size()-1]));
return Max;
}
};
lc.213 打家劫舍 II
在 lc.198 基础上,房屋被围成一圈。
class Solution {
public:
void getmax(int a, int& Max, vector<int> nums){
for(int i = a ; i < nums.size() ; i ++){
Max = max(Max , nums[i-2]);
nums[i] = max (nums[i-1] , nums[i] + Max);
}
Max = max(Max , max(nums[nums.size()- 5 + a] , nums[nums.size()- 4 + a]));
}
int rob(vector<int>& nums) {
int Max = 0 ;
if(nums.size() == 1) return nums[0];
if(nums.size() == 2) return max(nums[1],nums[0]);
getmax(3, Max, nums); int Max1 = Max;
Max = 0;
getmax(2, Max, nums);
Max = max(Max , Max1);
return Max;
}
};
修修补补的,写得有点乱。对 1 ~ n-1 计算一次,再对 2 ~ n 计算一次。getmax函数可以做成返回一个 int 值,直接导出当前的 max 值,看上去就没这么乱了。
lc.256 粉刷房子
假如有一排房子,共 n 个,每个房子可以被粉刷成红色、蓝色或者绿色这三种颜色中的一种,你需要粉刷所有的房子并且使其相邻的两个房子颜色不能相同。
当然,因为市场上不同颜色油漆的价格不同,所以房子粉刷成不同颜色的花费成本也是不同的。每个房子粉刷成不同颜色的花费是以一个 n x 3 的正整数矩阵 costs 来表示的。
例如,costs[0] 表示第 0 号房子粉刷成红色的成本花费;costs[1] 表示第 1 号房子粉刷成绿色的花费,以此类推。
请计算出粉刷完所有房子最少的花费成本。
class Solution {
public:
int minCost(vector<vector<int>>& costs) {
int cost1 = 0, cost2 = 0, cost3 = 0;
int c1 = costs[0][0] , c2 = costs[0][1] , c3 = costs[0][2] ;
int minc = 20 ;
for(int i = 1 ; i < costs.size() ; i ++){
cost1 = min(costs[i][0] + c2 , costs[i][0] + c3);
cost2 = min(costs[i][1] + c1 , costs[i][1] + c3);
cost3 = min(costs[i][2] + c1 , costs[i][2] + c2);
c1 = cost1 , c2 = cost2 , c3 = cost3;
}
minc = min( c1 , min( c2 , c3 ));
return minc;
}
};
一开始想开三个数组,感觉会有点大。用 6 个值更迭一下。
lc.265 粉刷房子 II
假如有一排房子,共 n 个,每个房子可以被粉刷成 k 种颜色中的一种,你需要粉刷所有的房子并且使其相邻的两个房子颜色不能相同。
当然,因为市场上不同颜色油漆的价格不同,所以房子粉刷成不同颜色的花费成本也是不同的。每个房子粉刷成不同颜色的花费是以一个 n x k 的矩阵来表示的。
例如,costs[0] 表示第 0 号房子粉刷成 0 号颜色的成本花费;costs[1] 表示第 1 号房子粉刷成 2 号颜色的成本花费,以此类推。请你计算出粉刷完所有房子最少的花费成本。
- wrong ans
class Solution {
public:
int minCostII(vector<vector<int>>& cost) {
int m1 = 0 , m2 = 0 , c1 = 10000 , c2 = 10000 ;
for(int i = 0 ; i < cost.size() ; i ++){
int min1 = 10000 , min2 = 10000 , col1 = 0 , col2 = 0 ;
for(int j = 0 ; j < cost[0].size() ; j ++){
if(cost[i][j] < min1){
min2 = min1;
col2 = col1;
min1 = cost[i][j];
col1 = j;
}
else if(cost[i][j] < min2){
min2 = cost[i][j];
col2 = j;
}
}
if(i == 0){
m1 = min1 ; c1 = col1 ; m2 = min2 ; c2 = col2 ; continue;
}
int mm1 = 0 , mm2 = 0 , cc1 = 0 , cc2 = 0 ;
if(col1 != c1){
mm1 = m1 + min1; cc1 = col1;
if(m2 + min1 < m1 + min2){
if(col1 != c2) {mm2 = min1 + m2 ; cc2 = col1 ;}
else if(col2 != c1) {mm2 = min2 + m1 ; cc2 = col2 ;}
else{mm2 = m2 + min2 ; cc2 = col2 ;}
}
else{
if(col2 != c1) {mm2 = min2 + m1 ; cc2 = col2 ;}
else if(col1 != c2) {mm2 = min1 + m2 ; cc2 = col1 ;}
else{mm2 = m2 + min2 ; cc2 = col2 ;}
}
}
else{
if(m1 + min2 < m2 + min1){
if(col2 != c1){ mm1 = m1 + min2 ; cc1 = col2 ; mm2 = m2 + min1 ; cc2 = col1; }
else{ mm1 = m2 + min1 ; cc1 = col1 ; mm2 = m2 + min2 ; cc2 = col2; }
}
else{
mm1 = m2 + min1 ; cc1 = col1 ;
if(col2 != c1){ mm2 = m1 + min2 ; cc2 = col2 ;}
else{ mm2 = m2 + min2 ; cc2 = col2 ;}
}
}
m1 = mm1 , m2 = mm2 , c1 = cc1 , c2 = cc2 ;
}
return m1;
}
};
错误数据:
[[7,19,11,3,7,15,17,5,6,18,1,15,18,11],[13,18,18,8,13,12,11,13,4,8,2,4,5,20],[14,5,18,4,7,6,1,6,11,6,16,6,13,17],[18,17,11,3,12,4,8,6,2,7,10,9,19,3],[4,3,2,14,11,15,18,1,17,1,6,14,14,9],[9,13,15,14,5,1,1,6,11,15,16,12,10,18],[19,2,11,3,13,4,13,7,16,16,20,18,20,8],[8,19,20,9,18,13,17,1,2,4,3,20,15,9],[9,10,11,6,14,20,4,1,5,15,13,10,13,5],[13,11,9,11,9,16,3,19,1,11,6,7,12,13],[14,1,15,14,11,12,7,14,12,11,6,9,5,5]]
从加粗处开始出错。计算结果小了 1 ,问题应该是缺少了某个必要条件导致。
不信邪,就想要讨论出来。
- 开辟
class Solution {
public:
int minCostII(vector<vector<int>>& costs) {
int n = costs.size(), k = costs[0].size();
vector<vector<int>> dp(n + 1, vector<int>(k, 0));
int c1 = 0, c2 = 0;
for(int i = 0; i < n; i++){
int tmp1 = 2e9, tmp2 = 2e9;
for(int j = 0; j < k; j++){
int &d = dp[i + 1][j];//d来表示dp[i + 1][j], 代码看起来干净
if(dp[i][j] != c1) d = costs[i][j] + c1;
else d = costs[i][j] + c2;
if(d < tmp1) tmp2 = tmp1, tmp1 = d;
else if(d < tmp2) tmp2 = d;
}
c1 = tmp1, c2 = tmp2;
}
return *min_element(dp[n].begin(), dp[n].end());
}
};
lc.121 买卖股票
class Solution {
public:
int maxProfit(vector<int>& prices) {
int inf = 1e9;
int minprice = inf, maxprofit = 0;
for (int price: prices) {
maxprofit = max(maxprofit, price - minprice);
minprice = min(price, minprice);
}
return maxprofit;
}
};
lc.714 买卖股票含手续费
买卖一次交一次 fee;
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
int n = prices.size();
int dp0 = 0, dp1 = -prices[0];
for (int i = 1; i < n; ++i) {
int newDp0 = max(dp0, dp1 + prices[i] - fee);
int newDp1 = max(dp1, dp0 - prices[i]);
dp0 = newDp0;
dp1 = newDp1;
}
return dp0;
}
};
lc.309 买卖股票含冷冻期
class Solution {
public:
int maxProfit(vector<int>& prices) {
if (prices.empty()) {
return 0;
}
int n = prices.size();
int f0 = -prices[0];
int f1 = 0;
int f2 = 0;
for (int i = 1; i < n; ++i) {
int newf0 = max(f0, f2 - prices[i]);
int newf1 = f0 + prices[i];
int newf2 = max(f1, f2);
f0 = newf0;
f1 = newf1;
f2 = newf2;
}
return max(f1, f2);
}
};
lc.152 乘积最大子数组
class Solution {
public:
int maxProduct(vector<int>& nums) {
int maxF = nums[0], minF = nums[0], ans = nums[0];
for (int i = 1; i < nums.size(); ++i) {
int mx = maxF, mn = minF;
maxF = max(mx * nums[i], max(nums[i], mn * nums[i]));
minF = min(mn * nums[i], min(nums[i], mx * nums[i]));
ans = max(maxF, ans);
}
return ans;
}
};
lc.485 最大连续1的个数
给定一个二进制数组,找出其中最大连续 1 的个数。
class Solution {
public:
int findMaxConsecutiveOnes(vector<int>& nums) {
int dp = 0 , ans = 0 ;
for(int i = 0 ; i < nums.size() ; i ++){
if(nums[i] == 0){
ans = max(ans , dp);
dp = 0;
}
else dp ++;
}
ans = max (ans , dp);
return ans;
}
};
lc.487 最大连续1的个数 II
给定一个二进制数组,你可以最多将 1 个 0 翻转为 1,找出其中最大连续 1 的个数。
class Solution {
public:
int findMaxConsecutiveOnes(vector<int>& nums) {
vector<int> cache;
int cnt = 0 , ans = 0 ;
for(int i = 0 ; i < nums.size() ; i ++){
if(nums[i] == 0){
cache.emplace_back(cnt);
cnt = 0;
}
else cnt ++;
}
cache.emplace_back(cnt);
if(cache.size() == 0) return 1;
if(cache.size() == 1) return cache[0];
for(int i = 0 ; i < cache.size() - 1 ; i ++){
ans = max(ans , cache[i] + cache[i + 1] + 1);
}
return ans;
}
};
边界条件是真的讨厌。if(cache.size() == 0) return 1; if(cache.size() == 1) return cache[0];
一定要考虑边界条件。
- DP
class Solution {
public:
int findMaxConsecutiveOnes(vector<int>& nums) {
int n=(int)nums.size(),ans=0,dp0=0,dp1=0;
for (int i=0;i<n;++i){
if (nums[i]){
dp1++;
dp0++;
}
else{
dp1=dp0+1;
dp0=0;
}
ans=max(ans,max(dp0,dp1));
}
return ans;
}
};
crazy!用DP就不用再开一个数组了。
lc.1004 最大连续1的个数 III
给定一个由若干 0 和 1 组成的数组 A,我们最多可以将 K 个值从 0 变成 1 。
返回仅包含 1 的最长(连续)子数组的长度。
- 滑动窗口
class Solution {
public:
int longestOnes(vector<int>& nums, int k) {
int l = 0 , r = 0 , cnt0 = 0 , ans = 0 ;
for(;r < nums.size(); r ++){
if(nums[r] == 0){
cnt0 ++;
}
while(cnt0 > k){
if(nums[l] == 0) cnt0 --;
l ++;
}
ans = max(ans , r - l + 1);
}
return ans;
}
};
lc.376 摆动序列
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
int n = nums.size();
if (n < 2) {
return n;
}
vector<int> up(n), down(n);
up[0] = down[0] = 1;
for (int i = 1; i < n; i++) {
if (nums[i] > nums[i - 1]) {
up[i] = max(up[i - 1], down[i - 1] + 1);
down[i] = down[i - 1];
} else if (nums[i] < nums[i - 1]) {
up[i] = up[i - 1];
down[i] = max(up[i - 1] + 1, down[i - 1]);
} else {
up[i] = up[i - 1];
down[i] = down[i - 1];
}
}
return max(up[n - 1], down[n - 1]);
}
};
lc.1746 经过一次操作后的最大子数组和
你有一个整数数组 nums。你只能将一个元素 nums[i] 替换为 nums[i] * nums[i]。
返回替换后的最大子数组和。
- dp1 表示 之前已经平方过的和 , dp2 表示 之前未使用过平方的和。
class Solution {
public:
int maxSumAfterOperation(vector<int>& nums) {
int dp1 = 0 , dp2 = 0 , ans = 0 ;
for(int i = 0 ; i < nums.size() ; i ++){
dp1 = max (dp1 + nums[i] , dp2 + nums[i] * nums[i]);
dp2 = max (dp2 + nums[i] , 0);
ans = max (ans , dp1);
}
return ans;
}
};
神奇的状态方程。妙极了。
lc.1230 抛硬币
有一些不规则的硬币。在这些硬币中,prob[i] 表示第 i 枚硬币正面朝上的概率。
请对每一枚硬币抛掷 一次,然后返回正面朝上的硬币数等于 target 的概率。
- 0-1 背包
class Solution {
public:
double probabilityOfHeads(vector<double>& prob, int target) {
vector<double> dp(target + 1, 0);
dp[0] = 1;
for(auto& p : prob){
for(int i = target; i >= 1; i--){
dp[i] = dp[i-1] * p + dp[i] * (1 - p);
}
dp[0] = dp[0] * (1 - p);
}
return dp.back();
}
};
滚动数组
序列DP
lc.1143 最长公共子序列
给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。
两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
- 二维DP
class Solution {
public:
int longestCommonSubsequence(string text1, string text2) {
int m = text1.size() , n = text2.size() ;
vector<vector<int>> dp (m + 1 , vector<int> (n + 1));
for(int i = 1 ; i <= m ; i ++ ){
char t1 = text1[i - 1];
for(int j = 1 ; j <= n ; j ++){
char t2 = text2[j - 1];
if(t1 == t2){
dp[i][j] = dp[i-1][j-1] + 1;
}
else {
dp[i][j] = max(dp[i - 1][j] , dp[i][j - 1]);
}
}
}
return dp[m][n];
}
};
- 减为两行记录值,压缩空间
class Solution {
public:
int longestCommonSubsequence(string text1, string text2) {
if (text1.size() < text2.size()) {
swap(text1, text2);
}
vector<vector<int>> m (2, vector<int>(text2.size(), 0));
int ans = 0;
for (int i = 0; i < text1.size(); ++i) {
for (int j = 0; j < text2.size(); ++j) {
if (text1[i] == text2[j]) {
m[1][j] = j?m[0][j-1]+1:1;
} else {
m[1][j] = max(m[0][j], j?m[1][j-1]:0);
}
}
swap(m[0], m[1]);
}
return m[0][text2.size() - 1];
}
};
为什么时间变快了···离谱。
lc.1035 不相交的线
- lc.1143
class Solution {
public:
int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) {
int m = nums1.size() , n = nums2.size();
vector<vector<int>> dp(m + 1 , vector<int> (n + 1 , 0)) ;
for(int i = 1 ; i <= m ; i ++){
int n1 = nums1[i - 1];
for(int j = 1 ; j <= n ; j ++){
int n2 = nums2[j - 1];
if(n1 == n2) dp[i][j] = dp[i-1][j-1] + 1;
else dp[i][j] = max(dp[i-1][j] , dp[i][j-1]);
}
}
return dp[m][n];
}
};
lc.712 两个字符串的最小ASCII删除
给定两个字符串s1, s2,找到使两个字符串相等所需删除字符的ASCII值的最小和。
class Solution {
public:
int minimumDeleteSum(string s1, string s2) {
vector<vector<int>> dp(s1.size()+1, vector<int>(s2.size()+1, 0));
for(int i = 0; i < s1.size(); ++i) {
for(int j = 0; j < s2.size(); ++j) {
if(s1[i] == s2[j]) {
dp[i+1][j+1] = dp[i][j] + s1[i];
} else {
dp[i+1][j+1] = max(dp[i+1][j],dp[i][j+1]);
}
}
}
int dels = dp[s1.size()][s2.size()] * 2;
for(int i = 0; i < s1.size(); ++i) {
dels -= s1[i];
}
for(int j = 0; j < s2.size(); ++j) {
dels -=s2[j];
}
return -dels;
}
};
也可以按最长公共子序列做。
lc.300 最长递增子序列
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n = (int)nums.size();
if (n == 0) {
return 0;
}
vector<int> dp(n, 0);
for (int i = 0; i < n; ++i) {
dp[i] = 1;
for (int j = 0; j < i; ++j) {
if (nums[j] < nums[i]) {
dp[i] = max(dp[i], dp[j] + 1);
}
}
}
return *max_element(dp.begin(), dp.end());
}
};
- 贪心 + DP
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int len = 1, n = (int)nums.size();
if (n == 0) {
return 0;
}
vector<int> d(n + 1, 0);
d[len] = nums[0];
for (int i = 1; i < n; ++i) {
if (nums[i] > d[len]) {
d[++len] = nums[i];
} else {
int l = 1, r = len, pos = 0; // 如果找不到说明所有的数都比 nums[i] 大,此时要更新 d[1],所以这里将 pos 设为 0
while (l <= r) {
int mid = (l + r) >> 1;
if (d[mid] < nums[i]) {
pos = mid;
l = mid + 1;
} else {
r = mid - 1;
}
}
d[pos + 1] = nums[i];
}
}
return len;
}
};
朴素的LIS问题可以加一个贪心数组,用二分来查找加的值在什么位置。
lc.673 最长递增子序列的个数
- 贪心 + DP wrong ans
class Solution {
public:
int findNumberOfLIS(vector<int>& nums) {
int cnt = 1 , len = 1 ;
vector<int> dp ;
dp.emplace_back(nums[0]);
for(int i = 1 ; i < nums.size() ; i ++){
if(nums[i] > dp[len - 1]){
dp.emplace_back(nums[i]);
len ++ ;
}
else{
int l = 0 , r = len - 1 ;
while(l < r){
int mid = l + r + 1 >> 1 ;
if(dp[mid] <= nums[i]) l = mid ;
else r = mid - 1 ;
}
if(nums[i] == dp[l] && l == len - 1) cnt ++;
if(nums[i] > dp[l]){
dp[l + 1] = nums[i];
cnt ++;
}
}
}
return cnt ;
}
};
边界条件和特殊情况太多。wc了两次。
- 贪心 + 前缀和 + DP
class Solution {
int binarySearch(int n, function<bool(int)> f) {
int l = 0, r = n;
while (l < r) {
int mid = (l + r) / 2;
if (f(mid)) {
r = mid;
} else {
l = mid + 1;
}
}
return l;
}
public:
int findNumberOfLIS(vector<int> &nums) {
vector<vector<int>> d, cnt;
for (int v : nums) {
int i = binarySearch(d.size(), [&](int i) { return d[i].back() >= v; });
int c = 1;
if (i > 0) {
int k = binarySearch(d[i - 1].size(), [&](int k) { return d[i - 1][k] < v; });
c = cnt[i - 1].back() - cnt[i - 1][k];
}
if (i == d.size()) {
d.push_back({v});
cnt.push_back({0, c});
} else {
d[i].push_back(v);
cnt[i].push_back(cnt[i].back() + c);
}
}
return cnt.back().back();
}
};
lc.354
lc.368
lc.446
lc.740
lc.978
lc.1035
lc.1143
lc.1473
lc.1713
lc.1048 最长字符串链
给出一个单词列表,其中每个单词都由小写英文字母组成。
如果我们可以在 word1 的任何地方添加一个字母使其变成 word2,那么我们认为 word1 是 word2 的前身。例如,“abc” 是 “abac” 的前身。
词链是单词 [word_1, word_2, …, word_k] 组成的序列,k >= 1,其中 word_1 是 word_2 的前身,word_2 是 word_3 的前身,依此类推。
从给定单词列表 words 中选择单词组成词链,返回词链的最长可能长度。
- WRONF ANS
class Solution {
public:
int longestStrChain(vector<string>& words) {
sort(words.begin() , words.end() , [](auto a , auto b){
return a.size() < b.size();
});
int ans = 1 , n = words.size() ;
vector<int> f(n , 1);
for(int i = 0 ; i < n ; i ++){
for(int j = i ; j >= 0 ; j --){
int li = words[i].size() , lj = words[j].size();
if(li == lj) continue;
if(li == lj + 1){
int cnt = 0 ;
for(auto x : words[i]){
if(words[j].find(x)) cnt ++;
}
if(cnt == words[j].size()){
f[i] = max(f[i] , f[j] + 1);
}
}
else break;
}
ans = max(ans , f[i]);
}
return ans;
}
};
中间的判断按照for(auto x : words[i]){ if(words[j].find(x)) cnt ++; }
查找,如果words[i]字符串中的重复字符无法判断,带来错误解答。
- sort排序 + 双指针判断 + DP寻值
class Solution {
public:
int longestStrChain(vector<string>& words) {
int n = words.size();
sort(words.begin(), words.end(), [](auto a, auto b){
return a.size() < b.size();
});
vector<int> f(n);
int ans = 0;
for(int i = 0; i < n; i++){
f[i] = 1;
for(int j = i - 1; j >= 0; j--){
int r1 = words[i].size(), r2 = words[j].size();
auto str1 = words[i], str2 = words[j];
if(r1 == r2) continue;
if(r1 - 1 == r2){
int l1 = 0, l2 = 0;
while(l1 < r1 && l2 < r2){
if(str1[l1] != str2[l2]){
l1++;
}else{
l1++;
l2++;
}
}
if(l2 == r2)
f[i] = max(f[i], f[j] + 1);
}else{
break;
}
}
ans = max(ans, f[i]);
}
return ans;
}
};
感觉各个小知识点被连起来解决问题了。
lc.646 最长数对链
给出 n 个数对。 在每一个数对中,第一个数字总是比第二个数字小。
现在,我们定义一种跟随关系,当且仅当 b < c 时,数对(c, d) 才可以跟在 (a, b) 后面。我们用这种形式来构造一个数对链。
给定一个数对集合,找出能够形成的最长数对链的长度。你不需要用到所有的数对,你可以以任何顺序选择其中的一些数对来构造。
- DP lc.1048 的方案,按左端点值排序后挨个比较 On2
class Solution {
public:
int findLongestChain(vector<vector<int>>& pairs) {
sort(pairs.begin(), pairs.end(), [](vector<int> &a, vector<int>b) {
if (a[0] == b[0]) return a[1] < b[1];
return a[0] < b[0];
});
int n = pairs.size(), ans = 1;
vector<int> dp(n, 1);
for (int i = 1; i < n; i++) {
dp[i] = 1;
for (int j = 0; j < i; j++) {
if (pairs[i][0] > pairs[j][1]) dp[i] = max(dp[i], dp[j] + 1);
}
ans = max(ans, dp[i]);
}
return ans;
}
};
- 贪心
class Solution {
public:
int findLongestChain(vector<vector<int>>& pairs) {
sort(pairs.begin(), pairs.end(), [](vector<int> &a, vector<int>b) {
return a[1] < b[1];
});
int n = pairs.size(), ans = 1, r = pairs[0][1];
for (int i = 1; i < n; i++) {
if (pairs[i][0] > r) {
ans++;
r = pairs[i][1];
}
}
return ans;
}
};
将pairs按照区间右端点的值由小到大进行排序,然后选取尽可能多的区间即可。时间复杂度O(nlogn) 空间复杂度O(1) 。 只需要遍历一遍。
lc.368 最大整除子集
给你一个由 无重复 正整数组成的集合 nums ,请你找出并返回其中最大的整除子集 answer ,子集中每一元素对 (answer[i], answer[j]) 都应当满足:
answer[i] % answer[j] == 0 ,或
answer[j] % answer[i] == 0
如果存在多个有效解子集,返回其中任何一个均可。
class Solution {
public:
vector<int> largestDivisibleSubset(vector<int>& nums) {
sort(nums.begin(),nums.end());
vector<int> ans; int n = nums.size() ;
vector<int> dp(n , 1);
int maxsize = 1 , maxval = 0 ;
for(int i = 1 ; i < n ; i ++){
for(int j = 0 ; j < i ; j ++){
if(nums[i] % nums[j] == 0){
dp[i] = max(dp[i] , dp[j] + 1);
}
}
maxsize = dp[i] > maxsize ? dp[i] : maxsize ;
maxval = dp[i] > maxsize ? nums[i] : maxval ;
}
if(maxsize == 1){
ans.emplace_back(nums[0]);
return ans;
}
for(int i = n - 1 ; i >= 0 && maxsize > 0 ; i --){
if(maxval % nums[i] == 0 && dp[i] == maxsize){
ans.emplace_back(nums[i]);
maxval = nums[i];
maxsize -- ;
}
}
return ans;
}
};
遍历DP以找到最大子集,逆着再遍历返回最大子集
lc.647 回文子串
给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。
回文字符串 是正着读和倒过来读一样的字符串。
子字符串 是字符串中的由连续字符组成的一个序列。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
- 遍历判断
class Solution {
public:
bool isHw(int left , int right , string& s){
while(left <= right){
if(s[left] == s[right]){
left ++ ; right -- ;
}
else return false ;
}
return true ;
}
int countSubstrings(string s) {
int cnt = 0 ;
for(int i = 0 ; i < s.size() ; i ++){
for(int j = i ; j >= 0 ; j --){
if(isHw(j , i , s)){
cnt ++;
}
}
}
return cnt ;
}
};
虽然慢,但是AC了。
- Manacher 算法 中心拓展
class Solution {
public:
int countSubstrings(string s) {
int num = 0;
int n = s.size();
for(int i=0;i<n;i++)//遍历回文中心点
{
for(int j=0;j<=1;j++)//j=0,中心是一个点,j=1,中心是两个点
{
int l = i;
int r = i+j;
while(l>=0 && r<n && s[l--]==s[r++])num++;
}
}
return num;
}
};
Manacher 算法是在线性时间内求解最长回文子串的算法。中心只会是一个数或两个数 两种可能,所以遍历中心点。
lc.5 最长回文子串
给你一个字符串 s,找到 s 中最长的回文子串。
- 中心拓展
class Solution {
public:
string longestPalindrome(string s) {
int n = s.size() ;
int maxLength = 0 ;
string ans;
for(int i = 0 ; i < n ; i ++){
for(int j = 0 ; j <= 1 ; j ++){
int left = i , right = i + j , len = 0;
while(left >= 0 && right < n && s[left] == s[right]){
len = right - left + 1 ;
left -- ; right ++ ;
}
if(len > maxLength){
maxLength = len ;
ans.clear();
for(int x = left + 1; x <= right - 1 ; x ++){
ans += s[x];
}
}
}
}
return ans;
}
};
注意边界条件,测试的时候多加点边界条件测试。
- manacher
class Solution {
public:
int expand(const string& s, int left, int right) {
while (left >= 0 && right < s.size() && s[left] == s[right]) {
--left;
++right;
}
return (right - left - 2) / 2;
}
string longestPalindrome(string s) {
int start = 0, end = -1;
string t = "#";
for (char c: s) {
t += c;
t += '#';
}
t += '#';
s = t;
vector<int> arm_len;
int right = -1, j = -1;
for (int i = 0; i < s.size(); ++i) {
int cur_arm_len;
if (right >= i) {
int i_sym = j * 2 - i;
int min_arm_len = min(arm_len[i_sym], right - i);
cur_arm_len = expand(s, i - min_arm_len, i + min_arm_len);
} else {
cur_arm_len = expand(s, i, i);
}
arm_len.push_back(cur_arm_len);
if (i + cur_arm_len > right) {
j = i;
right = i + cur_arm_len;
}
if (cur_arm_len * 2 + 1 > end - start) {
start = i - cur_arm_len;
end = i + cur_arm_len;
}
}
string ans;
for (int i = start; i <= end; ++i) {
if (s[i] != '#') {
ans += s[i];
}
}
return ans;
}
};
我们只需要在中心扩展法的过程中记录右臂在最右边的回文字符串,将其中心作为 j,在计算过程中就能最大限度地避免重复计算。
解决中心奇偶数个的统一的方法很妙:
确实快。
lc.516 最长回文子序列
给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。
子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。
class Solution {
public:
int longestPalindromeSubseq(string s) {
int n = s.length();
vector<vector<int>> dp(n, vector<int>(n));
for (int i = n - 1; i >= 0; i--) {
dp[i][i] = 1;
char c1 = s[i];
for (int j = i + 1; j < n; j++) {
char c2 = s[j];
if (c1 == c2) {
dp[i][j] = dp[i + 1][j - 1] + 2;
} else {
dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);
}
}
}
return dp[0][n - 1];
}
};
lc.1055 形成字符串的最短路径
对于任何字符串,我们可以通过删除其中一些字符(也可能不删除)来构造该字符串的子序列。
给定源字符串 source 和目标字符串 target,找出源字符串中能通过串联形成目标字符串的子序列的最小数量。如果无法通过串联源字符串中的子序列来构造目标字符串,则返回 -1。
- 贪心(遍历)
class Solution {
public:
int shortestWay(string source, string target) {
int shortestWay = 0,i=0,j;
while(i<target.length()){
j = i;
for(auto &e:source){
if(e==target[i])
i++;
}
if(i==j)// 出现未曾出现的字符,导致target匹配的指针未前进
return -1;
shortestWay++;
}
return shortestWay;
}
};
这题一眼看到就在想用 KMP 来做。谁料贪心暴力就已经有 100% 的速度。又去搜july的KMP讲解看了好一会儿,还是打算用 KMP 来做一遍复习复习。
路径DP
lc.64 最小路径和
给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
if (grid.size() == 0 || grid[0].size() == 0) {
return 0;
}
int rows = grid.size(), columns = grid[0].size();
auto dp = vector < vector <int> > (rows, vector <int> (columns));
dp[0][0] = grid[0][0];
for (int i = 1; i < rows; i++) {
dp[i][0] = dp[i - 1][0] + grid[i][0];
}
for (int j = 1; j < columns; j++) {
dp[0][j] = dp[0][j - 1] + grid[0][j];
}
for (int i = 1; i < rows; i++) {
for (int j = 1; j < columns; j++) {
dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
}
}
return dp[rows - 1][columns - 1];
}
};
lc.562 矩阵中最长的连续1线段
给定一个01矩阵 M,找到矩阵中最长的连续1线段。这条线段可以是水平的、垂直的、对角线的或者反对角线的。
class Solution {
public:
int longestLine(vector<vector<int>>& M) {
if (M.empty())
return 0;
int ans = 0;
int* horizontal = new int[M[0].size()];
int* vertical = new int[M[0].size()];
int* diagonal = new int[M[0].size()];
int* antidiagonal = new int[M[0].size()];
for (int i = 0; i != M.size(); ++i) {
int* vertical_new = new int[M[0].size()];
int* diagonal_new = new int[M[0].size()];
int* antidiagonal_new = new int[M[0].size()];
for (int j = 0; j != M[0].size(); ++j) {
if (M[i][j] == 0) {
horizontal[j] = 0;
vertical_new[j] = 0;
diagonal_new[j] = 0;
antidiagonal_new[j] = 0;
} else {
horizontal[j] = j > 0 ? horizontal[j - 1] + 1 : 1;
vertical_new[j] = i > 0 ? vertical[j] + 1 : 1;
diagonal_new[j] = i > 0 && j > 0 ? diagonal[j - 1] + 1 : 1;
antidiagonal_new[j] = i > 0 && j < M[0].size() - 1 ? antidiagonal[j + 1] + 1 : 1;
ans = max(ans, horizontal[j]);
ans = max(ans, vertical_new[j]);
ans = max(ans, diagonal_new[j]);
ans = max(ans, antidiagonal_new[j]);
}
}
delete[] vertical;
delete[] diagonal;
delete[] antidiagonal;
vertical = vertical_new;
diagonal = diagonal_new;
antidiagonal = antidiagonal_new;
}
return ans;
}
};
java
lc.1182 与目标颜色间的最短距离
给你一个数组 colors,里面有 1、2、 3 三种颜色。
我们需要在 colors 上进行一些查询操作 queries,其中每个待查项都由两个整数 i 和 c 组成。
现在请你帮忙设计一个算法,查找从索引 i 到具有目标颜色 c 的元素之间的最短距离。
如果不存在解决方案,请返回 -1。
class Solution {
public:
vector<int> shortestDistanceColor(vector<int>& colors, vector<vector<int>>& queries) {
int n = colors.size();
vector<vector<int>> dis(3, vector<int>(n, 1e9));
for(int i = 0, a = -1, b = -1, c = -1; i < n; ++i){
if(colors[i] == 1) a = i;
else if(colors[i] == 2) b = i;
else c = i;
if(a != -1) dis[0][i] = min(dis[0][i], i - a);
if(b != -1) dis[1][i] = min(dis[1][i], i - b);
if(c != -1) dis[2][i] = min(dis[2][i], i - c);
}
for(int i = n - 1, a = -1, b = -1, c = -1; i >= 0; --i){
if(colors[i] == 1) a = i;
else if(colors[i] == 2) b = i;
else c = i;
if(a != -1) dis[0][i] = min(dis[0][i], a - i);
if(b != -1) dis[1][i] = min(dis[1][i], b - i);
if(c != -1) dis[2][i] = min(dis[2][i], c - i);
}
vector<int> ans(queries.size());
for(int i = 0; i < queries.size(); ++i){
int t = dis[queries[i][1] - 1][queries[i][0]];
ans[i] = t == 1e9 ? -1 : t;
}
return ans;
}
};
lc.343 整数拆分
给定一个正整数 n ,将其拆分为 k 个 正整数 的和( k >= 2 ),并使这些整数的乘积最大化。
返回 你可以获得的最大乘积 。
- DP
class Solution {
public:
int integerBreak(int n) {
if (n < 4) {
return n - 1;
}
vector <int> dp(n + 1);
dp[2] = 1;
for (int i = 3; i <= n; i++) {
dp[i] = max(max(2 * (i - 2), 2 * dp[i - 2]), max(3 * (i - 3), 3 * dp[i - 3]));
}
return dp[n];
}
};
- math
class Solution {
public:
int integerBreak(int n) {
if (n <= 3) {
return n - 1;
}
int quotient = n / 3;
int remainder = n % 3;
if (remainder == 0) {
return (int)pow(3, quotient);
} else if (remainder == 1) {
return (int)pow(3, quotient - 1) * 4;
} else {
return (int)pow(3, quotient) * 2;
}
}
};
crazy
记忆化
lc.254 因子的组合
整数可以被看作是其因子的乘积。
请实现一个函数,该函数接收一个整数 n 并返回该整数所有的因子组合。
- 回溯
class Solution {
public:
vector<vector<int>> getFactors(int n) {
for(int i(2) ; n>=i*i ; ++i){
if(n % i == 0){
result.push_back(i);
backtrack(n/i, i);
result.pop_back();
}
}
return results;
}
private:
vector<vector<int>> results;
vector<int> result;
void backtrack(const int& n, const int& num){
result.push_back(n);
results.push_back(result);
result.pop_back();
//即只有i<= sqrt(n)的时候才会接着递归。
for(int i(num) ; n >= i*i ; ++i){
if(n % i == 0){
result.push_back(i);
backtrack(n/i, i);
result.pop_back();
}
}
}
};
从大向下拆分因子
lc.583 两个字符串的删除操作
lc.329 矩阵中的最长递增路径
计数DP
lc.576 出界的路径数
lc.650 只有两个键的键盘
lc.361 轰炸敌人
lc.96 不同的二叉搜索树
lc.1130 叶值的最小代价生成树
组合DP
lc.322 零钱兑换
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
- 无限背包
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
int Max = amount + 1;
vector<int> dp(amount + 1, Max);
dp[0] = 0;
for (int i = 1; i <= amount; ++i) {
for (int j = 0; j < (int)coins.size(); ++j) {
if (coins[j] <= i) {
dp[i] = min(dp[i], dp[i - coins[j]] + 1);
}
}
}
return dp[amount] > amount ? -1 : dp[amount];
}
};