第八课 二分

news2024/12/24 8:54:34

文章目录

  • 第八课 二分
    • lc704.二分查找--简单
      • 题目描述
      • 代码展示
    • 二分模版
    • lc34.排序数组中查找元素的第一个和最后一个位置--中等
      • 题目描述
      • 代码展示
    • lc69.x的平方根--简单
      • 题目描述
      • 代码展示
    • lc74.搜索二维矩阵--中等
      • 题目描述
      • 代码展示
    • lc153.寻找旋转排序数组中的最小值--中等
      • 题目描述
      • 代码展示
    • lc154.寻找寻找排序数组中的最小值II--困难
      • 题目描述
      • 代码展示
    • 三分查找
    • lc162.寻找峰值--中等
      • 题目描述
      • 代码展示
    • lc374.猜数字大小--简单
      • 题目描述
      • 代码展示
    • 二分答案--最优性问题转化为判定问题的基本技巧
    • lc410.分割数组的最大值--困难
      • 题目描述
      • 代码展示
    • lc1482.制作m束花所需的最少天数--中等
      • 题目描述
      • 代码展示
    • lc1011.在D天内送达包裹的能力--中等
      • 题目描述
      • 代码展示
    • lc911.在线选举--中等
      • 题目描述
      • 代码展示
    • lc875.爱吃香蕉的珂珂--中等
      • 题目描述
      • 代码展示

第八课 二分

lc704.二分查找–简单

题目描述

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

示例 1:

输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4

示例 2:

输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1

提示:

  1. 你可以假设 nums 中的所有元素是不重复的。
  2. n 将在 [1, 10000]之间。
  3. nums 的每个元素都将在 [-9999, 9999]之间。

代码展示

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

二分模版

image-20231007150416695

image-20231007150453974

image-20231007150510451

image-20231007150522090

image-20231007150534498

lc34.排序数组中查找元素的第一个和最后一个位置–中等

题目描述

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]

示例 2:

输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]

示例 3:

输入:nums = [], target = 0
输出:[-1,-1]

提示:

  • 0 <= nums.length <= 105
  • -109 <= nums[i] <= 109
  • nums 是一个非递减数组
  • -109 <= target <= 109

代码展示

class Solution {
     
  // 两次二分查找,分开查找第一个和最后一个
  // 时间复杂度 O(log n), 空间复杂度 O(1)
  // [1,2,3,3,3,3,4,5,9]
  public int[] searchRange(int[] nums, int target) {
    int left = 0;
    int right = nums.length - 1;
    int first = -1;
    int last = -1;
    // 找第一个等于target的位置
    while (left <= right) {
      int middle = (left + right) / 2;
      if (nums[middle] == target) {
        first = middle;
        right = middle - 1; //重点
      } else if (nums[middle] > target) {
        right = middle - 1;
      } else {
        left = middle + 1;
      }
    }

    // 最后一个等于target的位置
    left = 0;
    right = nums.length - 1;
    while (left <= right) {
      int middle = (left + right) / 2;
      if (nums[middle] == target) {
        last = middle;
        left = middle + 1; //重点
      } else if (nums[middle] > target) {
        right = middle - 1;
      } else {
        left = middle + 1;
      }
    }

    return new int[]{first, last};
  }
}

lc69.x的平方根–简单

题目描述

给你一个非负整数 x ,计算并返回 x算术平方根

由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。

**注意:**不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5

示例 1:

输入:x = 4
输出:2

示例 2:

输入:x = 8
输出:2
解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。

提示:

  • 0 <= x <= 231 - 1

代码展示

class Solution {
public:
    int mySqrt(int x) {
        // 找最大的ans,满足ans^2<=x
        long long left = 0, right = x;
        while (left < right) {
            long long mid = (left + right + 1) / 2;
            if (mid * mid <= x) {
                left = mid;
            } else {
                right = mid - 1;
            }
        }
        return right;
    }
};

lc74.搜索二维矩阵–中等

题目描述

给你一个满足下述两条属性的 m x n 整数矩阵:

  • 每行中的整数从左到右按非递减顺序排列。
  • 每行的第一个整数大于前一行的最后一个整数。

给你一个整数 target ,如果 target 在矩阵中,返回 true ;否则,返回 false

示例 1:

img

输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 3
输出:true

示例 2:

img

输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 13
输出:false

提示:

  • m == matrix.length
  • n == matrix[i].length
  • 1 <= m, n <= 100
  • -104 <= matrix[i][j], target <= 104

