Longestincreasingsubsequencegreedy

package dynamic

// LongestIncreasingSubsequenceGreedy is a function to find the longest increasing
// subsequence in a given array using a greedy approach.
// The dynamic programming approach is implemented alongside this one.
// Worst Case Time Complexity: O(nlogn)
// Auxiliary Space: O(n), where n is the length of the array(slice).
// Reference: https://www.geeksforgeeks.org/construction-of-longest-monotonically-increasing-subsequence-n-log-n/
func LongestIncreasingSubsequenceGreedy(nums []int) int {
	longestIncreasingSubsequence := make([]int, 0)

	for _, num := range nums {
		// find the leftmost index in longestIncreasingSubsequence with value >= num
		leftmostIndex := lowerBound(longestIncreasingSubsequence, num)

		if leftmostIndex == len(longestIncreasingSubsequence) {
			longestIncreasingSubsequence = append(longestIncreasingSubsequence, num)
		} else {
			longestIncreasingSubsequence[leftmostIndex] = num
		}
	}

	return len(longestIncreasingSubsequence)
}

// Function to find the leftmost index in arr with value >= val, mimicking the inbuild lower_bound function in C++
// Time Complexity: O(logn)
// Auxiliary Space: O(1)
func lowerBound(arr []int, val int) int {
	searchWindowLeft, searchWindowRight := 0, len(arr)-1

	for searchWindowLeft <= searchWindowRight {
		middle := (searchWindowLeft + searchWindowRight) / 2

		if arr[middle] < val {
			searchWindowLeft = middle + 1
		} else {
			searchWindowRight = middle - 1
		}
	}

	return searchWindowRight + 1
}