目录
剑指Offer(5)--替换空格
把字符串中出现的每个空格替换成“%20”。
#include<iostream>
using namespace std;
//假设string空间够进行替换
void RelaceString(char *string, int length)
{
if (string == nullptr || length < 1)
return;
int originalLength = 0;
int spaceNum = 0;
int i = 0;
while (string[i] != '\0')
{
++originalLength;
if (string[i] == ' ')
++spaceNum;
++i;
}
int newLength = originalLength + spaceNum * 2;
if (newLength < originalLength)
return;
//开始时0位置放的是正常的元素,originalLength位置放的'\0'
while (originalLength >= 0 && newLength>originalLength)
{
if (string[originalLength] == ' ')
{
string[newLength--] = '0';
string[newLength--] = '2';
string[newLength--] = '%';
}
else
string[newLength--] = string[originalLength];
--originalLength;
}
}
int main(void)
{
const int length = 100;
char str[length] = "hdas l d";
RelaceString(str, 100);
for (char i : str)
cout << i;
cout << endl;
system("pause");
return 0;
}
剑指Offer(19)--正则表达式匹配
请实现一个函数用来匹配包括'.'和'*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但是与"aa.a"和"ab*a"均不匹配。
思路:
其实就是针对表达式中的*和.以及正常的字符分开来考虑。
#include<iostream>
using namespace std;
bool matchCore(char *str, char *pattern);
bool match(char *string, char *pattern)
{
if (string == nullptr || pattern == nullptr)
return false;
return matchCore(string, pattern);
}
bool matchCore(char *str, char *pattern)
{
if (*str == '\0' && *pattern == '\0')
return true;
//注意:*str == '\0' && *pattern != '\0'这种情况有可能是true
//比如:str=a,pattern=a*
if (*str != '\0' && *pattern == '\0')
return false;
if (*(pattern + 1) == '*')
{
if (*pattern == *str || (*pattern == '.' && *str != '\0'))
{
return matchCore(str + 1, pattern + 2)//对应情况是*前面的字符只出现了一次
|| matchCore(str + 1, pattern)//对应情况是*前面的字符出现了多次或者 *前面出现了.
/*
这种情况也会在if中发生
char str1[] = "abc";
char str2[] = "a*abc";
*/
|| matchCore(str, pattern + 2);
}
else
{
return matchCore(str, pattern + 2);
}
}
if (*str == *pattern || (*pattern == '.' && *str != '\0'))
return matchCore(str + 1, pattern + 1);
return false;
}
int main(void)
{
char str1[] = "abc";
char str2[] = "a*abc";
bool result = match(str1, str2);
if (result)
cout << "1" << endl;
else
cout << "0" << endl;
system("pause");
return 0;
}
char firstNotRepeatChar(char *string)
{
if (string == nullptr)
return '\0';
int hashTable[256];
for (int i = 0; i < 256; ++i)
hashTable[i] = 0;
char *str = string;
while (*str != '\0')
{
hashTable[*(str++)]++;
}
str = string;
while (*str != '\0')
{
if (hashTable[*str] == 1)
return *str;
++str;
}
return '\0';
}
剑指Offer(20)--表示数值的字符串
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100","5e2","-123","3.1416"和"-1E-16"都表示数值。 但是"12e","1a3.14","1.2.3","+-5"和"12e+4.3"都不是。
思路
这道题还是比较简单的。表示数值的字符串遵循如下模式:
[sign]integral-digits[.[fractional-digits]][e|E[sign]exponential-digits]
其中,('['和']'之间的为可有可无的部分)。
在数值之前可能有一个表示正负的'+'或者'-'。接下来是若干个0到9的数位表示数值的整数部分(在某些小数里可能没有数值的整数部分)。如果数值是一个小数,那么在小数后面可能会有若干个0到9的数位表示数值的小数部分。如果数值用科学记数法表示,接下来是一个'e'或者'E',以及紧跟着的一个整数(可以有正负号)表示指数。
#include<iostream>
using namespace std;
/*
扫描字符串中的0-9的位数,如果属于0-9,则++,否则停止,
最后判断是否在这个字符串中出现过0-9
*/
bool scanUnsignedInteger(const char **str)
{
const char *before = *str;
while (**str != '\0' && **str >= '0' && **str <= '9')
++(*str);
//如果为true,说明有过移动,说明出现过数字
return *str > before;
}
/*
扫描可能以正负的'+'或者'-'为起始的0-9的数值
*/
bool scanInteger(const char **str)
{
if (**str == '+' || **str == '-')
++(*str);
return scanUnsignedInteger(str);
}
bool isNumeric(const char *str)
{
if (str == nullptr)
return false;
bool numeric = scanInteger(&str);
if (*str == '.')
{
++str;
//小数点前面或者后面有数字就行
//scanUnsignedInteger(&str)是在判断小数点后面是否有数字
numeric = scanUnsignedInteger(&str) || numeric;
}
if (*str == 'e' || *str == 'E')
{
++str;
//e的前面必须有数字,e的整数部分必须是整数
//scanInteger(&str)是在判断e的后面是否为整数
numeric = numeric && scanInteger(&str);
}
return numeric && *str == '\0';
}
int main(void)
{
char *c1 = "12e+5";
if (isNumeric(c1))
cout << "yes" << endl;
else
cout << "no" << endl;
system("pause");
return 0;
}
剑指Offer(38)--字符串的排列
题目
输入一个字符串,打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
#include<iostream>
#include<string>
using namespace std;
/*
打印一个字符串的全部排列
思路:第二个位置和以后的位置,轮流来坐第一个位置,
生成字符串,然后,这些固定了第一个位置的字符串
再按照同样的方式确定第二个字符串(第三个及以后的
轮流来做第二个位置)
*/
void printAllPermutations(string str,int i)
{
if (i == str.size()-1)
{
cout << str << endl;
return;
}
for (int j = i; j < str.size(); ++j)
{
swap(str[i], str[j]);
printAllPermutations(str, i + 1);
}
}
int main(void)
{
string test = "abyc";
printAllPermutations(test, 0);
system("pause");
return 0;
}
打印一个字符串的全部子序列,包括空字符串
#include<iostream>
#include<string>
using namespace std;
/*
打印一个字符串的全部子序列,包括空字符串
思路:
每次遇到字符,就分两种进行递归,一种是要这个字符
另一种是不要
*/
//递归的第一步,先要想到如何终止
void printAllSub(string &str, int i, string &res)
{
if (i == str.size())
{
cout << res << endl;
return;
}
//选择不要这个字符
printAllSub(str, i + 1, res);
//选择要这个字符
printAllSub(str, i + 1, res + str[i]);
}
int main(void)
{
string test = "abc";
string s = "";
printAllSub(test, 0, s);
system("pause");
return 0;
}
剑指Offer(46)--把数字翻译成字符串
给定一个数字,按照如下规则翻译成字符串:0翻译成“a”,1翻译成“b”…25翻译成“z”。一个数字有多种翻译可能,例如12258一共有5种,分别是bccfi,bwfi,bczi,mcfi,mzi。实现一个函数,用来计算一个数字有多少种不同的翻译方法。
递归版本
从头到末尾统计,会有大量的重复计算
f(i)=f(i+1)+g(i,i+1)*f(i+2);当第i位和第i+1位两位数字拼接起来的数字在10~25的范围内,函数g(i,i+1)为1,否则为0;
#include<iostream>
#include<string>
using namespace std;
int TranslationCore(const string& number, int i, const int length);
int Translation(int number)
{
if (number < 0)
return 0;
string s = to_string(number);
int length = s.length();
return TranslationCore(s,0,length);
}
int TranslationCore(const string& number, int i, const int length)
{
if (i == length - 1)
return 1;
int digit1 = number[i] - '0';
int digit2 = number[i + 1] - '0';
int num = digit1 * 10 + digit2;
//能和下一个结合
if (num >= 10 && num <= 25)
{
if (i == length - 2)
return 2;
else
return TranslationCore(number, i + 1, length) +
TranslationCore(number, i + 2, length);
}
else
{
//如果不能和下一个结合,那么结果还是下一个
return TranslationCore(number, i + 1, length);
}
}
int main(void)
{
int number = 12258;
int result = Translation(number);
cout << result << endl;
system("pause");
return 0;
}
非递归版本-动态规划
int numDecodings(string s)
{
int length = s.length();
if (length == 0 || s[0] == '0')
return 0;
vector<int> v(length, 0);
v[0] = 1;
for (int i = 1; i < length; ++i)
{
if (s[i] != '0')
v[i] = v[i - 1];
if (s[i - 1] == '1' || s[i - 1] == '2' && s[i] >= '0' && s[i] <= '6')
{
if (i == 1)
v[i] += 1;
else
v[i] += v[i - 2];
}
}
return v[length - 1];
}
剑指Offer(48)--最长不重复字符的子字符串
题目:
请从字符串中找出一个最长的不包含重复数字的子字符串,计算该最长子字符串的长度。假设字符串中仅包含 ‘a’~’z’ 之间的字符。例如,在字符串 “arabcacfr” 中,最长的不包含重复字符的子字符串是 “rabc” 或 “acfr”,所以应当输出 4。
思路:
从string由前到后进行统计,如果没有出现重复的字符,那么curLength就一直++;直到遇上一个先前已经出现过的字符停住进行判断,如果先前已经出现过的那个字符不在当前的curLength范围中,那么CurLength继续++,不受影响。如果出现在当前的curLength范围中,那么判断此时的这个curLength和maxLength的大小关系以进行更新,而且curLength也将从先前出现过的那个字符的后一个字符开始计算。
#include<iostream>
#include<string>
using namespace std;
int SubString(const string& str)
{
int curLength = 0;
int maxLength = 0;
int *posiotion = new int[26];
for (int i = 0; i < 26; ++i)
posiotion[i] = -1;
for (int i = 0; i < str.length(); ++i)
{
int prevIndex = posiotion[str[i] - 'a'];
if (prevIndex<0 || i - prevIndex>curLength)
++curLength;
else
{
if (curLength > maxLength)
maxLength = curLength;
curLength = i - prevIndex;
}
posiotion[str[i] - 'a'] = i;
}
if (curLength > maxLength)
maxLength = curLength;
delete[] posiotion;
return maxLength;
}
剑指Offer(58)--翻转字符串和左旋转字符串
翻转单词顺序。比如“I am a student” -> "student a am I"
思路:
第一步:翻转句子中的所有字符
第二步:翻转每个单词中字符的顺序
#include<iostream>
using namespace std;
void Reverse(char *begin, char *end)
{
while (begin < end)
{
char temp = *begin;
*begin = *end;
*end = temp;
++begin;
--end;
}
}
char *ReverseString(char *string)
{
if (string == nullptr)
return nullptr;
char *m_begin = string;
char *m_end = string;
while (*m_end != '\0')
++m_end;
--m_end;
//翻转整个句子
Reverse(m_begin, m_end);
m_begin = m_end = string;
while (*m_begin != '\0')
{
if (*m_begin == ' ')
{
++m_begin;
++m_end;
}
else if (*m_end == ' ' || *m_end == '\0')
{
Reverse(m_begin, --m_end);
m_begin = ++m_end;
}
else
{
++m_end;
}
}
return string;
}
左旋转字符串
输入字符串abcdefg和2,得到cdefgab
思路:
以n为分割点,先旋转前半部分
再旋转后半部分
最后旋转整个字符串
右旋字符串
输入字符串abcdefg和2,得到cdefgab
思路:
旋转整个字符串
以n为分割点,先旋转前半部分
再旋转后半部分
#include<iostream>
using namespace std;
void Reverse(char *begin, char *end)
{
while (begin < end)
{
char temp = *begin;
*begin = *end;
*end = temp;
++begin;
--end;
}
}
char *LeftRotateString(char *string, int n)
{
if (string == nullptr || n < 0)
return nullptr;
int length = 0;
char *str = string;
while (*str != '\0')
{
++length;
++str;
}
if (n > length)
return nullptr;
char *firstStart = string;
char *firstEnd = string + n - 1;
char *secondStart = string + n;
char *secondEnd = --str;
//翻转字符串的前面n个字符
Reverse(firstStart, firstEnd);
//翻转字符串的后面部分
Reverse(secondStart, secondEnd);
//翻转整个字符串
Reverse(firstStart, secondEnd);
return string;
}
剑指Offer(50)--第一个只出现一次的字符
思路:定义哈希表的键值是字符,而值是该字符出现的次数。然后从头开始扫描字符串两次。第一次扫描字符串时,每扫描到一个字符,就在哈希表的对应项中把次数加1.接下来第二次扫描时,每扫描到一个字符,就从哈希表中得到该字符出现的次数。这样,第一个只出现一次的字符就是符合要求的输出。
char FirstNotRepeatingChar(const char* string)
{
if (string == nullptr)
return '\0';
const int tableSize = 256;
int hashTable[tableSize];
for (int i = 0; i < tableSize; ++i)
hashTable[i] = 0;
const char* pHashKey = string;
while (*(pHashKey) != '\0')
hashTable[*(pHashKey++)] ++;
pHashKey = string;
while (*pHashKey != '\0')
{
if (hashTable[*pHashKey] == 1)
return *pHashKey;
pHashKey++;
}
return '\0';
}
int main(void)
{
char string[] = "yaabc";
char result = FirstNotRepeatingChar(string);
cout << result << endl;
system("pause");
return 0;
}
#include<iostream>
using namespace std;
char firstNotRepeatChar(char *string)
{
if (string == nullptr)
return '\0';
int hashTable[256];
for (int i = 0; i < 256; ++i)
hashTable[i] = -1;
char *str = string;
int index = 0;
while (*str != '\0')
{
//如果某个字符是第一次出现,赋予以它为下标的值为index(越小代表这个字符越早出现)
if (hashTable[*str] == -1)
hashTable[*str] = index;
//如果某个字符不是第一次出现,赋予以它为下标的值为-2
else if (hashTable[*str] >= 0)
hashTable[*str] = -2;
++ str;
++index;
}
str = string;
//表示一个很大的数
int minIndex = 0x7FFFFFFF;
char ch = '\0';
for (int i = 0; i < 256; ++i)
{
if (hashTable[i] > 0 && hashTable[i] < minIndex)
{
ch = (char)(i);
minIndex = hashTable[i];
}
}
return ch;
}
int main(void)
{
char string[] = "ababckc";
char result = firstNotRepeatChar(string);
cout << result << endl;
system("pause");
return 0;
}
字符串切割
void split(const char *s, vector<string> &strs, char delim = ' ') {
if(s == nullptr) {
return;
}
const char *head, *tail;
head = tail = s;
while(*head != '\0') {
while(*head != '\0' && *head == delim) {
head++;
}
tail = head;
while(*tail != '\0' && *tail != delim) {
tail++;
}
if(head != tail) {
strs.push_back(string(head, tail));
head = tail;
} else {
break;
}
}
}
int main()
{
char *s = "ddsa das eqw 23 g gf";
vector<string> vs;
split(s, vs);
for (auto i : vs)
cout << i << endl;
system("pause");
return 0;
}
最长回文子串
思路一:
中心扩展就是把给定的字符串的每一个字母当做中心,向两边扩展,这样来找最长的子回文串。算法复杂度为O(N^2)。
但是要考虑两种情况:
1、像aba,这样长度为奇数。
2、像abba,这样长度为偶数。
所以得走两遍,都看一下。
string findLongestPalindrome(string s)
{
int length = s.size();
int maxLength = 0;
int start = 0;
//当字符串的长度为奇数
for (int i = 0; i < length; ++i)
{
int j = i - 1, k = i + 1;
while (j >= 0 && k < length &&s[j] == s[k])
{
if (k - j + 1 > maxLength)
{
maxLength = k - j + 1;
start = j;
}
--j;
++k;
}
}
//当字符串的长度为偶数
for (int i = 0; i < length; ++i)
{
int j = i , k = i + 1;
while (j >= 0 && k < length &&s[j] == s[k])
{
if (k - j + 1 > maxLength)
{
maxLength = k - j + 1;
start = j;
}
--j;
++k;
}
}
if (maxLength > 0)
return s.substr(start, maxLength);
}
思路二:动态规划
/*
动态规划法,时间复杂度为O(n^2),空间复杂度为O(n^2)
*/
string longestPalindrome(string s)
{
int const n = s.size();
vector<vector<bool>> v(n, vector<bool>(n, false));
size_t maxLength = 1;//最长回文子串的长度
size_t start = 0;//最长回文子串的起点
for (size_t i = 0; i < s.size(); ++i)
{
v[i][i] = true;
for (size_t j = 0; j < i; ++j)
{
//v[j][i]==true;说明s字符串在i-j区间内为回文结构
//i-j<2说明是相邻的相等,这也算回文
v[j][i] = (s[j] == s[i] && (i - j < 2 || v[j + 1][i - 1]));
if (v[j][i] && maxLength < (i - j + 1))
{
maxLength = i - j + 1;
start = j;
}
}
}
return s.substr(start, maxLength);
}
注意:
#include<iostream>
using namespace std;
/*
下面这个代码说明,如果形参是指针,那么在函数中
经过对指针的操作之后,发生变化的是指针指向的位置
的值,而指针所指的位置不会变化,比如下面的str++,
在函数返回时并未发生移动
*/
void fun1(char *str)
{
*str = 'k';
str++;
}
void fun2(char *str)
{
char *str1 = str;
cout << *str << endl;
fun1(str);
cout << *str << endl;
}
int main(void)
{
char string[] = "abc";
fun2(string);
system("pause");
return 0;
}
求两个字符串的最长公共子串
最长公共子串要求在原字符串中是连续的,而子序列只需要保持相对顺序一致,并不要求连续。
动态规划解题,状态转移方程如下:
问题:有两个字符串str和str2,求出两个字符串中最长公共子串长度。
比如:str=acbcbcef,str2=abcbced,则str和str2的最长公共子串为bcbce,最长公共子串长度为5。
#include<iostream>
#include<vector>
#include<string>
using namespace std;
string getLCS(string str1, string str2)
{
int maxLen = 0, maxEnd = 0;
int m = str1.size(), n = str2.size();
vector<vector<int>> record(m + 1 , vector<int>(n + 1, 0));
for (int i = 1; i <= m; ++i)
{
for (int j = 1; j <= n; ++j)
{
if (str1[i-1] == str2[j-1])
record[i][j] = record[i - 1][j - 1] + 1;
else
record[i][j] = 0;
if (record[i][j]>maxLen)
{
maxLen = record[i][j];
maxEnd = i - 1;
}
}
}
return str1.substr(maxEnd - maxLen + 1, maxLen);
}
int main(void)
{
string str1 = "acbctbcef", str2 = "abcbced";
cout << getLCS(str1, str2) << endl;
system("pause");
return 0;
}
原文地址:https://blog.csdn.net/qq_25800311/article/details/81607168
求两个字符串的最长公共子序列
动态规划解题,状态转移方程如下:
int longestSubstring(string s1, string s2)
{
if (s1.size() == 0 || s2.size() == 0)
return 0;
int m = s1.size(), n = s2.size();
vector<vector<int>> v(m + 1, vector<int>(n + 1, 0));
for (int i = 1; i <= m; ++i)
{
for (int j = 1; j <= n; ++j)
if (s1[i - 1] == s2[j - 1])
v[i][j] = v[i - 1][j - 1] + 1;
else
v[i][j] = v[i - 1][j] > v[i][j - 1] ? v[i - 1][j] : v[i][j - 1];
}
return v[m][n];
}
上面的程序是没有得出最长子序列,只是得出了长度;如果求解最长子序列,则需要进行记录。
#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
using namespace std;
int longestSubstring(string s1, string s2, vector<vector<int>> &v, vector<vector<char>> &help)
{
if (s1.size() == 0 || s2.size() == 0)
return 0;
int m = s1.size(), n = s2.size();
v.resize(m + 1);
help.resize(m + 1);
for (int i = 0; i < v.size(); ++i)
v[i].resize(n + 1);
for (int i = 0; i < help.size(); ++i)
help[i].resize(n + 1);
for (int i = 1; i <= m; ++i)
{
for (int j = 1; j <= n; ++j)
if (s1[i - 1] == s2[j - 1])
{
v[i][j] = v[i - 1][j - 1] + 1;
help[i][j] = 'c';//标记
}
else if (v[i - 1][j] >= v[i][j - 1]) {
v[i][j] = v[i - 1][j];
help[i][j] = 'u';//从上边下来的
}
else {
v[i][j] = v[i][j-1];
help[i][j] = 'l';//从左边下来的
}
}
return v[m][n];
}
void print_lcs(vector< vector<char>> &help, string s, int m, int n)
{
if (m == 0 || n == 0)
return;
if (help[m][n] == 'c') {
print_lcs(help, s, m - 1, n - 1);
cout << s[n - 1];
}
else if (help[m][n] == 'u')
print_lcs(help, s, m - 1, n);
else
print_lcs(help, s, m, n - 1);
}
int main()
{
string s1 = "afhgta";
string s2 = "fktsad";
vector< vector<int>> v;
vector< vector<char>> help;
int result = longestSubstring(s1, s2, v, help);
cout<<result<<endl;
print_lcs(help, s2, s1.size(), s2.size());
system("pause");
return 0;
}
最长递增子序列
问题
给定一个长度为N的数组,找出一个最长的单调自增子序列(不一定连续,但是顺序不能乱)。例如:给定一个长度为6的数组A{5, 6, 7, 1, 2, 8},则其最长的单调递增子序列为{5,6,7,8},长度为4。
解法1:最长公共子序列法
这个问题可以转换为最长公共子序列问题。如例子中的数组A{5,6, 7, 1, 2, 8},则我们排序该数组得到数组A‘{1, 2, 5, 6, 7, 8},然后找出数组A和A’的最长公共子序列即可。显然这里最长公共子序列为{5, 6, 7, 8},也就是原数组A最长递增子序列。
解法2:动态规划法(时间复杂度O(N^2))
设长度为N的数组为{a0,a1, a2, ...an-1),则假定以aj结尾的数组序列的最长递增子序列长度为L(j),则L(j)={ max(L(i))+1, i<j且a[i]<a[j] }。也就是说,我们需要遍历在j之前的所有位置i(从0到j-1),找出满足条件a[i]<a[j]的L(i),求出max(L(i))+1即为L(j)的值。最后,我们遍历所有的L(j)(从0到N-1),找出最大值即为最大递增子序列。时间复杂度为O(N^2)。
例如给定的数组为{5,6,7,1,2,8},则L(0)=1, L(1)=2, L(2)=3, L(3)=1, L(4)=2, L(5)=4。所以该数组最长递增子序列长度为4,序列为{5,6,7,8}。算法代码如下:
#include<iostream>
#include<vector>
using namespace std;
int LongestIncreSubseq(const vector<int> &v)
{
int size = v.size();
vector<int> record(size, 1);
for (int j = 1; j < size; ++j)
{
for (int i = 0; i < j; ++i)
if (v[j]>v[i] && record[j] < record[i] + 1)
record[j] = record[i] + 1;
}
int result = 0;
for (int i = 0; i < size; ++i)
{
if (record[i] > result)
result = record[i];
}
return result;
}
int main()
{
vector<int> v { 1, 4, 5, 6, 2, 3, 8 }; //测试数组
cout << LongestIncreSubseq(v) << endl;
system("pause");
return 0;
}
最长公共前缀
#include<iostream>
#include<vector>
#include<string>
using namespace std;
/*
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 ""。
*/
string longestCommonPrefix(vector<string>& strs)
{
string result = "";
if (strs.empty())
return result;
string s1 = strs[0];
for (int i = 0; i < s1.size();++i)
{
for (int j = 1; j < strs.size(); ++j)
{
if (s1[i] != strs[j][i])
return result;
}
result += s1[i];
}
return result;
}
字符串匹配
int ViolentMatch(string s, string p)
{
int len1 = s.size();
int len2 = p.size();
int i = 0, j = 0;
while (i < len1 && j < len2)
{
if (s[i] == s[j])
{
++i;
++j;
}
else
{
i = i - j + 1;
j = 0;
}
}
if (j == p.size())
return i - j;
else
return -1;
}
字符串相加
string addStrings(string num1, string num2) {
string res = "";
int m = num1.size(), n = num2.size(), i = m - 1, j = n - 1, carry = 0;
while (i >= 0 || j >= 0) {
int a = i >= 0 ? num1[i--] - '0' : 0;
int b = j >= 0 ? num2[j--] - '0' : 0;
int sum = a + b + carry;
res.insert(res.begin(), sum % 10 + '0');
carry = sum / 10;
}
return carry ? "1" + res : res;
}