持续更新中....
目录
3.两数之和 II - 输入有序数组 167(双指针,二分查找)
5.翻转字符串里的单词 151(STL函数/正则表达式去空格)
一、链表(双指针,链表成环,取链表倒数第k个节点)
由于链表网上博客比较详细,所以偷个懒就不整理了。
- 主要参考:链表与二叉树。链表问题主要是用双指针方法解决的,非常简练。(包含链表成环问题、查找删除倒数第k个节点、合并链表、删除表中元素)
- 补充:leetcode链表面试题目集锦
- 补充:轻松搞定面试中的链表题目
二、数组与字符串
1.反转字符串 344
- 双指针,移动首尾指针交换字符直至中间
- 使用C++STL库,reverse(s.begin(),s.end())
2.最长公共前缀 14
知识点:取子字符串str.substr(pos,len)
- str为空串,则直接返回空串;
- 依次将第一个字符串的每个字符与其他字符串比对,直至出现不匹配,记录最多相同字符的个数cnt
- 返回第一个字符串的子串,strs[0].substr(0,cnt);
3.两数之和 II - 输入有序数组 167(双指针,二分查找)
题目描述:
给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。
函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。
说明:
返回的下标值(index1 和 index2)不是从零开始的。
你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
示例:
输入: numbers = [2, 7, 11, 15], target = 9
输出: [1,2]
解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。
1.双指针思想:使用两个指针,初始分别位于第一个元素和最后一个元素位置,比较这两个元素之和与目标值的大小。如果和等于目标值,我们发现了这个唯一解。如果比目标值小,我们将较小元素指针增加一。如果比目标值大,我们将较大指针减小一。
2.二分查找思想:第一重循环,移动左端点i(从0到n-1);第二重循环,j从i+1到n-1使用二分查找numbers[j],使得numbers[j]+numbers[i]=target。
4.长度最小的子数组 209(双指针,二分查找)
知识点:c++ 关于如何获取int型的最大值(包含在头文件<climits>中,符号常量INT_MAX)
题目描述:
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组。
如果不存在符合条件的连续子数组,返回 0。
示例:
输入: s = 7, nums = [2,3,1,2,4,3]
输出: 2
解释: 子数组 [4,3] 是该条件下的长度最小的连续子数组。
双指针思想:我们都保持子数组的左端点不动去找右端点。其实一旦知道这个位置开始的子数组不会是最优答案了,我们就可以移动左端点。我们用 2 个指针,一个指向数组开始的位置,一个指向数组最后的位置,并维护区间内的和 sum大于等于 s 同时数组长度最小。
(双指针)
class Solution {
public:
int minSubArrayLen(int s, vector<int>& nums) {
int cnt = INT_MAX;
int n = nums.size();
for(int i=0, left=0, sum=0;i<n;i++){
sum += nums[i]; //找到和可以大于等于s的位置
while(sum>=s){ //移动左端点,直至最优解
cnt = min(cnt,i-left+1);
sum -= nums[left++];
}
}
return cnt==INT_MAX?0:cnt;
}
};
二分查找思想:新建数组prevsum(前n项和),第一重循环固定左端点i,第二重循环二分查找从i开始最小的连续子数组末尾(即prevsum[mid]-prevsum[i]>=s&&prevsum[mid-1]-prevsum[i]<s)。
(二分查找)
class Solution {
public:
int minSubArrayLen(int s, vector<int>& nums) {
int cnt= 0;
int n = nums.size();
if(n==0) return 0;
vector<int> prevsum;
prevsum.push_back(0);
for(int i=0, sum=0;i<n;i++){
sum += nums[i];
prevsum.push_back(sum);
}
for(int i=0;i<n;i++){
for(int left=i+1, right=n;left<=right;){
int mid = (left + right)/2;
if(prevsum[mid]-prevsum[i]>=s&&prevsum[mid-1]-prevsum[i]<s){
if(i>0){
int t = mid-i;
if(t<cnt) cnt = t;
}
else cnt = mid - i;
break;
}
else if(prevsum[mid]-prevsum[i]>=s){
right = mid -1;
}
else{
left = mid + 1;
}
}
}
return cnt;
}
};
5.翻转字符串里的单词 151(STL函数/正则表达式去空格)
给定一个字符串,逐个翻转字符串中的每个单词。
示例 1:
输入: "the sky is blue"
输出: "blue is sky the"
示例 2:
输入: " hello world! "
输出: "world! hello"
解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
示例 3:
输入: "a good example"
输出: "example good a"
解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/reverse-words-in-a-string
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路:1)处理首尾空格、中间连续空格;2)翻转字符串;3)翻转单词
知识点:如下。参考链接:1)CCF第三题字符串处理stl函数及正则表达式 2)C++ 解析正则表达式
(字符串处理STL函数)
//查找
1)int string.find(const char *s, int pos=0,int n);//从pos开始查找字符串s中前n个字符在当前串中的位置(n和pos都可以不写)
2)int find(const string &s, int pos = 0) const;//从pos开始查找字符串s在当前串中的位置
3)find_first_of('c');//查找字符c第一次在字符串中出现的位置,如果不存在则返回-1
4)find_first_not_of('c');//查找字符串中第一个不是字符c的字符位置,一般可以配合erase来使用,用来处理字符串前缀
5)find_last_of('c');
6)find_last_not_of('c');
//删除erase
1)erase(it);
2)erase(first,last);//迭代器
3)erase(pos,len);
//替代
1)str.replace(pos,len,str2);
2)str.replace(it1,it2,str2);
(正则表达式)
//处理空格
regex pattern("规则");
str = regex_replace(str,pattern,str2);
1)\s 匹配任何空白字符,包括空格、制表符、换页符等等
2)^ 匹配输入字符串的开始位置
3)$ 匹配输入字符串的结尾位置
4)+ 匹配前面的子表达式一次或多次
5){n,} n 是一个非负整数。至少匹配n 次。
//按空格分割字符串,存入数组
regex split("\\s");
sregex_token_iterator
p(str.begin(),str.end(),split,-1);
sregex_token_iterator end;
vector<string> words;
while(p!=end){
words.push_back(*p++);
}
//STL函数
lass Solution {
public:
string reverseWords(string s) {
s.erase(0,s.find_first_not_of(' '));
s.erase(s.find_last_not_of(' ')+1,s.size());
int pos = s.find(" ");
while(pos!=-1){
s.replace(pos,2," ");
pos = s.find(" ");
}
reverse(s.begin(),s.end());
pos = s.find(' ');
string::iterator left = s.begin();
string::iterator right = left+pos;
while(pos!=-1){
reverse(left,right);
left = right + 1;
pos = s.find(' ',pos+1);
right = s.begin() + pos;
}
reverse(left,s.end());
return s;
}
};
//正则表达式
class Solution {
public:
string reverseWords(string s) {
string str = s;
//处理空格
regex htblank("^\\s+|\\s+$");
regex midblank("\\s{2,}");
str = regex_replace(str,htblank,"");
str = regex_replace(str,midblank," ");
//翻转整个字符串
reverse(str.begin(),str.end());
//根据空格分割字符串
regex split("\\s");
sregex_token_iterator
p(str.begin(),str.end(),split,-1);
sregex_token_iterator end;
vector<string> words;
while(p!=end){
words.push_back(*p++);
}
//反转单词,重新拼接
str.clear();
for(vector<string>::iterator it=words.begin();it!=words.end();it++){
reverse((*it).begin(),(*it).end());
str += *it + " ";
}
str.erase(str.end()-1);
return str;
}
};
验证回文字符串(双指针,正则表达式提取字母和数字)
给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。
说明:本题中,我们将空字符串定义为有效的回文串。
示例 1:
输入: "A man, a plan, a canal: Panama"
输出: true
双指针:先大小写转换,再用首尾指针逐个比对,跳过非字母非数字(推荐)。
正则表达式思路:提取字母和数字,大小写转换,逐个字母比对(或者直接reverse看两个字符串是否相等)。
\w 匹配字母、数字、下划线。等价于'[A-Za-z0-9_]'
\W 匹配非字母、数字、下划线。等价于 '[^A-Za-z0-9_]'。
//大小写转换STL函数
include <algorithm>
transform(str1.begin(),str1.end(),str1.begin(),::tolower);
transform(str2.begin(),str2.end(),str2.begin(),::toupper);
//双指针
class Solution {
public:
bool isPalindrome(string s) {
transform(s.begin(),s.end(),s.begin(),::tolower);
int left = 0, right = s.size()-1;
while(left<right){
if((s[left]<='9'&&s[left]>='0')||(s[left]>='a'&&s[left]<='z')){
if((s[right]<='9'&&s[right]>='0')||(s[right]>='a'&&s[right]<='z')){
if(s[left]!=s[right]) return false;
else{
left++;
right--;
}
}
else{
right--;
}
}
else{
left++;
}
}
return true;
}
};
//正则表达式
class Solution {
public:
bool isPalindrome(string s) {
regex notword("\\W");
sregex_token_iterator
p(s.begin(),s.end(),notword,-1);
sregex_token_iterator end;
//一定要用新的字符串保存*p
/*原因:程序员负责确保传递给迭代器构造函数的 std::basic_regex 对象活得长于迭代器。因为迭代器存储指向 regex 的指针,故在销毁 regex 后自增迭代器会访问悬垂指针。*/
/*作者把s.clear()重用,结果就是第一个字符会变成空格,蜜汁调了很久*/
string result;
while(p!=end){
result += *p++;
}
transform(result.begin(),result.end(),result.begin(),::tolower);//全部转小写
for(int i=0,len=result.size();i<len/2;i++){
if(result[i]!=result[len-i-1]) return false;
}
return true;
}
};
6.删除排序数组中的重复项 26(双指针)
给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
示例 1:
给定数组 nums = [1,1,2],
函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。
你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,0,1,1,1,2,2,3,3,4],
函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。
你不需要考虑数组中超出新长度后面的元素。
思路:官方题解。简单的理解,就是要把不同的元素搬到数组的最前端。设置两个指针slow和fast,不断移动fast直至遇到与slow不同的元素,将它搬到slow下面的位置,直至fast遍历完整个数组。
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
if(nums.size()==0||nums.size()==1) return nums.size();
int slow = 0, fast = 1;
while(fast<nums.size()){
if(nums[slow]!=nums[fast]){
nums[++slow] = nums[fast];
}
fast++;
}
return slow+1;
}
};
移动零 283(数组,双指针)
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
示例:
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/move-zeroes
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路:和上一题26删除数组中重复元素一样。
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int slow, fast;
int len = nums.size();
//查找第一个0的位置
for(slow=0;slow<len;slow++){
if(!nums[slow]) break;
}
for(fast = slow+1;fast<len;fast++){
if(nums[fast]!=0){
nums[slow++] = nums[fast];
nums[fast]=0;
}
}
}
};