代码展示

class Solution {
public:       //两次二分查找
    bool searchMatrix(vector<vector<int>> matrix, int target) {
        auto row = upper_bound(matrix.begin(), matrix.end(), target, [](const int b, const vector<int> &a) {
            return b < a[0];
        });
        if (row == matrix.begin()) {
            return false;
        }
        --row;
        return binary_search(row->begin(), row->end(), target);
    }
};
class Solution {
public:       //一次二分查找
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        int m = matrix.size(), n = matrix[0].size();
        int low = 0, high = m * n - 1;
        while (low <= high) {
            int mid = (high - low) / 2 + low;
            int x = matrix[mid / n][mid % n];
            if (x < target) {
                low = mid + 1;
            } else if (x > target) {
                high = mid - 1;
            } else {
                return true;
            }
        }
        return false;
    }
};

lc153.寻找旋转排序数组中的最小值–中等

题目描述

已知一个长度为 n 的数组,预先按照升序排列,经由 1n旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:

  • 若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]
  • 若旋转 7 次,则可以得到 [0,1,2,4,5,6,7]

注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]]

给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素

你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

输入:nums = [3,4,5,1,2]
输出:1
解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。

示例 2:

输入:nums = [4,5,6,7,0,1,2]
输出:0
解释:原数组为 [0,1,2,4,5,6,7] ,旋转 4 次得到输入数组。

示例 3:

输入:nums = [11,13,15,17]
输出:11
解释:原数组为 [11,13,15,17] ,旋转 4 次得到输入数组。

提示:

  • n == nums.length
  • 1 <= n <= 5000
  • -5000 <= nums[i] <= 5000
  • nums 中的所有整数 互不相同
  • nums 原来是一个升序排序的数组,并进行了 1n 次旋转

代码展示

class Solution {
public:
    int findMin(vector<int>& nums) {
        int left = 0;
        int right = nums.size() - 1;

        while (left < right) {
            int mid = left + (right - left) / 2;

            // 如果中间元素大于右边元素,说明最小值在右半部分
            if (nums[mid] > nums[right]) {
                left = mid + 1;
            } else {
                // 否则最小值在左半部分或者就是中间元素
                right = mid;
            }
        }

        // 最小元素就是左边界元素
        return nums[left];
    }
};

lc154.寻找寻找排序数组中的最小值II–困难

题目描述

已知一个长度为 n 的数组,预先按照升序排列,经由 1n旋转 后,得到输入数组。例如,原数组 nums = [0,1,4,4,5,6,7] 在变化后可能得到:

  • 若旋转 4 次,则可以得到 [4,5,6,7,0,1,4]
  • 若旋转 7 次,则可以得到 [0,1,4,4,5,6,7]

注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]]

给你一个可能存在 重复 元素值的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素

你必须尽可能减少整个过程的操作步骤。

示例 1:

输入:nums = [1,3,5]
输出:1

示例 2:

输入:nums = [2,2,2,0,1]
输出:0

提示:

  • n == nums.length
  • 1 <= n <= 5000
  • -5000 <= nums[i] <= 5000
  • nums 原来是一个升序排序的数组,并进行了 1n 次旋转

代码展示

class Solution {
public:
    int findMin(vector<int>& nums) {
        int left = 0;
        int right = nums.size() - 1;

        while (left < right) {
            int mid = left + (right - left) / 2;

            // 如果中间元素大于右边元素,说明最小值在右半部分
            if (nums[mid] > nums[right]) {
                left = mid + 1;
            } else if (nums[mid] < nums[right]) {
                // 如果中间元素小于右边元素,说明最小值在左半部分或者就是中间元素
                right = mid;
            } else {
                // 如果中间元素等于右边元素,无法确定最小值在左半部分还是右半部分
                // 只能将右边界向左移动一个位置,继续搜索
                right--;
            }
        }

        // 最小元素就是左边界元素
        return nums[left];
    }
};
//这段代码在二分查找的基础上,处理了可能存在重复元素值的情况。在中间元素等于右边元素时,将右边界
//向左移动一个位置,继续搜索,以确保找到最小元素。这样的算法复杂度仍然是 O(log n)。

三分查找

image-20231007152314664

image-20231007152335721

image-20231007152347531

lc162.寻找峰值–中等

题目描述

峰值元素是指其值严格大于左右相邻值的元素。

给你一个整数数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。

你可以假设 nums[-1] = nums[n] = -∞

