题:

给定一个序列S以及它的一个子序列T,求S的所有包含T的子序列。例:

S = [1, 2, 3, 2, 4]
T = [1, 2, 4]
则S的所有包含T的子序列为:
[1, 2, 3, 2, 4]
[1, 2, 3, 4]
[1, 2, 2, 4]
[1, 2, 4]
 

解:

首先可以拆解为两个问题:
1. 求S的所有子序列;其中又涉及到去重的问题。
2. 求S的所有子序列中包含T的子序列。
 
暂时先不考虑去重,看看问题1怎么解:

一、求S的子序列

单纯求一个序列的所有子序列的话,就是求序列的所有组合。一般的思路为:S中每个元素有输出和不输出两种状态,解集为所有元素是否输出的状态组合。由于两个元素有两种状态,所以解集的大小就是2的n次方(n为S的长度),也就是一个长度为n的二进制序列的所有可能值。所以求所有子序列的代码:
 
void PrintDistinctSubByFlags(char* seq, int seq_len, bool* seq_flags)
{
printf("\r\n");
char buf[] = " ";
for (int i = ; i < seq_len; ++i)
{
if (seq_flags[i])
{
buf[] = seq[i];
printf(buf);
}
}
} void DistinctSubInner(char* seq, int seq_len, bool* seq_flags, int seq_flags_idx)
{
if (seq_flags_idx >= seq_len)
{
PrintDistinctSubByFlags(seq, seq_len, seq_flags);
return;
} seq_flags[seq_flags_idx] = false;
DistinctSubInner(seq, seq_len, seq_flags, seq_flags_idx + );
seq_flags[seq_flags_idx] = true;
DistinctSubInner(seq, seq_len, seq_flags, seq_flags_idx + );
} void DistinctSub(char* whole_seq)
{
if(!whole_seq || !*whole_seq)
{
return;
} bool* seq_flags = new bool[strlen(whole_seq) + ];
DistinctSubInner(whole_seq, strlen(whole_seq), seq_flags, );
delete seq_flags;
}
 
可以换一个方式来写,不使用位数组标记,而是从序列的首元素开始处理,分输出和不输出两种情况,再递归处理首元素之外的子序列,这样可以直接生成输出序列(只包含要输出的元素):
 
void DistinctSubInner(char* whole_seq, char* sub_seq, int sub_seq_len)
{
if (!*whole_seq)
{
PrintDistinctSub(sub_seq_len);
return;
} sub_seq[sub_seq_len] = *whole_seq;
DistinctSubInner(whole_seq + , sub_seq, sub_seq_len + ); // output head of S
DistinctSubInner(whole_seq + , sub_seq, sub_seq_len); // not ouput head of S
} void DistinctSub(char* whole_seq)
{
if(!whole_seq || !*whole_seq)
{
return;
} sub_seq = new char[strlen(whole_seq)];
DistinctSubInner(whole_seq, sub_seq, );
delete sub_seq;
}
 
在这个基础上,可以再加入子序列的匹配逻辑:

二、求S中所有包含T的子序列

void DistinctSubInner(char* whole_seq, char* min_seq, char* sub_seq, int sub_seq_len)
{
if (!*whole_seq)
{
if(!*min_seq)
{
PrintDistinctSub(sub_seq, sub_seq_len);
}
else
{
// unmatch sub sequence
}
return;
} sub_seq[sub_seq_len] = *whole_seq; if (*whole_seq == *min_seq)
{
// 1. output head of S and match head of T
DistinctSubInner(whole_seq + , min_seq + , sub_seq, sub_seq_len + );
} // 2. output head of S but do not match head of T
DistinctSubInner(whole_seq + , min_seq, sub_seq, sub_seq_len + ); // 3. do not ouput head of S
DistinctSubInner(whole_seq + , min_seq, sub_seq, sub_seq_len);
} void DistinctSub(char* whole_seq, char* min_seq)
{
if(!whole_seq || !*whole_seq || !min_seq || !*min_seq)
{
return;
} char* sub_seq = new char[strlen(whole_seq) + ];
DistinctSubInner(whole_seq, min_seq, sub_seq, );
delete sub_seq;
}
 
 
这里S的首元素是否输出和是否和T的首元素进行匹配一共有三种组合:
1. S的首元素输出,但是不和T的首元素匹配(不相同无法匹配或故意不匹配);
2. S的首元素输出,且S的首元素与T的首元素相同,进行匹配;
3. S的首元素不输出。
 
