手把手刷数组算法
二分搜索
二分查找
链接: 二分查找.
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 if (nums[mid] > target) {
right = mid - 1;
}
}
return -1;
}
};
寻找左侧边界的二分搜索
class Solution {
public:
int search(vector<int>& nums, int target) {
if (nums.size()== 0) return -1;
int left = 0;
int right = nums.size();
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
right = mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid;
}
}
// target 比所有数都大
if (left == nums.length) return -1;
// 类似之前算法的处理方式
return nums[left] == target ? left : -1;
}
};
与二分查找形式统一
int left_bound(vector<int>& nums, int target) {
int left = 0, right = nums.size()- 1;
// 搜索区间为 [left, right]
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
// 搜索区间变为 [mid+1, right]
left = mid + 1;
} else if (nums[mid] > target) {
// 搜索区间变为 [left, mid-1]
right = mid - 1;
} else if (nums[mid] == target) {
// 收缩右侧边界
right = mid - 1;
}
}
// 检查出界情况
if (left >= nums.size() || nums[left] != target) {
return -1;
}
return left;
}
寻找右侧边界的二分查找
class Solution {
public:
int search(vector<int>& nums, int target) {
if (nums.size()== 0) return -1;
int left = 0;
int right = nums.size();
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
left = mid + 1;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid;
}
}
if (left == 0) return -1;
return nums[left-1] == target ? (left-1) : -1;
}
};
与二分查找形式统一
int right_bound(vector<int>& nums, int target) {
int left = 0, right = nums.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] == target) {
// 这里改成收缩左侧边界即可
left = mid + 1;
}
}
// 这里改为检查 right 越界的情况
if (right < 0 || nums[right] != target) {
return -1;
}
return right;
}
在排序数组中查找元素的第一个和最后一个位置
逻辑统一
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
return {left_bound(nums, target), right_bound(nums, target)};
}
int left_bound(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) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] == target) {
right = mid - 1;
}
}
if (left >= nums.size() || nums[left] != target) {
return -1;
}
return left;
}
int right_bound(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) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] == target) {
left = mid + 1;
}
}
if (right < 0 || nums[right] != target) {
return -1;
}
return right;
}
};
带权重的随机选择算法
class Solution {
private:
vector<int> sums; //sums为前缀和数组
int total = 0;
public:
Solution(vector<int>& w) {
sums.resize(w.size());
for (int i = 0; i < w.size(); ++ i) //构造前缀和数组
{
total += w[i];
sums[i] = total;
}
}
int pickIndex() {
int rnd = rand() % total; //生成最大值范围内的随机数
int left = 0, right = sums.size() - 1;
while (left < right) //二分法在前缀和数组中找到第一个大于随机数的元素下标
{
int mid = left + (right - left) / 2;
if (rnd < sums[mid])
{
right = mid;
}
else
{
left = mid + 1;
}
}
return left;
}
};
田忌赛马
但是你仔细想想,现在 T2 已经是可以战胜 Q1 的,Q1 可是齐王的最快的马耶,齐王剩下的那些马里,怎么可能还有比 T2 更强的马?所以,没必要节约,最后我们得出的策略就是:将齐王和田忌的马按照战斗力排序,然后按照排名一一对比。如果田忌的马能赢,那就比赛,如果赢不了,那就换个垫底的来送人头,保存实力。
链接: 优势洗牌.
class Solution {
public:
vector<int> advantageCount(vector<int>& nums1, vector<int>& nums2) {
int n = nums1.size();
//先按照从大到小排序
sort(nums1.begin(), nums1.end(), greater<int>());
vector<pair<int, int>> sorted2(n);
for(int i = 0; i < n; i++){
sorted2[i] = {nums2[i], i};
}
sort(sorted2.begin(), sorted2.end(), [](const auto& a, const auto& b){return a.first > b.first;});
vector<int> res(n);
// nums1[left] 是最小值,nums1[right] 是最大值
int left = 0, right = n - 1;
//遍历 B 中所有的马匹
for (int i = 0; i < n; i++) {
// 当前 B 中的马匹
auto [cur, idx] = sorted2[i];
// A 打不过 B,找最弱的来
if(nums1[left] <= cur) {
res[idx] = nums1[right];
right--;
} else {
//打的过,就直接比
res[idx] = nums1[left];
left++;
}
}
return res;
}
};
常数时间删除/查找数组中的任意元素
O(1)时间插入、删除和获取随机元素
链接: O(1)时间插入、删除和获取随机元素.
class RandomizedSet {
private:
vector<int> nums; // 存储元素的值
unordered_map<int, int> valToIndex;// 记录每个元素对应在 nums 中的索引
public:
RandomizedSet() {
srand((unsigned)time(NULL));
}
bool insert(int val) {
// 若 val 已存在,不用再插入
if (valToIndex.count(val)) {
return false;
}
// 若 val 不存在,插入到 nums 尾部,
// 并记录 val 对应的索引值
valToIndex[val] = nums.size();
nums.push_back(val);
return true;
}
bool remove(int val) {
// 若 val 不存在,不用再删除
if (!valToIndex.count(val)) {
return false;
}
// 先拿到 val 的索引
int index = valToIndex[val];
// 将最后一个元素对应的索引修改为 index
valToIndex[nums.back()] = index;
// 交换 val 和最后一个元素
swap(nums[index], nums.back());
// 在数组中删除元素 val
nums.pop_back();
// 删除元素 val 对应的索引
valToIndex.erase(val);
return true;
}
int getRandom() {
int randomIndex = rand() % nums.size();
return nums[randomIndex];
}
};
黑名单中的随机数
链接: 黑名单中的随机数.
class Solution {
public:
int sz;
unordered_map<int, int> mapping;
// 构造函数,输入参数
Solution(int n, vector<int>& blacklist) {
sz = n - blacklist.size();
// 先将所有黑名单数字加入 map
for (int b : blacklist) {
// 这里赋值多少都可以
// 目的仅仅是把键存进哈希表
// 方便快速判断数字是否在黑名单内
mapping[b] = 666;
}
int last = n - 1;
for (int b : blacklist) {
// 如果 b 已经在区间 [sz, N)
// 可以直接忽略
if (b >= sz) {
continue;
}
while (mapping.count(last)) {
last--;
}
mapping[b] = last;
last--;
}
}
// 在区间 [0,N) 中等概率随机选取一个元素并返回
// 这个元素不能是 blacklist 中的元素
int pick() {
// 随机选取一个索引
int index = rand() % sz;
// 这个索引命中了黑名单,
// 需要被映射到其他位置
if (mapping.count(index)) {
return mapping[index];
}
// 若没命中黑名单,则直接返回
return index;
}
};