在这里插入图片描述

文章概述

最长递减子序列(Longest Decreasing Subsequence, LDS)是动态规划领域中的经典问题,与最长递增子序列(LIS)密切相关。这个问题在实际应用中有广泛的用途,从股票价格分析、数据排序到时间序列分析,都需要用到这个算法。最长递减子序列问题的核心是找到一个数组中最长的子序列,使得子序列中的元素严格递减。

最长递减子序列问题与最长递增子序列问题在本质上是对称的。通过理解LIS的各种解决方案,我们可以轻松地应用到LDS问题上。同时,LDS问题也有其独特的应用场景,例如在股票交易中,我们可能需要找到最长的下跌趋势来分析市场走势。

本文将深入探讨如何在KMP(Kotlin Multiplatform)框架下实现最长递减子序列问题的多种解决方案,并展示如何在OpenHarmony鸿蒙平台上进行跨端调用。我们将对比不同算法的时间复杂度、空间复杂度和实际性能,帮助开发者选择最合适的方案。

算法原理详解

问题定义

给定一个整数数组,找到其中最长的子序列,使得子序列中的元素严格递减。返回该子序列的长度,或者返回具体的子序列。

例如:

  • 输入:[10, 9, 2, 5, 3, 7, 101, 18]
  • 输出:3(最长递减子序列为 [10, 9, 2][10, 9, 3][10, 9, 5] 等)

另一个例子:

  • 输入:[5, 4, 3, 2, 1]
  • 输出:5(整个数组都是递减的)

解决方案对比

方案1:动态规划法(DP)

这是最直观的方法。定义dp[i]为以第i个元素结尾的最长递减子序列的长度。对于每个位置i,我们遍历所有之前的位置j,如果nums[j] > nums[i],则dp[i] = max(dp[i], dp[j] + 1)

时间复杂度:O(n²)
空间复杂度:O(n)
优点:易于理解和实现,易于扩展
缺点:性能较差,不适合大规模数据
适用场景:数组规模较小(n < 1000)

方案2:二分查找优化法(Binary Search)

这是解决LDS问题的最优方案。我们维护一个数组tails,其中tails[i]表示长度为i+1的递减子序列的最大末尾元素。对于每个新元素,我们使用二分查找找到它应该插入的位置。

时间复杂度:O(n log n)
空间复杂度:O(n)
优点:时间复杂度最优,适合大规模数据
缺点:实现相对复杂,需要理解二分查找
适用场景:数组规模较大(n > 10000)

方案3:贪心二分查找法(Greedy Binary Search)

这是方案2的另一种表述方式,强调了贪心的思想。通过维护一个递减的序列,对于每个新元素,我们要么扩展序列,要么替换序列中的某个元素。

时间复杂度:O(n log n)
空间复杂度:O(n)
优点:思想清晰,性能最优
缺点:需要理解贪心策略
适用场景:所有规模的数据

方案4:路径追踪法(Path Tracking)

在求解LDS长度的基础上,我们还需要返回具体的子序列。这需要额外的数据结构来追踪路径。

时间复杂度:O(n log n)
空间复杂度:O(n)
优点:可以返回具体的子序列
缺点:需要额外的空间和逻辑
适用场景:需要具体子序列的场景

方案5:反向转换法(Reverse Transformation)

利用LDS与LIS的对称性,我们可以通过反向数组或取反数值,将LDS问题转化为LIS问题。这种方法的优点是可以复用LIS的高效实现。

时间复杂度:O(n log n)
空间复杂度:O(n)
优点:可以复用LIS的实现,代码简洁
缺点:需要额外的数据转换
适用场景:已有LIS实现的场景

算法选择指南

  • 数组规模小且需要易于理解:使用DP法
  • 数组规模大且只需要长度:使用二分查找法
  • 需要具体的子序列:使用路径追踪法
  • 性能要求最高:使用贪心二分查找法
  • 已有LIS实现:使用反向转换法

Kotlin实现

完整的Kotlin代码实现

/**
 * 最长递减子序列(Longest Decreasing Subsequence)算法工具类 - KMP OpenHarmony
 * 提供多种解决方案来求解最长递减子序列
 */
object LDSUtils {
    
