diff --git a/Sprint-2/improve_with_precomputing/common_prefix/common_prefix.py b/Sprint-2/improve_with_precomputing/common_prefix/common_prefix.py index f4839e70..52a6b9e0 100644 --- a/Sprint-2/improve_with_precomputing/common_prefix/common_prefix.py +++ b/Sprint-2/improve_with_precomputing/common_prefix/common_prefix.py @@ -7,12 +7,21 @@ def find_longest_common_prefix(strings: List[str]): In the event that an empty list, a list containing one string, or a list of strings with no common prefixes is passed, the empty string will be returned. """ + if not strings or len(strings) < 2: + return "" + + # Pre-computation: Sort the strings lexicographically. + # The longest common prefix of any two strings in the collection + # will always be shared by at least one pair of adjacent strings in the sorted list. + sorted_strings = sorted(strings) + longest = "" - for string_index, string in enumerate(strings): - for other_string in strings[string_index+1:]: - common = find_common_prefix(string, other_string) - if len(common) > len(longest): - longest = common + + for i in range(len(sorted_strings) - 1): + common = find_common_prefix(sorted_strings[i], sorted_strings[i + 1]) + if len(common) > len(longest): + longest = common + return longest @@ -22,3 +31,16 @@ def find_common_prefix(left: str, right: str) -> str: if left[i] != right[i]: return left[:i] return left[:min_length] + + +# Time Complexity Comparison: +# Original version: It used a nested loop to check every possible pair of strings. +# This gave it a slow quadratic time complexity (O(N^2)), which gets very heavy +# and slow as the list grows larger. +# +# New version: We sort the strings alphabetically first. When a list is sorted, +# any two strings with the longest common prefix will always be right next to each other. +# Because of this, we only need a single loop to check neighbor pairs. +# +# Result: This changes the time complexity from O(N^2) to O(N log N), +# making the code much faster and more efficient. diff --git a/Sprint-2/improve_with_precomputing/count_letters/count_letters.py b/Sprint-2/improve_with_precomputing/count_letters/count_letters.py index 62c3ec07..7badf008 100644 --- a/Sprint-2/improve_with_precomputing/count_letters/count_letters.py +++ b/Sprint-2/improve_with_precomputing/count_letters/count_letters.py @@ -1,14 +1,32 @@ +from typing import Set + + def count_letters(s: str) -> int: """ - count_letters returns the number of letters which only occur in upper case in the passed string. + Returns the number of alphabetic letters that only occur in upper case in the passed string. + Optimized to O(N) time complexity using sets. """ - only_upper = set() - for letter in s: - if is_upper_case(letter): - if letter.lower() not in s: - only_upper.add(letter) + if not s: + return 0 + + # Collect all unique upper-case and lower-case characters + upper_chars: Set[str] = {char for char in s if char.isupper()} + lower_chars: Set[str] = {char.lower() for char in s if char.islower()} + + # Find upper-case letters whose lower-case versions do not exist in the string + only_upper = {char for char in upper_chars if char.lower() not in lower_chars} + return len(only_upper) -def is_upper_case(letter: str) -> bool: - return letter == letter.upper() +# Time and Space Complexity Comparison: +# +# Original version: +# - Time Complexity: O(N^2). It used a loop to look at every character, and inside the loop, it searched the entire string s (letter.lower() not in s). This nested search makes it quadratic and slow for long texts. +# - Space Complexity: O(N). It stored unique letters in a set. +# +# Refactored version (with Sets): +# - Time Complexity: O(N). It passes through the string to collect uppercase and lowercase characters into sets first. Because checking a set takes O(1) time, looking up the lowercase letter is instant. +# - Space Complexity: O(N). It uses extra memory to store unique characters in sets, but this is a minor trade-off for a massive increase in speed. +# +# Result: The time complexity drops from O(N^2) to O(N), making the code much faster and more efficient for large inputs.