本系列题解语言使用C ++ 11
每一题只讲一种解法(结合时空复杂度以及代码难度选取最优解)
T1.两数之和
哈希表法:
使用哈希表,可以将寻找 target - x 的时间复杂度降低到从O(1)
这样我们创建一个哈希表,对于每一个 x,我们首先查询哈希表中是否存在 target - x,然后将 x 插入到哈希表中,即可保证不会让 x 和自己匹配。
(两个元素x,y必然是一前一后出现的,如果存在符合条件的解,在遍历到x时,哈希表里没有符合的y,此时把x加入到了哈希表里,当遍历到y时,就可以在哈希表里找到对应的x了,所以只需要一次遍历)
tip:
- 本题需要用unordered_map,其底层实现为哈希表,可以做到O(1)而map底层为红黑树,可以做到O(nlogn)
- hash.count(x) 查询元素r在哈希表中出现过几次
class Solution { public: vector<int> twoSum(vector<int>& nums, int target) { unordered_map<int, int> heap; // unordered_map底层实现是哈希表 for (int i = 0; i < nums.size(); i ++){ int r = target - nums[i]; if (heap.count(r)) return {heap[r], i}; heap[nums[i]] = i; // 将当前数加入哈希表 } return {} //本行不可能执行,最好加上防止报错 } };
T2.两数相加
链表模拟:
将两个链表看成是相同长度的进行遍历,如果一个链表较短则在前面补 0,比如 987 + 23 = 987 + 023 = 1010.每一位计算的同时需要考虑上一位的进位问题,而当前位计算结束后同样需要更新进位值如果两个链表全部遍历完毕后,进位值为 1,则在新链表最前方添加节点 1
tip:
- 对于链表问题,返回结果为头结点时,通常需要先初始化一个预先指针 dummy,该指针的下一个节点指向真正的头结点 head。目的在于链表初始化时无可用节点值,而且链表构造过程需要指针移动,进而会导致头指针丢失,无法返回结果。
- new其实就是告诉计算机开辟一段新的空间,但是和一般的声明不同的是,new开辟的空间在堆上,而一般声明的变量存放在栈上。通常来说,当在局部函数中new出一段新的空间,该段空间在局部函数调用结束后仍然能够使用,可以用来向主函数传递参数。另外需要注意的是,new的使用格式,new出来的是一段空间的首地址。所以一般需要用指针来存放这段地址。具体的代码如下:
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode() : val(0), next(nullptr) {} * ListNode(int x) : val(x), next(nullptr) {} * ListNode(int x, ListNode *next) : val(x), next(next) {} * }; */ class Solution { public: ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) { auto dummy = new ListNode(-1), cur = dummy; //建立虚拟原点,cur储存链尾 int t = 0; while(l1 || l2 || t){ if(l1) t += l1->val, l1 = l1->next; if(l2) t += l2->val, l2 = l2->next; cur = cur->next = new ListNode(t % 10); //更新cur t /= 10; } return dummy->next; //返回真实头节点 } };
T3.无重复字符的最长子串
经典双指针
使用unordered_map维护滑动窗口
tip:
- 也可以用std::unordered_set去重
class Solution { public: int lengthOfLongestSubstring(string s) { unordered_map<char, int> heap; //使用哈希表将字符串和索引关联性存储 int res = 0; for (int i = 0, j = 0; i < s.size(); i ++){ heap[s[i]] ++; //记录新字符 while(heap[s[i]] > 1) heap[s[j ++ ]] --; //移动j,直到所维护区间不重复 res = max(res, i - j + 1); } return res; } };
T4.寻找两个正序数组的中位数
class Solution { public: double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) { int tot = nums1.size() + nums2.size(); if(tot % 2 == 0){ //分奇偶讨论 int left = find(nums1, 0, nums2, 0, tot / 2); int right = find(nums1, 0, nums2, 0, tot / 2 + 1); return (left + right) / 2.0; //一定要用2.0 } else return find(nums1, 0, nums2, 0, tot / 2 + 1); } int find(vector<int>& nums1, int i, vector<int>& nums2, int j, int k){ if(nums1.size() - i > nums2.size() - j) return find(nums2, j, nums1, i, k); if(k == 1){ //特判 if(nums1.size() == i) return nums2[j]; else return min(nums1[i], nums2[j]); } if(nums1.size() == i) return nums2[j + k - 1]; int si = min((int)nums1.size(), i + k / 2), sj = j + k - k / 2; if(nums1[si - 1] > nums2[sj - 1]) return find(nums1, i, nums2, sj, k - (sj - j)); else return find(nums1, si, nums2, j, k - (si - i)); } };
T5.最长回文子串
双指针法:
tip:
substr(begin(), len()):返回从begin()为开头, 长度为len()的子串
class Solution { public: string longestPalindrome(string s) { string res; //答案返回字符串 for (int i = 0; i < s.size(); i ++){ int l = i - 1, r = i + 1; // 当s.size()是奇数时,l = i - 1, r = i + 1 while (l >= 0 && r < s.size() && s[l] == s[r]) l --, r ++; if (res.size() < r - l - 1) res = s.substr(l + 1, r - l - 1); l = i, r = i + 1; //当s.size()是偶数时,l = i, r = i + 1 while (l >= 0 && r < s.size() && s[l] == s[r]) l --, r ++; if(res.size() < r - l - 1) res = s.substr(l + 1, r - l - 1); } return res; } };
T6.N 字形变换
class Solution { public: string convert(string s, int numRows) { if (numRows < 2) return s; // 给定行数为 1 时结果与原字符串一样 vector<string> res(numRows); // 创建 res 保存每行结果 int i = 0; // 行数标志 int flag = -1; // 往上走还是往下走的标志 for (char &ch : s) { // 遍历 s res[i] += ch; if (i == 0 || i == numRows - 1) { // 行首行尾变向 flag = -flag; } i += flag; } for (int i = 1; i < numRows; i++) { // 将每行接起来就是结果 res[0] += res[i]; } return res[0]; } };
T7. 整数反转
tip:
- c++取模特性:正数取模 得到正数,负数取模得到负数
- INT_MAX == 2^32 INT_MIN == -2^32
- 各个位数合成使用秦九韶算法
class Solution { public: int reverse(int x) { long long res = 0; //记得开long long while(x){ res = res * 10 + x % 10; //秦九韶算法 x /= 10; } if(res > INT_MAX) return 0; //INT_MAX == 2^32 if(res < INT_MIN) return 0; return res; } };
T8.字符串转换整数 (atoi)
本题需要处理好一以下几点:
- 去除空白字符。
- 记录正负号
- 边界判断:当前数值×10+num会不会超出边界
class Solution { public: int myAtoi(string s) { int k = 0; while (k < s.size() && s[k] == ' ') k ++; //特判掉空字符串和前导空格 int minus = 1; //用int存储符号 if(s[k] == '-') minus = -1, k ++; //判断符号 else if (s[k] == '+') k ++; long long res = 0; //日常开longlong while(k < s.size() && s[k] >= '0' && s[k] <= '9') { res = res * 10 + s[k] - '0'; //秦九韶算法 k ++; if(res > INT_MAX) break; //优化 } res *= minus; if(res > INT_MAX) res = INT_MAX; //最后处理溢出 if(res < INT_MIN) res = INT_MIN; return res; } };
T9.回文数
经典判断回文串:
- 方法一(反向迭代器):return x == string(s.rbegin(), s.rend());
- 方法二:秦九韶算法
class Solution { public: bool isPalindrome(int x) { // if(x < 0) return 0; // string s = to_string(x); // return s == string(s.rbegin(), s.rend()); if(x < 0) return 0; int y = x; long long res = 0; while(x) { res = res * 10 + x % 10; x /= 10; } return y == res; } };
class Solution {
public:
bool isMatch(string s, string p) {
int n = s.size(), m = p.size();
s = ' ' + s, p = ' ' + p;
vector<vector<bool>> f(n + 1, vector<bool>(m + 1));
f[0][0] = true;
for(int i = 0; i <= n; i ++)
for(int j = 1; j <= m; j ++){
if(j + 1 <= m && p[j + 1] == '*') continue;
if(i && p[j] != '*')
f[i][j] = f[i - 1][j - 1] && (s[i] == p[j] || p[j] == '.') ;
else if(p[j] == '*')
f[i][j] = f[i][j - 2] || i && f[i - 1][j] && (s[i] == p[j - 1] || p[j - 1] == '.');
}
return f[n][m];
}
};