|
| 1 | +# Longest Increasing Subsequence (Dynamic Programming) |
| 2 | +# |
| 3 | +# The Longest Increasing Subsequence (LIS) problem is a classic dynamic programming problem. |
| 4 | +# Given an array of integers, find the length of the longest subsequence that is strictly |
| 5 | +# increasing. A subsequence is derived from the array by deleting some or no elements |
| 6 | +# without changing the order of the remaining elements. |
| 7 | +# |
| 8 | +# Time Complexity: O(n²) for basic DP, O(n log n) for optimized binary search version |
| 9 | +# Space Complexity: O(n) for both approaches |
| 10 | +# |
| 11 | +# Applications: |
| 12 | +# - Bioinformatics (DNA sequence analysis) |
| 13 | +# - Stock market analysis (longest upward trend) |
| 14 | +# - Scheduling problems |
| 15 | +# - Game theory (optimal play sequences) |
| 16 | +# - Data compression and pattern recognition |
| 17 | + |
| 18 | +# Basic DP solution for Longest Increasing Subsequence |
| 19 | +longest_increasing_subsequence <- function(nums) { |
| 20 | + #' Find the length of the longest increasing subsequence using Dynamic Programming |
| 21 | + #' @param nums: Numeric vector of integers |
| 22 | + #' @return: List containing max length, DP array, and one possible LIS |
| 23 | + |
| 24 | + n <- length(nums) |
| 25 | + |
| 26 | + # Handle edge cases |
| 27 | + if (n == 0) { |
| 28 | + return(list( |
| 29 | + max_length = 0, |
| 30 | + dp_array = c(), |
| 31 | + lis_sequence = c(), |
| 32 | + dp_table = c() |
| 33 | + )) |
| 34 | + } |
| 35 | + |
| 36 | + if (n == 1) { |
| 37 | + return(list( |
| 38 | + max_length = 1, |
| 39 | + dp_array = c(1), |
| 40 | + lis_sequence = nums, |
| 41 | + dp_table = c(1) |
| 42 | + )) |
| 43 | + } |
| 44 | + |
| 45 | + # Initialize DP array: dp[i] = length of LIS ending at index i |
| 46 | + dp <- rep(1, n) |
| 47 | + |
| 48 | + # Fill DP array |
| 49 | + for (i in 2:n) { |
| 50 | + for (j in 1:(i - 1)) { |
| 51 | + if (nums[j] < nums[i]) { |
| 52 | + dp[i] <- max(dp[i], dp[j] + 1) |
| 53 | + } |
| 54 | + } |
| 55 | + } |
| 56 | + |
| 57 | + # Find maximum length |
| 58 | + max_length <- max(dp) |
| 59 | + |
| 60 | + # Backtrack to find one possible LIS |
| 61 | + lis_sequence <- c() |
| 62 | + current_length <- max_length |
| 63 | + |
| 64 | + for (i in n:1) { |
| 65 | + if (dp[i] == current_length) { |
| 66 | + lis_sequence <- c(nums[i], lis_sequence) |
| 67 | + current_length <- current_length - 1 |
| 68 | + if (current_length == 0) break |
| 69 | + } |
| 70 | + } |
| 71 | + |
| 72 | + return(list( |
| 73 | + max_length = max_length, |
| 74 | + dp_array = dp, |
| 75 | + lis_sequence = lis_sequence, |
| 76 | + dp_table = dp |
| 77 | + )) |
| 78 | +} |
| 79 | + |
| 80 | +# Optimized O(n log n) solution using binary search |
| 81 | +longest_increasing_subsequence_optimized <- function(nums) { |
| 82 | + #' Find the length of the longest increasing subsequence using binary search |
| 83 | + #' @param nums: Numeric vector of integers |
| 84 | + #' @return: Length of the longest increasing subsequence |
| 85 | + |
| 86 | + n <- length(nums) |
| 87 | + |
| 88 | + if (n == 0) return(0) |
| 89 | + if (n == 1) return(1) |
| 90 | + |
| 91 | + # tails[i] stores the smallest tail of all increasing subsequences of length i+1 |
| 92 | + tails <- c() |
| 93 | + |
| 94 | + for (num in nums) { |
| 95 | + # Binary search for the position to replace or extend |
| 96 | + pos <- binary_search_insert_position(tails, num) |
| 97 | + |
| 98 | + if (pos > length(tails)) { |
| 99 | + # Extend the sequence |
| 100 | + tails <- c(tails, num) |
| 101 | + } else { |
| 102 | + # Replace the element at position pos |
| 103 | + tails[pos] <- num |
| 104 | + } |
| 105 | + } |
| 106 | + |
| 107 | + return(length(tails)) |
| 108 | +} |
| 109 | + |
| 110 | +# Helper function for binary search |
| 111 | +binary_search_insert_position <- function(arr, target) { |
| 112 | + #' Binary search to find the position where target should be inserted |
| 113 | + #' @param arr: Sorted numeric vector |
| 114 | + #' @param target: Value to insert |
| 115 | + #' @return: Position (1-indexed) where target should be inserted |
| 116 | + |
| 117 | + if (length(arr) == 0) return(1) |
| 118 | + |
| 119 | + left <- 1 |
| 120 | + right <- length(arr) |
| 121 | + |
| 122 | + while (left <= right) { |
| 123 | + mid <- left + (right - left) %/% 2 |
| 124 | + |
| 125 | + if (arr[mid] < target) { |
| 126 | + left <- mid + 1 |
| 127 | + } else { |
| 128 | + right <- mid - 1 |
| 129 | + } |
| 130 | + } |
| 131 | + |
| 132 | + return(left) |
| 133 | +} |
| 134 | + |
| 135 | +# Function to find all possible LIS sequences (simplified version) |
| 136 | +find_all_lis <- function(nums) { |
| 137 | + #' Find all possible longest increasing subsequences |
| 138 | + #' @param nums: Numeric vector of integers |
| 139 | + #' @return: List of all possible LIS sequences |
| 140 | + |
| 141 | + n <- length(nums) |
| 142 | + if (n == 0) return(list()) |
| 143 | + |
| 144 | + # Calculate DP array |
| 145 | + dp <- rep(1, n) |
| 146 | + for (i in 2:n) { |
| 147 | + for (j in 1:(i - 1)) { |
| 148 | + if (nums[j] < nums[i]) { |
| 149 | + dp[i] <- max(dp[i], dp[j] + 1) |
| 150 | + } |
| 151 | + } |
| 152 | + } |
| 153 | + |
| 154 | + max_length <- max(dp) |
| 155 | + |
| 156 | + # For simplicity, return just one LIS (same as the main function) |
| 157 | + # Finding all possible LIS is complex and not essential for the algorithm demonstration |
| 158 | + result <- longest_increasing_subsequence(nums) |
| 159 | + return(list(result$lis_sequence)) |
| 160 | +} |
| 161 | + |
| 162 | +# Helper function to print DP table |
| 163 | +print_lis_dp <- function(dp_array, nums) { |
| 164 | + cat("DP Array for Longest Increasing Subsequence:\n") |
| 165 | + cat("Input Array:", paste(nums, collapse = ", "), "\n") |
| 166 | + cat("DP Array :", paste(dp_array, collapse = ", "), "\n") |
| 167 | + cat("Max Length :", max(dp_array), "\n\n") |
| 168 | +} |
| 169 | + |
| 170 | +# =========================== |
| 171 | +# Example Usage & Testing |
| 172 | +# =========================== |
| 173 | +cat("=== Longest Increasing Subsequence (Dynamic Programming) ===\n\n") |
| 174 | + |
| 175 | +# Test 1: Basic Example |
| 176 | +nums1 <- c(10, 9, 2, 5, 3, 7, 101, 18) |
| 177 | +cat("Test 1: Basic Example\n") |
| 178 | +cat("Input Array:", paste(nums1, collapse = ", "), "\n\n") |
| 179 | + |
| 180 | +result1 <- longest_increasing_subsequence(nums1) |
| 181 | +print_lis_dp(result1$dp_array, nums1) |
| 182 | +cat("Maximum Length:", result1$max_length, "\n") |
| 183 | +cat("One LIS Sequence:", paste(result1$lis_sequence, collapse = ", "), "\n\n") |
| 184 | + |
| 185 | +# Test 2: Optimized Version |
| 186 | +cat("Test 2: Optimized O(n log n) Version\n") |
| 187 | +max_len_opt <- longest_increasing_subsequence_optimized(nums1) |
| 188 | +cat("Maximum Length (Optimized):", max_len_opt, "\n") |
| 189 | +cat("Verification: Both methods match:", result1$max_length == max_len_opt, "\n\n") |
| 190 | + |
| 191 | +# Test 3: All Possible LIS |
| 192 | +cat("Test 3: All Possible LIS Sequences\n") |
| 193 | +all_lis <- find_all_lis(nums1) |
| 194 | +cat("Total number of LIS sequences:", length(all_lis), "\n") |
| 195 | +for (i in seq_along(all_lis)) { |
| 196 | + cat("LIS", i, ":", paste(all_lis[[i]], collapse = ", "), "\n") |
| 197 | +} |
| 198 | +cat("\n") |
| 199 | + |
| 200 | +# Test 4: Edge Cases |
| 201 | +cat("Test 4: Edge Cases\n") |
| 202 | +cat("Empty array:", longest_increasing_subsequence(c())$max_length, "\n") |
| 203 | +cat("Single element:", longest_increasing_subsequence(c(5))$max_length, "\n") |
| 204 | +cat("All same elements:", longest_increasing_subsequence(c(3, 3, 3, 3))$max_length, "\n") |
| 205 | +cat("Strictly decreasing:", longest_increasing_subsequence(c(5, 4, 3, 2, 1))$max_length, "\n") |
| 206 | +cat("Strictly increasing:", longest_increasing_subsequence(c(1, 2, 3, 4, 5))$max_length, "\n\n") |
| 207 | + |
| 208 | +# Test 5: Larger Dataset |
| 209 | +cat("Test 5: Larger Dataset (n=20)\n") |
| 210 | +set.seed(42) |
| 211 | +nums_large <- sample(1:100, 20) |
| 212 | +cat("Input Array:", paste(nums_large, collapse = ", "), "\n\n") |
| 213 | + |
| 214 | +result_large <- longest_increasing_subsequence(nums_large) |
| 215 | +cat("Maximum Length:", result_large$max_length, "\n") |
| 216 | +cat("One LIS Sequence:", paste(result_large$lis_sequence, collapse = ", "), "\n\n") |
| 217 | + |
| 218 | +# Test 6: Performance Comparison |
| 219 | +cat("Test 6: Performance Comparison (n=1000)\n") |
| 220 | +n <- 1000 |
| 221 | +nums_perf <- sample(1:1000, n) |
| 222 | +capacity <- 200 |
| 223 | + |
| 224 | +start_time <- Sys.time() |
| 225 | +res_opt <- longest_increasing_subsequence_optimized(nums_perf) |
| 226 | +opt_time <- as.numeric(Sys.time() - start_time, units = "secs") |
| 227 | + |
| 228 | +cat("Optimized O(n log n) result:", res_opt, "\n") |
| 229 | +cat("Time taken:", sprintf("%.4f sec", opt_time), "\n") |
| 230 | + |
| 231 | +# Verify correctness with basic DP (smaller sample for time comparison) |
| 232 | +nums_small <- nums_perf[1:100] |
| 233 | +start_time <- Sys.time() |
| 234 | +res_basic <- longest_increasing_subsequence(nums_small) |
| 235 | +basic_time <- as.numeric(Sys.time() - start_time, units = "secs") |
| 236 | + |
| 237 | +cat("Basic O(n²) result (n=100):", res_basic$max_length, "\n") |
| 238 | +cat("Time taken:", sprintf("%.4f sec", basic_time), "\n") |
| 239 | + |
| 240 | +# Test 7: Real-world Example - Stock Prices |
| 241 | +cat("Test 7: Real-world Example - Stock Price Trend\n") |
| 242 | +stock_prices <- c(100, 102, 98, 105, 103, 107, 110, 108, 112, 115, 113, 118, 120, 117, 125) |
| 243 | +cat("Stock Prices:", paste(stock_prices, collapse = ", "), "\n") |
| 244 | + |
| 245 | +stock_result <- longest_increasing_subsequence(stock_prices) |
| 246 | +cat("Longest upward trend length:", stock_result$max_length, "\n") |
| 247 | +cat("Longest upward trend:", paste(stock_result$lis_sequence, collapse = ", "), "\n") |
| 248 | +cat("Percentage increase:", |
| 249 | + sprintf("%.2f%%", (stock_result$lis_sequence[length(stock_result$lis_sequence)] / |
| 250 | + stock_result$lis_sequence[1] - 1) * 100), "\n") |
0 commit comments