如果S的首元素不输出的话,自然就不能和T进行匹配,所以没有第4种可能。
 
那么,这里有一个问题,如果S的首元素和T的首元素相同时,为什么要分匹配和不匹配两种情况呢?原因在于如果S中包含两个相同的元素能够进行匹配的话,这么做可以使T中对应元素能够匹配到S中的不同位置,从而形成。
 
接下来再考虑重复元素的问题。
 

三、去重

我看到的重复问题有两类:
1. 如果S中有多个位置能够匹配T中某一个元素的话,是否需要匹配不同位置?
2. 当已生成的输出序列中有连续两个相同的元素时,会形成重复解。
 
分开来看。

3.1 是否需要匹配不同位置?

这其实也是两个子问题:
a. 匹配不同位置会不会造成重复的解?
 
以S=[1,2,3,2,4], T=[1,2,4]为例。T中第2个元素可以匹配到S的第2和第4个位置。
显然,在S中两个可选匹配位置(第2和第4)之间的区域(此例中第3个元素)不输出的情况下,匹配到这两个位置的结果集是相同的,所以匹配到不同位置会有重复解。
 
b. 只匹配单一位置的话会不会造成漏解?
 
如果只匹配S中的第一个可选位置的话,那么输出解的组合可以更改为:
1. 输出S的首元素,并且如果S的首元素与T的首元素相同,就匹配;
2. 不输出S的首元素。
 
这样问题就转换成了:这样一个逻辑形成的解空间,能否够覆盖将T中元素匹配到其它位置形成的解空间?为了解答这个问题,我们需要再回到用位数组来表示解的表达方式。如果S中某个元素被匹配的话,那么它必定是要输出的,解空间类似:
{x,x,x,x,1,x,x...} // x表示状态未定0|1,0表示不输出,1表示输出;
 
以上面的例子来说:
如果T中第2个元素匹配S中的第2个元素,解空间为: {1,1,x,x,1} & {1,0,x,1,1} = {1,x,x,x,1}
如果T中第2个元素匹配S中的第4个元素,解空间为: {1,x,x,1,1},显然是{1,x,x,x,1}的子集。
 
所以,结论是只需要S中的任何一个位置即可。
 
这样程序就变成了:
 
void DistinctSubInner(char* whole_seq, int sub_seq_len)
{
if (!*whole_seq)
{
if(!*min_seq)
{
PrintDistinctSub(sub_seq_len);
}
return;
} sub_seq[sub_seq_len] = *whole_seq; if (*whole_seq == *min_seq)
{
DistinctSubInner(whole_seq + , min_seq + , sub_seq_len + );
}
else
{
DistinctSubInner(whole_seq + , min_seq, sub_seq_len + );
} DistinctSubInner(whole_seq + , sub_seq_len);
}
 

3.2 如何解决连续相同元素造成的重复解?

以[1, 1]例,它的子序列为:
[1, 1]
[1]    只输出第一个元素
[1]    只输出第二个元素
 
解决此问题的一个简单的做法是,如果序列中有连续相同的元素,则在第一个元素输出的情况下,忽略第二个元素不输出的解。
 
void DistinctSubInner(char* whole_seq, int sub_seq_len)
{
if (!*whole_seq)
{
if(!*min_seq)
{
PrintDistinctSub(sub_seq_len);
}
return;
} sub_seq[sub_seq_len] = *whole_seq; if (*whole_seq == *min_seq)
{
DistinctSubInner(whole_seq + , min_seq + , sub_seq_len + );
}
else
{
DistinctSubInner(whole_seq + , min_seq, sub_seq_len + );
} if(sub_seq[sub_seq_len] != sub_seq[sub_seq_len - ])
{
DistinctSubInner(whole_seq + , min_seq, sub_seq_len);
}
}
 
