写一些自己LeetCode的刷题过程及总结01
##leetcode上有2000+的题,不可能都刷完,我的刷题顺序是先分类型,然后再分难度,不断提升,当然过程中也参考了其他大神们的一些建议,光刷题收获不大,最重要的还是不断归纳总结,没事刷两道,坚持写总结其实也挺有意思的。##
##还在不断更新总结!##
##本文仅用来记录自己平时的学习收获##
##有朝一日我也能写出漂亮的代码!##
一、数组
1.1 leetcode部分数组题目及代码
704.二分查找
27.移除元素
209.长度最小的子数组
54.螺旋矩阵
59.螺旋矩阵2
1、704.二分查找
//之前写了一个关于二分找的博客,对一些二分查找的题目进行了总结分类,这里不再多说了
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) return mid;
else if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
};
2、27.移除元素
//通过双指针求解
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int left = 0;
for (int j = 0; j < nums.size(); ++j) {
if (nums[j] != val) {
nums[left++] = nums[j];
}
}
return left;
}
};
3、209.长度最小的子数组
//使用滑动窗口求解
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int slow = 0;
//滑动窗口中的数值之和
int sum = 0;
//返回的最小长度
int minLen = INT_MAX;
for (int fast = 0; fast < nums.size(); ++fast) {
sum += nums[fast];
//每次更新left(起始位置),并判断滑动窗口中的数值是否符合条件
while (sum >= target) {
if (minLen > fast - slow + 1) minLen = fast - slow + 1;
//滑动窗口的精髓,不断更新left(起始位置)
sum -= nums[slow];
++slow;
}
}
//没有符合条件的返回0
if (minLen == INT_MAX) return 0;
return minLen;
}
};
4、54.螺旋矩阵
//这两道螺旋矩阵的题没有涉及什么算法,只是纯粹的模拟过程,但是比较考察对代码的掌控能力
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
vector<int> vec;
if (matrix.size() == 0) return vec;
int up = 0;
int down = matrix.size() - 1;
int left = 0;
int right = matrix[0].size() - 1;
while (true) {
for (int i = left; i <= right; ++i) vec.push_back(matrix[up][i]);
if (++up > down) break;
for (int i = up; i <= down; ++i) vec.push_back(matrix[i][right]);
if (--right < left) break;
for (int i = right; i >= left; --i) vec.push_back(matrix[down][i]);
if (--down < up) break;
for (int i = down; i >= up; --i) vec.push_back(matrix[i][left]);
if (++left > right) break;
}
return vec;
}
};
5、59.螺旋矩阵2
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
int up = 0;
int down = n - 1;
int left = 0;
int right = n - 1;
int num = 1;
int goal = n * n;
vector<vector<int>> vec(n, vector<int> (n, 0));
while (num <= goal) {
for (int i = left; i <= right; ++i) {
vec[up][i] = num;
++num;
}
++up;
for (int i = up; i <= down; ++i) {
vec[i][right] = num;
++num;
}
--right;
for (int i = right; i >= left; --i) {
vec[down][i] = num;
++num;
}
--down;
for (int i = down; i >= up; --i) {
vec[i][left] = num;
++num;
}
++left;
}
return vec;
}
};
1.2 数组总结
总的来说数组的题目种类比较多,不好做细致的分类,第一部分这里只列出了一些基础类的题目,比如二分查找、双指针、滑动窗口(其实也可以看做是双指针)等,至于回溯、动规等放在后面。
这里记录一下双指针的一些细节问题:
1、一般来说数组或链表的遍历问题,能用暴力法解决的应该都能用双指针。可以分为同向双指针和反向双指针。
2、同向双指针:一快一慢,或者一个先动当满足或不满足某一条件时另一个再动,两个都初始化为0或nums.size()-1。(如27题)
3、反向双指针:从首位出发,初始化为0和nums.size()-1。(如977题)
4、同向双指针在遍历结束后可保证数组中元素的相对位置不变;而反向双指针在遍历结束后不能保证元素的相对位置不变。
二、链表
2.1 链表的定义
//单链表
struct ListNode {
int val;//数据域
ListNode* next;//指针域
ListNode() : val(0), next(nullptr) {} //构造函数
ListNode(int x) : val(x), next(nullptr) {} //构造函数
ListNode(int x, ListNode* node) : val(x), next(node) {} //构造函数
};
2.2 leetcode部分链表题目及代码
203.移除链表元素
707.设计链表
206.翻转链表
24.两两交换链表中的节点
19.删除链表的倒数第N个节点
142.环形链表2
1、203.移除链表元素
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
//设置一个虚拟头节点方便后面进行删除操作
ListNode* preHead = new ListNode(0, head);
ListNode* pre = preHead;
ListNode* cur = head;
while (cur != nullptr) {
if (cur->val == val) {
pre->next = cur->next;
cur = cur->next;
} else {
pre = pre->next;
cur = cur->next;
}
}
return preHead->next;
}
};
2、707.设计链表
//这道题基本上覆盖了链表的基本操作
class MyLinkedList {
public:
struct ListNode {
int val;
ListNode* next;
ListNode(int x) : val(x), next(nullptr) {}
};
//初始化链表
MyLinkedList() {
//这里定义的是一个虚拟头节点,并不是真正的链表头节点,所以m_size = 0
m_dummyHead = new ListNode(0);
m_size = 0;
}
//获取链表中第 index 个节点的值。如果索引无效,则返回-1。
int get(int index) {
if (index > m_size - 1 || index < 0) return -1;
ListNode* cur = m_dummyHead->next;
while (index--) {
cur = cur->next;
}
return cur->val;
}
//在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
void addAtHead(int val) {
ListNode* cur = new ListNode(val);
cur->next = m_dummyHead->next;
m_dummyHead->next = cur;
++m_size;
}
//将值为 val 的节点追加到链表的最后一个元素。
void addAtTail(int val) {
ListNode* node = new ListNode(val);
ListNode* cur = m_dummyHead;
while (cur->next != nullptr) {
cur = cur->next;
}
cur->next = node;
++m_size;
}
//在链表中的第 index 个节点之前添加值为 val 的节点。
//如果 index 等于链表的长度,则该节点将附加到链表的末尾。
//如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
void addAtIndex(int index, int val) {
if (index > m_size) return;
//else if (index <= 0) addAtHead(val);
else {
ListNode* node = new ListNode(val);
ListNode* cur = m_dummyHead;
while (index--) {
cur = cur->next;
}
node->next = cur->next;
cur->next = node;
++m_size;
}
}
//如果索引 index 有效,则删除链表中的第 index 个节点.
void deleteAtIndex(int index) {
if (index >= 0 && index < m_size) {
ListNode* cur = m_dummyHead;
while (index--) {
cur = cur->next;
}
ListNode* temp = cur->next;
cur->next = cur->next->next;
delete temp;
--m_size;
}
}
private:
int m_size;
ListNode* m_dummyHead;
};
3、206.翻转链表
//解法1:递归
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (head == nullptr || head->next == nullptr) return head;
ListNode* node = reverseList(head->next);
head->next->next = head;
head->next = nullptr;
return node;
}
};
//解法2:迭代
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* pre = nullptr;
ListNode* cur = head;
while (cur != nullptr) {
ListNode* next = cur->next;
cur->next = pre;
pre = cur;
cur = next;
}
return pre;
}
};
4、24.两两交换链表中的节点
//遇到这种操作多个指针的,建议画个图辅助理解
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
if (head == nullptr || head->next == nullptr) return head;
ListNode* dummyHead = new ListNode(0, head);
ListNode* cur = dummyHead;
while (cur->next != nullptr && cur->next->next != nullptr) {
//记录两个临时节点
ListNode* temp1 = cur->next;
ListNode* temp2 = cur->next->next->next;
cur->next = cur->next->next;
cur->next->next = temp1;
cur->next->next->next = temp2;
//cur向前移动两位,准备下一次交换
cur = cur->next->next;
}
return dummyHead->next;
}
};
5、19.删除链表的倒数第N个节点
//让fast指针多走n步,就可以找到倒数第n个节点
//第876题链表的中间节点 与之类似
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyHead = new ListNode(0, head);
ListNode* slow = dummyHead;
ListNode* fast = dummyHead;
while (n-- && fast != nullptr) {
fast = fast->next;
}
//fast再向前走一步,这样当fast=nullptr的时候slow就刚好指向要删除节点的上一个节点
fast = fast->next;
while (fast != nullptr) {
fast = fast->next;
slow = slow->next;
}
slow->next = slow->next->next;
return dummyHead->next;
}
};
6、142.环形链表2
这道题开始有些不好理解,可以分为两步:
a、判断是否有环
b、找环的入口
a、首先来看怎样判断链表有环,定义两个指针slow和fast,让slow每次前进一个节点,让fast每次前进两个节点,如果slow和fast相遇(slow == fast),那么说明有环。
b、然后再来找环的入口,相遇时: slow指针走过的节点数为: x + y, fast指针走过的节点数: x + y + n (y + z),n为fast指针在环内走了n圈才遇到slow指针,(y+z)为 一圈内节点的个数。
因为fast指针是一步走两个节点,slow指针一步走一个节点, 所以 fast指针走过的节点数 = slow指针走过的节点数 * 2:(x + y) * 2 = x + y + n (y + z)两边消掉一个(x+y): x + y = n (y + z)因为要找环形的入口,那么要求的是x,因为x表示头结点到环形入口节点的的距离。
所以要求x ,将x单独放在左面:x = n (y + z) - y ,再从n(y+z)中提出一个(y+z)来,整理公式之后为如下公式:x = (n - 1) (y + z) + z 注意这里n一定是大于等于1的,因为 fast指针至少要多走一圈才能相遇slow指针。
先拿n为1的情况来举例,意味着fast指针在环形里转了一圈之后,就遇到了slow指针了。当 n为1的时候,公式就化解为 x = z,这就意味着,从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是环形入口的节点。
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* slow = head;
ListNode* fast = head;
while (fast != nullptr && fast->next != nullptr) {
fast = fast->next->next;
slow = slow->next;
if (slow == fast) {
slow = head;
while (slow != fast) {
slow = slow->next;
fast = fast->next;
}
return slow;
}
}
return nullptr;
}
};
2.3 链表总结
遇到删除链表元素、交换链表元素、向链表中某一位置插入元素的题,可以通过创建虚拟头节点来简化操作。
总之,个人感觉链表的题几乎都能用双指针去解决,关于双指针这里不在解释了。
三、哈希表
3.1 哈希表
哈希表是根据关键码的值而直接进行访问的数据结构,其实简单的说数组就是一张哈希表,哈希表中的关键码就是数组的索引,通过索引直接访问数组中的元素。
一般来说,哈希表用来快速判断一个元素是否出现在集合中,或是用来去除重复元素。
举个例子:如果我们要判断一个叫”张三“的学生是否在这个班级中,如果我们用数组arr[]来存储所有的学生信息,那么我们在查找是需要遍历整个数组,对arr[i]的姓名进行判断,看他是否是“张三”,这样的话时间复杂度是O(n)。但是如果使用哈希表的话,在初始化时把班级里学生的姓名存在哈希表里,在查询时就可以通过索引直接知道这位同学是否在这个班级中,时间复杂度为O(1)。
但是,这里有一个问题,如何才能将学生姓名映射到哈希表上,这就涉及到了哈希函数。
3.2 哈希函数
通过哈希函数,可以把学生的姓名直接映射为哈希表上的索引,然后就可以通过查询索引来快速知道某位同学是否在这个班级中。
哈希函数通过hashcode把姓名转化为数值,一般hashcode是通过特定的编码方式将其他数据类型转化为不同的数值,这样就可以实现把学生名字映射为哈希表上的索引了。
一般来说,每一位同学的姓名会对应到唯一的索引处,这也就是哈希表可以去重的原因。但是,难免会有几位同学的名字映射到了哈希表的同一个索引处,这就是哈希碰撞,这里不在解释了。
3.3 C++中常见的三种哈希结构
数组
set(集合)
map(映射)
其中,红黑树是一种二叉搜索树(为了解决二叉搜索树的瘸腿问题),所以set、multiset、map和mulitmap是有序的。
当需要使用集合来解决哈希问题时,优先使用unordered_set,它的查询和删除效率是最优的;如果要求集合是有序的,使用set;如果要求不仅有序还要有重复元素,那就用multiset。
3.4 leetcode部分哈希表题目及代码
242.有效的字母异位词
349.两个数组的交集
350.两个数组的交集II
202.快乐树
1.两数之和
383.赎金信
454.四数相加2
15.三数之和
149.直线上最多的点数
169.多数元素
205.同构字符串
1418.点菜展示表
1、242.有效的字母异位词
class Solution {
public:
bool isAnagram(string s, string t) {
//这里用一个数组来充当哈希表
int arr[26] = {0};
for (char ch : s) {
++arr[ch - 'a'];
}
for (char ch : t) {
--arr[ch - 'a'];
}
for (int i = 0; i < 26; ++i) {
if (arr[i] != 0) return false;
}
return true;
}
};
2、349.两个数组的交集
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> resultSet;
unordered_set<int> tempSet(nums1.begin(), nums1.end());
for (int val : nums2) {
if (tempSet.find(val) != tempSet.end()) {
resultSet.insert(val);
}
}
return vector<int> (resultSet.begin(), resultSet.end());
}
};
3、350.两个数组的交集II
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
unordered_map<int, int> map;
vector<int> ret;
for (int num : nums1) {
++map[num];
}
for (int num : nums2) {
if (map.find(num) != map.end() && map[num] != 0) {
--map[num];
ret.push_back(num);
}
}
return ret;
}
};
4、202.快乐树
//这道题一开始脑子没有转过这个弯儿,
//题目中说了有可能无限循环,那么为什么会出现无限循环呢?
//如果当一次求和后的值出现了重复(跟之前某次求和后的值重复了),那么就会出现无线循环。
//所以这道题就变成了:判断求和后的值sum是否出现重复,那么判断重复就要想到哈希。
class Solution {
public:
//用来计算每个位的平方和
int getSum(int num) {
int sum = 0;
while (num != 0) {
int temp = num % 10;
sum += temp * temp;
num /= 10;
}
return sum;
}
bool isHappy(int n) {
unordered_set<int> tempSet;
while (true) {
int sum = getSum(n);
if (sum == 1) return true;
//如果这个sum曾经出现过,就证明陷入了无限循环,return false
if (tempSet.find(sum) != tempSet.end()) return false;
else tempSet.insert(sum);
n = sum;
}
}
};
5、1.两数之和
//终于到这道题了,梦开始的地方,总结到这里突然有些感触,想到自己当时连STL都没学就跑来刷题的样子了,也是头铁,基础还是要打牢。
//题目这里让返回的是下标,如果要直接返回对应的元素的,直接用set就行
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> tempMap;
for (int i = 0; i < nums.size(); ++i) {
unordered_map<int, int>::iterator it = tempMap.find(target - nums[i]);
if (it != tempMap.end()) {
return {it->second, i};
}
tempMap.insert(pair<int, int> (nums[i], i));
}
return {};
}
};
6、383.赎金信
//这道题。。。怎么说呢,跟我分享的第一个也就是第242道题基本一样
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
int arr[26] = {0};
for (char ch : magazine) {
++arr[ch - 'a'];
}
for (char ch : ransomNote) {
--arr[ch - 'a'];
if (arr[ch - 'a'] < 0) return false;
}
return true;
}
};
7、454.四数相加2
//这道题是一个很经典的哈希法题目,A[i]+B[j]+C[k]+D[l]=0 => (A[i]+B[j])+(C[k]+D[l]) = 0.
//也即是A[i] + B[j] = 0 - C[k] + D[l]
//再具体的思路直接放到代码中了
class Solution {
public:
int fourSumCount(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>& D) {
//定义一个map,key存放a+b的数值,val存放a+b的数值出现的次数
unordered_map<int, int> tempMap;
//遍历A、B两个数组,统计两元素之和以及和出现的次数
for (int a : A) {
for (int b : B) {
++tempMap[a + b];
}
}
//count统计a+b+c+d=0的次数
int count = 0;
//遍历C、D两个数组,如果找到0-(c+d)在map中出现过,就用count加上出现的次数
for (int c : C) {
for (int d : D) {
if (tempMap.find(0 - (c + d)) != tempMap.end()) {
count += tempMap[0 - (c + d)];
}
}
}
return count;
}
};
8、15.三数之和
//本来不想把这道题放在这里,但是做了前一道题后再看这道题时第一眼想到的就是哈希,所以想用哈希法解一下。
//两层for循环就可以确定a和b的数值,然后c = 0 - (a + b)可以用哈希法来确定是否出现过(类似于上一道题:454.四数相加2),
//但是有一个很麻烦的问题,就是题目中说的不可以包含重复的三元组。
//虽然这道题可以用哈希法求解,但是因为不好做剪枝操作,所以最好别用哈希法,
//可以参考leetcode中其他大佬们说的双指针法求解,关于双指针解法的过程我会在后续的双指针文章中给出。
//哈希法也是参考了其他大佬的过程,感觉还是比较麻烦、不容易理解
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
sort(nums.begin(), nums.end());
//找出a+b+c=0
//a = nums[i], b = nums[j], c = 0 - (a + b)
for (int i = 0; i < nums.size(); ++i) {
//排序后如果第一个元素a已经大于0,那么就不可能构成三元组
if (nums[i] > 0) break;
//对三元组a去重
if (i > 0 && nums[i] == nums[i - 1]) continue;
unordered_set<int> tempSet;
for (int j = i + 1; j < nums.size(); ++j) {
//对三元组b去重
if (j > i + 2 && nums[j] == nums[j - 1] && nums[j] == nums[j - 2]) continue;
int c = 0 - (nums[i] + nums[j]);
if (tempSet.find(c) != tempSet.end()) {
result.push_back({nums[i], nums[j], c});
//对三元组c去重
tempSet.erase(c);
} else {
tempSet.insert(nums[j]);
}
}
}
return result;
}
};
9、18.四数之和
//跟上一道题一样,更麻烦些,还是别用哈希了,我会放在后面的双指针文章中。
10、149.直线上最多的点数
class Solution {
public:
int maxPoints(vector<vector<int>>& points) {
int len = points.size();
if (len < 3) return len;
int maxCount = 2;
for (int i = 0; i < len - 1; ++i) {
unordered_map<double, int> tempMap;
for (int j = i + 1; j < len; ++j) {
double dx = points[i][0] - points[j][0];
double dy = points[i][1] - points[j][1];
double k = (dx == 0) ? INT_MAX : dy / dx;
if (tempMap.find(k) == tempMap.end()) {
tempMap[k] = 2;
} else {
++tempMap[k];
}
maxCount = max(tempMap[k], maxCount);
}
}
return maxCount;
}
};
11、169.多数元素
class Solution {
public:
int majorityElement(vector<int>& nums) {
int len = nums.size();
unordered_map<int, int> m;
for (int num : nums) {
++m[num];
}
int ret;
for (auto it = m.begin(); it != m.end(); ++it) {
if (it->second > len / 2) {
ret = it->first;
break;
}
}
return ret;
}
};
12、205.同构字符串
class Solution {
public:
bool isIsomorphic(string s, string t) {
unordered_map<char, char> map1;
unordered_map<char, char> map2;
for (int i = 0, j = 0; i < s.size(); i++, j++) {
if (map1.find(s[i]) == map1.end()) { // map1保存s[i] 到 t[j]的映射
map1[s[i]] = t[j];
}
if (map2.find(t[j]) == map2.end()) { // map2保存t[j] 到 s[i]的映射
map2[t[j]] = s[i];
}
// 发现映射 对应不上,立刻返回false
if (map1[s[i]] != t[j] || map2[t[j]] != s[i]) {
return false;
}
}
return true;
}
};
13、1418.点菜展示表
//这道题其实不难理解,只是比较繁琐,考察代码掌握能力
class Solution {
public:
vector<vector<string>> displayTable(vector<vector<string>>& orders) {
// 从订单中获取餐品名称和桌号,统计每桌点餐数量
unordered_set<string> nameSet;
unordered_map<int, unordered_map<string, int>> foodsMap;
for (auto& order : orders) {
nameSet.insert(order[2]);
int id = stoi(order[1]);
++foodsMap[id][order[2]];
}
// 提取餐品名称,并按字母顺序排列
vector<string> names;
for (auto& name : nameSet) {
names.push_back(name);
}
sort(names.begin(), names.end());
// 提取桌号,并按餐桌桌号升序排列
vector<int> ids;
for (auto& [id, _] : foodsMap) {
ids.push_back(id);
}
sort(ids.begin(), ids.end());
// 填写点菜展示表
int m = foodsMap.size();
int n = nameSet.size();
vector<vector<string>> table(m + 1, vector<string> (n + 1));
table[0][0] = "Table";
copy(names.begin(), names.end(), table[0].begin() + 1);
for (int i = 0; i < m; ++i) {
int id = ids[i];
auto& temp = foodsMap[id];
table[i + 1][0] = to_string(id);
for (int j = 0; j < n; ++j) {
table[i + 1][j + 1] = to_string(temp[names[j]]);
}
}
return table;
}
};
3.5 哈希表总结
当需要快速判断一个元素是否出现在集合中,或是要对元素进行去重的时候,就要考虑哈希法。
但是哈希法牺牲了空间换取时间,因为需要用额外的哈希表来存储数据,才能实现快速查找。
四、字符串
4.1 leetcode部分字符串题目及代码
344.反转字符串
541.反转字符串2
剑指offer05.替换空格
58.最后一个单词
151.翻转字符串里的单词
剑指offer58-II左旋转字符串
28.实现strStr()
1、344.反转字符串
//这道题也可以放到双指针里,不过太简单了
//其实也可以用reverse(s.begin(), s.end())一行解决,。。。没必要
class Solution {
public:
void reverseString(vector<char>& s) {
int left = 0;
int right = s.size() - 1;
while (left < right) {
char ch = s[left];
s[left] = s[right];
s[right] = ch;
++left;
--right;
}
}
};
2、541.反转字符串2
//在遍历过程中只要让i += 2*k每次移动2k个就行
class Solution {
public:
string reverseStr(string s, int k) {
//每隔2k个字符的前k个字符进行反转
for (int i = 0; i < s.size(); i += 2*k) {
//剩余字符少于大于等于k个,反转前k个
if (i + k <= s.size()) {
reverse(s, i, i + k - 1);
continue;
}
//剩余字符不足k个,反转剩余字符
reverse(s, i, s.size() - 1);
}
return s;
}
void reverse(string& str, int start, int end) {
int left = start;
int right = end;
while (left < right) {
char ch = str[left];
str[left] = str[right];
str[right] = ch;
++left;
--right;
}
}
};
3、剑指offer05.替换空格
class Solution {
public:
string replaceSpace(string s) {
string ret = "";
for (char ch : s) {
if (ch == ' ') ret += "%20";
else ret += ch;
}
return ret;
}
};
//放一个别人写的进阶解法,不需要额外的辅助空间,直接对原字符串进行扩充
//首先扩充数组到每个空格被替换成%20后的大小
//然后从后向前替换空格,也就是双指针法,j指向旧长度的末尾,i指向新长度的末尾
//注:为什么要从后向前,为什么不从前向后
//从前向后填充就是O(n^2)了,因为每次添加元素后,都要将添加元素之后的所有元素向后移动。
//很多数组填充问题,都可以先扩充数组大小然后从后向前填充
class Solution {
public:
string replaceSpace(string s) {
//统计空格个数
int count = 0;
for (char ch : s) {
if (ch == ' ') ++count;
}
int oldSize = s.size();
//扩充s的大小
s.resize(oldSize + count * 2);
int newSize = s.size();
for (int j = oldSize - 1, i = newSize - 1; j < i; --j, --i) {
if (s[j] != ' ') {
s[i] = s[j];
} else {
s[i] = '0';
s[i - 1] = '2';
s[i - 2] = '%';
i -= 2;
}
}
return s;
}
4、58.最后一个单词
class Solution {
public:
int lengthOfLastWord(string s) {
int i = s.size() - 1;
int count = 0;
while (i >= 0 && s[i] == ' ') --i;
while (i >= 0 && s[i] != ' ') {
--i;
++count;
}
return count;
}
};
5、151.翻转字符串里的单词
//跟上一个题有点像
class Solution {
public:
string reverseWords(string str) {
if (str.size() <= 1) return str;
int i = str.size() - 1;
string ret = "";
while (i >= 0) {
int count = 0;
while (i >= 0 && str[i] == ' ') --i;
while (i >= 0 && str[i] != ' ') {
--i;
++count;
}
if (count > 0) {
ret += str.substr(i + 1, count) + ' ';
}
}
return ret.substr(0, ret.size() - 1);
}
};
6、剑指offer58-II左旋转字符串
//当然也可以用reverse(),不推荐
class Solution {
public:
string reverseLeftWords(string s, int n) {
if (s.size() < n) return s;
string ret = "";
for (int i = n; i < s.size(); ++i) {
ret += s[i];
}
for (int i = 0; i < n; ++i) {
ret += s[i];
}
return ret;
}
};
7、28.实现strStr()
//其实这道题是KMP算法的经典例子,由于我现在还没搞懂KMP,所以先给出其它解答方法
//后面会详细介绍KMP
class Solution {
public:
int strStr(string haystack, string needle) {
if (haystack.size() < needle.size()) return -1;
for (int i = 0; i <= haystack.size() - needle.size() + 1; ++i) {
if (haystack.substr(i, needle.size()) == needle) return i;
}
return -1;
}
};
4.2 字符串总结
字符串也可以理解成一个字符数组,所以可以按照操作数组的方式去操作字符串,C++中封装了很多操作字符串的库函数,但在解题过程中尽量不要使用,尤其是涉及到题目的关键部分。
双指针法在数组、链表和字符串中都会经常使用,后面会专门对双指针法的题目进行总结。
字符串的反转系列比较考察对代码的掌控能力,比如第541题,虽然难度不大但是需要在for循环上做文章。
最后是KMP,常用来解决字符串的匹配问题,这个也会在后续文章中进行总结。