1 | https://github.com/labuladong/fucking-algorithm |
什么叫ARTS?
每周完成一个ARTS:
- Algorithm:每周至少做一个 leetcode 的算法题
- Review:阅读并点评至少一篇英文技术文章
- Tip:学习至少一个技术技巧
- Share:分享一篇有观点和思考的技术文章
作者:陈皓
链接:https://www.zhihu.com/question/301150832/answer/529809529
来源:知乎
Reverse String
翻转字符串char[],O(1)空间复杂度。
1 | Input: ["h","e","l","l","o"] |
方法一
- 从数组的头尾同时遍历,交换方法为a=a^b;b=a^b;a=a^b;
- 如果中间还剩一个,就不处理。
1 | class Solution { |
方法二
- 直接调用算法
1 | class Solution { |
Reverse Integer
32位有符号整形,将数字部分颠倒,如果颠倒后异常,需要处理为0。
1 | Input: -123 |
方法一
- 需要依次取出各位至数组中。
- 然后采用算法颠倒。
- 再依次打包成整数,如果溢出,再进行处理。如果a*b>max,则有a>max/b。
1 | class Solution { |
方法二
- 依次取个位的数,同时将这个数放在最高位组合出倒数,一次循环完成。
- 在循环的过程中,不断检测是否会溢出,如果即将溢出,则直接返回0.
1 | class Solution { |
First Unique Character in a String
给一个字符串,找到第一个不是重复的字母,给出地址,在字符串只考虑小写字母的前提下。
1 | s = "leetcode" |
方法一
- 从前往后依次将字母存入int数组[26],index为字母,value为个数,由于个数可能有很多,得用int数组才行。
- 将字符串套入数组检查,找到只出现一次的第一个字母,即value==1,此时的i为地址。
1 | class Solution { |
方法二
- 从前往后依次存入unordered_map,key为字母,value为个数。
- 将字符串套入map中检查,找到只出现一次的第一个字母,map的value==1,此时的i为地址。
1 | class Solution { |
方法三
优化点思考,第二轮查找第一个单数的时候不需要重新遍历字符串了,直接遍历更新的字典找到第一个单数。
- 遍历字符串,更新字典,包括是哪个字符、字符的位置、字符出现的次数。需要用到pair<int, int>
- 遍历字典,找到字符出现次数等于1且字符的位置最小的那个位置。
1 | class Solution { |
Valid Anagram
判断s是否为t的同字母异序词
1 | Input: s = "anagram", t = "nagaram" |
方法一
- 依次遍历字符串s,建立int[26]数组,存入的是该遍历到的字符个数。
- 再依次遍历字符串t,检索之前建立的数组,判断遍历到的该字符是否个数为0,否则不是同字母异序词,如果大于0,直接做减减处理。
1 | class Solution { |
方法二
- 依次遍历字符串s,建立字典,存入的是该遍历到的字符个数。
- 再依次遍历字符串t,检索之前建立的字典,判断遍历到的该字符是否个数为0,否则不是同字母异序词,如果大于0,直接做减减处理。
1 | class Solution { |
Valid Palindrome
判断字符串是否为回文,假设字符串仅考虑由字母数字组成,忽略大小写,假设空字符为回文。
1 | Input: "A man, a plan, a canal: Panama" |
方法一
- 双指针方案,从头和从尾依次遍历并对比,出现不同则退出。
- 怎么去掉空格,判断大小写,去掉其他符号,注意全都是其他字符串的情况或直接用isalnum函数。
1 | class Solution { |
方法二
- 同样是双指针方法,只是写法不同,先找到alnum字符,采用while if结构,如果不是alnum字符,则进入循环,再查找,只有同时满足时,才进行对比。
- 进行对比,不同则退出。
1 | class Solution { |
String to Integer (atoi)
将字符串转换为数字,如果首部有空格则忽略,其他非数字字符,则返回0,末尾含有字母则忽略,需要考虑负数,数字范围为[−231, 231 − 1]。
1 | Input: " -42" |
方法一
- 从头遍历字符串,如果发现有空格则跳过,如果发现是正负符号,则赋给符号变量。
- 再接着遍历,只对数字进行处理,同时计算数值大小(提前计算是否溢出,如果溢出了,边界值应该怎么给),如果发现有非数字字符,则退出遍历,返回计算结果。
- 难点:判断是非溢出,及其处理措施?如果a>=0,b>=0,则a*10+b>INT_MAX转化为a>(INT_MAX-b)/10;如果a<0,b<=0(一定要包括0),则a*10+b<INT_MIN转化为a<(INT_MAIN-b)/10;对于没有溢出的情况计算结果为a=a*10+b.
1 | class Solution { |
方法二
- 对前缀进行完善,空格分while循环进行判断,正负号只判断一次,最后再进行数字判断。
- 对溢出部分进行完善,如何判断a*10+b>INT_MAX?
- a*10>INT_MAX
- a*10==INT_MAX && b>INT_MAX%10
- 针对以上两种情况,如果符号位为正,则返回INT_MAX,如果符号位为负,则返回INT_MIN。比如[-10,9],当取直为10时,10>9,如果是负数,返回-10,如果是正数,返回9.
- 其他情况,返回a*10+b
1 | class Solution { |
Implement strStr()
找到当前字符串在另外一个字符串中出现的位置,空字符串按照0处理,找不到返回-1。
1 | Input: haystack = "hello", needle = "ll" |
方法一
- 两个字符串都从0开始遍历,同时进行比较,如果比较有相同字符,则needle指针加加,如果没有相同字符,则needle置0,haystack指针回退到刚开始对比的下一个位置(重要)。
- 如果任意一个遍历完,则退出,如果是needle遍历完,还要返回haystack的指针位置-needle长度的位置。
1 | class Solution { |
方法二
- 使用两个while循环结构,这样就不需要特意进行回退了,但是时间复杂度变大了。
- 两个字符串都从0开始遍历,均在内循环进行,同时进行比较,如果有相同的字符,则内循环加加,否则退出内循环。
- 如果外循环遍历完,则退出,如果内循环遍历完,则当前的外循环位置即为需要的地址。
1 | class Solution { |
Count and Say
count-and-say序列是整数序列,前五个术语如下:
1 | 1. 1 |
第二个数:一个1,第三个数,两个1,第四个数,一个2,一个1,第五个数,一个1,一个2,两个1.
给出5,要求返回111221.
方法一
- 从原始字符串前面开始遍历,个数++,遇到不同的数或者原始字符串末尾,则将上一个字符出现的次数以及是哪个字符添加的新字符串末尾。
- 如果只是不同的数,清空个数。
- 如果遇到末尾,则将新字符串赋值给原字符串,并清空新字符串/个数,最后退出遍历。
- 依次递归到第n个,返回此时的字符串。
1 | class Solution { |
方法二
- 优化点:for循环可以转化为while循环
- 将赋值的部分提取出来,关键在于怎么找到变化的临界点或者字符串的末尾,只要任意一个不满足就表明是边界了,故用&&语句比较合适。
- 使用+语句,减少语句。
1 | class Solution { |
Longest Common Prefix
找出字符串数组的共有前缀:
1 | Input: ["flower","flow","flight"] |
方法一
- 设置公共前缀str为第一个字符串。
- 再str与下一个进行对比,更新公共str。
- 直到对比完所有字符串。
- 如果发现str为空,直接退出。
1 | class Solution { |
方法二
- 先对数组进行长度的排序。
- 只对第一个和最后一个进行对比,找出公共部分即可。
1 | class Solution { |
寻找中心索引
数组 中心下标 是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。
先求出数组总和sun,
要使得下标i左右元素和相等,sum - nums[i] 等于 左侧元素和的2倍;1
2
3
4
5
6
7
8
9
10
11
12
13
14int pivotIndex(vector<int>& nums) {
int sum = 0;
int left = 0;
for(int num : nums) {
sum += num;
}
for(int i = 0; i < nums.size(); i++) {
if (sum - nums[i] == 2*left) {
return i;
}
left += nums[i];
}
return -1;
}
旋转矩阵
给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。
这里的方法不需要记录已经走过的路径,所以执行用时和内存消耗都相对较小
- 首先设定上下左右边界
- 其次向右移动到最右,此时第一行因为已经使用过了,可以将其从图中删去,体现在代码中就是重新定义上边界
- 判断若重新定义后,上下边界交错,表明螺旋矩阵遍历结束,跳出循环,返回答案
- 若上下边界不交错,则遍历还未结束,接着向下向左向上移动,操作过程与第一,二步同理
- 不断循环以上步骤,直到某两条边界交错,跳出循环,返回答案
1 | vector<int> spiralOrder(vector<vector<int>>& matrix) { |
最大数
给定一组非负整数 nums,重新排列每个数的顺序(每个数不可拆分)使之组成一个最大的整数。
注意:输出结果可能非常大,所以你需要返回一个字符串而不是整数。
对于 nums 中的任意两个值 a 和 b,我们无法直接从常规角度上确定其大小/先后关系。
但我们可以根据「结果」来决定a 和b 的排序关系:
如果拼接结果ab 要比ba 好,那么我们会认为a 应该放在b 前面。1
2
3
4
5
6
7
8
9
10string largestNumber(vector<int>& nums) {
vector<string> vs;
for(auto x : nums) vs.push_back(to_string(x));
sort(vs.begin(),vs.end(),[](const auto& A,const auto& B){
return A + B > B + A;
});
string ans;
for(const auto& x : vs) ans += x;
return ans[0] == '0' ? "0" : ans;
}
数字在升序数组中出现的次数
给定一个长度为 n 的非降序数组和一个非负数整数 k ,要求统计 k 在数组中出现的次数
有序序列,就使用二分查找的思路。一开始的思路是先使用二分法找到k,然后从k开始向两边统计k的个数,但统计的这个时间复杂度达到了O(n),导致整个算法的复杂度O(nlogn),而通过两次二分查找,分别找到第一个k和最后一个k,可以使时间复杂度减少为O(logn)
1 | class Solution { |
1 | int GetNumberOfK(vector<int>& nums, int k) { |
不存在的正整数
在由整数组成的数组中,从小到大找出第一个不存在的正整数
用例1:
输入:[]
输出: [1]
用例2:
输入:[-4, 0, 4, 1]
输出:[2]
用例3:
输入:[-1, 0, 3, 1, 2, 25]
输出:[4]
用例4:
输入:[2, 1, 4, 3]
输出:[5]
用例5:
输入:[2, 2, 1, 4]
输出:[3]
用例6:
输入:[2, 1]
输出:[3]
1 |
|
1 | import "sort" |