你必须实现时间复杂度为 O(log n) 的算法来解决此问题。

示例 1:

输入:nums = [1,2,3,1]
输出:2
解释:3 是峰值元素,你的函数应该返回其索引 2。

示例 2:

输入:nums = [1,2,1,3,5,6,4]
输出:1 或 5 
解释:你的函数可以返回索引 1,其峰值元素为 2;
     或者返回索引 5, 其峰值元素为 6。

提示:

  • 1 <= nums.length <= 1000
  • -231 <= nums[i] <= 231 - 1
  • 对于所有有效的 i 都有 nums[i] != nums[i + 1]

代码展示

class Solution {
public:
    int findPeakElement(vector<int>& nums) {
        int left = 0;
        int right = nums.size() - 1;

        while (left < right) {
            int mid = left + (right - left) / 2;

            // 如果中间元素小于其右边元素,说明峰值在右半部分
            if (nums[mid] < nums[mid + 1]) {
                left = mid + 1;
            } else {
                // 否则峰值在左半部分或者就是中间元素
                right = mid;
            }
        }

        // 返回左边界,即峰值所在位置
        return left;
    }
};
//这段代码使用二分查找,通过比较中间元素和其右边元素的大小来决定峰值在哪一侧,然后不断缩小搜索范
//围,直到找到峰值元素的索引。由于每次将搜索范围缩小一半,这个算法的时间复杂度为 O(log n)。

lc374.猜数字大小–简单

题目描述

猜数字游戏的规则如下:

  • 每轮游戏,我都会从 1n 随机选择一个数字。 请你猜选出的是哪个数字。
  • 如果你猜错了,我会告诉你,你猜测的数字比我选出的数字是大了还是小了。

你可以通过调用一个预先定义好的接口 int guess(int num) 来获取猜测结果,返回值一共有 3 种可能的情况(-110):

  • -1:我选出的数字比你猜的数字小 pick < num
  • 1:我选出的数字比你猜的数字大 pick > num
  • 0:我选出的数字和你猜的数字一样。恭喜!你猜对了!pick == num

返回我选出的数字。

示例 1:

输入:n = 10, pick = 6
输出:6

示例 2:

输入:n = 1, pick = 1
输出:1

示例 3:

输入:n = 2, pick = 1
输出:1

示例 4:

输入:n = 2, pick = 2
输出:2

提示:

  • 1 <= n <= 231 - 1
  • 1 <= pick <= n

代码展示

// Forward declaration of guess API.
int guess(int num);

class Solution {
public:
    int guessNumber(int n) {
        int left = 1;
        int right = n;

        while (left <= right) {
            int mid = left + (right - left) / 2;
            int result = guess(mid);

            if (result == 0) {
                return mid; // 找到了答案
            } else if (result == 1) {
                left = mid + 1; // 答案在右半部分
            } else {
                right = mid - 1; // 答案在左半部分
            }
        }

        return -1; // 未找到答案,这里可以根据具体情况返回 -1 或其他合适的值
    }
};

二分答案–最优性问题转化为判定问题的基本技巧

image-20231007152915513

lc410.分割数组的最大值–困难

题目描述

给定一个非负整数数组 nums 和一个整数 m ,你需要将这个数组分成 m 个非空的连续子数组。

设计一个算法使得这 m 个子数组各自和的最大值最小。

示例 1:

输入:nums = [7,2,5,10,8], m = 2
输出:18
解释:
一共有四种方法将 nums 分割为 2 个子数组。 
其中最好的方式是将其分为 [7,2,5] 和 [10,8] 。
因为此时这两个子数组各自的和的最大值为18,在所有情况中最小。

示例 2:

输入:nums = [1,2,3,4,5], m = 2
输出:9

示例 3:

输入:nums = [1,4,4], m = 3
输出:4

提示:

  • 1 <= nums.length <= 1000
  • 0 <= nums[i] <= 106
  • 1 <= m <= min(50, nums.length)

image-20231007153013014

代码展示

