I'm a little late with this answer, and from your question and the other answers I can't figure out if the problem has changed from TLE to memoization.
But if it's about reducing the execution time, I think your major problem is the creation of all those sub strings.
Below I've refactored your algorithm so it no longer creates strings but instead creates the values to check by looking chars up in the code string itself. I've changed the names of your variables - which doesn't improve the execution time but only my understanding of the code :-):
public int NumDecodings(string code)
{
if (code == null || code.Length == 0) return 0;
int count1 = Decode(code, 0, 1);
int count2 = Decode(code, 0, 2);
return count1 + count2;
}
public int Decode(string code, int offset, int numLength)
{
if (offset + numLength - 1 >= code.Length || code[offset] == '0')
return 0;
int value = 0;
if (numLength == 1)
value = code[offset] - '0';
else
{
value = (code[offset] - '0') * 10 + (code[offset + 1] - '0');
}
if (value > 26 || value < 1)
return 0;
else if (offset + numLength - 1 == code.Length - 1 && value < 27 && value > 0)
return 1;
int count1 = offset + numLength - 1 + 1 < code.Length ? Decode(code, offset + numLength, 1) : 0;
int count2 = offset + numLength - 1 + 2 < code.Length ? Decode(code, offset + numLength, 2) : 0;
return count1 + count2;
}
When testing against 5000 random valid strings with a max length of 60 the above refactoring reduces the total execution time from about 1100 to 60 ms in release mode.