1.数组理论基础
数组是存放在连续内存空间上的相同类型数据的集合。
- 数组下标都是从0开始的
- 数组内存空间的地址是连续的
- 数组元素是不能删的,只能覆盖
2.二分查找
2.1二分查找
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1; // 定义target在左闭右闭的区间里,[left, right]
while (left <= right) { // 当left==right,区间[left, right]依然有效,所以用 <=
int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2
if (nums[middle] > target) {
right = middle - 1; // target 在左区间,所以[left, middle - 1]
} else if (nums[middle] < target) {
left = middle + 1; // target 在右区间,所以[middle + 1, right]
} else { // nums[middle] == target
return middle; // 数组中找到目标值,直接返回下标
}
}
// 未找到目标值
return -1;
}
};
2.2 搜索插入位置
class Solution {
public:
int searchInsert(vector<int>& nums, int target)
{
int left = 0;
int right = nums.size() - 1;
while (left <= right)
{
int middle = (right + left) / 2;
if (nums[middle] < target)
{
left = middle + 1;
}
else if (nums[middle] > target)
{
right = middle -1;
}
else
{
return middle;
}
}
return right + 1;
}
};
2.3 在排序数组中查找元素的第一个和最后一个位置(*)
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target)
{
int leftBoder = getleftBoder(nums, target);
int rightBoder = getrightBoder(nums, target);
return {leftBoder, rightBoder};
}
private:
int getrightBoder(vector<int>& nums, int target)
{
int rightBoder = -1;
int left = 0;
int right = nums.size() - 1;
while(left <= right)
{
int mid = (right + left) / 2;
if (nums[mid] < target)
{
left = mid + 1;
}
else if (nums[mid] > target)
{
right = mid - 1;
}
else if (nums[mid] == target)
{
rightBoder = mid;
left = mid + 1;
}
}
return rightBoder;
}
int getleftBoder(vector<int>& nums, int target)
{
int leftBoder = -1;
int left = 0;
int right = nums.size() - 1;
while(left <= right)
{
int mid = (right + left) / 2;
if (nums[mid] < target)
{
left = mid + 1;
}
else if (nums[mid] > target)
{
right = mid - 1;
}
else if (nums[mid] == target)
{
leftBoder = mid;
right = mid - 1;
}
}
return leftBoder;
}
};
这一题需要注意的是找左边界的时候,即使遇到nums[mid] == target也需要进行向左找,right = mid - 1;
找右边界的时候,同样地,继续向右找,left = mid + 1;
2.4 x的平方根
class Solution {
public:
int mySqrt(int x)
{
int rightBoder = 0;
if (x == 0 || x == 1)
{
return x;
}
int left = 0; int right = x/2;
while (left <= right)
{
uint64_t mid = (left + right) / 2;
if (mid *mid <= x)
{
rightBoder = mid;
left = mid + 1;
}
else
{
right = mid - 1;
}
}
return rightBoder;
}
};
2.5 有效的完全平方数
class Solution {
public:
bool isPerfectSquare(int num)
{
if (num == 0 || num == 1)
{
return true;
}
int left = 0; int right = num/2;
while (left <= right)
{
uint64_t mid = (left + right)/2;
if (mid * mid == num)
{
return true;
}
else if (mid * mid < num)
{
left = mid + 1;
}
else if (mid * mid > num)
{
right = mid - 1;
}
}
return false;
}
};
3 移除元素(双指针)
双指针法
题目
class Solution {
public:
int removeElement(vector<int>& nums, int val)
{
int fast = 0;
int slow = 0;
for (; fast < nums.size(); fast++)
{
if (nums[fast] != val)
{
nums[slow++] = nums[fast];
}
}
return slow;
}
};
3.1删除有序数组中的重复项
class Solution {
public:
int removeDuplicates(vector<int>& nums)
{
int slow = 1, fast = 1;
for (; fast < nums.size(); fast++)
{
if (nums[fast] == nums[fast - 1])
continue;
nums[slow++] = nums[fast];
}
return slow;
}
};
3.2 移动零
class Solution {
public:
void moveZeroes(vector<int>& nums)
{
int slow = 0, fast = 0;
for (; fast < nums.size(); fast++)
{
if(nums[fast] != 0)
{
nums[slow++] = nums[fast];
}
}
for (int i = slow; i < nums.size(); i++)
{
nums[i] = 0;
}
}
};
3.3 比较含退格的字符串(*)
class Solution {
public:
string dealBackspace(string s)
{
int slow = 0, fast = 0;
for (;fast < s.size(); fast++)
{
if (s[fast] != '#')
s[slow++] = s[fast];
else if (slow > 0)
{
slow--;
}
}
return s.substr(0, slow);
}
bool backspaceCompare(string s, string t)
{
return dealBackspace(s) == dealBackspace(t);
}
};
class Solution {
public:
string traversal(string s)
{
string res;
int num = 0;
for (int i = s.size() - 1; i >= 0; --i)
{
if (s[i] == '#')
{
num++;
}
else if (num > 0)
{
num--;
}
else
{
res += s[i];
}
}
return res;
}
bool backspaceCompare(string s, string t)
{
return traversal(s) == traversal(t);
}
};
这题一开始在traversal函数中没有返回新的string,因为这个函数无法修改原始的string,因此要新定义string进行返回
第二点是traversal中使用for循环比while循环好
3.4 有序数组的平方
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums)
{
vector<int> res(nums.size(), 0);//初始化res的大小
int left = 0;
int right = nums.size() - 1;
int top = nums.size() - 1;
while (left <= right)
{
if (nums[left]*nums[left] >= nums[right]*nums[right])
{
res[top--] = nums[left]*nums[left];
left++;
}
else if (nums[left]*nums[left] < nums[right]*nums[right])
{
res[top--] = nums[right]*nums[right];
right--;
}
}
return res;
}
};
4 长度最小的子数组(滑动窗口)(*)
class Solution
{
public:
int minSubArrayLen(int target, vector<int>& nums)
{
int length = INT_MAX;
int left = 0;
int sum = 0;
for (int right = 0; right < nums.size(); right++)
{
sum += nums[right];
while (sum >= target)
{
length = min(length, right - left + 1);
sum -= nums[left++];
}
}
return length == INT_MAX ? 0 : length;
}
};
class Solution
{
public:
int minSubArrayLen(int target, vector<int>& nums)
{
int min_len = INT_MAX;
int left = 0, right = 0;
int sum = 0;
while (right < nums.size())
{
sum += nums[right];
while (sum >= target)
{
min_len = min(min_len, right - left + 1);
sum -= nums[left];
left++;
}
right++;
}
return min_len == INT_MAX ? 0 : min_len;
}
};
4.1 水果成篮(**)
class Solution {
public:
int totalFruit(vector<int>& fruits) {
int i = 0, j = 0; // i为窗口起始位置,j为窗口结束位置
unordered_map<int, int> a; // 使用unordered_map代替数组作为哈希表
int max_fruits = 0;
int sum = 0; // 记录种类
for (j = 0; j < fruits.size(); ++j) {
if (a[fruits[j]] == 0) { // 当前种类的果树数量为零
sum++; // 果树种类+1
}
a[fruits[j]]++; // 相同类型树数目+1
while (sum > 2) { // 当果树种类大于2,就要对窗口进行收缩
a[fruits[i]]--; // 从窗口起始位置遍历,将当前果树的数量减1
if (a[fruits[i]] == 0) { // 如果当前果树的数量为零
sum--; // 果树种类-1
}
++i; // 继续遍历,直到果树种类只有两种
}
max_fruits = std::max(max_fruits, j - i + 1); // 更新最大值
}
return max_fruits;
}
};
示例用unordered_map实现
我自己做的时候用的unordered_set,效率较低
更简单的版本
class Solution {
public:
int totalFruit(vector<int>& fruits)
{
unordered_map<int, int> mp;
int max_num = 0; // 采摘的最大水果树数量
int left = 0; // 滑动窗口的左边界
for (int right = 0; right < fruits.size(); right++)
{
mp[fruits[right]]++;
// 当窗口内的水果种类超过2种时,调整左边界
while (mp.size() > 2)
{
mp[fruits[left]]--;
if (mp[fruits[left]] == 0)
{
mp.erase(fruits[left]);
}
left++;
}
// 更新最大数量
max_num = max(max_num, right - left + 1);
}
return max_num;
}
};
class Solution {
public:
int totalFruit(vector<int>& fruits)
{
unordered_set<int> set;
int left = 0, right = 0;
int max_sum = 0;
for(; right < fruits.size(); right++)
{
if(set.size() < 2)
{
set.insert(fruits[right]);
max_sum = max(max_sum, right - left + 1);
//cout << "第" << right << "次遍历" << "水果种类个数" << set.size() << endl;
}
else if (set.find(fruits[right]) != set.end())
{
max_sum = max(max_sum, right - left + 1);
//cout << "最大水果个数:" << max_sum << endl;
}
else
{
set.clear();
set.insert(fruits[right]);
set.insert(fruits[right - 1]);
//找窗口起始位置
int start = right - 1;
while(start > 0)
{
if (fruits[start - 1] != fruits[start])
{
break;
}
start--;
}
left = start;
//cout <<"窗口起始位置" << left << endl;
}
}
return max_sum;
}
};
4.2 最小覆盖子串(**)
class Solution {
public:
string minWindow(string s, string t) {
unordered_map<char, int> hs, ht;
for (auto c: t) ht[c] ++ ;
string res;
int cnt = 0;
for (int i = 0, j = 0; i < s.size(); i ++ ) {
hs[s[i]] ++ ;
if (hs[s[i]] <= ht[s[i]]) cnt ++ ;
while (hs[s[j]] > ht[s[j]]) hs[s[j ++ ]] -- ;
if (cnt == t.size()) {
if (res.empty() || i - j + 1 < res.size())
res = s.substr(j, i - j + 1);
}
}
return res;
}
};
超时的解法
class Solution
{
private:
bool traversal(const string& s, unordered_map<char,int>& t_map, int left, int right)
{
//需要满足t中重复字符的数量
unordered_map<char, int> s_map;
for (int i = left; i <= right; i++)
{
if (t_map.find(s[i]) != t_map.end())
{
s_map[s[i]]++;
}
}
for (auto &pair : t_map)
{
if (s_map[pair.first] < pair.second)
{
return false;
}
}
return true;
}
public:
string minWindow(string s, string t)
{
unordered_map<char, int> t_map;
for(int i = 0; i < t.size(); i++)
{
t_map[t[i]]++;
}
int left = 0, right = 0;
int min_left = 0;
int min_right = 0;
int min_len = INT_MAX;
string min_s = "";
while (right < s.size())
{
//找到窗口起始位置(必须为t中的字符串)
while (left < s.size() && t.find(s[left]) == string::npos)
{
left++;
}
//窗口向右扩展,直到窗口中出现t的所有字符
right = max(right, left);//???
while(right < s.size() && !traversal(s, t_map, left, right))
{
right++;
}
if (right >= s.size()) break;
//比较最小窗口长度和当前窗口长度
//若当前窗口长度小于最小窗口长度,则截取子字符串
if (min_len > right - left + 1)
{
min_len = right - left + 1;
min_s = s.substr(left, right - left + 1);
}
//窗口初始位置后移
left++;
}
return min_s;
}
};
优化
捉住滑动窗口本质,窗口什么时候扩展,什么时候收缩
class Solution
{
public:
string minWindow(string s, string t)
{
if (s.size() == 0 || t.size() == 0)
return "";
unordered_map<char, int> t_map;
for (char c : t)
{
t_map[c]++;
}
unordered_map<char, int> win_map;
int min_len = INT_MAX;
int min_left = 0;
int left = 0, right = 0;
int matched = 0;
//窗口向右扩展
while (right < s.size())
{
char rightc = s[right];
if (t_map.find(rightc) != t_map.end())
{
win_map[rightc]++;
if (win_map[rightc] == t_map[rightc])
{
matched++;
}
}
//如果窗口满足条件,窗口左边界需要收缩
while (matched == t_map.size())
{
if (min_len > right - left + 1)
{
min_len = right - left + 1;
min_left = left;
}
char leftc = s[left];
if (t_map.find(leftc) != t_map.end())
{
win_map[leftc]--;
if (win_map[leftc] < t_map[leftc])
{
matched--;
}
}
left++;
}
right++;
}
return min_len == INT_MAX ? "" : s.substr(min_left, min_len);
}
};
5 螺旋矩阵II(*)
class Solution {
public:
vector<vector<int>> generateMatrix(int n)
{
vector<vector<int> > matrix(n, vector<int>(n));
int num = 1;
int top = 0;int bottom = n - 1;
int left = 0; int right = n - 1;
while (num <= n * n)
{
//从左到右填充上边界
for (int i = left; i <= right; i++)
{
matrix[top][i] = num++;
}
top++;
//从上到下填充右边界
for (int i = top; i <= bottom; i++)
{
matrix[i][right] = num++;
}
right--;
//从右到左填充下边界
if (top <= bottom)
{
for (int i = right; i >= left; i--)
{
matrix[bottom][i] = num++;
}
bottom--;
}
//从下到上填充右边界
if (left <= right)
{
for (int i = bottom; i >= top; i--)
{
matrix[i][left] = num++;
}
left++;
}
}
return matrix;
}
};
以矩阵的层为单位进行填充,由外层向内层填充
class Solution {
private:
int dir[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; // 右、下、左、上四个方向
int select_next_dir(int curx, int cury, int layer, int n) {
if (curx == layer && cury < n - 1 - layer) {
return 0; // 右
}
else if (curx < n - 1 - layer && cury == n - 1 - layer) {
return 1; // 下
}
else if (curx == n - 1 - layer && cury > layer) {
return 2; // 左
}
else {
return 3; // 上
}
}
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> res(n, vector<int>(n, 0));
int num = 1;
int curx = 0, cury = 0;
int layer = 0; // 当前层数
while (num <= n * n)
{
res[curx][cury] = num++;
int nextdir = select_next_dir(curx, cury, layer, n);
int nextx = curx + dir[nextdir][0];
int nexty = cury + dir[nextdir][1];
// 如果下一个位置越界或已填充,说明需要进入下一层
if (nextx < layer || nextx >= n - layer || nexty < layer || nexty >= n - layer || res[nextx][nexty] != 0)
{
layer++;
curx = layer;
cury = layer;
} else
{
curx = nextx;
cury = nexty;
}
}
return res;
}
};