class Solution {
public:
    int splitArray(vector<int>& nums, int k) {
        long long left = 0;
        long long right = 0;
        
        // 计算 left 和 right 的边界
        for (int num : nums) {
            left = max(left, (long long)num);
            right += num;
        }
        
        while (left < right) {
            long long mid = left + (right - left) / 2;
            
            if (canSplit(nums, k, mid)) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        
        return left;
    }
    
    bool canSplit(vector<int>& nums, int k, long long maxSum) {
        int count = 1;
        long long sum = 0;
        
        for (int num : nums) {
            sum += num;
            if (sum > maxSum) {
                count++;
                sum = num;
            }
        }
        
        return count <= k;
    }
};

image-20231007153132011

image-20231007153145183

image-20231007153219677

本质:

二分答案的本质是建立一个单调分段0/1函数,定义域为解空间(答案),值域为0或1,
在这个函数上二分查找分界点

lc1482.制作m束花所需的最少天数–中等

题目描述

给你一个整数数组 bloomDay,以及两个整数 mk

现需要制作 m 束花。制作花束时,需要使用花园中 相邻的 k 朵花

花园中有 n 朵花,第 i 朵花会在 bloomDay[i] 时盛开,恰好 可以用于 一束 花中。

请你返回从花园中摘 m 束花需要等待的最少的天数。如果不能摘到 m 束花则返回 -1

示例 1:

输入:bloomDay = [1,10,3,10,2], m = 3, k = 1
输出:3
解释:让我们一起观察这三天的花开过程,x 表示花开,而 _ 表示花还未开。
现在需要制作 3 束花,每束只需要 1 朵。
1 天后:[x, _, _, _, _]   // 只能制作 1 束花
2 天后:[x, _, _, _, x]   // 只能制作 2 束花
3 天后:[x, _, x, _, x]   // 可以制作 3 束花,答案为 3

示例 2:

输入:bloomDay = [1,10,3,10,2], m = 3, k = 2
输出:-1
解释:要制作 3 束花,每束需要 2 朵花,也就是一共需要 6 朵花。而花园中只有 5 朵花,无法满足制作要求,返回 -1 。

示例 3:

输入:bloomDay = [7,7,7,7,12,7,7], m = 2, k = 3
输出:12
解释:要制作 2 束花,每束需要 3 朵。
花园在 7 天后和 12 天后的情况如下:
7 天后:[x, x, x, x, _, x, x]
可以用前 3 朵盛开的花制作第一束花。但不能使用后 3 朵盛开的花,因为它们不相邻。
12 天后:[x, x, x, x, x, x, x]
显然,我们可以用不同的方式制作两束花。

示例 4:

输入:bloomDay = [1000000000,1000000000], m = 1, k = 1
输出:1000000000
解释:需要等 1000000000 天才能采到花来制作花束

示例 5:

输入:bloomDay = [1,10,2,9,3,8,4,7,5,6], m = 4, k = 2
输出:9

提示:

  • bloomDay.length == n
  • 1 <= n <= 10^5
  • 1 <= bloomDay[i] <= 10^9
  • 1 <= m <= 10^6
  • 1 <= k <= n

代码展示

class Solution {
public:
    int minDays(vector<int>& bloomDay, int m, int k) {
        // 如果需要的花束数量大于花朵总数除以每束花的数量,无法制作足够的花束,返回 -1
        if (m > bloomDay.size() / k) {
            return -1;
        }
        
        // 初始化最小和最大等待天数
        int low = INT_MAX, high = 0;
        int length = bloomDay.size();
        for (int i = 0; i < length; i++) {
            low = min(low, bloomDay[i]); // 更新最小等待天数
            high = max(high, bloomDay[i]); // 更新最大等待天数
        }
        
        // 二分查找
        while (low < high) {
            int days = (high - low) / 2 + low; // 当前猜测的等待天数
            if (canMake(bloomDay, days, m, k)) {
                high = days; // 在当前天数内可以制作足够的花束,将 high 缩小到 days
            } else {
                low = days + 1; // 在当前天数内无法制作足够的花束,将 low 增大到 days + 1
            }
        }
        
        return low; // 返回最小等待天数
    }

