最长递增子序列Onlogn

如何解决最长递增子序列Onlogn

| LIS:维基百科 我不明白一件事: 为什么X [M [i]]是不递减的序列?     

解决方法

首先看一下n ^ 2算法:
dp[0] = 1;
for( int i = 1; i < len; i++ ) {
   dp[i] = 1;
   for( int j = 0; j < i; j++ ) {
      if( array[i] > array[j] ) {
         if( dp[i] < dp[j]+1 ) {
            dp[i] = dp[j]+1;
         }
      }
   }
}
现在,改进发生在第二个循环,基本上,您可以使用二进制搜索来提高速度。除了数组dp [],还有另一个数组c [],c非常特殊,c [i]的意思是:最长递增序列的最后一个元素的最小值,其长度为i。
sz = 1;
c[1] = array[0]; /*at this point,the minimum value of the last element of the size 1 increasing sequence must be array[0]*/
dp[0] = 1;
for( int i = 1; i < len; i++ ) {
   if( array[i] < c[1] ) {
      c[1] = array[i]; /*you have to update the minimum value right now*/
      dp[i] = 1;
   }
   else if( array[i] > c[sz] ) {
      c[sz+1] = array[i];
      dp[i] = sz+1;
      sz++;
   }
   else {
      int k = binary_search( c,sz,array[i] ); /*you want to find k so that c[k-1]<array[i]<c[k]*/
      c[k] = array[i];
      dp[i] = k;
   }
}
    ,这是《 The Hitchhiker编程竞赛指南》中的O(n * lg(n))解决方案(请注意:此实现假定列表中没有重复项):
set<int> st;
set<int>::iterator it;
st.clear();
for(i=0; i<n; i++) {
  st.insert(array[i]);
  it=st.find(array[i]);
  it++;
  if(it!=st.end()) st.erase(it);
}
cout<<st.size()<<endl;
要考虑重复,可以检查例如号码是否已在集合中。如果是,请忽略该数字,否则使用与以前相同的方法进行操作。或者,可以颠倒操作的顺序:先删除,然后插入。下面的代码实现了此行为:
set<int> st;
set<int>::iterator it;
st.clear();
for(int i=0; i<n; i++) {
    it = st.lower_bound(a[i]);
    if (it != st.end()) st.erase(it);
    st.insert(a[i]);
}
cout<<st.size()<<endl;
通过维护包含原始数组中LIS先前元素位置的父数组,可以扩展第二种算法以查找最长的递增子序列(LIS)本身。
typedef pair<int,int> IndexValue;

struct IndexValueCompare{
    inline bool operator() (const IndexValue &one,const IndexValue &another){
        return one.second < another.second;
    }
};

