LeetCode Question

Thought

The Sum Array

Definition

Consider we have an array sum[len][char] to store the total number of happy strings start with char when the total length is len.

  • char: We use 0, 1, 2 to represent a, b, c here
  • len: An non-negative integer specify the total length of the string

Array Elements

Let’s find the recursive relations between the elements in this array.

sum[len][0] = sum[len - 1][1] + sum[len - 1][2]

This is easy to understand: If the n-length happy string starting with a, then it could be:

"a" + any_happy_str(start="b", length=n - 1)
"a" + any_happy_str(start="c", length=n - 1)

The similar rule applies to sum[len][1] and sum[len][2], they just add up the value of array elements representing different starting characters in n - 1 length layer.

If we calculate the element based on this rule, we will find out the final array looks like the one below:

len 0  1  2  3  4  5  6  7  ...
===============================
[a] 0  1  2  4  8  16 32 64 ...
[b] 0  1  2  4  8  16 32 64 ...
[c] 0  1  2  4  8  16 32 64 ...

So the char dimension is unnecessary, and we have:

sum[length] = power_of_two(length - 1)  # length >= 1

Calculate -th Happy String with Length

Let’s start with the first character, we need to check about the sum array, the logic is like below:

if k <= arr[n][0]:
    first = 'a'
else if k <= arr[n][0] + arr[n][1]:
    first = 'b'
else if k <= arr[n][0] + arr[n][1] + arr[n][2]:
    first = 'c'
else:
    raise NoAnswer("Total number of happy strings is less than k")

If we use the optimized sum array definitions described below in Array Elements, we have:

if k <= arr[n]:
    first = 'a'
else if k <= arr[n] * 2:
    first = 'b'
else if k <= arr[n] * 3:
    first = 'c'
else:
    raise NoAnswer("Total number of happy strings is less than k")

Now, let’s consider how to actually determine every character in the happy string based on length and sequence number .

Define that “sequence number” as the index of the happy string in terms of lexical order starting at . For example, if there is an answer, then the answer’s sequence number will be . In another word, sequence number is that “how many happy strings before this happy string in terms of dictionary order”.

  • determined[]: Store already determined characters
  • offset: Store the smallest possible sequence number of determined[]

When we say the smallest possible sequence number of determined[], we are say that we assume that we choose characters for undetermined positions in a way that the final sequence number is the smallest. That actually means we are choosing the sequence of characters for the undetermined positions with the smallest possible lexical directory order.

Based on this, we will have the following algorithm:

# given by question
n = N
k = K
 
offset = 0
determined: list = []
cur_idx = -1
 
Char = Literal['a', 'b', 'c']
 
def valid_offset(v) -> bool:
    return v < k
 
def cur_len() -> int:
    return n - cur_idx
 
def cur_sum() -> int:
    # check about "The Sum Array" chapter for more info
    return sum[cur_len()]
 
def possible_choice(idx: num) -> list[Char]:
    _all = ['a', 'b', 'c']
    if idx == 0:
        return _all
    return _all.without_element(determined[idx - 1])
 
def solve() -> str:
    while valid_offset(offset) and cur_idx < n:
        # update index
        # pointing to positions we are going to determine a character for
        cur_idx++
        
        
        ch_list = possible_choice(cur_idx)
        find = False
        # go through all possible character for this position
        for ch in ch_list:
            # shift to next possible character if not exceeding 
            # the required sequence number
            if valid_offset(offset + cur_sum()):
                continue
            # could not shift, then choose this char
            find = True
            determined.append(ch)
        # if we shift until all possible char used up in a certain idx, 
        # that means we failed to find the answer
        # in another word, we could not reach k-th happy string
        if not find:
            raise NoAnswer()
    
    assert len(determined) == n
    return determined.join("")

Code Implementation

#include<string>
#include<vector>
 
using std::string, std::vector;
 
constexpr unsigned int MAX_N = 10;
 
class SumTable {
private:
    unsigned short m_sum[MAX_N + 1];
 
public:
    constexpr SumTable() :m_sum(MAX_N + 1) {
        m_sum[0] = 0;
        m_sum[1] = 1;
        for (int i = 2; i <= MAX_N; ++i) {
            m_sum[i] = m_sum[i - 1] * 2;
        }
    }
 
    constexpr unsigned short operator[](const size_t& idx) const {
        return m_sum[idx];
    }
};
 
constexpr auto sum = SumTable();
 
class Solution {
private:
    vector<char> determined;
    unsigned short offset = 0;
    unsigned short n;
    unsigned short k;
 
    bool valid_offset(const int& v) const {
        return v < k;
    }
 
    int cur_idx() const {
        return determined.size();
    }
 
    size_t cur_len() const {
        return n - cur_idx();
    }
 
    unsigned short cur_sum() const {
        return sum[cur_len()];
    }
 
    const vector<char>& possible_char() const {
        const static vector<char> all{ 'a','b','c' };
        const static vector<char> no_a{ 'b','c' };
        const static vector<char> no_b{ 'a','c' };
        const static vector<char> no_c{ 'a','b' };
 
        if (cur_idx() == 0) {
            return all;
        }
 
        auto back = determined.back();
        if (back == 'a') {
            return no_a;
        }
        else if (back == 'b') {
            return no_b;
        }
        return no_c;
    }
 
public:
    string getHappyString(int n, int k) {
        // init variables
        this->n = n;
        this->k = k;
        determined.clear();
        offset = 0;
 
        bool find;
        while (valid_offset(offset) && cur_idx() < n) {
            const auto& ch_list = possible_char();
 
            find = false;
            for (const auto& ch : ch_list) {
                if (valid_offset(offset + cur_sum())) {
                    offset += cur_sum();
                    continue;
                }
                find = true;
                determined.push_back(ch);
                break;
            }
            if (!find) {
                return "";
            }
        }
 
        string ans;
        for (const auto& ch : determined) {
            ans.push_back(ch);
        }
        return ans;
    }
};