算法:最大升序子序列长度

刚开始写,如有错误还望各位看官指出。若有描述不清,也请诸位提点。

导读

通过动态规划使用C语言实现求数组中最大升序(或降序)子序列长度问题。

原题

原题链接

Longest Increasing Subsequence

Given an unsorted array of integers, find the length of longest increasing subsequence.

For example,

Given[10, 9, 2, 5, 3, 7, 101, 18],

The longest increasing subsequence is[2, 3, 7, 101], therefore the length is4. Note that there may be more than one LIS combination, it is only necessary for you to return the length.

Your algorithm should run in O(n2) complexity.

Follow up:Could you improve it to O(nlogn) time complexity?

Credits:

Special thanks to@pbrotherfor adding this problem and creating all test cases.

Subscribeto see which companies asked this question.

翻译:

给出一个无序的整形数组,找出它的最大升序子序列。
举个栗子,
示例数组 arr[] = {10, 9, 2, 5, 3, 7, 101, 18},
数组arr的最长升序子序列是 {2, 3, 7, 101},因此长度是4。
请注意,可能有一个以上的LIS(最长上升子序列)的组合,只需要返回长度就好。
时间复杂度O(n²)前提下实现。
进阶:时间复杂度O(nlogn)前提下实现。
以下不译。

正文

以下为两种时间复杂度的C语言解法,已将log相关代码注释掉了,并粘上分步log,并简要概述思路,共同学习进步。

O(n²)

思路:
定义一个数组lis,记录以目标序列nums[0]开头,以每个元素结尾的子串的最大升序子串长度。
取出lis的最大值即为结果。
重要判断是代码中的 if (nums[i] > nums[j] && lis[j] + 1 > lis[i])。

// 此为O(n2)方法
int lengthOfLIS_01(int* nums, int numsSize) {
    if (numsSize < 2) {
        return numsSize;
    }
    int lis[numsSize];
//    printf("OriginalArray:");
//    printArray(nums, numsSize);
//    // 此for循环只是为了log美观 无意义
//    for (int i =0; i < numsSize; i++) {
//        lis[i] = 0;
//    }
    int i, j, result = 0;
    for (i = 0; i < numsSize; i++) {
        lis[i] = 1;
        for (j = 0; j < i; j++) {
            if (nums[i] > nums[j] && lis[j] + 1 > lis[i]) {
                lis[i] = lis[j] + 1;
            }
        }
        result = result > lis[i] ? result : lis[i];
//        printArray(lis, numsSize);
    }
    return result;
}

O(n²) lengthOfLIS_01 的 Log(为了便于观察理解 打印时隐去了值为0的元素)
此处打印的是记录以每个元素结尾的子串中最大升序子串长度的数组。

OriginalArray:10, 9, 2, 5, 3, 7,80,18, 1, 2, 3, 4, 5, 6, 7, 8, 9,
 1,
 1, 1,
 1, 1, 1,
 1, 1, 1, 2,
 1, 1, 1, 2, 2,
 1, 1, 1, 2, 2, 3,
 1, 1, 1, 2, 2, 3, 4,
 1, 1, 1, 2, 2, 3, 4, 4,
 1, 1, 1, 2, 2, 3, 4, 4, 1,
 1, 1, 1, 2, 2, 3, 4, 4, 1, 2,
 1, 1, 1, 2, 2, 3, 4, 4, 1, 2, 3,
 1, 1, 1, 2, 2, 3, 4, 4, 1, 2, 3, 4,
 1, 1, 1, 2, 2, 3, 4, 4, 1, 2, 3, 4, 5,
 1, 1, 1, 2, 2, 3, 4, 4, 1, 2, 3, 4, 5, 6,
 1, 1, 1, 2, 2, 3, 4, 4, 1, 2, 3, 4, 5, 6, 7,
 1, 1, 1, 2, 2, 3, 4, 4, 1, 2, 3, 4, 5, 6, 7, 8,
 1, 1, 1, 2, 2, 3, 4, 4, 1, 2, 3, 4, 5, 6, 7, 8, 9,
result = 9

O(nlogn)

思路:
定义数组tmp记录当前最大升序子串(∴tmp必然升序),用tmp的末位元素对比目标序列nums[1]开始的每个元素,若当前nums元素 > tmp末位元素,当前nums元素插入tmp。否则用当前nums元素替换tmp中合理位置元素即可。

// 折半(二分)查找
int binarySearch(int *array, int lenght, int key) {
    int low = 0;
    int high = lenght - 1;
    int middle;
    while (low <= high) {
        middle = low + (high - low) / 2;
        if (array[middle] > key) {
            high = middle - 1;
        } else if (array[middle] < key) {
            low = middle + 1;
        } else {
            return middle;
        }
    }
    return low;
}