    /**
     * 方案1:动态规划法
     * 时间复杂度:O(n²)
     * 空间复杂度:O(n)
     * 
     * 原理:
     * dp[i] 表示以第i个元素结尾的最长递减子序列长度
     * 对于每个位置i,遍历所有j < i,如果nums[j] > nums[i],
     * 则 dp[i] = max(dp[i], dp[j] + 1)
     */
    fun ldsDP(nums: IntArray): Int {
        if (nums.isEmpty()) return 0
        
        val n = nums.size
        val dp = IntArray(n) { 1 }
        
        for (i in 1 until n) {
            for (j in 0 until i) {
                if (nums[j] > nums[i]) {
                    dp[i] = maxOf(dp[i], dp[j] + 1)
                }
            }
        }
        
        return dp.maxOrNull() ?: 0
    }
    
    /**
     * 方案2:二分查找优化法(推荐)
     * 时间复杂度:O(n log n)
     * 空间复杂度:O(n)
     * 
     * 原理:
     * 维护数组tails,其中tails[i]表示长度为i+1的递减子序列的最小末尾
     * 对于每个新元素,使用二分查找找到它应该插入的位置
     * 注意:这里是递减,所以tails数组本身是递减的
     */
    fun ldsBinarySearch(nums: IntArray): Int {
        if (nums.isEmpty()) return 0
        
        val tails = mutableListOf<Int>()
        
        for (num in nums) {
            val pos = binarySearchPositionForLDS(tails, num)
            
            if (pos == tails.size) {
                tails.add(num)
            } else {
                tails[pos] = num
            }
        }
        
        return tails.size
    }
    
    /**
     * 二分查找辅助函数 - 用于递减序列
     * 找到第一个小于target的位置
     */
    private fun binarySearchPositionForLDS(arr: List<Int>, target: Int): Int {
        var left = 0
        var right = arr.size
        
        while (left < right) {
            val mid = (left + right) / 2
            if (arr[mid] > target) {
                left = mid + 1
            } else {
                right = mid
            }
        }
        
        return left
    }
    
    /**
     * 方案3:返回最长递减子序列本身
     * 时间复杂度:O(n log n)
     * 空间复杂度:O(n)
     */
    fun ldsWithPath(nums: IntArray): List<Int> {
        if (nums.isEmpty()) return emptyList()
        
        val n = nums.size
        val tails = mutableListOf<Int>()
        val indices = mutableListOf<Int>()
        val parent = IntArray(n) { -1 }
        
        for (i in nums.indices) {
            val num = nums[i]
            val pos = binarySearchPositionForLDS(tails, num)
            
            if (pos > 0) {
                parent[i] = indices[pos - 1]
            }
            
            if (pos == tails.size) {
                tails.add(num)
                indices.add(i)
            } else {
                tails[pos] = num
                indices[pos] = i
            }
        }
        
        // 回溯构建结果
        val result = mutableListOf<Int>()
        var idx = indices.last()
        while (idx != -1) {
            result.add(nums[idx])
            idx = parent[idx]
        }
        
        return result.reversed()
    }
    
    /**
     * 方案4:贪心二分查找法(最优)
     * 时间复杂度:O(n log n)
     * 空间复杂度:O(n)
     */
    fun ldsGreedyBinarySearch(nums: IntArray): Int {
        if (nums.isEmpty()) return 0
        
        val tails = IntArray(nums.size)
        var size = 0
        
        for (num in nums) {
            var left = 0
            var right = size
            
            while (left < right) {
                val mid = (left + right) / 2
                if (tails[mid] > num) {
                    left = mid + 1
                } else {
                    right = mid
                }
            }
            
            tails[left] = num
            if (left == size) {
                size++
            }
        }
        
        return size
    }
    
    /**
     * 方案5:反向转换法 - 利用LIS求解LDS
     * 时间复杂度:O(n log n)
     * 空间复杂度:O(n)
     */
    fun ldsViaReverse(nums: IntArray): Int {
        if (nums.isEmpty()) return 0
        
        // 将数组反向取负,转化为LIS问题
        val transformed = nums.map { -it }.toIntArray()
        return lisOptimized(transformed)
    }
    
    /**
     * LIS优化实现(用于反向转换法)
     */
    private fun lisOptimized(nums: IntArray): Int {
        if (nums.isEmpty()) return 0
        
        val tails = mutableListOf<Int>()
        
        for (num in nums) {
            val pos = binarySearchPositionForLIS(tails, num)
            
            if (pos == tails.size) {
                tails.add(num)
            } else {
                tails[pos] = num
            }
        }
        
        return tails.size
    }
    
    /**
     * 二分查找辅助函数 - 用于递增序列
     */
    private fun binarySearchPositionForLIS(arr: List<Int>, target: Int): Int {
        var left = 0
        var right = arr.size
        
        while (left < right) {
            val mid = (left + right) / 2
            if (arr[mid] < target) {
                left = mid + 1
            } else {
                right = mid
            }
        }
        
        return left
    }
    
