算法10 最长等差序列问题

题目:

  给定一个大小为n的数组,要求写出一个算法,求其最长的等差数列的子序列

分析:

  该题需要分几种情况考虑。

1. 原数组是有序的,所要求的的子序列可以不连续。

  对于数组arr[],不同的等差值d=1,2,3,4,5```(arr[max]-arr[min])可以求出不同的最长等差数列,然后在这些等差数列中求出最长的那个即可我们首先转化为求一个数组的固定等差值的最长等差子序列。如数组1,2,4,6,8,9,求等差值2的最长等差子序列为2,4,6,8

 1.1 固定等差值的最长子序列

  求符合条件的最长子序列可以用动态规划来做,设dis[i]记录数组arr[]的第1-i个元素子数组的最长等差子序列长度,状态转移方程式:

  dis[i]=1;

  dis[i]=dis[k]+1, k是数组第1-i个元素中,与元素i等差为d并且距离i最近的元素(k<i);

  代码

 1 int RegularArithmeticSeq(int arr[],int len,int d)
 2 {
 3     if (arr==NULL||len<1)
 4         throw std::exception("Invalid input.");
 5
 6     int* dis=new int[len+1]();
 7
 8     int re_len=0,re_index=0;
 9     for (int i=1;i<=len;i++)
10     {
11         dis[i]=1;
12         for (int k=i;k>0;k--)
13         {
14             int cur_d=arr[i-1]-arr[k-1];
15
16             if (cur_d==d&&dis[i]<=dis[k])
17             {
18                 dis[i]=dis[k]+1;
19             }
20
21             if (re_len<=dis[i])
22             {
23                 re_len=dis[i];
24                 re_index=i;
25             }
26
27             if (cur_d>d)
28                 break;
29         }
30     }
31
32     int out=arr[re_index-1];
33     for (int n=0;n<re_len;n++)
34     {
35         cout<<out<<‘ ‘;
36         out=out-d;
37     }
38     cout<<endl;
39
40     delete[] dis;
41
42     return re_len;
43 }

  上述代码的时间复杂度为O(n*n),时间复杂度为O(n);

  对于寻找1-i中最后一个和i等差的元素k,除了顺序遍历,还可以用二分查找法进行一点优化,查找的时间复杂度为O(logn)

 1 int find_k(int arr[],int n,int d)
 2 {
 3     int small=0,big=n-1,k=0,sum=0;
 4     while (small<=big)
 5     {
 6         k=small+(big-small)/2;
 7         sum=arr[k]+d;
 8         if (sum<arr[n-1])
 9         {
10             small=k+1;
11         }else if(sum>arr[n-1])
12         {
13             big=k-1;
14         }else
15         {
16             if (k<=big&&(arr[k+1]+d)!=sum)
17             {
18                 return k;
19             }else
20             {
21                 small=k+1;
22             }
23         }
24     }
25
26     return n-1;
27 }

 优化代码:

 1 int RegularArithmeticSeq2(int arr[],int len,int d,int& re_idx)
 2 {
 3     if (arr==NULL||len<1)
 4         throw std::exception("Invalid input.");
 5
 6     int* dis=new int[len]();
 7
 8     int* dif=new int[len]();
 9     for (int i=0;i<len;i++)
10     {
11         dif[i]=find_k(arr,i+1,d);
12     }
13
14     int re_len=0,re_index=0;
15     for (int i=0;i<len;i++)
16     {
17         dis[i]=1;
18         if (dif[i]!=i)
19         {
20             dis[i]=dis[dif[i]]+1;
21         }
22         if (re_len<=dis[i])
23         {
24             re_len=dis[i];
25             re_index=i;
26         }
27     }
28
29     int out=arr[re_index];
30     re_idx=re_index+1;
31     for (int n=0;n<re_len;n++)
32     {
33         cout<<out<<‘ ‘;
34         out=out-d;
35     }
36     cout<<endl;
37
38     delete[] dis;
39     delete[] dif;
40
41     return re_len;
42 }

  上述过程中时间复杂度降为O(n*logn)

 1.2 非固定等差值的最长子序列  

  在解决了固定等差值得最长子序列后,就可以着手求非固定等差值的最长子序列了。等差值d的范围是1,2···(arr[max]-arr[min]),循环着m个等差值,然后求出最长的等差子序列即可

  时间复杂度(n*logn*m),m<=n-1;空间复杂度O(n*m)

  代码 

 1 int ArithmeticSeq(int arr[],int len)
 2 {
 3     if (arr==NULL||len<1)
 4         throw std::exception("Invalid input.");
 5
 6     int max_d=arr[len-1]-arr[0];
 7     int re_len=0,re_d=0,re_index=0;
 8
 9     for (int d=1;d<=max_d;d++)
10     {
11         int idx=0;
12         int dlen=RegularArithmeticSeq2(arr,len,d,idx);
13         if(re_len<=dlen)
14         {
15             re_len=dlen;
16             re_d=d;
17             re_index=idx;
18         }
19     }
20
21     int out=arr[re_index-1];
22     for (int n=0;n<re_len;n++)
23     {
24         cout<<out<<‘ ‘;
25         out=out-re_d;
26     }
27     cout<<endl;
28
29     return re_len;
30 }

  也有另外一种解法:设dis[i][d]为数组中第1到i个元素中等差值为d的最长子序列的长度(序列可以不连续),则动态规划式;

  dis[i][d]=1,默认等1,即在1-i个元素中等差值为d的最长子序列长度至少是1(i元素本身)

  dis[i][d]=dis[k][d]+1, k为1-i个元素中最后一个与i元素的等差d的元素

  代码

 1 int ArithmeticSeq(int arr[],int len)
 2 {
 3     if (arr==NULL||len<1)
 4         throw std::exception("Invalid input.");
 5
 6     int max_d=arr[len-1]-arr[0];
 7     int** dis=new int*[len+1];
 8     for (int i=0;i<=len;i++)
 9     {
10         dis[i]=new int[max_d+1]();
11         dis[i][0]=1;
12     }
13
14     int re_len=0,re_d=0,re_index=0;
15     for (int i=1;i<=len;i++)
16     {
17         for (int d=1;d<=max_d;d++)
18         {
19             dis[i][d]=1;
20             for (int k=1;k<i;k++)
21             {
22                 int cur_d=arr[i-1]-arr[k-1];
23                 if (cur_d==d&&dis[i][d]<=dis[k][d])
24                 {
25                     dis[i][d]=dis[k][d]+1;
26                 }
27
28                 if (re_len<=dis[i][d])
29                 {
30                     re_len=dis[i][d];
31                     re_d=d;
32                     re_index=i;
33                 }
34             }
35
36         }
37     }
38
39     int out=arr[re_index-1];
40     for (int n=0;n<re_len;n++)
41     {
42         cout<<out<<‘ ‘;
43         out=out-re_d;
44     }
45     cout<<endl;
46
47     for (int i=0;i<=len;i++)
48     {
49         delete[] dis[i];
50     }
51     delete[] dis;
52
53     return re_len;
54 }

2. 原数组是无序的,所要求的的子序列可以不连续。

  网上有不用排序的解法,但笔者觉得先排序还是更好些,也容易理解。当然,解此题的关键是找到在循环i和d的过程中找到k,对于非排序数组用顺序查找k,也可以。

  代码略

3. 原数组是无序的,所要求的的子序列连续。

 1 int ArithmeticSeq3(int arr[],int len)
 2 {
 3     if (arr==NULL||len<1)
 4         throw std::exception("Invalid input.");
 5
 6     int re_len=1,d=1;
 7     int max_len=0,re_d=0,re_index=0;
 8     for (int i=1;i<len;i++)
 9     {
10         if((arr[i]-arr[i-1])==d)
11         {
12             re_len=re_len+1;
13         }else
14         {
15             re_len=2;
16         }
17         d=arr[i]-arr[i-1];
18         if (max_len<=re_len)
19         {
20             max_len=re_len;
21             re_d=d;
22             re_index=i;
23         }
24     }
25
26     int out=arr[re_index];
27     for (int n=0;n<max_len;n++)
28     {
29         cout<<out<<‘ ‘;
30         out=out-re_d;
31     }
32     cout<<endl;
33
34     return re_len;
35 }
时间: 2024-10-16 14:31:01

算法10 最长等差序列问题的相关文章

算法提高 最长字符序列

最长字符序列 问题描述 设x(i), y(i), z(i)表示单个字符,则X={x(1)x(2)--x(m)},Y={y(1)y(2)--y(n)},Z={z(1)z(2)--z(k)},我们称其为字符序列,其中m,n和k分别是字符序列X,Y,Z的长度,括号()中的数字被称作字符序列的下标. 如果存在一个严格递增而且长度大于0的下标序列{i1,i2--ik},使得对所有的j=1,2,--k,有x(ij)=z(j),那么我们称Z是X的字符子序列.而且,如果Z既是X的字符子序列又是Y的字符子序列,那

数据结构与算法学习之路:LIS——最长递增序列的动态规划算法和二分思想算法

一.最长递增序列的问题描述: 求一个整数序列的最长递增子序列,子序列不要求是连续的.例如: Input:4,6,9,6,7,6,3,8,10:Output:5 二.解决方法: 1.用动态规划的方法解决.从问题我们可以知道,我们最终得到的最长递增子序列,其任意一段子序列也是对应序列中的最长子序列.这样说可能不好理解,就以上面的例子来说: 最长子序列为:4,6, 7, 8, 10.在这段子序列的子序列里选一个,例如:4,6,7.则4,6,7也是4,6,9,6,7,6,3这段序列的最长子序列. 对于动

华为机试测试- 求有序数组中最长的等差序列

原题目是求一个无序数组中最长的等差数列. 求随机数构成的数组中找到长度大于=3 的最长的等差数列, 输出等差数列由小到大:如果没有符合条件的就输出格式:输入[1,3,0,5,-1,6]输出[-1,1,3,5] 思路: 1.排序 2.d的取值范围是[0,max-min],共有max-min+1种情况 3.对每一种情况进行查找 4.对于公差d,要求最长的等差序列,需要两个循环,一个外循环从头到尾遍历,内循环从外循环的起始遍历点开始利用公式 arr[i]=arr[start]+len*d,假如符合就长

LeetCode 128. 最长连续序列(Longest Consecutive Sequence)

题目描述 给定一个未排序的整数数组,找出最长连续序列的长度. 要求算法的时间复杂度为 O(n). 示例: 输入: [100, 4, 200, 1, 3, 2] 输出: 4 解释: 最长连续序列是 [1, 2, 3, 4].它的长度为 4. 解题思路 利用并查集的思想,构造一个map记录数组中以每个数所在的最长连续序列长度.每次遍历到一个数时,首先检查map中是否存在该数,若存在直接跳过,否则作如下更新操作: 找到左右相邻数字是否在map中,若存在则分别记录他们所在的最长连续序列长度,并更新当前的

LeetCode--128--最长连续序列(python)

给定一个未排序的整数数组,找出最长连续序列的长度. 要求算法的时间复杂度为 O(n). 示例: 输入: [100, 4, 200, 1, 3, 2]输出: 4解释: 最长连续序列是 [1, 2, 3, 4].它的长度为 4. 暴力超时... class Solution: def longestConsecutive(self, nums: List[int]) -> int: longestSequence = 0 for num in nums: curNum = num streak =

zoj1986 Bridging Signals (dp,最长递增序列,LIS)

A - Bridging Signals Time Limit:2000MS Memory Limit:65536KB 64bit IO Format:%lld & %llu Submit Status Practice ZOJ 1986 Description 'Oh no, they've done it again', cries the chief designer at the Waferland chip factory. Once more the routing designer

(算法)最长递增子序列

问题: Given an array of N integer, find the length of the longest increasing subsequence. For example, given [1,-5,4,5,10,-1,-5,7], the longest increasing subsequence is length 4.(1,4,510) 思路: 1.枚举 枚举数组所有的子序列,然后判断它们是否为递增子序列(回溯法). 2.转化 将数组排序,然后找出新数组和旧数组

uva103(最长递增序列,dag上的最长路)

题目的意思是给定k个盒子,每个盒子的维度有n dimension 问最多有多少个盒子能够依次嵌套 但是这个嵌套的规则有点特殊,两个盒子,D = (d1,d2,...dn) ,E = (e1,e2...en) 只要盒子D的任意全排列,小于盒子E,那么就说明 盒子D能放入盒子E中,其实就是将两个盒子的维度排序,如果前一个盒子的维度依次小于后一个盒子,那么就说明前一个盒子能放入后一个盒子中 这个题目能够转化为最长递增子序列. 首先将盒子的维度从小到大排序,然后将k个盒子,按照排序后的第一维度从小到大排

manacher算法处理最长的回文子串(二)

在上篇<manacher算法处理最长的回文子串(一)>解释了manacher算法的原理,接着给该算法,该程序在leetcode的最长回文子串中通过.首先manacher算法维护3个变量.一个名为radius[i]的数组,表示以i为中心轴承的回文子串的半径,如abcdcba中,字符d的下标为4,则他的radius[4]=3,下标的为0的a的半径为radius[0]=0,即中心轴不考虑其中.一个idx表示上一次以idx为中心轴的回文.如果当以i为中心的回文在以idx为中心的回文内.则idx不更新,