vector<int> LIS(const vector<int> &sequence){
    vector<int> parent(sequence.size());
    set<IndexValue,IndexValueCompare> s;
    for(int i = 0; i < sequence.size(); ++i){
        IndexValue iv(i,sequence[i]);
        if(i == 0){
            s.insert(iv);
            continue;
        }
        auto index = s.lower_bound(iv);
        if(index != s.end()){
            if(sequence[i] < sequence[index->first]){
                if(index != s.begin()) {
                    parent[i] = (--index)->first;
                    index++;
                }
                s.erase(index);
            }
        } else{
            parent[i] = s.rbegin()->first;
        }
        s.insert(iv);
    }
    vector<int> result(s.size());
    int index = s.rbegin()->first;
    for(auto iter = s.rbegin(); iter != s.rend(); index = parent[index],++iter){
        result[distance(iter,s.rend()) - 1] = sequence[index];
    }
    return result;
}
    ,我们需要维护序列递增的列表。 通常,我们有一组长度可变的活动列表。我们正在向这些列表添加元素A [i]。我们以长度减少的顺序扫描列表(用于结束元素)。我们将验证所有列表的结束元素,以找到其结束元素小于A [i](底值)的列表。 我们的策略由以下条件决定, 1.如果A [i]在活动列表的所有最终候选中最小,则我们将开始新的长度为1的活动列表。 2.如果在活动列表的所有最终候选中A [i]最大,我们将克隆最大的活动列表,并将其扩展A [i]。 3.如果介于A [i]之间,我们将找到一个列表,该列表的最大结束元素小于A [i]。克隆此列表并将其扩展A [i]。我们将丢弃所有与此修改列表长度相同的其他列表。 请注意,在构造活动列表的任何时候,都将保持以下条件。 “较小列表的结尾元素小于较大列表的结尾元素”。 一个例子将很清楚,让我们从wiki举例: {0,8,4,12,2,10,6,14,1,9,5,13,3,11,7,15}。 A [0] =0。情况1.没有活动列表,请创建一个。 0。 -------------------------------------------------- --------------------------- A [1] =8。情况2。克隆并扩展。 0。 0、8 -------------------------------------------------- --------------------------- A [2] =4。情况3.克隆,扩展和丢弃。 0。 0,4。 0,8.舍弃 -------------------------------------------------- --------------------------- A [3] =12。情况2。克隆并扩展。 0。 0,4。 0、4、12 -------------------------------------------------- --------------------------- A [4] =2。情况3.克隆,扩展和丢弃。 0。 0,2。 0,4.丢弃。 0、4、12 -------------------------------------------------- --------------------------- A [5] =10。情况3.克隆,扩展和丢弃。 0。 0,2。 0、2、10 0、4、12。舍弃。 -------------------------------------------------- --------------------------- A [6] =6。情况3.克隆,扩展和丢弃。 0。 0,2。 0、2、6 0、2、10。舍弃。 -------------------------------------------------- --------------------------- A [7] =14。情况2。克隆并扩展。 0。 0,2。 0、2、6 0、2、6、14 -------------------------------------------------- --------------------------- A [8] =1。情况3.克隆,扩展和丢弃。 0。 0、1 0,2.舍弃。 0、2、6 0、2、6、14 -------------------------------------------------- --------------------------- A [9] =9。情况3.克隆,扩展和丢弃。 0。 0、1 0、2、6 0、2、6、9。 0、2、6、14。舍弃。 -------------------------------------------------- --------------------------- A [10] =5。情况3.克隆,扩展和丢弃。 0。 0、1 0、1、5 0、2、6。舍弃。 0、2、6、9。 -------------------------------------------------- --------------------------- A [11] =13。情况2。克隆并扩展。 0。 0、1 0、1、5 0、2、6、9。 0、2、6、9、13。 -------------------------------------------------- --------------------------- A [12] =3。情况3.克隆,扩展和丢弃。 0。 0、1 0、1、3 0,1,5.丢弃。 0、2、6、9。 0、2、6、9、13。 -------------------------------------------------- --------------------------- A [13] =11。情况3.克隆,扩展和丢弃。 0。 0、1 0、1、3 0、2、6、9。 0、2、6、9、11 0、2、6、9、13。舍弃。 -------------------------------------------------- --------------------------- A [14] =7。情况3.克隆,扩展和丢弃。 0。 0、1 0、1、3 0、1、3、7。 0、2、6、9。舍弃。 0、2、6、9、11 -------------------------------------------------- -------------------------- A [15] =15。情况2。克隆并扩展。 0。 0、1 0、1、3 0、1、3、7。 0、2、6、9、11 0、2、6、9、11、15。<-LIS列表 另外,确保我们保持以下条件:“较小列表的结束元素小于较大列表的结束元素”。 该算法称为耐心排序。 http://en.wikipedia.org/wiki/Patience_sorting 因此,从套牌中挑选一套西装。从经过改组的套装中找出牌中最长的递增子序列。您将永远不会忘记这种方法。 复杂度:O(NlogN) 资料来源:http://www.geeksforgeeks.org/longest-monotonically-increasing-subsequence-size-n-log-n/     ,算法背后的基本思想是保留给定长度的LIS列表,该列表以尽可能小的元素结尾。构建这样的序列 在已知的最后一个元素序列中查找前任(假设其长度为
