string manipulation
subsequence counting
algorithm
programming
text processing

Find the number of occurrences of a subsequence in a string

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Finding the number of occurrences of a subsequence within a string is a common problem in computer science, especially relevant in fields such as bioinformatics, text processing, and coding theory. Unlike substrings, which are contiguous sequences of characters, subsequences maintain the order of characters but do not require contiguity. This article will delve into the technicalities of finding the occurrences of a subsequence in a string, exploring various methods and examples to solidify understanding.

Problem Definition

Given a string `S` and a subsequence `T`, the task is to count how many distinct occurrences of `T` exist as a subsequence within `S`.

Example

Consider `S = "ababc"` and `T = "abc"`. The subsequence "abc" appears twice in "ababc":

  1. `S[0] = 'a'`, `S[1] = 'b'`, `S[3] = 'c'`
  2. `S[0] = 'a'`, `S[2] = 'b'`, `S[3] = 'c'`

Method: Dynamic Programming

Dynamic programming provides an efficient way to solve this problem by breaking it down into subproblems. Let's define a 2D table `dp` where `dp[i][j]` represents the number of times the subsequence `T[0...j-1]` appears in the string `S[0...i-1]`.

Steps & Formula

  1. Initialization:
    • `dp[0][0]` is initialized to 1 since an empty subsequence is a subsequence of any string.
    • For `i >= 1`, `dp[i][0]` is also initialized to 1 for the same reason.
  2. Filling the Table:
    • For each character `i` in `S` and each character `j` in `T`:
      • If `S[i-1]` matches `T[j-1]`:
        dp[i][j]=dp[i1][j1]+dp[i1][j]dp[i][j] = dp[i-1][j-1] + dp[i-1][j]
      • Otherwise:
        dp[i][j]=dp[i1][j]dp[i][j] = dp[i-1][j]
  3. Result: The value at `dp[m][n]` gives the required count of subsequences where `m` and `n` are lengths of `S` and `T` respectively.

Detailed Example

Let `S = "rabbbit"` and `T = "rabbit"`. Here's how the table is filled:

i\j01 ('r')2 ('a')3 ('b')4 ('b')5 ('i')6 ('t')
01000000
11100000
21100000
31110000
41111000
51112100
61112300
71112330
81112333

The entry at `dp[8][6]` (bottom-right corner) is 3, reflecting the number of ways to form "rabbit" from "rabbbit".

Complexity Analysis

  • Time Complexity: The dynamic programming approach has a time complexity of O(m×n)O(m \times n) where `m` and `n` are the lengths of `S` and `T` respectively.
  • Space Complexity: The space complexity is also O(m×n)O(m \times n) due to the dp table. However, optimizations can reduce this to O(n)O(n) by keeping only the current and previous rows.

Other Techniques

Recursion with Memoization

A recursive solution involves exploring all subsequences by including or excluding the current character, using memoization to cache results and avoid redundant calculations.

Combinatorial Techniques

If `S` and `T` are small enough, combinatorial approaches using bit masking can enumerate all subsequences, but these are rarely practical for larger inputs.

Conclusion

The dynamic programming approach provides a robust and efficient method for solving the problem of counting subsequences. Understanding the breakdown and construction of the `dp` table allows for handling larger strings and more complex subsequences efficiently. The application of this technique spans a variety of problems beyond the simple counting of subsequences, offering insights into string matching and alignment tasks. For practical implementations, careful consideration of time and space optimizations is essential.


Course illustration
Course illustration

All Rights Reserved.