2. Add Two Numbers
思路:结合示例可以看到其实就是完成链表对应位置相加来模拟加法运算。
首先想到要返回头结点,则头结点的创建过程单独拿出来(两个都非空,则头结点一定存在)。并设置好pre=head。
在后续中模式pre->next=node,pre=node。在都不为空的情况下,每次都要检查上一次计算设置的carryFlag,以及重新设置carryFlag。
当有一个为空时,不用针对l1和12两种情况重复写代码,只需l1=l1?l1:l2即可。这时也要注意可能的进位,直到两个都为空。
两个都为空,再考察是否有进位,如果有要新建一个结点。这个其实合并了两种情况:初始长度相等和初始长度不等。
get:使用初始化列表(4种情况必须用初始化列表)。注意变量的作用域,不能仅在if..else...中完成后续使用变量的定义。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
bool carryFlag = 0;
ListNode *head = NULL;
if (l1->val+l2->val < 10) {
head = new ListNode(l1->val+l2->val);
carryFlag = 0;
} else {
head = new ListNode(l1->val+l2->val-10);
carryFlag = 1;
}
ListNode *pre = head;
ListNode *node = NULL;
l1 = l1->next;
l2 = l2->next;
while(l1 && l2) {
if (l1->val+l2->val+carryFlag < 10) {
node = new ListNode(l1->val+l2->val+carryFlag);
carryFlag = 0;
} else {
node = new ListNode(l1->val+l2->val+carryFlag-10);
carryFlag = 1;
}
pre->next = node;
pre = node;
l1 = l1->next;
l2 = l2->next;
}
l1 = l1?l1:l2;
if (l1) {
while(l1) {
if (l1->val+carryFlag < 10) {
node = new ListNode(l1->val+carryFlag);
carryFlag = 0;
} else {
node = new ListNode(l1->val+carryFlag-10);
carryFlag = 1;
}
pre->next = node;
pre = node;
l1 = l1->next;
}
}
if (carryFlag) {
node = new ListNode(1);
pre->next = node;
}
return head;
}
};
3. Longest Substring Without Repeating Characters
思路:该子串一定有开始位置在0-len-1,所以对子串的首字符在0-len-1之间遍历,在这个过程中不断后移临时pos,直到有重复字符出现。在这个过程中维持更新最大长度即可。在程序写好之后可以看到,如果是空串也是可以正确处理的。
这个问题可以参照kmp做一个优化,当这个过程中出现重复字符时,下一次遍历index就可以直接从该重复字符上一次出现的下一个位置开始,因为从前面的index开始的话必然会再次在该位置上看到该重复字符,不可能产生更长的不重复子串。则需要记录每个字符出现的位置,为此开一个int型的字符数大小的数组,并在每次index发生更新时进行重新赋值-1。
外层循环index从0-len-1遍历,内层循环pos从index到len-1做遍历,因为有index = arr[s[pos]]+1的更新,外层循环写成while比for更方便。
另外注意内层循环while(pos<len)跳出时有两种情况:找到重复字符,此时需要index更新开始下次大迭代;pos=len,此时不需要再继续了,这已经是可能的最大长度了。对这两种情况要判断并分别执行不同处理。
get:ascii字符的范围是0-127,所以正好可以开128的数组。(如果限定了具体字符范围,可能提前终止,比如最大可能长度就是26)
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int len = s.length();
int index = 0;
int arr[128];
int maxCnt = 0;
while(index < len) {
for (int i = 0; i < 128; i++) {
arr[i] = -1;
}
int cnt = 0;
int pos = index;
while(pos < len) {
if (arr[s[pos]] != -1) {
break;
} else {
arr[s[pos]] = pos;
cnt++;
}
pos++;
}
if (cnt > maxCnt) {
maxCnt = cnt;
}
if (pos == len) {
break;
} else {
index = arr[s[pos]]+1;
}
}
return maxCnt;
}
};
5. Longest Palindromic Substring
思路:求最长回文子串,想法用动态规划来解。首先要明确dp[i][j]的意义为首尾在i,j的回文串长度。状态转移方程:if(s[i] == s[j]),且中间构成回文串(这一点很重要),dp[i][j]=dp[i+1][j-1]+2;if(s[i]!=s[j]),dp[i][j]=0。另外,可以采取遍历回文子串中间字符的方法来做,复杂度同样为O(n^2)。
在写出大致转移方程后,一定要认真考虑三个方面。初始状态:dp[i][i] = 1。循环顺序:i递减,j递增;而且要满足i<j,不仅如此在s[i]=s[j]时,若i+1=j,则dp[i][j]=2,若dp[i+1][j-1]=0(即中间不构成回文串),则dp[i][j]=0。
另外,该题目要求返回最长回文子串,在长度发生更新时要记录下标。这里初始长度的设置:考虑到空串,直接返回空串;对非空串,初始长度设置为1,并记录下标为0,0。这一点很细节,如果不厘清的话容易出各种问题。
在解本题时,出现的问题主要在不自觉地混淆回文串与回文序列,导致写的方程不对。
get:c++中,string.substr(pos, n)可以用于截取子串。
class Solution {
public:
string longestPalindrome(string s) {
int len = s.length();
if (!len) {
return "";
}
int dp[len][len];
for (int i = 0; i < len; i++) {
dp[i][i] = 1;
}
int maxLen = 1;
int index_i = 0;
int index_j = 0;
for (int i = len-2; i >= 0; i--) {
for (int j = i+1; j < len; j++) {
if (s[i] == s[j]) {
if (i+1 == j) {
dp[i][j] = 2;
} else if (!dp[i+1][j-1]){
dp[i][j] = 0;
} else {
dp[i][j] = dp[i+1][j-1]+2;
}
} else {
dp[i][j] = 0;
}
if (dp[i][j] > maxLen) {
maxLen = dp[i][j];
index_i = i;
index_j = j;
}
}
}
return s.substr(index_i,index_j-index_i+1);
}
};
6. ZigZag Conversion
思路:这道题只需要简单模拟这个Z字形排列即可,不需要去找每一行在原string中出现位置。
具体来说:定义一个numRows维的vector,对string中每个字符依次加入到对应的vector即可。
在这个过程中,用row==0,row==numRows-1来确定下次移动方向的更改。另外需要将第一个字符放入第一个vector中,并设置preRow和direction,避免与前面规则的冲突。还有一些特殊情况需要处理:string为空,行为1。
get:vector型的数组:vector<int> vecArr[numRows]。
class Solution {
public:
string convert(string s, int numRows) {
int len = s.length();
if (!len) {
return "";
}
if (numRows == 1)
return s;
vector<int> vecArr[numRows];
vecArr[0].push_back(s[0]);
int direction = 1;
int preRow = 0;
for (int i = 1; i < len; i++) {
vecArr[preRow+direction].push_back(s[i]);
preRow = preRow+direction;
if (preRow == numRows-1) {
direction = -1;
} else if (preRow == 0) {
direction = 1;
}
}
string resultString = "";
for (int i = 0; i < numRows; i++) {
for (int j = 0; j < vecArr[i].size(); j++) {
resultString += vecArr[i][j];
}
}
return resultString;
}
};
8. String to Integer(atoi)
思路:按照string中字符出现顺序自左向右做atoi过程。
首先,略过所有‘ ’,在实现时while(index<len && str[index]==' ' && index++);,这里用到了短路求值的技巧。若index == len,则return 0(不存在不是‘ ’的字符,这里连带着将空串处理了)此时str[index]必须是+,-,0-9,否则return 0。此时若str[index]==+,-,设置sign后略过,否则设置sign=1。接下来的必须为0-9,开始处理数字。这个过程中需要用long long型来记录数字,并每次检查是否超过一定范围(字符串很长,即使long long也是不够用的)。最后按照要求return即可。
get:(result >1<<32)是不对的,1是int,左移32越界;正确写法:(result > (long long)1<<32)。
class Solution {
public:
int myAtoi(string str) {
int len = str.length();
int index =0;
while(index<len && str[index]==' ' && ++index);
if (index == len) {
return 0;
}
if ((str[index]<'0' || str[index]>'9') && str[index]!='+' && str[index]!='-') {
return 0;
}
int sign = 1;
if (str[index] == '+') {
index++;
} else if(str[index] == '-') {
sign = -1;
index++;
}
if (str[index]<='9' && str[index]>='0') {
while(index<len && str[index]=='0' && ++index);
long long result = 0;
while(index<len && str[index]<='9' && str[index]>='0') {
result = result*10+str[index]-'0';
if (result > (long long)1<<32) {
break;
}
index++;
}
int int_max = ~(1<<31);
int int_min = 1<<31;
result = result*sign;
if (result < int_min) {
result = int_min;
} else if (result > int_max) {
result = int_max;
}
return result;
} else {
return 0;
}
}
};