    bool canMake(vector<int>& bloomDay, int days, int m, int k) {
        int bouquets = 0; // 制作的花束数量
        int flowers = 0; // 当前花束中的花朵数量
        int length = bloomDay.size();
        for (int i = 0; i < length && bouquets < m; i++) {
            if (bloomDay[i] <= days) {
                flowers++; // 符合开放条件的花朵数量递增
                if (flowers == k) { // 达到花束大小
                    bouquets++; // 制作的花束数量递增
                    flowers = 0; // 重置当前花束中的花朵数量
                }
            } else {
                flowers = 0; // 当前花朵无法用于制作花束,重置数量
            }
        }
        
        return bouquets >= m; // 制作的花束数量是否达到目标
    }
};

lc1011.在D天内送达包裹的能力–中等

题目描述

传送带上的包裹必须在 days 天内从一个港口运送到另一个港口。

传送带上的第 i 个包裹的重量为 weights[i]。每一天,我们都会按给出重量(weights)的顺序往传送带上装载包裹。我们装载的重量不会超过船的最大运载重量。

返回能在 days 天内将传送带上的所有包裹送达的船的最低运载能力。

示例 1:

输入:weights = [1,2,3,4,5,6,7,8,9,10], days = 5
输出:15
解释:
船舶最低载重 15 就能够在 5 天内送达所有包裹,如下所示:
第 1 天:1, 2, 3, 4, 5
第 2 天:6, 7
第 3 天:8
第 4 天:9
第 5 天:10

请注意,货物必须按照给定的顺序装运,因此使用载重能力为 14 的船舶并将包装分成 (2, 3, 4, 5), (1, 6, 7), (8), (9), (10) 是不允许的。 

示例 2:

输入:weights = [3,2,2,4,1,4], days = 3
输出:6
解释:
船舶最低载重 6 就能够在 3 天内送达所有包裹,如下所示:
第 1 天:3, 2
第 2 天:2, 4
第 3 天:1, 4

示例 3:

输入:weights = [1,2,3,1,1], days = 4
输出:3
解释:
第 1 天:1
第 2 天:2
第 3 天:3
第 4 天:1, 1

提示:

  • 1 <= days <= weights.length <= 5 * 104
  • 1 <= weights[i] <= 500

代码展示

class Solution {
public:
    int shipWithinDays(vector<int>& weights, int days) {
        int left = *max_element(weights.begin(), weights.end()); // 载重的下界
        int right = accumulate(weights.begin(), weights.end(), 0); // 载重的上界
        
        while (left < right) {
            int mid = left + (right - left) / 2; // 当前的运载能力猜测
            int daysNeeded = 1; // 当前载重下的天数
            int currentLoad = 0; // 当前运载的重量
            
            for (int weight : weights) {
                if (currentLoad + weight > mid) {
                    daysNeeded++; // 超过了当前运载能力,需要增加天数
                    currentLoad = 0; // 重置当前运载重量
                }
                currentLoad += weight; // 加载当前货物
            }
            
            if (daysNeeded <= days) {
                right = mid; // 可以在给定的天数内完成运输,缩小上界
            } else {
                left = mid + 1; // 需要更多的天数来完成运输,增加下界
            }
        }
        
        return left; // 返回最低的运载能力
    }
};

这段代码首先初始化左右指针,左指针 left 是所有货物中的最大重量,右指针 right 是所有货物总重量之和。然后,使用二分查找,在每一轮查找中,计算一个猜测的运载能力 mid,然后模拟按照这个运载能力来运送货物,计算需要的天数 daysNeeded。如果 daysNeeded 小于等于给定的天数 days,说明当前运载能力足够,可以在规定的天数内完成运输,所以将 right 缩小到 mid。如果 daysNeeded 大于 days,说明需要更多的天数来完成运输,因此将 left 增加到 mid + 1。最终,当 leftright 相等时,就找到了最低的运载能力,返回 left 即可。这个运算过程的时间复杂度是 O(n * log(sum(weights))),其中 n 是货物的数量。

lc911.在线选举–中等

题目描述

给你两个整数数组 personstimes 。在选举中,第 i 张票是在时刻为 times[i] 时投给候选人 persons[i] 的。

对于发生在时刻 t 的每个查询,需要找出在 t 时刻在选举中领先的候选人的编号。

t 时刻投出的选票也将被计入我们的查询之中。在平局的情况下,最近获得投票的候选人将会获胜。

实现 TopVotedCandidate 类:

  • TopVotedCandidate(int[] persons, int[] times) 使用 personstimes 数组初始化对象。
  • int q(int t) 根据前面描述的规则,返回在时刻 t 在选举中领先的候选人的编号。

示例:

输入:
["TopVotedCandidate", "q", "q", "q", "q", "q", "q"]
[[[0, 1, 1, 0, 0, 1, 0], [0, 5, 10, 15, 20, 25, 30]], [3], [12], [25], [15], [24], [8]]
输出:
[null, 0, 1, 1, 0, 0, 1]

解释:
TopVotedCandidate topVotedCandidate = new TopVotedCandidate([0, 1, 1, 0, 0, 1, 0], [0, 5, 10, 15, 20, 25, 30]);
topVotedCandidate.q(3); // 返回 0 ,在时刻 3 ,票数分布为 [0] ,编号为 0 的候选人领先。
topVotedCandidate.q(12); // 返回 1 ,在时刻 12 ,票数分布为 [0,1,1] ,编号为 1 的候选人领先。
topVotedCandidate.q(25); // 返回 1 ,在时刻 25 ,票数分布为 [0,1,1,0,0,1] ,编号为 1 的候选人领先。(在平局的情况下,1 是最近获得投票的候选人)。
topVotedCandidate.q(15); // 返回 0
topVotedCandidate.q(24); // 返回 0
topVotedCandidate.q(8); // 返回 1

提示:

  • 1 <= persons.length <= 5000
  • times.length == persons.length
  • 0 <= persons[i] < persons.length
  • 0 <= times[i] <= 109
  • times 是一个严格递增的有序数组
  • times[0] <= t <= 109
  • 每个测试用例最多调用 104q

代码展示

为了解决这个问题,可以使用前缀和和二分查找的方法来快速找到在时刻 t 领先的候选人的编号。

首先,我们需要创建一个前缀和数组,用于记录每个时刻对应的候选人的得票数。然后,在查询时,可以使用二分查找找到时刻 ttimes 数组中的位置,然后查找该位置对应的前缀和数组中的候选人编号,这个编号就是在时刻 t 领先的候选人。

以下是具体的代码实现:

class TopVotedCandidate {
public:
    TopVotedCandidate(vector<int>& persons, vector<int>& times) {
        int n = persons.size();
        leadingPerson.resize(n);
        prefixVotes.resize(n);
        vector<int> votesCount(n, 0);
        
        int maxVotes = 0;
        for (int i = 0; i < n; i++) {
            int person = persons[i];
            votesCount[person]++;
            if (votesCount[person] >= maxVotes) {
                maxVotes = votesCount[person];
                leadingPerson[i] = person;
            } else {
                leadingPerson[i] = leadingPerson[i - 1];
            }
            prefixVotes[i] = leadingPerson[i];
        }
        
        this->times = times;
    }
    
    int q(int t) {
        int index = binarySearch(t);
        return prefixVotes[index];
    }
    
private:
    vector<int> leadingPerson;
    vector<int> prefixVotes;
    vector<int> times;
    
    int binarySearch(int t) {
        int left = 0;
        int right = times.size() - 1;
        
        while (left < right) {
            int mid = left + (right - left + 1) / 2;
            if (times[mid] <= t) {
                left = mid;
            } else {
                right = mid - 1;
            }
        }
        
        return left;
    }
};

在构造函数中,我们首先计算了每个时刻的领先候选人并存储在 leadingPerson 数组中。然后,我们将该数组复制到 prefixVotes 数组中,以便在查询时快速获取领先的候选人。在查询函数 q 中,我们使用二分查找来找到时刻 t 对应的位置,并返回对应的候选人。

lc875.爱吃香蕉的珂珂–中等

题目描述

珂珂喜欢吃香蕉。这里有 n 堆香蕉,第 i 堆中有 piles[i] 根香蕉。警卫已经离开了,将在 h 小时后回来。

珂珂可以决定她吃香蕉的速度 k (单位:根/小时)。每个小时,她将会选择一堆香蕉,从中吃掉 k 根。如果这堆香蕉少于 k 根,她将吃掉这堆的所有香蕉,然后这一小时内不会再吃更多的香蕉。

珂珂喜欢慢慢吃,但仍然想在警卫回来前吃掉所有的香蕉。

返回她可以在 h 小时内吃掉所有香蕉的最小速度 kk 为整数)。

示例 1:

输入:piles = [3,6,7,11], h = 8
输出:4

示例 2:

输入:piles = [30,11,23,4,20], h = 5
输出:30

示例 3:

输入:piles = [30,11,23,4,20], h = 6
输出:23

提示:

  • 1 <= piles.length <= 104
  • piles.length <= h <= 109
  • 1 <= piles[i] <= 109

代码展示

这个问题可以使用二分查找来解决。我们可以在可能的速度范围内进行二分查找,找到使得珂珂可以在 h 小时内吃掉所有香蕉的最小速度 k

以下是具体的代码实现:

class Solution {
public:
    int minEatingSpeed(vector<int>& piles, int h) {
        int left = 1; // 最低速度
        int right = *max_element(piles.begin(), piles.end()); // 最高速度
        
        while (left < right) {
            int mid = left + (right - left) / 2; // 当前猜测的速度
            if (canEatAll(piles, mid, h)) {
                right = mid; // 可以在规定时间内吃完,减小速度
            } else {
                left = mid + 1; // 需要更多时间,增加速度
            }
        }
        
        return left; // 返回最小速度
    }
    
private:
    bool canEatAll(vector<int>& piles, int speed, int h) {
        int hoursNeeded = 0;
        for (int pile : piles) {
            hoursNeeded += (pile + speed - 1) / speed; // 使用向上取整来计算小时
        }
        return hoursNeeded <= h;
    }
};

在这段代码中,我们首先初始化左右指针,左指针 left 是最低速度 1,右指针 right 是堆中最多香蕉的数量。然后,在每一轮二分查找中,我们计算当前速度 mid 下是否可以在规定时间内吃完所有香蕉,这是通过 canEatAll 函数来计算的。如果可以,就缩小右边界,如果不能,就增加左边界。最终返回左指针 left 即可。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1066751.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

剑指offer——JZ33 二叉搜索树的后序遍历序列 解题思路与具体代码【C++】

一、题目描述与要求 二叉搜索树的后序遍历序列_牛客题霸_牛客网 (nowcoder.com) 题目描述 输入一个整数数组&#xff0c;判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则返回 true ,否则返回 false 。假设输入的数组的任意两个数字都互不相同。 数据范围&#xff…

12P2532X162-233A KJ3222X1-BA1 CE4003S2B3 EMERSON CONTROLLER

12P2532X162-233A KJ3222X1-BA1 CE4003S2B3 EMERSON CONTROLLER EDGEBoost I/O模块是一种可扩展的模块化解决方案&#xff0c;集成到Premio的工业计算机中&#xff0c;通过即插即用的可扩展性提供增强的可靠性。这些附加模块有助于解决在加固边缘出现的设计限制和兼容性问题。…

VUE3技术报告

文章目录 node和webstorm基本概念1. Node.js2. npm3.Webpack4. Vue webstorm创建vue项目1. 通过npx create-vue创建vue项目2. 通过npx --package vue/cli vue创建vue项目 VUE3起步-创建应用-挂载应用1. createApp 创建函数&mount挂载应用2. 创建应用中的data选项3. methods…

Django实战项目-学习任务系统-用户登录

第一步&#xff1a;先创建一个Django应用程序框架代码 1&#xff0c;先创建一个Django项目 django-admin startproject mysite将创建一个目录&#xff0c;其布局如下&#xff1a;mysite/manage.pymysite/__init__.pysettings.pyurls.pyasgi.pywsgi.py 2&#xff0c;再创建一个…

实时监视分析 IIS 日志

Microsoft IIS服务器&#xff0c;无论是Web还是FTP&#xff0c;对于企业来说都是必不可少的。但是&#xff0c;IT 安全管理员的工作并不止于部署 IIS 服务器&#xff0c;部署后&#xff0c;管理员必须采取安全措施来保护这些服务器&#xff0c;监视 IIS 服务器安全性的一种行之…

免杀对抗-反沙盒+反调试

反VT-沙盒检测-Go&Python 介绍&#xff1a; 近年来&#xff0c;各类恶意软件层出不穷&#xff0c;反病毒软件也更新了各种检测方案以提高检率。 其中比较有效的方案是动态沙箱检测技术&#xff0c;即通过在沙箱中运行程序并观察程序行为来判断程序是否为恶意程序。简单来说…

ubuntu 设置x11vnc服务

Ubuntu 18.04 设置x11vnc服务 自带的vino-server也可以用但是不好用&#xff0c;在ubuntu论坛上看见推荐的x11vnc&#xff08;ubuntu关于vnc的帮助页面&#xff09;&#xff0c;使用设置一下&#xff0c;结果发现有一些坑需要填&#xff0c;所以写下来方便下次使用 转载请说明…

<el-input> textarea文本域显示滚动条(超过高度就自动显示)+ <el-input >不能正常输入,输入了也不能删除的问题

需求&#xff1a;首先是给定高度&#xff0c;输入文本框要自适应这个高度。文本超出高度就会显示滚动条否则不显示。 <el-row class"textarea-row"><el-col :span"3" class"first-row-title">天气</el-col><el-col :span&…

外卖小程序源码vs定制开发:何时选择哪种方式?