需要注意的是,不能在S上作这个判断,因为形如[1,2,1]的序列,虽然两个1不连续,但在2不输出的情况下,同样会形成重复解。所以只能在生成的结果序列上作处理。
 
事实上,S中可能不只是包含两个相同的元素,还可能包含两个相同的子序列,例如[1,2,1,2],上面的逻辑似乎并不能防止形成重复的[1,2]解。但是,很有趣的是,上面代码就是不会导致形成重复的子序列解。为什么?请跟我一样看下测试结果的输出,大概就能看出原委了,这个已经不知道怎么表达了...
 
所以,暂时的解就是这样。补上测试代码:
 
int main(int argc, _TCHAR* argv[])
{
char* whole_seqs[] = {
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",}; for (int i = ; i < sizeof(whole_seqs) / sizeof(char*); ++i)
{
printf("\r\n\r\ndistinct sub sequence of \"%s\" for \"%s\" : ===============", whole_seqs[i], "");
DistinctSub(whole_seqs[i], "");
} getchar();
}

后话

 
作为一个算法的话,这显然不能说over了。
首先,算法的正确性,应该做作大量的测试验证。譬如,还有没有没有想到的重复解的情况?对于这个算法,我并没有十分的把握,还需要验证来发现问题并确认正确性。
其次,在去重这一块,我觉得我的思路似乎并不直观,以及并没有和数学里的问题应对起来。而现实中很多算法其实都能转换为数学问题。所以我怀疑有更简洁和明了的思路。
上午百度了一下,网上有一堆人问这个,想必应该是别的思路的,不过我还是先把我的思路记下来。再去学习。
 
顺便再吐槽一下,博客园的编辑器依然烂成翔...等宽代码字体都没有,代码缩进又被吃了。有个高亮的功能聊胜于无吧...本地纯文本或markdown但是consolas+雅黑字体好太多了,线上的就只能将就看下。
 

算法题:求一个序列S中所有包含T的子序列(distinct sub sequence)的更多相关文章

  1. 动态规划求一个序列的最长回文子序列(Longest Palindromic Substring )

    1.问题描述 给定一个字符串(序列),求该序列的最长的回文子序列. 2.分析 需要理解的几个概念: ---回文 ---子序列 ---子串 http://www.cnblogs.com/LCCRNblo ...

  2. Expm 10_2 实现Ford-Fulkerson算法,求出给定图中从源点s到汇点t的最大流,并输出最小割。

    package org.xiu68.exp.exp10; import java.util.ArrayDeque; import java.util.ArrayList; import java.ut ...

  3. 求一个集合S中m个元素的所有排列以及一个数组A的全排列—递归实现版完整代码

    说明,本文全文代码均用dart语言实现. 求一个集合S中m个元素的所有排列情况,并打印,非常适合用递归的思路实现.本文给出了两种实现方法,一种是给定的填充排列数组长度是固定的,一种是可变长度的.两种方 ...

  4. Excel-判断一个文本字符串中是否包含数字! 判断一个文本字符串是否是纯汉字!

    0.判断一个文本字符串中是否包含数字!/判断一个文本字符串是否是纯汉字! 公式=IF(LENB(A1)=2*LEN(A1),"都是汉字","含有非汉字字符") ...

  5. 从一道算法题实现一个文本diff小工具

    众所周知,很多社区都是有内容审核机制的,除了第一次发布,后续的修改也需要审核,最粗暴的方式当然是从头再看一遍,但是编辑肯定想弄死你,显然这样效率比较低,比如就改了一个错别字,再看几遍可能也看不出来,所 ...

  6. [java大数据面试] 2018年4月百度面试经过+三面算法题:给定一个数组,求和为定值的所有组合.

    给定一个数组,求和为定值的所有组合, 这道算法题在leetcode应该算是中等偏下难度, 对三到五年工作经验主要做业务开发的同学来说, 一般较难的也就是这种程度了. 简述经过: 不算hr面,总计四面, ...

  7. hdu 1394 求一个序列的最小逆序数 单点增 区间求和

    题目的意思就好比给出一个序列 如:0 3 4 1 2 设逆序数初始n = 0: 由于0后面没有比它小的,n = 0 3后面有1,2 n = 2 4后面有1,2,n = 2+2 = 4: 所以该序列逆序 ...

  8. 设计一个算法,求非空二叉树中指定的第k层(k&gt;1)的叶子节点的个数

    思想:採用基于层序遍历的方法. 用level扫描各层节点,若某一层的节点出队后.rear指向该层中最右节点.则将rear赋值给last(对于第一层.last=1).在出队时,若front=last,表 ...

  9. 一道算法题加深我对C++中map函数的理解

    一.一道题目引发我对map函数的考量 首先是题目大意:有n个银行,a[i]表示这个人在第i个银行有a[i]块钱(可以是负数),所有银行的钱加起来正好是0.每次只能在相邻的银行之间转账,问最少要转多少次 ...

随机推荐

  1. Spark安装和简单示例

    spark的安装 先到官网下载安装包 注意第二项要选择和自己hadoop版本相匹配的spark版本,然后在第4项点击下载.若无图形界面,可用windows系统下载完成后传送到centos中. 本例中安 ...

  2. Appium 测试APK

    介绍 Appium是一个开源.跨平台的测试框架,可以用来测试原生及混合的移动端应用.Appium支持iOS.Android及FirefoxOS平台测试.Appium使用WebDriver的json w ...

  3. [Scala]Scala学习笔记六 文件

    1. 读取行 读取文件,可以使用scala.io.Source对象的fromFile方法.如果读取所有行可以使用getLines方法: val source = Source.fromFile(&qu ...

  4. Windows7下PHP 7.1搭建开发环境

    引言: PHP天生就是用来解决互联网时代的Web语言问题的专业工具,本文将记录在windows上搭建PHP的过程以及其中碰到的问题. 配置版本信息 OS: Windows 7 PHP: 7.1.7-n ...

  5. 如何实现PLC与THINGWORX工业物联网平台对接

    物联网(Internet of Things),简称 IoT,对于制造商来说,是行业乃至世界范围内的一股变革浪潮.在我们设计和制造的产品中,将会不断嵌入各种软件.传感器和启用 IP 的连接功能.IDC ...

  6. Android深入理解JNI(二)类型转换、方法签名和JNIEnv

    相关文章 Android深入理解JNI系列 前言 上一篇文章介绍了JNI的基本原理和注册,这一篇接着带领大家来学习JNI的数据类型转换.方法签名和JNIEnv. 1.数据类型的转换 首先给出上一篇文章 ...

  7. Scrapy爬虫库使用初体验

    安装pip install Scrapy 中间可能会遇到的问题: 超时,网络问题需要多次尝试 缺少vc++库,官网可以下载 win32api缺失,https://sourceforge.net/pro ...

  8. matplotlib 数据可视化

    图的基本结构 通常,使用 numpy 组织数据, 使用 matplotlib API 进行数据图像绘制. 一幅数据图基本上包括如下结构: Data: 数据区,包括数据点.描绘形状 Axis: 坐标轴, ...

  9. linux系统编程-进程

    进程 现实生活中 在很多的场景中的事情都是同时进行的,比如开车的时候 手和脚共同来驾驶汽车,再比如唱歌跳舞也是同时进行的: 如下是一段视频,迈克杰克逊的一段视频: http://v.youku.com ...

  10. linux下升级svn版本到1.8

    CentOS6.5默认yum安装的svn版本为1.6,有时候遇到比较高级的应用就可能不够使用,这时候就需要升级一下svn的版本,可以升级到的版本为1.8 ====== 完美的分割线 ====== 1. ...