五月训练 Day9

0. Leetcode 35. 搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。

分析与解答

典型的二分查找问题,套用模板即可。
开区间 ( l , r ) (l, r) (l,r) 二分查找

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int l(-1), r(nums.size());
        while (l < r - 1) { // 中间区间长度为 1
            int mid = (l + r) / 2;
            // (l, r) 两端开区间搜索
            if (nums[mid] > target) {
                r = mid;
            } else if (nums[mid] < target) {
                l = mid;
            } else {
                return mid;
            }
        }
        
        // r 总是指向大于 target 的位置,l 总是指向小于 taeget 的位置
        return r;
    }
};

闭区间 [ l , r ] [l, r] [l,r] 二分查找

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int l(0), r(nums.size() - 1);
        // 注意闭区间 [l, r] 查找时的边界条件和更新与上一种方法的区别
        while (l <= r) {
            int mid = (l + r) / 2;
            if (nums[mid] > target) {
                r = mid - 1;
            } else if (nums[mid] < target) {
                l = mid + 1;
            } else {
                return mid;
            }
        }
        
        // 最后 l 所指为满足 nums[mid] < target 的后一个数
        return l;
    }
};

这里给出两种二分查找模板以供对比。算法时间复杂度为 O ( l o g N ) O(logN) O(logN)

1. Leetcode 704. 二分查找

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

分析与解答

同样给定一个有序数组,依然为典型的二分查找问题。

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int l(0), r(nums.size() - 1);
        
        while (l <= r) {
            int mid = (l + r) / 2;
            if (nums[mid] < target) {
                l = mid + 1;
            } else if (nums[mid] > target) {
                r = mid - 1;
            } else {
                return mid;
            }
        }
        
        return -1;
    }
};

算法时间复杂度为 O ( l o g N ) O(logN) O(logN)

2. Leetcode 剑指 Offer 53 - I. 在排序数组中查找数字 I

统计一个数字在排序数组中出现的次数。

分析与解答

由于数组为已排序数组,因此要统计某个数字出现的次数只要找到满足 n u m s [ i ] < t a r g e t nums[i] < target nums[i]<target 的最大下标与满足 n u m s [ j ] > t a r g e t nums[j] > target nums[j]>target 的最小下标即可,此时数字的出现次数为 j − i − 1 j - i - 1 ji1 次。这种情况下就将原问题转化为两次二分查找问题:

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int lb(0), rb(0);
        int l(-1), r(nums.size());
        // 查找左边界,满足 nums[i] < target 的最大 i
        while (l < r - 1) {
            int mid = (l + r) / 2;
            if (nums[mid] >= target) {
                r = mid;
            } else {
                l = mid;
            }
        }
        lb = l;
        l = -1;
        r = nums.size();
        // 查找右边界,满足 nums[j] > target 的最小 j
        while (l < r - 1) {
            int mid = (l + r) / 2;
            if (nums[mid] > target) {
                r = mid;
            } else {
                l = mid;
            }
        }
        rb = r;
        
        return rb - lb - 1;
    }
};

算法时间复杂度为 O ( l o g N ) O(logN) O(logN)

3. Leetcode 911. 在线选举

给你两个整数数组 persons 和 times 。在选举中,第 i 张票是在时刻为 times[i] 时投给候选人 persons[i] 的。
对于发生在时刻 t 的每个查询,需要找出在 t 时刻在选举中领先的候选人的编号。
在 t 时刻投出的选票也将被计入我们的查询之中。在平局的情况下,最近获得投票的候选人将会获胜。
实现 TopVotedCandidate 类:
TopVotedCandidate(int[] persons, int[] times) 使用 persons 和 times 数组初始化对象。
int q(int t) 根据前面描述的规则,返回在时刻 t 在选举中领先的候选人的编号。
提示:
1 <= persons.length <= 5000
times.length == persons.length
0 <= persons[i] < persons.length
0 <= times[i] <= 109
times 是一个严格递增的有序数组
times[0] <= t <= 109
每个测试用例最多调用 104 次 q

分析与解答

该问题为前缀和与二分查找的应用。首先在初始化时使用前缀和,计算每个 times[i] 时刻投票后对应的获胜者,将其存入 m_winner 中。之后,查询时使用二分查找,找到满足 t < t i m e s [ i ] t < times[i] t<times[i] 的最大 i i i,此时 m_winner[i - 1] 即为最终答案。

class TopVotedCandidate {
private:
    vector<int> m_winner; // 存储 times[i] 投票后对应的获胜者
    vector<int> m_persons;
    vector<int> m_times; // 查询时二分查找使用,查找满足 t < times[i] 的最大 i

public:
    TopVotedCandidate(vector<int>& persons, vector<int>& times) {
        m_persons = persons;
        m_times = times;

        // 计算每个时刻对应的获胜者
        // 对应时刻 times[i] 时的获胜者,-1 表示没有计算过
        m_winner.resize(persons.size(), -1);
        int curWinner(-1); // 当前获胜者
        vector<int> vote;
        vote.resize(5000, 0); // 最多 5000 个候选人
        for (int i= 0; i < persons.size(); ++i) {
            vote[persons[i]]++;
            if (curWinner < 0) { // 第一次投票
                curWinner = persons[i];
            } else { // 后续根据最高票选择获胜者,平手选最近的
                if (vote[persons[i]] >= vote[curWinner]) {
                    curWinner = persons[i];
                }
            }
            m_winner[i] = curWinner; // 更新获胜者向量
        }
    }
    
    int q(int t) {
        // 二分查找时间 t 在 times 中的位置,times[i - 1] <= t < times[i]
        int l(-1), r(m_times.size());
        while (l < r - 1) {
            int mid = (l + r) / 2;
            if (m_times[mid] <= t) {
                l = mid;
            } else {
                r = mid;
            }
        }

        return m_winner[r - 1];
    }
};

/**
 * Your TopVotedCandidate object will be instantiated and called as such:
 * TopVotedCandidate* obj = new TopVotedCandidate(persons, times);
 * int param_1 = obj->q(t);
 */

查询算法时间复杂度为 O ( l o g N ) O(logN) O(logN)

总结

二分查找模板理解之后十分简单,适用于大部分有序数组中的查找问题(例如前几天的两数和问题),是一种适用性很广的算法。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值