// 此为O(nlog(n))方法
int lengthOfLIS_02(int* nums, int numsSize) {
    // 长度若为0或1,无须比较直接返回
    if (numsSize < 2) {
        return numsSize;
    }
    // 声明临时数组,赋值首元素,当前lenght = 1
    int tmp[numsSize], position;
//    printf("OriginalArray:");
//    printArray(nums, numsSize);
//    // 此for循环只是为了log美观 无意义
//    for (int i =0; i < numsSize; i++) {
//        tmp[i] = 0;
//    }
    tmp[0] = nums[0];
    int lenght = 1;
    // 遍历nums[1]~nums[numsSize-1]
    for (int i = 1; i < numsSize; i++) {
        // 若当前nums元素 > tmp最大元素,插入tmp,length++
        if (nums[i] > tmp[lenght - 1]) {
            tmp[lenght] = nums[i];
            lenght++;
//            printf("Part-A i=%2d len=%2d Arr:", i, lenght);
        } else {
            // 二分定位 用当前nums元素 替换 tmp中合理位置元素
            position = binarySearch(tmp, lenght, nums[i]);
            tmp[position] = nums[i];
//            printf("Part-B i=%2d pos=%2d Arr:", i, position);
        }
//        printArray(tmp, numsSize);
    }
//    printf("Finished\n");
//    printArray(tmp, numsSize);
    return lenght;
}

O(nlogn) lengthOfLIS_02 的 Log(为了便于观察理解 打印时隐去了值为0的元素)

OriginalArray:10,  9,  2,  5,  3,  7, 80, 18,  1,  2,  3,  4,  5,  6,  7,  8,  9, 
Part-B i= 1 pos= 0 Arr: 9, 
Part-B i= 2 pos= 0 Arr: 2, 
Part-A i= 3 len= 2 Arr: 2,  5, 
Part-B i= 4 pos= 1 Arr: 2,  3, 
Part-A i= 5 len= 3 Arr: 2,  3,  7, 
Part-A i= 6 len= 4 Arr: 2,  3,  7, 80, 
Part-B i= 7 pos= 3 Arr: 2,  3,  7, 18, 
Part-B i= 8 pos= 0 Arr: 1,  3,  7, 18, 
Part-B i= 9 pos= 1 Arr: 1,  2,  7, 18, 
Part-B i=10 pos= 2 Arr: 1,  2,  3, 18, 
Part-B i=11 pos= 3 Arr: 1,  2,  3,  4, 
Part-A i=12 len= 5 Arr: 1,  2,  3,  4,  5, 
Part-A i=13 len= 6 Arr: 1,  2,  3,  4,  5,  6, 
Part-A i=14 len= 7 Arr: 1,  2,  3,  4,  5,  6,  7, 
Part-A i=15 len= 8 Arr: 1,  2,  3,  4,  5,  6,  7,  8, 
Part-A i=16 len= 9 Arr: 1,  2,  3,  4,  5,  6,  7,  8,  9, 
Finished
 1,  2,  3,  4,  5,  6,  7,  8,  9, 
result = 9

相关的其他函数(非重要内容 可忽略)

void testCode() {
    int arr[] = {10,9,2,5,3,7,80,18,1,2,3,4,5,6,7,8,9};
//    int arr[] = {3,5,6,2,5,4,19,5,6,7,12};
//    int count = lengthOfLIS_01(arr, sizeof(arr)/sizeof(int));
    int count = lengthOfLIS_02(arr, sizeof(arr)/sizeof(int));
    printf("result = %d\n", count);
}

void printArray(int *a, int count) {
    for (int i = 0; i < count; i++) {
        if (a[i] != 0)
        printf("%2d ,",a[i]);
    }
    printf("\n");
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 描述 给定一个整数序列,找到最长上升子序列(LIS),返回LIS的长度。 说明 最长上升子序列的定义:最长上升子序...
    6默默Welsh阅读 701评论 1 0
  • 背景 一年多以前我在知乎上答了有关LeetCode的问题, 分享了一些自己做题目的经验。 张土汪:刷leetcod...
    土汪阅读 12,792评论 0 33
  • 转载自:https://egoistk.github.io/2016/09/10/Java%E6%8E%92%E5...
    chad_it阅读 1,012评论 0 18
  • 概述:排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    每天刷两次牙阅读 3,755评论 0 15
  • 概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    蚁前阅读 5,248评论 0 52