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

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

 

前置条件: 给定无序数组. 以下所以的问题均以此为前置条件. 例如无序数组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\nums213subsequences
0000[]
1001[3]
2010[1]
3011[1, 3]
...............


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

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

 1 public List<List<Integer>> allSubsequences(int[] nums){
 2     if (nums == null){
 3         return null;
 4     }
 5     int max = Math.pow(2, nums.length);
 6     List<List<Integer>> result = new ArrayList<>();
 7     for (int k = 0; k < max; k++){
 8         List<Integer> 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<List<Integer>> allSubsequences(int[] nums){
 2     if (nums == null){
 3         return null;
 4     }
 5     List<List<Integer>> result = new ArrayList<>();
 6     result.add(new ArrayList<>());//add empty subsequence
 7     for(int num : nums){
 8         List<List<Integer>> tmp = new ArrayList<>(result);
 9         for(List<Integer> 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<List<Integer>> allIncreasingSubsequences(int[] nums){
 2     if (nums == null){
 3         return null;
 4     }
 5     List<List<Integer>> result = new ArrayList<>();
 6     for(int num : nums){
 7         List<List<Integer>> tmp = new ArrayList<>();
 8         List<Integer> firstList = new ArrayList<>();
 9         firstList.add(num);
10         tmp.add(firstList);
11         for(List<Integer> list : result){
12             if(list.get(list.size() - 1) < num){
13                 List<Integer> 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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值