leetcode算法问题

94 阅读4分钟

问题七

题意描述

01背包问题的一个变形,在原题的基础上,要求背包的容量刚好占满。

思路

其实只需要改动一下初始化的方法。

在原本的01背包问题中,我们的状态转移方程为

f[i][j] = max(f[i - 1][j], f[i - 1][j - w[i]] + v[i])

我们可以发现,如果不是恰好装满,那么部分状态对我们来说就是无效的。

如果无效状态无法推出有效状态,我们就不必关心无效状态(背包没装满)时候,背包内物体的总价值。

因为背包恰好装满时候我们只关心有效状态,而无效状态又无法推出有效状态。

也就可以把所有无效状态时背包的总价值都设置为负无穷。

然后判断最终结果,如果是负无穷,说明无法装满。

代码

代码实现时,我只有一维数组表示状态,这是01背包问题中优化空间复杂度的方法。

因为在状态转移时,可以发现每次的状态转移只与两个状态有关,所以可以优化掉第一维,也就是表示哪件物体。

对应的,需要将第二个循环改为倒序,这样就可以保证每次只从过去的状态转移过来;如果是正序,就变为了完全背包了。

#include <iostream>
using namespace std;
#define max(N1,N2) N1>N2?N1:N2
#define INF 0x80000000
int main()
{
    int V, N;
    while (cin >> V >> N)
    {
        int v[1000], w[1000];
        int f[10000] ;
        for (int i = 0; i < 10000; i++)
        {
            f[i] = INF;
        }
        f[0] = 0;
        for (int i = 1; i <= N; i++)
        {
            cin >> v[i] >> w[i];
        }
        for (int i = 1; i <= N; i++)
        {
            for (int j = V; j >= v[i]; j--)
            {
                f[j] = max(f[j], f[j - v[i]] + w[i]);
                if (f[j] < 0)
                    f[j] = INF;
            }
        }
        if (f[V] > 0)
        {
            cout << f[V] << endl;
        }
        else
        {
            cout << "error" << endl;
        }
    }
    return 0;
}

问题九

题意描述

经典问题,最长不下降子序列。

思路

(1)最优子结构

如果一个数列A是数列B的最长上升子序列,那么相应的在数列A和数列B中去掉数列A中的某个数字之后,A剩下的序列肯定也是B剩下序列中最长的子序列 .

(2)重叠子问题

将上面的数列A和数列B去掉某一个数之后与未去之前具有相同的问题性质,也就是子问题。

复杂度为 O(n^2) 的算法就是在每个数的初始情况下的最大上升子序列长度为1。

讨论复杂度为 O(nlogn) 的算法:

维护一个数组 tail , 其中 taili存储长度为 i 的所有上升子序列中末尾最小的元素。在更新过程中,我们使用二分搜索来确定一个元素应当放置在 tail 中的位置。

我们还是从左向右来枚举,对于每个元素 ai,在 tail 中找到第一个大于 ai 的位置。

  • 如果找到这样的位置,即表示之前有一个序列的末尾可以被替换,所以修改其 tail 值为新 ai
  • 如果没有找到,说明 ai 比之前所有数字都大,那么把 ai 追加到 tail 末尾,最长的上升序列长度成功加 1

代码

#include<iostream>
​
using namespace std;
const int maxN = 105; 
int n;
int arr[maxN];
​
//使用O(n^2)的方法 
void getLIS1(){
    int f[maxN];//f[i]表示 arr[i]的最长不下降子序列的长度 
    fill(f,f+maxN,1);//初始化为1 
    int res = 0;    
    for(int i =1;i< n;i++){
        for(int j = 0;j<i;j++){
            if(arr[i] >= arr[j]){//如果当前的数不小于 arr[j]
                f[i] = max(f[i],f[j]+1);                
            }           
        }
        res = max(res,f[i]);
    }   
    cout << res <<"\n"; 
}
​
//使用O(nlogn)的方法
void getLIS2(){
    int tail[maxN ];//tail[i]表示长度为i的LNDS中结尾的元素 
    fill(tail,tail+maxN,0);
    int cnt = 1;
    tail[cnt] = arr[0];//第一个长度为1的最长不下降子序列的结尾元素是arr[0] 
    for(int i = 1;i< n;i++){
        if(arr[i] >= tail[cnt]){
            tail[++cnt] = arr[i];
        }
        else{//二分找出最小的 
            int idx= lower_bound(tail+1,tail+cnt+1,arr[i]) - tail;
            tail[idx] = arr[i];//换掉这个元素 
        }
    } 
    
    cout << cnt<<"\n";  
}
​
//计算最长不下降子序列 
int main(){
    cin >> n;
    for(int i = 0;i< n;i++){
        cin >> arr[i];
    }
    
    getLIS1(); 
    getLIS2();
}