k
) 尝试将当前元素附加到此序列,并为for6ѭ长度建立新的更好的解决方案 因为在第一步中,您搜索的值较小,然后X [i],所以新解(对于
k+1
)将具有大于最后一个元素的序列,而不是较短的序列。 希望对您有所帮助。     ,我想出了这个
set<int> my_set;
set<int>::iterator it;
vector <int> out;
out.clear();
my_set.clear();
for(int i = 1; i <= n; i++) {
    my_set.insert(a[i]);
    it = my_set.find(a[i]);
    it++;
    if(it != my_set.end()) 
        st.erase(it);
    else
        out.push_back(*it);
}
cout<< out.size();
    ,您无法理解,因为维基百科中的代码是错误的(我坚信是这样)。这不仅是错误的,而且变量的命名也很差。但这让我花时间了解它的工作原理:D。 现在,我读了耐心排序。我重写了算法。我还编写了更正的二进制搜索。 耐心排序就像插入排序 与插入排序类似,耐心排序通过二进制搜索找到下一个项目的适当位置。二进制搜索是对按排序顺序构建的卡堆进行的。让我为纸牌堆分配一个变量。(我说的是纸牌,因为耐心是一种简化的纸牌游戏)。
//! card piles contain pile of cards,nth pile contains n cards.
int top_card_list[n+1];
for(int i = 0; i <= n; i++) {
    top_card_list[i] = -1;
}
现在,
top_card_list
包含高度为
n
的牌堆的顶牌。耐心排序会将牌手放在小于它的最高顶牌上(或相反)。有关进一步的分类说明,请参阅Wikipedia页面上的耐心分类。
             3
  *   7      2                   
-------------------------------------------------------------
  Pile of cards above (top card is larger than lower cards)
 (note that pile of card represents longest increasing subsequence too !)
在一堆纸牌上的二进制搜索 现在,当我们为最长增长的子序列进行动态编程时找到一个数字,我们运行一个内部循环,即
O(n)
for(int i = 1; i < n; i++) { // outer loop
    for(int j = 0; j < i; j++) { // inner loop
        if(arr[i] > arr[j]) {
            if(memo_len[i] < (memo_len[j]+1)) {
                // relaxation
                memo_len[i] = memo_len[j]+1;
                result = std::max(result,memo_len[i]);
                pred[i] = j;
            }
        }
    }
 }
而且,在内部循环中可以找到比我们手边的卡片还要小的最高卡片。 但是我们知道我们可以通过二进制搜索来做到! (练习:证明正确性)这样,我们可以在15分钟内做到这一点。现在
O(number of piles)
=
O(number of cards)
(但卡号为52,应该是O(1)!,只是在开玩笑!)。因此,整个应用程序的运行时间为18分钟。 这是经过修改的带有二进制搜索的DP。
for(int i = 1; i < n; i++) {
    pile_height[i] = 1;
    const int j = pile_search(top_card_list,arr,pile_len,arr[i]);
    if(arr[i] > arr[j]) {
        if(pile_height[i] < (pile_height[j]+1)) {
            // relaxation
            pile_height[i] = pile_height[j]+1;
            result = std::max(result,pile_height[i]);
            pile_len = std::max(pile_len,pile_height[i]);
        }
    }
    if(-1 == top_card_list[pile_height[i]] || arr[top_card_list[pile_height[i]]] > arr[i]) {
        top_card_list[pile_height[i]] = i; // top card on the pile is now i
    }
}
这是下面的正确桩搜索。这只是一个二进制搜索,但它会找到顶卡的索引,该索引小于手头的卡。
inline static int pile_search(const int*top_card_list,const vector<int>& arr,int pile_len,int strict_upper_limit) {
    int start = 1,bound=pile_len;
    while(start < bound) {
        if(arr[top_card_list[bound]] < strict_upper_limit) {
            return top_card_list[bound];
        }
        int mid = (start+bound)/2 + ((start+bound)&1);
        if(arr[top_card_list[mid]] >= strict_upper_limit) {
            // go lower
            bound = mid-1;
        } else {
            start = mid;
        }
    }
    return top_card_list[bound];
}
注意,与维基百科不同,它返回unlike21ѭ(我的解决方案)。另请注意dp中
top_card_list[]
的更新位置。此代码已针对边界情况进行了测试。希望对您有所帮助。     ,这里有一个证明https://strncat.github.io/jekyll/update/2019/06/25/longest-increasing-subsequence.html 基本上,不可能不严格增加子序列。 证明是矛盾的:假设不是,那么我们有两种情况: 情况1)有一个元素M [j]终止于长度为j和j +某数的两个子序列。这是不可能的(链接中的证明) 情况2)与情况1略有不同,但推理基本相同。您如何才能在两个不同长度的两个子序列的末尾得到一个最小的数字?不可能。     

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-