主要参考《代码随想录》,次要参考leetcode上面各种好的题解思路
letcode网址https://leetcode.cn/problems/binary-search/submissions/
算法特点
区间定义方式
左闭右闭代码
class Solution {
public:
int search(vector& nums, int target) {
int left = 0;
int right = nums.size() - 1; // 定义target在左闭右闭的区间里
while (left <= right) { // 前闭后闭用<=
// 当left==right,区间[left, right]依然有效,所以用 <=
int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2
if (nums[middle] > target) {// 结果在左区间
right = middle - 1;
// target A在左区间,所以[left, middle - 1]
} else if (nums[middle] < target) {// 结果在右区间
left = middle + 1;
// target 在右区间,所以[middle + 1, right]
} else { // 相等找到目标值
return middle; // 数组中找到目标值,直接返回下标
}
}
return -1;// 未找到目标值
}
};
letcode网址https://leetcode.cn/problems/search-insert-position/
代码
class Solution {
public:
int searchInsert(vector& nums, int target) {
int n = nums.size();
int left = 0;
int right = n - 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;
}
}
// 分别处理如下四种情况
// 目标值在数组所有元素之前 [0, -1]
// 目标值等于数组中某一个元素 return middle;
// 目标值插入数组中的位置 [left, right],return right + 1
// 目标值在数组所有元素之后的情况 [left, right], 因为是右闭区间,所以 return right + 1
return right + 1;// 取整总是取的前一个
}
};
letcode网址https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/
代码
class Solution {
public:
vector searchRange(vector& nums, int target) {
int rightBorder = getRightBorder(nums, target);
int leftBorder = getLeftBorder(nums, target);
if (leftBorder == -2 || rightBorder == -2) return {-1, -1};
if (rightBorder - leftBorder > 1) return {leftBorder + 1, rightBorder - 1};
return {-1, -1};
}
private:
int getRightBorder(vector& nums, int target) {
int left = 0;
int right = nums.size() - 1;
int mid;
int rightBorder = -2;
while (left <= right) {
mid = (left + right) / 2;
if (target < nums[mid]) {
right = mid - 1;
}else{
left = mid + 1;
rightBorder = left;
}
}
return rightBorder;
}
int getLeftBorder(vector& nums, int target) {
int left = 0;
int right = nums.size() - 1;
int mid;
int leftBorder = -2;
while (left <= right) {
mid = (left + right) / 2;
if (target > nums[mid]){
left = mid + 1;
}else{
right = mid - 1;
leftBorder = right;
}
}
return leftBorder;
}
};
letcode网址https://leetcode.cn/problems/sqrtx/
代码
class Solution {
public:
int mySqrt(int x) {
// x的平方根一定在[1, x/2+1]内
int left = 1;
int right = (x >> 1) + 1; // 除以2的倍数可以使用位运算
// 区间使用方式:前闭后闭
while (left <= right) {
int mid = left + ((right - left) >> 1); // 防止溢出,使用位运算加快速度
if (mid > x / mid) { // 尽量使用除法和减法防止数值太大而内存溢出
right = mid - 1;
} else if (mid < x / mid) {
left = mid + 1;
} else {
return mid;
}
}
return right; // 终止条件left > right,因为是向下取整,所以返回的是right
}
};
letcode网址https://leetcode.cn/problems/valid-perfect-square/submissions/
代码
class Solution {
public:
bool isPerfectSquare(int num) {
int left = 1;
int right = (num >> 1) + 1;
long mid;
while (left <= right) {
mid = left + ((right - left) >> 1);
if (num < mid*mid) {// 不能使用num / mid 和mid进行比较,会向下取整导致错误
right = mid - 1;
}else if (num > mid*mid){
left = mid + 1;
}else
return true;
}
return false;
}
};
// 完全平方数可以使用从1开始的连续奇数相加得到,不能得到的就是非完全平方数
class Solution
{
public:
bool isPerfectSquare(int num)
{
int num1 = 1;
while(num > 0)
{
num -= num1;
num1 += 2;
}
return num == 0;
}
};
数组 中的元素不能删除,只能覆盖,删除中间元素需要后面的元素都向前移动进行覆盖
使用双指针:快指针寻找所需要的元素赋值给慢指针所指向的位置
代码实现https://leetcode.cn/problems/remove-element/submissions/
class Solution {
public:
int removeElement(vector& nums, int val) {
int slow = 0;
// 快指针寻找所需元素,慢指针获取赋值位置
for(int fast = 1; fast < nums.size(); ++fast){
if (nums[fast] != val){
nums[slow] = nums[fast];
slow++;
}
}
return slow;// 数组的新长度
}
};
int removeDuplicates(vector& nums) {
int slow = 0;
for (int fast = 1; fast < nums.size() ; ++fast){
if (nums[fast] == nums[slow]){
continue;
}else{
// 不相等就覆盖下一个slow的位置
++slow;
nums[slow] = nums[fast];
}
}
return slow + 1;
}
代码
void moveZeroes(vector& nums) {
int slow = 0;
for(int fast = 0; fast < nums.size(); ++fast){
if (nums[fast] != 0)
nums[slow++] = nums[fast];
}
// 慢指针最后停下的位置是判断条件符合的最后一个
while (slow < nums.size())
nums[slow++] = 0;
}
使用双指针,将字符串的处理封装成函数,时间100%,但是空间因为堆栈消耗可能大一些,只是超过60%
class Solution {
public:
bool backspaceCompare(string s, string t) {
int sLength = 0;
int tLength = 0;
int ptr = 0;
// 对两个串都进行消减处理
handleString(s, sLength);
handleString(t, tLength);
// 长度不等,直接否定
if (sLength != tLength)
return false;
// 比较每个元素是否相同
while(ptr < sLength) {
if (s[ptr] != t[ptr])
return false;
ptr++;
}
return true;
}
private:
// 对每个字符串进行消减
void handleString(string &str, int &length) {
int slow = 0;
int fast = 0;
while (fast < str.size()) {
if (str[fast] != '#') {
str[slow] = str[fast];
slow++;
fast++;
} else {
fast++;
if (slow != 0) slow--;// 退格到0将无法继续退格
}
}
length = slow;
}
};
寻找多种情况中,固定的哪一点。例如:有序(最值一定在端点处)
代码
class Solution {
public:
vector sortedSquares(vector& nums) {
int p = nums.size() - 1;
vector result(nums.size(), 0); // 初始化结果数组
int left = 0;
int right = nums.size() - 1;
while (left <= right) {
if ((nums[left] * nums[left]) > (nums[right] * nums[right])) {
result[p--] = nums[left] * nums[left];
left++;
} else {
result[p--] = nums[right] * nums[right];
right--;// 左边界++,右边界--
}
}
return result;
}
};
滑动窗口的核心:根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。
代码
int minSubArrayLen(int target, vector<int>& nums) {
// 双指针定义
int slow = 0;
int fast = 0;
// 过程变量记录
int minLength = 0;
int sum = 0;
int result = INT32_MAX;// int的最大值
for(;fast < nums.size(); ++fast) {
sum += nums[fast];
while (sum >= target) {// 核心就是if改为while
minLength = fast - slow + 1;// 取连续子序列的长度
result = result < minLength ? result : minLength;
// 比较记录最小长度
sum -= nums[slow++];// 慢指针滑动
}
}
return result == INT32_MAX ? 0 : result;
}
题目地址https://leetcode.cn/problems/fruit-into-baskets/
代码
int totalFruit(vector& fruits) {
// 设置窗口边界
int slow = 0;
int fast = 0;
// 设置条件判定值
unordered_map basket; //创建篮子
int fruit_counter = 0; //篮子中的水果种类数
int length = 0;
while (fast < fruits.size()) {
// 快指针前进的条件
if (basket[fruits[fast]] == 0)// 水果种类fruits[fast]是键,basket[fruits[fast]]是值
fruit_counter++;
basket[fruits[fast]]++; // 插入新水果
// 慢指针前进的条件
while (fruit_counter > 2) {
basket[fruits[slow]]--;
if (basket[fruits[slow]] == 0) {
fruit_counter--;
}
slow++;
}
// 善后处理
length = max(length, fast - slow + 1);
fast++;
}
return length;
}
什么时候使用哈希法? 查询集合中的一个元素是否存在
map是一种key value的存储结构,key保存要查询的键值,value保存对应其他信息
map类型容器操作方法
unordered_map mp;
// 容器的插入
// 1. 直接使用[] 符号
mp[key] = "value";// mp[key]整体表示值
// 2. 使用make_pair构造对象进行插入
mp.insert(make_pair(key, "value"));
// 3. 使用pair方法构造键值对插入
mp.insert(pair(key, "d"));
// 查找方法
if (mp.find(key) != mp.end())
function;// 找到返回迭代器,找不到返回mp.end()
// 统计方法
mp.count(key);// 统计key在map中出现的次数,没有返回0
// 获取key的值
// 1. 使用迭代器
string val = mp.find(key)->second;
// 2. at方法,不存在会报异常
string val = mp.at(2);
// 3. 使用[]操作符,不存在会插入
string val = mp[key];
// 删除元素
// 1. 根据键删除
mp.erase(key);
// 2.根据值删除
for(iter = mp.begin(); iter!=mp.end();) {
if (iter->second=="value") {
mymap.erase(iter++);
} else {// 防止删除后迭代器失效
iter++;
}
}
// 遍历map
map::iterator iter;
for(iter = mp.begin(); iter != mp.end(); iter++) {
cout<first<<": "<second< 最小滑窗模板:给定数组 nums,定义滑窗的左右边界 i, j,求满足某个条件的滑窗的最小长度。
while j < len(nums):
判断[i, j]是否满足条件
while 满足条件:
不断更新结果(注意在while内更新!)
i += 1 (最大程度的压缩i,使得滑窗尽可能的小)
j += 1
最大滑窗模板:给定数组 nums,定义滑窗的左右边界 i, j,求满足某个条件的滑窗的最大长度。
while j < len(nums):
判断[i, j]是否满足条件
while 不满足条件:
i += 1(一旦满足条件了就退出压缩i的过程,使得滑窗尽可能的大)
不断更新结果(注意在while外更新!)
j += 1
代码
class Solution {
public:
string minWindow(string s, string t) {
unordered_map sMap;// s的哈希表
unordered_map tMap;// t的哈希表
// 初始化t的哈希表
for (auto c: t) // c是临时变量
tMap[c]++ ;
string res;
int cnt = 0;// 主串中必需字符的数量
for (int fast = 0, slow = 0; fast < s.size(); fast++ ) {
//
sMap[s[fast]]++ ;// 主串哈希表的插值
if (sMap[s[fast]] <= tMap[s[fast]])
cnt++ ;
// 剔除不符合的字符,进行窗口的缩紧
while (sMap[s[slow]] > tMap[s[slow]])
sMap[s[slow++]]-- ;
// 符合条件的子串,并迭代记录最小的
if (cnt == t.size()) {
if (res.empty() || fast - slow + 1 < res.size())
res = s.substr(slow, fast - slow + 1);
}
}
return res;
}
};
代码
class Solution {
public:
vector> generateMatrix(int n) {
vector> res(n, vector(n, 0)); // 使用vector定义一个二维数组
int startx = 0, starty = 0; // 定义每循环一个圈的起始位置
int loop = n / 2; // 每个圈循环几次,例如n为奇数3,那么loop = 1 只是循环一圈,矩阵中间的值需要单独处理
int mid = n / 2; // 矩阵中间的位置,例如:n为3, 中间的位置就是(1,1),n为5,中间位置为(2, 2)
int count = 1; // 用来给矩阵中每一个空格赋值
int offset = 1; // 需要控制每一条边遍历的长度,每次循环右边界收缩一位
int i,j;
while (loop --) {
i = startx;
j = starty;
// 下面开始的四个for就是模拟转了一圈
// 模拟填充上行从左到右(左闭右开)
for (j = starty; j < n - offset; j++) {
res[startx][j] = count++;
}
// 模拟填充右列从上到下(左闭右开)
for (i = startx; i < n - offset; i++) {
res[i][j] = count++;
}
// 模拟填充下行从右到左(左闭右开)
for (; j > starty; j--) {
res[i][j] = count++;
}
// 模拟填充左列从下到上(左闭右开)
for (; i > startx; i--) {
res[i][j] = count++;
}
// 第二圈开始的时候,起始位置要各自加1, 例如:第一圈起始位置是(0, 0),第二圈起始位置是(1, 1)
startx++;
starty++;
// offset 控制每一圈里每一条边遍历的长度
offset += 1;
}
// 如果n为奇数的话,需要单独给矩阵最中间的位置赋值
if (n % 2) {
res[mid][mid] = count;
}
return res;
}
};
代码
class Solution {
public:
vector spiralOrder(vector>& matrix) {
if (matrix.size() == 0 || matrix[0].size() == 0)
return {};
vector res;
int top = 0; //上边界
int bottom = matrix.size() - 1; //下边界
int left = 0; //左边界
int right = matrix[0].size() - 1; //右边界
while (true) {
// 从左到右
for (int i = left; i <= right; ++ i)
res.push_back(matrix[top][i]);
// 每次从左往右执行一次,则要往下移一层
if (++ top > bottom) break;
// 从上到下
for (int i = top; i <= bottom; ++ i)
res.push_back(matrix[i][right]);
// 每次从上到下执行一次,则要往左移一列
if (-- right < left) break;
// 从右到左
for (int i = right; i >= left; -- i)
res.push_back(matrix[bottom][i]);
// 每次从右到左执行一次,则要往上移一行
if (-- bottom < top) break;
// 从下到上
for (int i = bottom; i >= top; -- i)
res.push_back(matrix[i][left]);
// 每次从下到上执行一次,则要往右移一列
if (++ left > right) break;
}
return res;
}
};
c