    /**
     * 性能演示函数 - 对比多种方法的性能
     */
    fun performanceDemo(size: Int = 1000): String {
        val result = StringBuilder()
        result.append("最长递减子序列(LDS)性能对比 (数组大小: $size)\n")
        result.append("=".repeat(70)).append("\n\n")
        
        // 生成测试数据
        val nums = IntArray(size) { (Math.random() * 10000).toInt() }
        
        // 方案1:动态规划法
        val time1 = if (size <= 1000) {
            measureTimeMillis {
                ldsDP(nums)
            }
        } else {
            -1L
        }
        
        if (time1 >= 0) {
            result.append("方案1 - 动态规划法\n")
            result.append("耗时: ${time1}ms\n")
            result.append("时间复杂度: O(n²)\n")
            result.append("空间复杂度: O(n)\n\n")
        } else {
            result.append("方案1 - 动态规划法\n")
            result.append("耗时: 跳过(数据太大)\n\n")
        }
        
        // 方案2:二分查找法
        val time2 = measureTimeMillis {
            ldsBinarySearch(nums)
        }
        result.append("方案2 - 二分查找法\n")
        result.append("耗时: ${time2}ms\n")
        result.append("时间复杂度: O(n log n)\n")
        result.append("空间复杂度: O(n)\n\n")
        
        // 方案3:返回路径
        val time3 = measureTimeMillis {
            ldsWithPath(nums)
        }
        result.append("方案3 - 返回子序列\n")
        result.append("耗时: ${time3}ms\n")
        result.append("时间复杂度: O(n log n)\n")
        result.append("空间复杂度: O(n)\n\n")
        
        // 方案4:贪心二分查找法
        val time4 = measureTimeMillis {
            ldsGreedyBinarySearch(nums)
        }
        result.append("方案4 - 贪心二分查找法(推荐)\n")
        result.append("耗时: ${time4}ms\n")
        result.append("时间复杂度: O(n log n)\n")
        result.append("空间复杂度: O(n)\n\n")
        
        // 方案5:反向转换法
        val time5 = measureTimeMillis {
            ldsViaReverse(nums)
        }
        result.append("方案5 - 反向转换法\n")
        result.append("耗时: ${time5}ms\n")
        result.append("时间复杂度: O(n log n)\n")
        result.append("空间复杂度: O(n)\n\n")
        
        // 性能对比
        result.append("性能对比总结\n")
        result.append("-".repeat(70)).append("\n")
        result.append("最快方案: 贪心二分查找法\n")
        result.append("推荐方案: 贪心二分查找法(综合考虑性能和实现复杂度)\n")
        result.append("性能提升: 相比DP法提升约 ${if (time1 > 0) (time1 / time4).toInt() else "N/A"}倍\n")
        
        return result.toString()
    }
}

// 扩展函数
fun IntArray.findLDS(): Int {
    return LDSUtils.ldsBinarySearch(this)
}

// 使用示例
fun main() {
    println("KMP OpenHarmony 最长递减子序列(LDS)算法演示\n")
    
    val nums = intArrayOf(10, 9, 2, 5, 3, 7, 101, 18)
    
    println("输入数组: ${nums.joinToString(", ")}\n")
    
    // 方案1
    val result1 = LDSUtils.ldsDP(nums)
    println("方案1 - 动态规划法: $result1")
    
    // 方案2
    val result2 = LDSUtils.ldsBinarySearch(nums)
    println("方案2 - 二分查找法: $result2")
    
    // 方案3
    val result3 = LDSUtils.ldsWithPath(nums)
    println("方案3 - 返回子序列: $result3")
    
    // 方案4
    val result4 = LDSUtils.ldsGreedyBinarySearch(nums)
    println("方案4 - 贪心二分查找法: $result4")
    
    // 方案5
    val result5 = LDSUtils.ldsViaReverse(nums)
    println("方案5 - 反向转换法: $result5")
    
    println("\n性能演示:")
    println(LDSUtils.performanceDemo(1000))
}

fun measureTimeMillis(block: () -> Unit): Long {
    val start = System.currentTimeMillis()
    block()
    return System.currentTimeMillis() - start
}

Kotlin实现的详细说明

Kotlin实现提供了五种不同的LDS解决方案。动态规划法是最直观的方法,通过维护一个dp数组来记录以每个位置结尾的最长递减子序列长度。这种方法易于理解,但时间复杂度为O(n²),只适合小规模数据。

