2.1 leetcode1 两数之和:
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那两个整数,并返回它们的数组下标。
思路: 遍历到 x 时,在 x 之前查找一个数 target - x, 如果存在则返回 x 和这个数的下标,可以创建一个哈希表,在遍历一个数 x 时,查找哈希表内有没有满足target - x 的数,如果有则满足题意,否则将 x 添加到哈希表中。由于哈希表插入、查找的时间复杂度都为 O(1) ,所以总的实践复杂度为 O(n)。
class Solution{
public:
vector<int>twoSum(vector<int> & nums, int target){
vector<int> res;
unordered_map<int,int> hash;
for(int i = 0; i < nums.size(); ++i){
auto x = target - nums[i];
if(hash.count(x))
return {hash[x],i};
hash[nums[i]] = i;
}
return {};
}
}
2.2 leetcode2 两数相加:
给你两个非空的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位数字。请你将两个数相加,并以相同形式返回一个表示和的链表。你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
**思路:**设两个相加的链表的值分别为a , b ,t 为进位,设定虚拟头节点dummy, 以及指向和链表尾节点的cur,当l1,l2以及进位t 不为0 时 进行循环,循环过程中在每一位运算中,和的新节点的值为 (a + b + t) % 10; 进位为 (a + b + t) / 10; cur 在每个循环内更新到指向新的尾节点。
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
// 设定虚拟头节点,以及指向和链表的尾节点的cur
ListNode * dummy = new ListNode(-1), cur = dummy;
int t = 0; // 进位
// 当l1,l2,以及进位为1时进行运算
while(l1 || l2 || t){
if(l1) t += l1->val;
if(l2) t += l2->val;
cur->next = new ListNode(t % 10);
cur = cur->next;
t /= 10;
}
return dummy->next;
}
};
2.3 无重复字符的最长子串
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
思路: 求一个序列里子串的最长值,可以按以 i 结尾做分类分为 n 类(0,1,2…n-1)。然后求这n类的最大值,可以使用哈希表记录到 i 的字符出现的次数,用 j 从0 开始向 i 方向移动,当hash[s[i]] > 1 时,就移动 j ,使 hash[s[i]] 变为1,此时 i - j + 1 就是 i 与 j 之间的最大 距离,依次枚举这 n 类,找出最大值即可。
双指针优化: 注意到当 i 向右移动时, j 也只能 向右移动,原因如图:所以问题存在单调性,可以使用双指针优化。
class Solution {
public:
int lengthOfLongestSubstring(string s) {
unordered_map<char,int>hash;
int res = 0;
for(int i = 0,j = 0; i < s.size(); ++ i){
hash[s[i]] ++;
while(hash[s[i]] > 1) hash[s[j ++]] --;
res = max(res,i - j + 1);
}
return res;
}
};
2.4 寻找两个正序数组的中位数
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。算法的时间复杂度应该为 O(log (m+n))
思路: 使用递归解决问题,可以将原问题转化为 在两个正序数组中寻找第 k 小的数。 如图,可以考虑 A[k/2] 和 B[k/2] 的关系将问题分为三种情况:
- 当 A[k/2] < B[k/2] 时 则分析如图,
- 当A[k/2] > B[k/2] 时,则可以将B[0-k/2] 部分删除,因为这一段一定在前 k 个数前面;
- 当 A[k/2] == B[k/2] 时,则A[k/2] 和 B[k/2] 就是要找的第k小的数。
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int total = nums1.size() + nums2.size();
// 总长为偶数,则找到中点左右的值,求平均数。
if(total % 2 == 0){
int left = findKthNumber(nums1,0,nums2,0,total / 2);
int right = findKthNumber(nums1,0,nums2,0,total / 2 + 1);
return (left + right) / 2.0;
}
// 总长为奇数,直接返回中点的数值。
else return findKthNumber(nums1,0,nums2,0,total / 2 + 1);
}
// 在两个正序数组中寻找第K小的数。使用递归,每次减去 k / 2 的部分,总的时间复杂的为O(k / 2)
int findKthNumber(vector<int> & nums1, int i, vector<int> & nums2, int j,int k ){
// 边界情况 1 : 假设nums1短一些, nums2长一点,否则就换一下
if(nums1.size() - i > nums2.size() - j) return findKthNumber(nums2,j,nums1,i,k);
// 如果nums1到头了,则返回nums2中第k 小的数
if(nums1.size() == i) return nums2[j + k - 1];
// 如果 k == 1 则在nums1,nums2的第一个元素间比较结果。
if(k == 1) return min(nums1[i],nums2[j]);
// si 是以 i 为起点的长度为 k / 2 的点后一位, sj 是以 j 为起点的长度为 k / 2 的后一位
int si = min(i + k / 2, int(nums1.size())), sj = j + k / 2;
// 如果第一段 k/2 的点大于 nums2的 k / 2 的点,说明无论如何从Nums2开始到sj这一段都在合并后数组的前k 个数之前,所以可以将 nums2这一段删去
if(nums1[si - 1] > nums2[sj - 1]) return findKthNumber(nums1,i,nums2,sj, k - (sj - j));
// 同理删除 nums1这一段的值。
else return findKthNumber(nums1,si,nums2,j, k -(si -i));
}
};
2.5 最长回文子串
给你一个字符串 s,找到 s 中最长的回文子串。
思路: 回文串分为两类,一类是总长是奇数个的回文串,一个是总数为偶数个的回文串,只要从中心点开始定义两个指针 l, r 向两边枚举,在不越界的前提下,看 l 与 r 指向的元素是否相同, 如果相同则继续向两侧走,不同则记录下最大长度, 此时回文串的长度为 r -1 - (l + 1) + 1 = r - l - 1, 然后使用substr function 从 l + 1, 开始截取 长度为 r - l - 1长度的字符串就是最长的子串了。
class Solution {
public:
string longestPalindrome(string s) {
string res;
// 枚举中心点。
for(int i = 0; i < s.size(); i++){
// 奇数条件下:l = i - 1, r = i + 1;
int l = i- 1, r = i + 1;
// 不越界并且l r 指向的字符相同,则l 向左走, r 向右走
while(l >= 0 && r < s.size() && s[l] == s[r]) l--,r++;
// 更新 res 的结果, 截取 l + 1 开始,长度为 r - l = 1 长度的字符串为最长回文子串。
if(res.size() < r-l-1) res = s.substr(l+1, r-l -1);
// 回文串总长偶数条件下, l = i, r = i + 1,开始枚举
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;
}
};
2.6 Z 字形变换
将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。
比如输入字符串为 "PAYPALISHIRING" 行数为 3 时,排列如下:
P A H N
A P L S I I G
Y I R
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"PAHNAPLSIIGYIR"。
请你实现这个将字符串进行指定行数变换的函数:
string convert(string s, int numRows);
思路: 定义一个指标 flag 表示移动方向, 初始值为 -1表示向上移动, 如果row == 1 || row == numrows - 1 时,即走到边界时改变移动的方向,然后之歌遍历字符,填入相应的数组中,最后再读出来即可。
class Solution {
public:
string convert(string s, int numRows) {
// 定义一个字符串数组,数组中存放读入对应位置的字符
vector<string> vs(numRows);
// 如果numRows == 1,则直接输出原字符串即可。
if (numRows < 2) return s;
// 定义游标flag表示移动的方向,初始-1表示向上移动
int row = 0, flag = -1;
for (int i = 0; i < s.size(); i++) {
// 遍历字符,填入对应位置的字符串里面。
vs[row] += s[i];
// 如果达到边界处,则改变移动的方向
if (row == 0 || row == numRows - 1)
flag = -flag;
row += flag;
}
string res = "";
for (auto r : vs)
res += r;
return res;
}
};
2.7 整数反转
给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。
如果反转后整数超过 32 位的有符号整数的范围 [−231, 231 − 1] ,就返回 0。
假设环境不允许存储 64 位整数(有符号或无符号)。
思路: c++中取余操作,正数取余得正数,负数取余得负数,对一个整数来说,翻转整数可以对其 % 10 获取最后一位数字,然后 除以 10 将原数最后一位删除,注意 对结果 r 来说,防止其越过 int 的范围, 在循环更新 r 之前为: r = r × 10 + x % 10 r = r \times 10 + x \% 10 r=r×10+x%10,先判断 r > ( I N T _ M A X − x % 10 ) ÷ 10 r > (INT\_MAX - x \% 10) \div 10 r>(INT_MAX−x%10)÷10 如果不等式成立则说明后续更新后溢出,所以返回 0.
class Solution{
public:
int reverse(int x){
int r = 0;
while(x){
// 更新之前先判断是否会溢出
if(r > 0 && r > (INT_MAX - x % 10) / 10) return 0;
if(r < 0 && r < (INT_MIN - x % 10) / 10) return 0;
r = r * 10 + x % 10;
x /= 10;
}
return r;
}
}
2.8 字符串转换整数 (atoi)
请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数(类似 C/C++ 中的 atoi 函数)。
函数 myAtoi(string s) 的算法如下:
读入字符串并丢弃无用的前导空格
检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。 确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正。
读入下一个字符,直到到达下一个非数字字符或到达输入的结尾。字符串的其余部分将被忽略。
将前面步骤读入的这些数字转换为整数(即,"123" -> 123, "0032" -> 32)。如果没有读入数字,则整数为 0 。必要时更改符号(从步骤 2 开始)。
如果整数数超过 32 位有符号整数范围 [−231, 231 − 1] ,需要截断这个整数,使其保持在这个范围内。具体来说,小于 −231 的整数应该被固定为 −231 ,大于 231 − 1 的整数应该被固定为 231 − 1 。
返回整数作为最终结果。
注意:
本题中的空白字符只包括空格字符 ' ' 。
除前导空格或数字后的其余字符串外,请勿忽略 任何其他字符。
思路: 模拟题, 首先去除前置空格,注意c++中空格为 ’ ', 随后判断读入的符号是 + 还是 - , 最后读入数字 字符是数字的条件是 s[k] >= ‘0’ && s[k] <= ‘9’, 最后每次另res * 10 + s[k] - ‘0’; 将字符串转化为数字, 注意 res * 10 + s[k]会爆 int, 必须判断一下, 还要注意, 正整数的最大值是2147483647, 负整数的最小值是 -2147483648, 所以在不考虑 正负号的res的过程中,res * 10 + s[k] - ‘0’ 可能正好等于 -2147483648,所以这一个要特判一下。
class Solution {
public:
int myAtoi(string s) {
int k = 0;
while( k < s.size() && s[k] == ' ') k ++; // 去除前置空格, k < s.size() 防止越界
if(k == s.size()) return 0; // 去除完空格后字符为空则返回0
// 根据读入的正负号记录下来
int minus = 1;
if(s[k] == '-') minus = -1, k++;
else if(s[k] == '+') k ++;
int res = 0;
// 读入数字, s[k] >= '0' && s[k] <= '9' 是数字
while(k < s.size() && s[k] >= '0' && s[k] <= '9'){
// -'0'将字符变为数字。
int x = s[k] - '0';
if(minus > 0 && res > (INT_MAX - x) / 10) return INT_MAX;
if(minus < 0 && -res < (INT_MIN + x) / 10) return INT_MIN;
if(-res * 10 - x == INT_MIN) return INT_MIN; // 必须特判。
res = res * 10 + x;
k++;
}
res *= minus;
return res;
}
};
2.9 回文数
给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。
回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
思路: 1、字符串做法: 将 x 转化为字符串,然后通过反向迭代器**string(s.rbegin(), s.rend())**将字符串翻转,然后比较两个字符串,如果相同则是回文数,否则不是回文数。
class Solution {
public:
bool isPalindrome(int x) {
if(x < 0) return false;
// to_string()函数将整数转换为字符串
string s = to_string(x);
// string()构造函数传入两个迭代器构造一个新的字符串通过反向迭代器调用。
return s==string(s.rbegin(),s.rend());
}
};
2、数值做法: 通过类似整数取反的操作, 将x 不断 % 10 取最低位, 再 / 10, 然后 r 不断 * 10 + x % 10 得到翻转之后的整数,然后比较两个数的值相等返回true,否则返回 false;
class Solution {
public:
bool isPalindrome(int x) {
if(x < 0) return false;
long long r = 0;
int px = x;
while(x){
r = r * 10 + x % 10;
x /= 10;
}
if(r == px) return true;
else return false;
}
};