在数字餐饮行业的蓬勃发展中&#xff0c;外卖应用程序已经成为餐厅和创业者的必备工具。然而&#xff0c;当涉及到开发外卖应用程序时&#xff0c;您会面临一个重要的决策&#xff1a;是使用外卖小程序源码还是进行定制开发&#xff1f;这两种方法各有优势和劣势&#xff0c;取…

这款可视化拖拽式低代码平台,真香!

目录 一、产品介绍 二、设计原理 三、界面展示 1、代码生成器 2、工作流程 3、门户设计 4、大屏设计 5、报表设计 6、第三方登录 7、多租户实现 8、分布式调度 9、消息中心 四、功能框架 我们在低代码领域探索了很多年&#xff0c;从2014 开始研发低代码前端渲染&#xff0c;从…

DRM全解析 —— CRTC详解(1)

本文参考以下博文&#xff1a; Linux内核4.14版本——drm框架分析(4)——crtc分析 特此致谢&#xff01; 1. 简介 CRTC实际上可以拆分为CRTC。CRT的中文意思是阴极摄像管&#xff0c;就是当初老电视上普遍使用的显像管&#xff08;老电视之所以都很厚&#xff0c;就是因为它…

初学者如何选择:前端开发还是后端开发?

#开发做前端好还是后端好【话题征文】# 作为一名有多年开发经验的过来人&#xff0c;我认为前端开发和后端开发都有其独特的魅力和挑战。下面我将就我的个人经历和观点来分享一些关于前端开发和后端开发的看法。 首先&#xff0c;让我们将编程世界的大城市比作前端开发和后端开…

微信小程序获取用户头像调整

微信小程序获取用户头像&#xff0c;由于用户隐私策略调整&#xff0c;腾讯对获取用户信息也进行了调整。 记录内容如下&#xff1a; 1 新方式 新的方式&#xff1a;当触发获取用户头像时&#xff0c;由用户选择头像图片&#xff0c;输入昵称。 具体代码如下&#xff0c;即&…

Mybatis-plus 使用

1. 注解使用 mybatis-plus提供了 TableName, TableId, TableField, TableLogic 四种注解&#xff0c;其含义分别为&#xff1a; TableName TableName("SPF_Require_Vehicle") 用于声明当前class所对应数据库中的表&#xff0c;如果class的名字和表的名字完全相同&…

基于SSM的宿舍管理系统(有报告)。Javaee项目。

演示视频&#xff1a; 基于SSM的宿舍管理系统&#xff08;有报告&#xff09;。Javaee项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring SpringMvc My…

面试高频手撕算法 - 背包问题2

目录 1. 完全背包 1.1 【模板】完全背包 1.2 零钱兑换 1.3 零钱兑换 II 1.4 完全平方数 2. 二维费用的背包问题 2.1 一和零 2.2 盈利计划 --- 如果背包问题原先没有基础的&#xff0c;建议先看上一篇博客 --- 面试高频手撕算法 - 01背包系列 1. 完全背包 1.1 【模板】…

天眼销|企业数据查询必备

首先&#xff0c;得介绍一下天眼销是一个什么样的平台&#xff0c;请往下看&#xff1a; 可能会有一些还是觉得懒得看&#xff0c;简单理解&#xff0c;它与我们熟知的某查查&#xff0c;天眼某相类似&#xff0c;有很多共同之处。比如&#xff1a;某查查的服务模式&#xff08…

数据结构与算法(七)--使用链表实现栈

一、前言 之前我们已经学习了链表的所有操作及其时间复杂度分析&#xff0c;我们可以了解到对于链表头的相关操作基本都是O(1)的&#xff0c;例如链表头增加、删除元素&#xff0c;查询元素等等。那我们其实有一个数据结构其实可以完美利用到这些操作的特点&#xff0c;都是在…

Idea升级版本后踩坑关于Local History

版本升级&#xff1a;IntelliJ IDEA 2022.1.2 (Ultimate Edition) Local History本地历史修改记录在idea升级后默认只保留 5天 以内的修改记录&#xff0c;导致时间过长的一些内容就自动被清除掉了。可通过File->Setting-Advanced Setting 进行修改。

数据结构——常见的十种排序算法

一、常见的十种排序算法&#xff1a; 冒泡排序、选择排序、插入排序、归并排序、快速排序、希尔排序、堆排序、计数排序、桶排序、基数排序 1.【知识框架】 补充&#xff1a; 内部排序&#xff1a;整个排序过程完全在内存中进行。 外部排序&#xff1a;由于待排序记录数据量太…