二分查找法是解决LDS问题的经典优化方案。关键的区别是,对于递减序列,我们维护的tails数组本身也是递减的,所以二分查找的逻辑与LIS略有不同。我们需要找到第一个小于当前元素的位置。

ldsWithPath函数在求解LDS长度的基础上,还返回具体的子序列。这需要额外的parent数组来追踪路径。通过回溯parent数组,我们可以重构出原始的LDS。

贪心二分查找法是最优的实现方式,它直接在原数组上进行二分查找,避免了使用List的开销。这个方法展现了算法优化的精妙之处。

反向转换法利用了LDS与LIS的对称性。通过将数组反向取负,我们可以将LDS问题转化为LIS问题,从而复用LIS的高效实现。这种方法在已有LIS实现的情况下非常有用。

JavaScript实现

完整的JavaScript代码实现

/**
 * 最长递减子序列(Longest Decreasing Subsequence)算法工具类 - JavaScript版本
 * 用于在Web和Node.js环境中使用
 */
class LDSJS {
    /**
     * 方案1:动态规划法
     * @param {number[]} nums - 输入数组
     * @returns {number} LDS长度
     */
    static ldsDP(nums) {
        if (nums.length === 0) return 0;
        
        const n = nums.length;
        const dp = new Array(n).fill(1);
        
        for (let i = 1; i < n; i++) {
            for (let j = 0; j < i; j++) {
                if (nums[j] > nums[i]) {
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
            }
        }
        
        return Math.max(...dp);
    }
    
    /**
     * 方案2:二分查找法
     * @param {number[]} nums - 输入数组
     * @returns {number} LDS长度
     */
    static ldsBinarySearch(nums) {
        if (nums.length === 0) return 0;
        
        const tails = [];
        
        for (const num of nums) {
            const pos = this.binarySearchPositionForLDS(tails, num);
            
            if (pos === tails.length) {
                tails.push(num);
            } else {
                tails[pos] = num;
            }
        }
        
        return tails.length;
    }
    
    /**
     * 二分查找辅助函数 - 用于递减序列
     */
    static binarySearchPositionForLDS(arr, target) {
        let left = 0;
        let right = arr.length;
        
        while (left < right) {
            const mid = Math.floor((left + right) / 2);
            if (arr[mid] > target) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        
        return left;
    }
    
    /**
     * 方案3:返回最长递减子序列本身
     * @param {number[]} nums - 输入数组
     * @returns {number[]} LDS子序列
     */
    static ldsWithPath(nums) {
        if (nums.length === 0) return [];
        
        const n = nums.length;
        const tails = [];
        const indices = [];
        const parent = new Array(n).fill(-1);
        
        for (let i = 0; i < n; i++) {
            const num = nums[i];
            const pos = this.binarySearchPositionForLDS(tails, num);
            
            if (pos > 0) {
                parent[i] = indices[pos - 1];
            }
            
            if (pos === tails.length) {
                tails.push(num);
                indices.push(i);
            } else {
                tails[pos] = num;
                indices[pos] = i;
            }
        }
        
        // 回溯构建结果
        const result = [];
        let idx = indices[indices.length - 1];
        while (idx !== -1) {
            result.push(nums[idx]);
            idx = parent[idx];
        }
        
        return result.reverse();
    }
    
    /**
     * 方案4:贪心二分查找法(最优)
     * @param {number[]} nums - 输入数组
     * @returns {number} LDS长度
     */
    static ldsGreedyBinarySearch(nums) {
        if (nums.length === 0) return 0;
        
        const tails = new Array(nums.length);
        let size = 0;
        
        for (const num of nums) {
            let left = 0;
            let right = size;
            
            while (left < right) {
                const mid = Math.floor((left + right) / 2);
                if (tails[mid] > num) {
                    left = mid + 1;
                } else {
                    right = mid;
                }
            }
            
            tails[left] = num;
            if (left === size) {
                size++;
            }
        }
        
        return size;
    }
    
    /**
     * 方案5:反向转换法 - 利用LIS求解LDS
     * @param {number[]} nums - 输入数组
     * @returns {number} LDS长度
     */
    static ldsViaReverse(nums) {
        if (nums.length === 0) return 0;
        
        // 将数组反向取负,转化为LIS问题
        const transformed = nums.map(x => -x);
        return this.lisOptimized(transformed);
    }
    
    /**
     * LIS优化实现(用于反向转换法)
     */
    static lisOptimized(nums) {
        if (nums.length === 0) return 0;
        
        const tails = [];
        
        for (const num of nums) {
            const pos = this.binarySearchPositionForLIS(tails, num);
            
            if (pos === tails.length) {
                tails.push(num);
            } else {
                tails[pos] = num;
            }
        }
        
        return tails.length;
    }
    
    /**
     * 二分查找辅助函数 - 用于递增序列
     */
    static binarySearchPositionForLIS(arr, target) {
        let left = 0;
        let right = arr.length;
        
        while (left < right) {
            const mid = Math.floor((left + right) / 2);
            if (arr[mid] < target) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        
        return left;
    }
    
    /**
     * 性能演示函数
     * @param {number} size - 数组大小
     * @returns {string} 性能报告
     */
    static performanceDemo(size = 1000) {
        let result = `最长递减子序列(LDS)性能对比 (数组大小: ${size})\n`;
        result += '='.repeat(70) + '\n\n';
        
        // 生成测试数据
        const nums = Array.from({ length: size }, () => Math.floor(Math.random() * 10000));
        
        // 方案1:动态规划法(仅对小数据集)
        let time1 = -1;
        if (size <= 1000) {
            const start1 = performance.now();
            LDSJS.ldsDP(nums);
            time1 = performance.now() - start1;
            result += `方案1 - 动态规划法: ${time1.toFixed(2)}ms\n`;
            result += '时间复杂度: O(n²)\n\n';
        } else {
            result += `方案1 - 动态规划法: 跳过(数据太大)\n\n`;
        }
        
        // 方案2:二分查找法
        const start2 = performance.now();
        LDSJS.ldsBinarySearch(nums);
        const time2 = performance.now() - start2;
        result += `方案2 - 二分查找法: ${time2.toFixed(2)}ms\n`;
        result += '时间复杂度: O(n log n)\n\n';
        
        // 方案3:返回路径
        const start3 = performance.now();
        LDSJS.ldsWithPath(nums);
        const time3 = performance.now() - start3;
        result += `方案3 - 返回子序列: ${time3.toFixed(2)}ms\n`;
        result += '时间复杂度: O(n log n)\n\n';
        
        // 方案4:贪心二分查找法
        const start4 = performance.now();
        LDSJS.ldsGreedyBinarySearch(nums);
        const time4 = performance.now() - start4;
        result += `方案4 - 贪心二分查找法(推荐): ${time4.toFixed(2)}ms\n`;
        result += '时间复杂度: O(n log n)\n\n';
        
        // 方案5:反向转换法
        const start5 = performance.now();
        LDSJS.ldsViaReverse(nums);
        const time5 = performance.now() - start5;
        result += `方案5 - 反向转换法: ${time5.toFixed(2)}ms\n`;
        result += '时间复杂度: O(n log n)\n\n';
        
        result += '性能对比总结\n';
        result += '-'.repeat(70) + '\n';
        result += '最快方案: 贪心二分查找法\n';
        result += '推荐方案: 贪心二分查找法(综合考虑性能和实现复杂度)\n';
        if (time1 > 0) {
            result += `性能提升: 相比DP法提升约 ${Math.round(time1 / time4)}倍\n`;
        }
        
        return result;
    }
}

// 导出供Node.js使用
if (typeof module !== 'undefined' && module.exports) {
    module.exports = LDSJS;
}

JavaScript实现的详细说明

JavaScript版本的实现与Kotlin版本在逻辑上完全一致,但充分利用了JavaScript的语言特性。在二分查找实现中,我们使用了Math.floor来处理整数除法,确保了正确的中点计算。

在ldsWithPath函数中,我们使用了JavaScript数组的reverse()方法来反转结果,这比手动反转更加简洁高效。在反向转换法中,我们使用了map方法来转换数组,这是函数式编程的典型应用。

JavaScript的performance.now()方法提供了微秒级的精度,使得性能测试更加准确。在实际应用中,开发者可以使用这个性能演示函数来选择最适合的算法。

ArkTS调用实现

完整的ArkTS代码实现

/**
 * 最长递减子序列(Longest Decreasing Subsequence)工具 - ArkTS版本(OpenHarmony鸿蒙)
 * 用于在鸿蒙应用中实现LDS算法
 */
import { webview } from '@kit.ArkWeb';
import { common } from '@kit.AbilityKit';

@Entry
@Component
struct LDSPage {
    @State inputArray: string = '10,9,2,5,3,7,101,18';
    @State result: string = '';
    @State selectedMethod: string = '贪心二分查找法';
    @State isLoading: boolean = false;
    @State ldsSequence: string = '';
    @State performanceData: string = '';
    @State showPerformance: boolean = false;
    
    // Web视图控制器
    webviewController: webview.WebviewController = new webview.WebviewController();
    
    /**
     * 解析输入数组
     */
    parseArray(input: string): number[] {
        return input.split(',').map(str => parseInt(str.trim())).filter(num => !isNaN(num));
    }
    
    /**
     * 方案1:动态规划法
     */
    ldsDP(nums: number[]): number {
        if (nums.length === 0) return 0;
        
        const n = nums.length;
        const dp = new Array(n).fill(1);
        
        for (let i = 1; i < n; i++) {
            for (let j = 0; j < i; j++) {
                if (nums[j] > nums[i]) {
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
            }
        }
        
        return Math.max(...dp);
    }
    
    /**
     * 方案2:二分查找法
     */
    ldsBinarySearch(nums: number[]): number {
        if (nums.length === 0) return 0;
        
        const tails: number[] = [];
        
        for (const num of nums) {
            const pos = this.binarySearchPositionForLDS(tails, num);
            
            if (pos === tails.length) {
                tails.push(num);
            } else {
                tails[pos] = num;
            }
        }
        
        return tails.length;
    }
    
    /**
     * 二分查找辅助函数
     */
    binarySearchPositionForLDS(arr: number[], target: number): number {
        let left = 0;
        let right = arr.length;
        
        while (left < right) {
            const mid = Math.floor((left + right) / 2);
            if (arr[mid] > target) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        
        return left;
    }
    
    /**
     * 方案3:返回LDS子序列
     */
    ldsWithPath(nums: number[]): number[] {
        if (nums.length === 0) return [];
        
        const n = nums.length;
        const tails: number[] = [];
        const indices: number[] = [];
        const parent = new Array(n).fill(-1);
        
        for (let i = 0; i < n; i++) {
            const num = nums[i];
            const pos = this.binarySearchPositionForLDS(tails, num);
            
            if (pos > 0) {
                parent[i] = indices[pos - 1];
            }
            
            if (pos === tails.length) {
                tails.push(num);
                indices.push(i);
            } else {
                tails[pos] = num;
                indices[pos] = i;
            }
        }
        
        // 回溯构建结果
        const result: number[] = [];
        let idx = indices[indices.length - 1];
        while (idx !== -1) {
            result.push(nums[idx]);
            idx = parent[idx];
        }
        
        return result.reverse();
    }
    
    /**
     * 方案4:贪心二分查找法(推荐)
     */
    ldsGreedyBinarySearch(nums: number[]): number {
        if (nums.length === 0) return 0;
        
        const tails = new Array(nums.length);
        let size = 0;
        
        for (const num of nums) {
            let left = 0;
            let right = size;
            
            while (left < right) {
                const mid = Math.floor((left + right) / 2);
                if (tails[mid] > num) {
                    left = mid + 1;
                } else {
                    right = mid;
                }
            }
            
            tails[left] = num;
            if (left === size) {
                size++;
            }
        }
        
        return size;
    }
    
    /**
     * 执行LDS计算
     */
    async executeLDS() {
        this.isLoading = true;
        this.showPerformance = false;
        
        try {
            const nums = this.parseArray(this.inputArray);
            
            if (nums.length === 0) {
                this.result = '错误:数组不能为空';
                this.isLoading = false;
                return;
            }
            
            let ldsLength = 0;
            
            switch (this.selectedMethod) {
                case '动态规划法':
                    ldsLength = this.ldsDP(nums);
                    break;
                case '二分查找法':
                    ldsLength = this.ldsBinarySearch(nums);
                    break;
                case '贪心二分查找法':
                    ldsLength = this.ldsGreedyBinarySearch(nums);
                    break;
            }
            
            this.result = `最长递减子序列长度: ${ldsLength}`;
            
            // 获取具体的LDS
            const lds = this.ldsWithPath(nums);
            this.ldsSequence = `LDS子序列: [${lds.join(', ')}]`;
        } catch (error) {
            this.result = '计算错误:' + error;
        }
        
        this.isLoading = false;
    }
    
    /**
     * 执行性能演示
     */
    async executePerformanceDemo() {
        this.isLoading = true;
        this.showPerformance = true;
        
        try {
            const size = 1000;
            const nums = Array.from({ length: size }, () => Math.floor(Math.random() * 10000));
            
            let result = `最长递减子序列(LDS)性能对比 (数组大小: ${size})\n`;
            result += '='.repeat(70) + '\n\n';
            
            // 方案1
            const start1 = Date.now();
            this.ldsDP(nums);
            const time1 = Date.now() - start1;
            result += `方案1 - 动态规划法: ${time1}ms\n`;
            result += '时间复杂度: O(n²)\n\n';
            
            // 方案2
            const start2 = Date.now();
            this.ldsBinarySearch(nums);
            const time2 = Date.now() - start2;
            result += `方案2 - 二分查找法: ${time2}ms\n`;
            result += '时间复杂度: O(n log n)\n\n';
            
            // 方案3
            const start3 = Date.now();
            this.ldsWithPath(nums);
            const time3 = Date.now() - start3;
            result += `方案3 - 返回子序列: ${time3}ms\n`;
            result += '时间复杂度: O(n log n)\n\n';
            
            // 方案4
            const start4 = Date.now();
            this.ldsGreedyBinarySearch(nums);
            const time4 = Date.now() - start4;
            result += `方案4 - 贪心二分查找法: ${time4}ms\n`;
            result += '时间复杂度: O(n log n)\n\n';
            
            result += '性能对比总结\n';
            result += '-'.repeat(70) + '\n';
            result += '最快方案: 贪心二分查找法\n';
            result += '推荐方案: 贪心二分查找法(综合考虑性能和实现复杂度)\n';
            result += `性能提升: 相比DP法提升约 ${Math.round(time1 / time4)}倍\n`;
            
            this.performanceData = result;
        } catch (error) {
            this.performanceData = '演示失败:' + error;
        }
        
        this.isLoading = false;
    }
    
    build() {
        Column() {
            // 顶部栏
            Row() {
                Text('最长递减子序列(LDS)')
                    .fontSize(24)
                    .fontWeight(FontWeight.Bold)
                    .fontColor(Color.White)
            }
            .width('100%')
            .height(60)
            .backgroundColor('#1565C0')
            .justifyContent(FlexAlign.Center)
            
            // 主内容
            Scroll() {
                Column({ space: 16 }) {
                    // 输入数组
                    Column() {
                        Text('输入数组:')
                            .fontSize(14)
                            .fontWeight(FontWeight.Bold)
                            .width('100%')
                        
                        TextInput({ placeholder: '请输入数组,用逗号分隔' })
                            .value(this.inputArray)
                            .onChange((value: string) => {
                                this.inputArray = value;
                            })
                            .width('100%')
                            .padding(8)
                            .backgroundColor(Color.White)
                            .borderRadius(4)
                    }
                    .width('100%')
                    .padding(12)
                    .backgroundColor('#E3F2FD')
                    .borderRadius(8)
                    
                    // 算法选择
                    Column() {
                        Text('选择算法:')
                            .fontSize(14)
                            .fontWeight(FontWeight.Bold)
                            .width('100%')
                        
                        Select([
                            { value: '动态规划法' },
                            { value: '二分查找法' },
                            { value: '贪心二分查找法' }
                        ])
                            .value(this.selectedMethod)
                            .onSelect((index: number, value: string) => {
                                this.selectedMethod = value;
                            })
                            .width('100%')
                    }
                    .width('100%')
                    .padding(12)
                    .backgroundColor('#E3F2FD')
                    .borderRadius(8)
                    
                    // 结果显示
                    if (this.result) {
                        Column() {
                            Text('计算结果:')
                                .fontSize(14)
                                .fontWeight(FontWeight.Bold)
                                .width('100%')
                            
                            Text(this.result)
                                .fontSize(12)
                                .width('100%')
                                .padding(8)
                                .backgroundColor('#F5F5F5')
                                .borderRadius(4)
                        }
                        .width('100%')
                        .padding(12)
                        .backgroundColor('#F5F5F5')
                        .borderRadius(8)
                    }
                    
                    // LDS子序列显示
                    if (this.ldsSequence) {
                        Column() {
                            Text('LDS子序列:')
                                .fontSize(14)
                                .fontWeight(FontWeight.Bold)
                                .width('100%')
                            
                            Text(this.ldsSequence)
                                .fontSize(12)
                                .width('100%')
                                .padding(8)
                                .backgroundColor('#E8F5E9')
                                .borderRadius(4)
                        }
                        .width('100%')
                        .padding(12)
                        .backgroundColor('#E8F5E9')
                        .borderRadius(8)
                    }
                    
                    // 性能数据显示
                    if (this.showPerformance && this.performanceData) {
                        Column() {
                            Text('性能对比:')
                                .fontSize(14)
                                .fontWeight(FontWeight.Bold)
                                .width('100%')
                            
                            Text(this.performanceData)
                                .fontSize(12)
                                .fontFamily('monospace')
                                .width('100%')
                                .padding(8)
                                .backgroundColor('#FFF3E0')
                                .borderRadius(4)
                        }
                        .width('100%')
                        .padding(12)
                        .backgroundColor('#FFF3E0')
                        .borderRadius(8)
                    }
                    
                    // 按钮组
                    Row({ space: 12 }) {
                        Button('执行计算')
                            .width('48%')
                            .onClick(() => this.executeLDS())
                            .enabled(!this.isLoading)
                        
                        Button('性能演示')
                            .width('48%')
                            .onClick(() => this.executePerformanceDemo())
                            .enabled(!this.isLoading)
                    }
                    .width('100%')
                    
                    // 加载指示器
                    if (this.isLoading) {
                        LoadingProgress()
                            .width(40)
                            .height(40)
                    }
                }
                .width('100%')
                .padding(16)
            }
            .layoutWeight(1)
        }
        .width('100%')
        .height('100%')
        .backgroundColor('#FAFAFA')
    }
}

ArkTS实现的详细说明

ArkTS版本为OpenHarmony鸿蒙平台提供了完整的用户界面。通过@State装饰器,我们可以管理应用的状态,当用户输入改变时,UI会自动更新。这个实现包含了输入数组的输入框,以及算法选择的下拉菜单。

在计算结果的显示中,我们不仅显示了LDS的长度,还显示了具体的LDS子序列。这样用户可以直观地看到算法找到的最长递减子序列是什么。

性能演示功能允许用户在应用中直接看到不同算法的性能差异。通过对比1000个元素的数组,用户可以清楚地看到为什么贪心二分查找法是推荐方案。特别是,动态规划法的O(n²)复杂度会导致明显的性能下降,而二分查找优化后的方案性能提升显著。

应用场景分析

1. 股票价格分析

在股票交易中,最长递减子序列可以用于识别最长的下跌趋势。通过找到股票价格的最长递减子序列,投资者可以识别出长期的下跌趋势,从而做出更好的投资决策。

2. 时间序列分析

在时间序列数据分析中,LDS用于识别数据的递减模式。这对于理解数据的趋势和周期性非常重要。

3. 排序和排列问题

在某些排序和排列问题中,LDS可以用于识别需要调整的元素序列。这对于优化算法性能非常有用。

4. 数据压缩

在数据压缩算法中,LDS可以用于识别数据中的递减模式,从而更有效地编码数据。

5. 图像处理

在图像处理中,LDS可以用于识别像素值的递减模式,这对于某些图像分析任务非常有用。

性能优化建议

1. 选择合适的算法

对于大多数实际应用,贪心二分查找法是最优选择。它提供了O(n log n)的时间复杂度,这是理论上的最优复杂度。只有在数组规模非常小(n < 100)时,才可能考虑使用DP法。

2. 数据预处理

在处理大规模数据时,可以先进行数据验证和去重。这可以减少实际需要处理的数据量,从而提高性能。

3. 内存优化

在处理非常大的数据集时,可以考虑使用流式处理,而不是一次性加载整个数组到内存中。

4. 并行处理

虽然LDS算法本身不易并行化,但在处理多个独立的LDS问题时,可以使用多线程或多进程来并行处理。

总结

最长递减子序列是一个经典的动态规划问题,虽然问题陈述简单,但其解决方案涉及多种不同的算法思想。从朴素的O(n²)动态规划到高效的O(n log n)二分查找优化,再到利用对称性的反向转换法,每种方法都展现了算法设计的精妙之处。

通过在KMP框架下实现这个算法,我们可以在多个平台上使用同一套代码,提高开发效率。贪心二分查找法是解决这个问题的最优方案,提供了O(n log n)的时间复杂度。在OpenHarmony鸿蒙平台上,我们可以通过ArkTS调用Kotlin编译的JavaScript代码,或者直接在ArkTS中实现算法,实现真正的跨平台开发。

掌握LDS算法的多种解决方案,不仅能够帮助开发者在面试中获得好成绩,更重要的是能够培养优化算法性能的思维方式。在实际项目中,灵活应用这些算法和优化技巧,可以显著提升应用的性能和用户体验。特别是在处理大规模数据时,选择正确的算法可以使性能提升数十倍甚至数百倍。

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