首页 > 无序数组及其子序列的相关问题研究

无序数组及其子序列的相关问题研究

算法中以数组为研究对象的问题是非常常见的. 除了排序大家经常会遇到之外, 数组的子序列问题也是其中的一大分类. 今天我就对自己经常遇到的无序数组的子序列相关问题在这里总结一下.

 

前置条件: 给定无序数组. 以下所以的问题均以此为前置条件. 例如无序数组nums = [2, 1, 3].

 

问题1:

求子序列的个数. 例如前置无序数组的子序列个数为8, 分别为[], [2], [2, 1], [1], [2, 1, 3], [1, 3], [2, 3], [3].

分析

这个问题应该非常好理解. 对于它的子序列sub而言, 数组元素nums[i], 它要么存在于sub中, 要么不存在于sub中, 只有这两种可能. 所以, 元素nums[i]能构建的子序列个数为count(nums[i]) = 2. 因此, 对于数组nums的所有子序列的个数就为count(nums[0]) * count(nums[1]) * ... * count(nums[n-1]) = 2 * 2 * ... * 2 = 2^n(2的n次幂). 

所以, 对于长度为n的无序数组, 它的所有子序列的个数为 2^n.

 

问题2:

获取nums的所有子序列. 例如前置无序数组的所有子序列为[], [2], [2, 1], [1], [2, 1, 3], [1, 3], [2, 3], [3].

分析:

首先, 我们按照问题1的思路, "对于它的子序列sub而言, 数组元素nums[i], 它要么存在于sub中, 要么不存在于sub中, 只有这两种可能", 我们可以创建一个只包含了0和1的长度为n的数组, 则这个二进制数组可以表示的10进制数字的范围恰好为[0, 2^n - 1]. 这个思路可以用下面这个表格表示如下(利用前置条件中给定的数组nums):

binary ums213subsequences
0000[]
1001[3]
2010[1]
3011[1, 3]
...............



所以, 我们可以遍历一下范围[0, 2^n - 1], 然后将10进制变量k转化为二进制数组, 然后利用二进制数组中1的个数和位置, 构建子序列.

这里提供一下上述思路的Java代码如下: 

 1 public List> allSubsequences(int[] nums){
 2     if (nums == null){
 3         return null;
 4     }
 5     int max = Math.pow(2, nums.length);
 6     List> result = new ArrayList<>();
 7     for (int k = 0; k < max; k++){
 8         List sub = new ArrayList<>();
 9         String binary = Integer.toBinaryString(k);
10         for(int i = binary.length() - 1; i >= 0; i--){
11             if (binary.charAt(i) == '1'){
12                 sub.add(nums[nums.length - i - 1]);
13             }
14         }
15         result.add(sub);
16     }
17     return result;
18 }

 

与此同时, 我们也可以利用动态规划的思想来解决这个问题.

对于元素nums[i], 我们将它的所有子序列表示为subs(i). 那么对于nums[i-1]和nums[i], 则存在这些一个关系:

subs(i) = [nums[i], (sub + nums[i])], 其中满足限制条件 sub in subs(i - 1). 

上述表达式想要表达的意思是: subs(i)生成的所有子序列, 由nums[i]和subs(i - 1)生成的所有子序列末尾加上nums[i]组成.

由此, 依据这种思路, 即利用动态规划思想生成所有子序列, 可以利用如下java代码实现:

 1 public List> allSubsequences(int[] nums){
 2     if (nums == null){
 3         return null;
 4     }
 5     List> result = new ArrayList<>();
 6     result.add(new ArrayList<>());//add empty subsequence
 7     for(int num : nums){
 8         List> tmp = new ArrayList<>(result);
 9         for(List list : tmp){
10             list.add(num);
11         }
12         result.addAll(tmp);
13     }
14     return result;
15 }

 

问题3:

求所有递增子序列的个数. 例如前置无序数组的递增子序列个数为5, 分别为[2], [1], [1, 3], [2, 3], [3].

分析

我们假设count(i)表示以nums[i]结尾的递增子序列个数. 则对于前置无序数组nums而言, 它的所有递增子序列的个数, 就是以nums[i]结尾的递增子序列的和, 表示为sum(count(i))(0 < i < n - 1).

理解了以上假设, 那么我们可以发现以下规律: 

count(0) = 1;

count(1) = 1; (nums[0] > nums[1])

count(2) = 1 + count(0) + count(1) (nums[0] < nums[2], nums[1] < nums[2])

             = 1 + 1 + 1 = 3;

所以, nums的所有递增子序列个数就是count = count(0) + count(1) + count(2) = 1 + 1 + 3 = 5;

因而, 我的思路是: 建立一个数组counts = new int[nums.length + 1]用于保存以nums[i]结尾的递增子序列的个数, 即counts[i] = count(i). 而counts[nums.length]则用于累积counts[i], 表示整个序列的递增子序列的个数.

上述思路的Java实现如下: 

 1 public int countIncreasingSubsequences(int[] nums){
 2     if (nums == null){
 3         return -1;
 4     }
 5     int[] counts = new int[nums.length + 1];
 6     for(int i = 0; i < nums.length; i++){
 7         counts[i] = 1;//单元素递增序列计数
 8         for(int j = 0; j < i; j++){
 9             if(nums[i] > nums[j]){
10                 counts[i] += counts[j];
11             }
12         }
13         counts[nums.length] += counts[i];
14     }
15     return counts[nums.length];
16 }

 

问题4:

求它的所有递增子序列. 例如前置无序数组的所有递增子序列为[2], [1], [1, 3], [2, 3], [3].

分析:

需要利用动态规划思想来分析这道题目. 其实, 大体思路跟求它的所有子序列相同, 只不过是本问题要求子序列是递增的, 所以, 就利用递增来修改求所有子序列的逻辑.

因而, 对于元素nums[i], 我们将它的所有子序列表示为subs(i). 那么对于nums[i-1]和nums[i], 则存在这些一个关系:

subs(i) = [nums[i], (sub + nums[i])], 其中满足限制条件 sub in subs(i - 1) & sub.lastElement < nums[i]. 

上述表达式想要表达的意思是: subs(i)生成的所有递增子序列, 由nums[i]和subs(i - 1)生成的所有末尾元素小于nums[i]的递增子序列末尾加上nums[i]组成.

由此, 依据这种思路, 即利用动态规划思想生成所有递增子序列, 可以利用如下java代码实现:

 1 public List> allIncreasingSubsequences(int[] nums){
 2     if (nums == null){
 3         return null;
 4     }
 5     List> result = new ArrayList<>();
 6     for(int num : nums){
 7         List> tmp = new ArrayList<>();
 8         List firstList = new ArrayList<>();
 9         firstList.add(num);
10         tmp.add(firstList);
11         for(List list : result){
12             if(list.get(list.size() - 1) < num){
13                 List newList = new ArrayList<>(list);
14                 newList.add(num);
15                 tmp.add(newList);
16             }
17         }
18     }
19     return result;
20 }

 

最后, 关于递减子序列的问题, 原理跟递增子序列问题是一样的, 这里就不再赘述, 有兴趣的同学, 可以自己手动写一下.

转载于:https://www.cnblogs.com/littlepanpc/p/7954896.html

更多相关:

  • 题目:调整数组顺序使奇数位于偶数前面 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。 示例: 输入:nums = [1,2,3,4] 输出:[1,3,2,4] 注:[3,1,2,4] 也是正确的答案之一。 提示: 1 <= nums.length <=...

  • 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。 你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。 示例: 给定 nums = [2, 7, 11, 15], target = 9 因为 nums[0] + nums[1]...

  • 题目描述: 给定一个整数数组 nums ,找出一个序列中乘积最大的连续子序列(该序列至少包含一个数)。 示例 1: 输入: [2,3,-2,4] 输出: 6 解释: 子数组 [2,3] 有最大乘积 6。 示例 2: 输入: [-2,0,-1] 输出: 0 解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。 该题目...

  • 题目描述如下: 给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 示例: 输入: [-2,1,-3,4,-1,2,1,-5,4], 输出: 6 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。 方法一(暴力求解): 针对数组中的每一个元素,穷举所有的可能性,...

  • WinForm绘制带有升序、降序的柱形图   private void HuiZhiTu( string strPaiXu){//初始数据int[] nums = { 150, 89, 200, 60, 70, 90 };if (strPaiXu == "升序"){//冒泡排序for (int i = 0; i <...

  • 题目:栈的压入、弹出序列 输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。 示例 1:...

  • 简单版 @for $i from 1 through 6 {&:nth-of-type(#{$i}) img {transition-delay: calc(0.1 *#{$i}s);//逐次延时效果} } 进阶版 @for $i from 1 through length($数组) {$color: nth($数组, $i);...

  • Gold Code Gold Code是以Robert Gold的名字命名的。它是一组特殊的二进制随机(伪随机)序列,其中成员序列之间的相关性很小。由于这种特性(较小的相关性),它被广泛地用作各种无线通信系统的扰码。 我们可以非常简单地利用 m序列 来生成Gold Code: 选择两个m序列,且这两个序列的移位寄存器的数量相同,然...

  • 翻译自:sharetechnote: LFSR LFSR Linear Feedback Shift Register - 线性反馈移位寄存器 LFSR 是一种移位寄存器电路,其中两个或多个中间步骤的输出线性组合并反馈到输入值,这就是为什么它被称为线性反馈移位寄存器的原因。 该电路具有以下特点: 如果初始状态相同,则最终会得到...

  • 如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。第一个差(如果存在的话)可能是正数或负数。少于两个元素的序列也是摆动序列。 例如, [1,7,4,9,2,5] 是一个摆动序列,因为差值 (6,-3,5,-7,3) 是正负交替出现的。相反, [1,4,7,2,5] 和 [1,7,4,5,5] 不是摆动序列,第一...

  • list_for_each_safeBidirect-list list_for_each_safe().https://biscuitos.github.io/blog/LIST_list_for_each_safe/...

  • /*hanzhiguang coded at 2009.07.30 1:20*/ // nesting_map.cpp : Defines the entry point for the console application. // /*-----------------------------------------------...

  • 已知k个已排序链表头节点指针,将这k个链表合并,合并后仍为有序的 ,返回合并后的头节点 如下三个链表: 合并后的结果如下: 方法一(STL sort算法进行排序): 先将输入的排序链表插入一个迭代器中,vector数组中国呢直接对数组中的链表节点进行按值排序即可 实现算法如下,最终实现源码见文末: bool cmp(Dat...

  • 本题要求实现一个函数,找到并返回链式表的第K个元素。 函数接口定义: ElementType FindKth( List L, int K ); 其中List结构定义如下: typedef struct LNode *PtrToLNode; struct LNode {ElementType Data;PtrToLNode Ne...

  • 一、前述 企业中linux搭建ftp服务器还是很实用的,所以本文针对centoos7和centoos6搭建服务器教程做个总结。 二、具体 1、显示如下图则表示已安装 vsftp软件。如果未显示则需要安装vsftpd软件。 如果没有则通过yarm源进行安装 yum install -y vsftpd 2、安装完成之后 进入到ftp的根...