题目:整数反转
-
给你一个 32 位的有符号整数
x
,返回将x
中的数字部分反转后的结果。如果反转后整数超过 32 位的有符号整数的范围 $[−2^{31}, 2^{31} − 1]
$,就返回 0。 -
记 rev 为翻转后的数字,为完成翻转,我们可以重复「弹出」x 的末尾数字,将其「推入」rev 的末尾,直至 x 为 0。
-
class Solution { public: int reverse(int x) { int rev=0; while(x!=0){ if(rev<INT_MIN/10 || rev > INT_MAX/10){ return 0; } int dig=x%10; x/=10; rev = rev*10+dig; } return rev; } };
-
时间复杂度:O(log∣x∣)。翻转的次数即 x 十进制的位数。空间复杂度:O(1)。
-
题目:字符串转换整数 (atoi)
-
请你来实现一个
myAtoi(string s)
函数,使其能将字符串转换成一个 32 位有符号整数(类似 C/C++ 中的atoi
函数)。函数myAtoi(string s)
的算法如下:- 读入字符串并丢弃无用的前导空格
- 检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。 确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正。
- 读入下一个字符,直到到达下一个非数字字符或到达输入的结尾。字符串的其余部分将被忽略。
- 将前面步骤读入的这些数字转换为整数(即,“123” -> 123, “0032” -> 32)。如果没有读入数字,则整数为
0
。必要时更改符号(从步骤 2 开始)。 - 如果整数数超过 32 位有符号整数范围
$[−2^{31}, 2^{31} − 1]$
,需要截断这个整数,使其保持在这个范围内。具体来说,小于−231
的整数应该被固定为−231
,大于231 − 1
的整数应该被固定为231 − 1
。 - 返回整数作为最终结果。
-
class Solution { public: int myAtoi(string s) { unsigned long len = s.length(); int index = 0; while(index<len){ if(s[index]!=' '){ break; } index++; } if(index==len){ return 0; } int sign=1; if(s[index]=='+'){ index++; }else if(s[index]=='-'){ sign = -1; index++; } int res=0; while(index<len){ char temp=s[index]; if(temp<'0' || temp>'9'){ break; } if(res>INT_MAX/10 ||(res==INT_MAX/10 && (temp-'0')>INT_MAX%10)){ return INT_MAX; } if(res<INT_MIN/10 || (res==INT_MIN/10 && (temp-'0')>-(INT_MIN%10))){ return INT_MIN; } res = res*10+sign*(temp-'0'); index++; } return res; } };
-
时间复杂度:O(N),这里 N 为字符串的长度;空间复杂度:O(1)。
-
字符串处理的题目往往涉及复杂的流程以及条件情况,如果直接上手写程序,一不小心就会写出极其臃肿的代码。因此,为了有条理地分析每个输入字符的处理方法,我们可以使用自动机这个概念:我们的程序在每个时刻有一个状态 s,每次从序列中输入一个字符 c,并根据字符 c 转移到下一个状态 s。这样,我们只需要建立一个覆盖所有情况的从 s 与 c 映射到 s 的表格即可解决题目中的问题。本题可以建立如下图所示的自动机:
-
另外自动机也需要记录当前已经输入的数字,只要在
s'
为in_number
时,更新我们输入的数字,即可最终得到输入的数字。 -
class A{ string state="start"; unordered_map<string,vector<string>> table={ {"start",{"start","signed","in_number","end"}}, {"signed",{"end","end","in_number","end"}}, {"in_number",{"end","end","in_number","end"}}, {"end",{"end","end","end","end"}} }; int get_c(char c){ if(isspace(c)){ return 0; } if(c=='+' || c=='-'){ return 1; } if(isdigit(c)){ return 2; } return 3; } public: int sign=1; long long res=0; void get(char c){ state=table[state][get_c(c)]; if(state=="in_number"){ res=res*10+c-'0'; res=sign==1?min(res,(long long) INT_MAX):min(res,-(long long)INT_MIN); }else if(state == "signed"){ sign=c=='+'?1:-1; } } }; class Solution { public: int myAtoi(string s) { A tempA; for(char c:s){ tempA.get(c); } return tempA.sign*tempA.res; } };
-
时间复杂度:O(n),其中 n 为字符串的长度。我们只需要依次处理所有的字符,处理每个字符需要的时间为 O(1)。空间复杂度:O(1)。自动机的状态只需要常数空间存储。
题目:回文数
-
给你一个整数
x
,如果x
是一个回文整数,返回true
;否则,返回false
。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。 -
class Solution { public: bool isPalindrome(int x) { string temp =to_string(x); int left=0,right=temp.size()-1; bool flag=true; while(left<right){ if(temp[left]==temp[right]){ left++; right--; }else{ flag=false; break; } } return flag; } };
-
所有负数都不可能是回文,例如:-123 不是回文,因为 - 不等于 3。所以我们可以对所有负数返回 false。除了 0 以外,所有个位是 0 的数字不可能是回文,因为最高位不等于 0。所以我们可以对所有大于 0 且个位是 0 的数字返回 false。对于数字 1221,如果执行 1221 % 10,我们将得到最后一位数字 1,要得到倒数第二位数字,我们可以先通过除以 10 把最后一位数字从 1221 中移除,1221 / 10 = 122,再求出上一步结果除以 10 的余数,122 % 10 = 2,就可以得到倒数第二位数字。如果我们把最后一位数字乘以 10,再加上倒数第二位数字,1 * 10 + 2 = 12,就得到了我们想要的反转后的数字。如果继续这个过程,我们将得到更多位数的反转数字。由于整个过程我们不断将原始数字除以
10
,然后给反转后的数字乘上10
,所以,当原始数字小于或等于反转后的数字时,就意味着我们已经处理了一半位数的数字了。 -
class Solution { public: bool isPalindrome(int x) { if(x<0||(x%10 ==0 && x!=0)){ return false; } int revert=0; while(x>revert){ revert = revert*10 +x%10; x/=10; } return x==revert ||x==revert/10; } };
-
时间复杂度:O(logn),对于每次迭代,我们会将输入除以 10,因此时间复杂度为 O(logn)。
题目:正则表达式匹配
-
给你一个字符串
s
和一个字符规律p
,请你来实现一个支持'.'
和'*'
的正则表达式匹配。‘.’ 匹配任意单个字符;‘*’ 匹配零个或多个前面的那一个元素。所谓匹配,是要涵盖 整个 字符串s
的,而不是部分字符串。 -
题目中的匹配是一个「逐步匹配」的过程:我们每次从字符串 p 中取出一个字符或者「字符 + 星号」的组合,并在 s 中进行匹配。对于 p 中一个字符而言,它只能在 s 中匹配一个字符,匹配的方法具有唯一性;而对于 p 中字符 + 星号的组合而言,它可以在 s 中匹配任意自然数个字符,并不具有唯一性。因此我们可以考虑使用动态规划,对匹配的方案进行枚举。
-
我们用 f[i] [j]表示 s 的前 i 个字符与 p 中的前 j 个字符是否能够匹配。在进行状态转移时,我们考虑 p 的第 j 个字符的匹配情况:
-
如果 p 的第 j 个字符是一个小写字母,那么我们必须在 s 中匹配一个相同的小写字母,即
-
f [ i ] [ j ] = f [ i − 1 ] [ j − 1 ] , i f s [ i ] = s [ j ] e l s e f a l s e f[i][j]=f[i-1][j-1],if~s[i]=s[j]~else~false f[i][j]=f[i−1][j−1],if s[i]=s[j] else false
-
也就是说,如果 s 的第 i 个字符与 p 的第 j 个字符不相同,那么无法进行匹配;否则我们可以匹配两个字符串的最后一个字符,完整的匹配结果取决于两个字符串前面的部分。
-
如果 p 的第 j 个字符是
*
,那么就表示我们可以对 p 的第 j−1 个字符匹配任意自然数次。在匹配 0 次的情况下,我们有 -
f [ i ] [ j ] = f [ i ] [ j − 2 ] f[i][j]=f[i][j-2] f[i][j]=f[i][j−2]
-
也就是我们「浪费」了一个字符 + 星号的组合,没有匹配任何 s 中的字符。
-
-
class Solution { public: bool isMatch(string s, string p) { int m=s.size(); int n=p.size(); auto matchs=[&](int i,int j){ if(i==0){ return false; } if(p[j-1]=='.'){ return true; } return s[i-1]==p[j-1]; }; vector<vector<int>> dp(m+1,vector<int>(n+1)); dp[0][0]=true; for(int i=0;i<=m;i++){ for(int j=1;j<=n;j++){ if(p[j-1]=='*'){ dp[i][j] |= dp[i][j-2]; if(matchs(i,j-1)){ dp[i][j] |= dp[i-1][j]; } }else{ if(matchs(i,j)){ dp[i][j] |= dp[i-1][j-1]; } } } } return dp[m][n]; } };
-
已知 dp[i-1] [j-1] 意思就是前面子串都匹配上了,不知道新的一位的情况。 那就分情况考虑,所以对于新的一位 p[j] s[i] 的值不同,要分情况讨论:
- 考虑最简单的
p[j] == s[i] : dp[i][j] = dp[i-1][j-1]
, 然后从p[j]
可能的情况来考虑,让p[j]=各种能等于的东西
。 p[j] == "." : dp[i][j] = dp[i-1][j-1]
p[j] ==" * ":
- 明白 * 的含义是 匹配零个或多个前面的那一个元素,所以要考虑他前面的元素 p[j-1]。* 跟着他前一个字符走,前一个能匹配上 s[i],* 才能有用,前一个都不能匹配上 s[i],* 也无能为力,只能让前一个字符消失,也就是匹配 0 次前一个字符。 所以按照 p[j-1] 和 s[i] 是否相等,我们分为两种情况:
p[j-1] != s[i] : dp[i][j] = dp[i][j-2]
,比如(ab, abc * )。遇到 * 往前看两个,发现前面 s[i] 的 ab 对 p[j-2] 的 ab 能匹配,虽然后面是 c*,但是可以看做匹配 0 次 c,相当于直接去掉 c ,所以也是 True。注意 (ab, abc*) 是 False。p[j-1] == s[i] or p[j-1] == "."
:* 前面那个字符,能匹配 s[i],或者 * 前面那个字符是万能的 .因为 . * 就相当于 . .,那就只要看前面可不可以匹配就行。
- 明白 * 的含义是 匹配零个或多个前面的那一个元素,所以要考虑他前面的元素 p[j-1]。* 跟着他前一个字符走,前一个能匹配上 s[i],* 才能有用,前一个都不能匹配上 s[i],* 也无能为力,只能让前一个字符消失,也就是匹配 0 次前一个字符。 所以按照 p[j-1] 和 s[i] 是否相等,我们分为两种情况:
- 考虑最简单的
-
如果 p.charAt(j) == s.charAt(i) : dp[i] [j] = dp[i-1] [j-1];
-
如果 p.charAt(j) == ‘.’ : dp[i] [j] = dp[i-1] [j-1];
-
如果 p.charAt(j) == ‘*’:
- 如果 p.charAt(j-1) != s.charAt(i) : dp[i] [j] = dp[i] [j-2] //in this case, a* only counts as empty
- 如果 p.charAt(i-1) == s.charAt(i) or p.charAt(i-1) == ‘.’:
- dp[i] [j] = dp[i-1] [j] //in this case, a* counts as multiple a
- or dp[i] [j] = dp[i] [j-1] // in this case, a* counts as single a
- or dp[i] [j] = dp[i] [j-2] // in this case, a* counts as empty
题目:盛最多水的容器
-
给定一个长度为
n
的整数数组height
。有n
条垂线,第i
条线的两个端点是(i, 0)
和(i, height[i])
。找出其中的两条线,使得它们与x
轴共同构成的容器可以容纳最多的水。返回容器可以储存的最大水量。**说明:**你不能倾斜容器。 -
设两指针 i , j ,指向的水槽板高度分别为 h[i] , h[j] ,此状态下水槽面积为 S(i,j) 。由于可容纳水的高度由两板中的 短板 决定,因此可得如下 面积公式 :
- S ( i , j ) = m i n ( h [ i ] , h [ j ] ) ∗ ( j − i ) S(i,j)=min(h[i],h[j])*(j-i) S(i,j)=min(h[i],h[j])∗(j−i)
-
在每个状态下,无论长板或短板向中间收窄一格,都会导致水槽 底边宽度 −1-1−1 变短:
- 若向内 移动短板 ,水槽的短板 min(h[i],h[j]) 可能变大,因此下个水槽的面积 可能增大 。
- 若向内 移动长板 ,水槽的短板 min(h[i],h[j]) 不变或变小,因此下个水槽的面积 一定变小 。
-
因此,初始化双指针分列水槽左右两端,循环每轮将短板向内移动一格,并更新面积最大值,直到两指针相遇时跳出;即可获得最大面积。
-
class Solution { public: int maxArea(vector<int>& height) { int left=0,right=height.size()-1; int max_res=0; while(left<right){ int temp = (right-left)*min(height[left],height[right]); if(temp>max_res){ max_res=temp; } if(height[left]<height[right]){ left++; }else{ right--; } } return max_res; } };
-
时间复杂度:O(N),双指针总计最多遍历整个数组一次。空间复杂度:O(1),只需要额外的常数级别的空间。
-
题目:整数转罗马数字
-
罗马数字包含以下七种字符:
I
,V
,X
,L
,C
,D
和M
。-
字符 数值 I 1 V 5 X 10 L 50 C 100 D 500 M 1000
-
例如, 罗马数字 2 写做
II
,即为两个并列的 1。12 写做XII
,即为X
+II
。 27 写做XXVII
, 即为XX
+V
+II
。通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做IIII
,而是IV
。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为IX
。这个特殊的规则只适用于以下六种情况:I
可以放在V
(5) 和X
(10) 的左边,来表示 4 和 9。X
可以放在L
(50) 和C
(100) 的左边,来表示 40 和 90。C
可以放在D
(500) 和M
(1000) 的左边,来表示 400 和 900。
-
-
罗马数字由 7 个不同的单字母符号组成,每个符号对应一个具体的数值。此外,减法规则(如问题描述中所述)给出了额外的 6 个复合符号。这给了我们总共 13 个独特的符号(每个符号由 1 个或 2 个字母组成),如下图所示。
-
根据罗马数字的唯一表示法,为了表示一个给定的整数 num,我们寻找不超过 num 的最大符号值,将 num 减去该符号值,然后继续寻找不超过 num 的最大符号值,将该符号拼接在上一个找到的符号之后,循环直至 num 为 000。最后得到的字符串即为 num 的罗马数字表示。
-
编程时,可以建立一个数值-符号对的列表 valueSymbols,按数值从大到小排列。遍历 valueSymbols 中的每个数值-符号对,若当前数值 value 不超过 num,则从 num 中不断减去 value,直至 num\textit{num}num 小于 value,然后遍历下一个数值-符号对。若遍历中 num 为 0 则跳出循环。
-
const pair<int, string> temp[] = { {1000, "M"}, {900, "CM"}, {500, "D"}, {400, "CD"}, {100, "C"}, {90, "XC"}, {50, "L"}, {40, "XL"}, {10, "X"}, {9, "IX"}, {5, "V"}, {4, "IV"}, {1, "I"}, }; class Solution { public: // const pair<int,string> temp[]={ // {1000,"M"}, // {900,"CM"}, // {500,"D"}, // {400,"CD"}, // {100,"C"}, // {90,"XC"}, // {50,"L"}, // {40,"XL"}, // {10,"X"}, // {9,"IX"}, // {5,"V"}, // {4,"IV"}, // {1,"I"}, // }; string intToRoman(int num) { string res; for(const auto &[val,sym]:temp){ while(num>=val){ num -= val; res += sym; } if(num==0){ break; } } return res; } };
-
问题是数组范围不能从类内初始化程序中自动推导出来。【error: array bound cannot be deduced from an in-class initializer】。类内初始化器可以被构造函数的成员初始化器列表覆盖。那么,如果构造函数选择用其他东西初始化数组怎么办?在您的情况下,可以使用大括号括起来的初始化程序列表覆盖初始化程序。
-
时间复杂度:O(1)。由于 valueSymbols 长度是固定的,且这 13 字符中的每个字符的出现次数均不会超过 3,因此循环次数有一个确定的上限。对于本题给出的数据范围,循环次数不会超过 15 次。空间复杂度:O(1)。
题目:罗马数字转整数
-
罗马数字包含以下七种字符:
I
,V
,X
,L
,C
,D
和M
。给定一个罗马数字,将其转换成整数。-
字符 数值 I 1 V 5 X 10 L 50 C 100 D 500 M 1000
-
例如, 罗马数字
2
写做II
,即为两个并列的 1 。12
写做XII
,即为X
+II
。27
写做XXVII
, 即为XX
+V
+II
。通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做IIII
,而是IV
。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为IX
。这个特殊的规则只适用于以下六种情况:I
可以放在V
(5) 和X
(10) 的左边,来表示 4 和 9。X
可以放在L
(50) 和C
(100) 的左边,来表示 40 和 90。C
可以放在D
(500) 和M
(1000) 的左边,来表示 400 和 900。
-
-
通常情况下,罗马数字中小的数字在大的数字的右边。若输入的字符串满足该情况,那么可以将每个字符视作一个单独的值,累加每个字符对应的数值即可。若存在小的数字在大的数字的左边的情况,根据规则需要减去小的数字。对于这种情况,我们也可以将每个字符视作一个单独的值,若一个数字右侧的数字比它大,则将该数字的符号取反。
-
class Solution { public: unordered_map<char,int> temp={ {'I',1}, {'V',5}, {'X',10}, {'L',50}, {'C',100}, {'D',500}, {'M',1000} }; int romanToInt(string s) { int res=0; int len_s=s.length(); for(int i=0;i<len_s;i++){ int val=temp[s[i]]; if(i<len_s-1 && val<temp[s[i+1]]){ res -= val; }else{ res += val; } } return res; } };
-
时间复杂度:O(n),其中 n 是字符串 s 的长度。空间复杂度:O(1)。