手把手刷数据结构-2.手把手刷数组算法2

二分搜索

二分查找

链接: 二分查找.

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;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一记绝尘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值