首页 > 其他分享 >二分答案法

二分答案法

时间:2024-10-09 13:10:57浏览次数:1  
标签:二分 arr return nums int mid long 答案

二分答案法

  • 估计最终答案的大概范围

  • 分析问题的答案和给定条件之间的单调性

  • 建立一个 f 函数,当答案固定的情况下,判断给定的条件是否达标

  • 在最终答案可能的范围上不断二分搜索,每次用 f 函数判断,直到二分结束,找到最合适的答案

875. 爱吃香蕉的珂珂

#include <vector>
#include <algorithm>

using namespace std;

class Solution {
public:
    // 返回要消耗的时间
    long timeConsuming(vector<int> &piles, int k) {
        long res = 0;
        for (const auto &item: piles)
            // item / k 向上取整,前提都是非负数
            res += (item + k - 1) / k;
        return res;
    }

    // 时间复杂度 O(n * log(max)),额外空间复杂度 O(1)
    int minEatingSpeed(vector<int> &piles, int h) {
        int left = 1;
        int right = 0;
        for (const auto &item: piles)
            right = max(right, item);
        int mid;

        while (left <= right) {
            mid = left + ((right - left) >> 1);
            if (timeConsuming(piles, mid) <= h) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return left;
    }
};

410. 分割数组的最大值

画匠问题:

  • 一维数组表示每个位置的画完成需要的时间,k 表示画匠人数
  • 每个画匠可以画连续的几幅画,画匠可以并行工作,求最小耗时
  • 其实就是把数组分成连续的 k 个子数组,使得所有子数组中和最大的那个的和尽量小
#include <vector>
#include <algorithm>

using namespace std;

class Solution {
public:
    // 每个连续部分的和不超过 limit 的情况下,需要多少个画匠完成全部画作
    int painterNeeded(vector<int> &nums, int limit) {
        int count = 1;
        int sum = 0;
        // 时间复杂度 O(n)
        for (const auto &num: nums) {
            // 表示完成不了
            if (num > limit) return INT_MAX;
            if (sum + num > limit) {
                count++;
                sum = num;
            } else {
                sum += num;
            }
        }
        return count;
    }

    // 时间复杂度 O(n * log(sum)),额外空间复杂度 O(1)
    int splitArray(vector<int> &nums, int k) {
        long left = 0;
        long right = 0;
        for (const auto &item: nums)
            right += item;
        long mid;

        while (left <= right) {
            mid = left + ((right - left) >> 1);
            if (painterNeeded(nums, mid) <= k) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return left;
    }
};

机器人跳跃问题

#include <vector>
#include <iostream>
#include <algorithm>

using namespace std;

// 以初始能量 energy 能否走完数组
bool finished(vector<int> &nums, int energy, int maxH) {
    for (const auto &item: nums) {
        energy += (energy - item);
        // 如果超过高度最大值,后面肯定通关了,可以提前返回
        if (energy >= maxH) return true;
        if (energy < 0) return false;
    }
    return true;
}

// 时间复杂度 O(n * log(maxH)),额外空间复杂度 O(1)
int main() {
    int n;
    cin >> n;
    vector<int> nums(n);
    int maxH = 0;
    for (int i = 0; i < n; ++i) {
        cin >> nums[i];
        maxH = max(maxH, nums[i]);
    }
    int left = 0;
    int right = maxH;
    int mid;

    while (left <= right) {
        mid = left + ((right - left) >> 1);
        if (finished(nums, mid, maxH)) {
            right = mid - 1;
        } else {
            left = mid + 1;
        }
    }
    cout << left;
}

719. 找出第 K 小的数对距离

#include <vector>
#include <algorithm>

using namespace std;

class Solution {
public:
    // 返回任意两数差值小于等于 limit 的数对个数
    int countLower(vector<int> &nums, int limit) {
        int count = 0;
        for (int l = 0, r = 0; l < nums.size(); ++l) {
            while (r + 1 < nums.size() && nums[r + 1] - nums[l] <= limit)
                r++;
            count += r - l;
        }
        return count;
    }

    // 时间复杂度 O(n * log(n) + n * log(max-min)),额外空间复杂度 O(1)
    int smallestDistancePair(vector<int> &nums, int k) {
        sort(nums.begin(), nums.end());
        int left = 0;
        int right = nums.back() - nums.front();
        int mid;

        while (left <= right) {
            mid = left + ((right - left) >> 1);
            if (countLower(nums, mid) >= k) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return left;
    }
};

2141. 同时运行 N 台电脑的最长时间

#include <vector>

using namespace std;

class Solution {
public:
    // 能否让 computers 台电脑共同运行 time 分钟
    bool finished(vector<int> &batteries, int computers, long time) {
        // 碎片电量总和
        long fragmentCharge = 0;
        for (const auto &charge: batteries) {
            if (charge > time) {
                // time 时间内全都给这台电脑供电,没有提供碎片电量
                computers--;
            } else {
                // 碎片电量
                fragmentCharge += charge;
            }
            // 碎片电量 >= 台数 * 要求
            if (fragmentCharge >= (long) computers * time) return true;
        }
        return false;
    }

    // 时间复杂度 O(n * log(sum)),额外空间复杂度 O(1)
    long long maxRunTime(int n, vector<int> &batteries) {
        long sum = 0;
        for (const auto &item: batteries)
            sum += item;
        long left = 0;
        long right = sum;
        long mid;

        while (left <= right) {
            mid = left + ((right - left) >> 1);
            if (finished(batteries, n, mid)) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return right;
    }
};
  • 贪心优化
#include <vector>

using namespace std;

class Solution {
public:
    // 能否让 computers 台电脑共同运行 time 分钟
    bool finished(vector<int> &batteries, int computers, long time) {
        // 碎片电量总和
        long fragmentCharge = 0;
        for (const auto &charge: batteries) {
            if (charge > time) {
                // time 时间内全都给这台电脑供电,没有提供碎片电量
                computers--;
            } else {
                // 碎片电量
                fragmentCharge += charge;
            }
            // 碎片电量 >= 台数 * 要求
            if (fragmentCharge >= (long) computers * time) return true;
        }
        return false;
    }

    // 时间复杂度 O(n * log(_max)),额外空间复杂度 O(1)
    long long maxRunTime(int n, vector<int> &batteries) {
        long sum = 0;
        int _max = 0;
        for (const auto &item: batteries) {
            sum += item;
            _max = max(_max, item);
        }

        // 优化
        if (sum > (long) _max * n) {
            // 所有电池的最大电量是 _max
            // 如果此时 sum > (long) _max * num,
            // 说明: 最终的供电时间一定在 >= max,而如果最终的供电时间 >= max
            // 说明: 对于最终的答案 X 来说,所有电池都是碎片电池
            // 那么寻找 ? * num <= sum 的情况中,尽量大的 ? 即可
            // 即 sum / num
            return sum / n;
        }
        // 最终的供电时间一定在 < _max 范围上

        long left = 0;
        long right = _max;
        long mid;

        while (left <= right) {
            mid = left + ((right - left) >> 1);
            if (finished(batteries, n, mid)) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return right;
    }
};

计算等位时间

  • 给定一个数组 arr 长度为 n,表示 n 个服务员,每服务一个人的时间
  • 给定一个正数 m,表示有 m 个人等位,如果你是刚来的人,每个客人都遵循有空位就上的原则,请问你需要等多久?
  • 假设 m 远远大于 n,比如 n <= 10^3, m <= 10^9,该怎么做是最优解?
package class051;

import java.util.PriorityQueue;

// 计算等位时间
// 给定一个数组arr长度为n,表示n个服务员,每服务一个人的时间
// 给定一个正数m,表示有m个人等位,如果你是刚来的人,请问你需要等多久?
// 假设m远远大于n,比如n <= 10^3, m <= 10^9,该怎么做是最优解?
// 谷歌的面试,这个题连考了2个月
// 找不到测试链接,所以用对数器验证
public class Code06_WaitingTime {

    // 堆模拟
    // 验证方法,不是重点
    // 如果m很大,该方法会超时
    // 时间复杂度O(m * log(n)),额外空间复杂度O(n)
    public static int waitingTime1(int[] arr, int m) {
        // 一个一个对象int[]
        // [醒来时间,服务一个客人要多久]
        PriorityQueue<int[]> heap = new PriorityQueue<>((a, b) -> (a[0] - b[0]));
        int n = arr.length;
        for (int i = 0; i < n; i++) {
            heap.add(new int[]{0, arr[i]});
        }
        for (int i = 0; i < m; i++) {
            int[] cur = heap.poll();
            cur[0] += cur[1];
            heap.add(cur);
        }
        return heap.peek()[0];
    }

    // 二分答案法
    // 最优解
    // 时间复杂度O(n * log(min * w)),额外空间复杂度O(1)
    public static int waitingTime2(int[] arr, int w) {
        int min = Integer.MAX_VALUE;
        for (int x : arr) {
            min = Math.min(min, x);
        }
        int ans = 0;
        for (int l = 0, r = min * w, m; l <= r; ) {
            // m中点,表示一定要让服务员工作的时间!
            m = l + ((r - l) >> 1);
            // 能够给几个客人提供服务
            if (f(arr, m) >= w + 1) {
                ans = m;
                r = m - 1;
            } else {
                l = m + 1;
            }
        }
        return ans;
    }

    // 如果每个服务员工作time,可以接待几位客人(结束的、开始的客人都算)
    public static int f(int[] arr, int time) {
        int ans = 0;
        for (int num : arr) {
            ans += (time / num) + 1;
        }
        return ans;
    }

    // 对数器测试
    public static void main(String[] args) {
        System.out.println("测试开始");
        int N = 50;
        int V = 30;
        int M = 3000;
        int testTime = 20000;
        for (int i = 0; i < testTime; i++) {
            int n = (int) (Math.random() * N) + 1;
            int[] arr = randomArray(n, V);
            int m = (int) (Math.random() * M);
            int ans1 = waitingTime1(arr, m);
            int ans2 = waitingTime2(arr, m);
            if (ans1 != ans2) {
                System.out.println("出错了!");
            }
        }
        System.out.println("测试结束");
    }

    // 对数器测试
    public static int[] randomArray(int n, int v) {
        int[] arr = new int[n];
        for (int i = 0; i < n; i++) {
            arr[i] = (int) (Math.random() * v) + 1;
        }
        return arr;
    }

}

刀砍毒杀怪兽问题

package class051;

// 刀砍毒杀怪兽问题
// 怪兽的初始血量是一个整数hp,给出每一回合刀砍和毒杀的数值cuts和poisons
// 第i回合如果用刀砍,怪兽在这回合会直接损失cuts[i]的血,不再有后续效果
// 第i回合如果用毒杀,怪兽在这回合不会损失血量,但是之后每回合都损失poisons[i]的血量
// 并且你选择的所有毒杀效果,在之后的回合都会叠加
// 两个数组cuts、poisons,长度都是n,代表你一共可以进行n回合
// 每一回合你只能选择刀砍或者毒杀中的一个动作
// 如果你在n个回合内没有直接杀死怪兽,意味着你已经无法有新的行动了
// 但是怪兽如果有中毒效果的话,那么怪兽依然会在血量耗尽的那回合死掉
// 返回至少多少回合,怪兽会死掉
// 数据范围 : 
// 1 <= n <= 10^5
// 1 <= hp <= 10^9
// 1 <= cuts[i]、poisons[i] <= 10^9
// 本题来自真实大厂笔试,找不到测试链接,所以用对数器验证
public class Code07_CutOrPoison {

    // 动态规划方法(只是为了验证)
    // 目前没有讲动态规划,所以不需要理解这个函数
    // 这个函数只是为了验证二分答案的方法是否正确的
    // 纯粹为了写对数器验证才设计的方法,血量比较大的时候会超时
    // 这个方法不做要求,此时并不需要理解,可以在学习完动态规划章节之后来看看这个函数
    public static int fast1(int[] cuts, int[] poisons, int hp) {
       int sum = 0;
       for (int num : poisons) {
          sum += num;
       }
       int[][][] dp = new int[cuts.length][hp + 1][sum + 1];
       return f1(cuts, poisons, 0, hp, 0, dp);
    }

    // 不做要求
    public static int f1(int[] cuts, int[] poisons, int i, int r, int p, int[][][] dp) {
       r -= p;
       if (r <= 0) {
          return i + 1;
       }
       if (i == cuts.length) {
          if (p == 0) {
             return Integer.MAX_VALUE;
          } else {
             return cuts.length + 1 + (r + p - 1) / p;
          }
       }
       if (dp[i][r][p] != 0) {
          return dp[i][r][p];
       }
       int p1 = r <= cuts[i] ? (i + 1) : f1(cuts, poisons, i + 1, r - cuts[i], p, dp);
       int p2 = f1(cuts, poisons, i + 1, r, p + poisons[i], dp);
       int ans = Math.min(p1, p2);
       dp[i][r][p] = ans;
       return ans;
    }

    // 二分答案法
    // 最优解
    // 时间复杂度O(n * log(hp)),额外空间复杂度O(1)
    public static int fast2(int[] cuts, int[] poisons, int hp) {
       int ans = Integer.MAX_VALUE;
       for (int l = 1, r = hp + 1, m; l <= r;) {
          // m中点,一定要让怪兽在m回合内死掉,更多回合无意义
          m = l + ((r - l) >> 1);
          if (f(cuts, poisons, hp, m)) {
             ans = m;
             r = m - 1;
          } else {
             l = m + 1;
          }
       }
       return ans;
    }

    // cuts、posions,每一回合刀砍、毒杀的效果
    // hp:怪兽血量
    // limit:回合的限制
    public static boolean f(int[] cuts, int[] posions, long hp, int limit) {
       int n = Math.min(cuts.length, limit);
       for (int i = 0, j = 1; i < n; i++, j++) {
          hp -= Math.max((long) cuts[i], (long) (limit - j) * (long) posions[i]);
          if (hp <= 0) {
             return true;
          }
       }
       return false;
    }

    // 对数器测试
    public static void main(String[] args) {
       // 随机测试的数据量不大
       // 因为数据量大了,fast1方法会超时
       // 所以在数据量不大的情况下,验证fast2方法功能正确即可
       // fast2方法在大数据量的情况下一定也能通过
       // 因为时间复杂度就是最优的
       System.out.println("测试开始");
       int N = 30;
       int V = 20;
       int H = 300;
       int testTimes = 10000;
       for (int i = 0; i < testTimes; i++) {
          int n = (int) (Math.random() * N) + 1;
          int[] cuts = randomArray(n, V);
          int[] posions = randomArray(n, V);
          int hp = (int) (Math.random() * H) + 1;
          int ans1 = fast1(cuts, posions, hp);
          int ans2 = fast2(cuts, posions, hp);
          if (ans1 != ans2) {
             System.out.println("出错了!");
          }
       }
       System.out.println("测试结束");
    }

    // 对数器测试
    public static int[] randomArray(int n, int v) {
       int[] ans = new int[n];
       for (int i = 0; i < n; i++) {
          ans[i] = (int) (Math.random() * v) + 1;
       }
       return ans;
    }

}

标签:二分,arr,return,nums,int,mid,long,答案
From: https://www.cnblogs.com/sprinining/p/18454010

相关文章

  • 二分查找算法
    二分查找算法基本思路二分查找的基本思路如下:找到中间元素:查找过程从数组的中间元素开始,如果中间元素恰好是目标元素,则查找过程结束。比较并缩小范围:如果中间元素不是目标元素,那么将中间元素与目标值进行比较:如果目标值小于中间元素,则说明目标值位于当前搜索范围的左半部分......
  • 二分搜索与二分答案
    二分前提条件数组的有序的数组数组中无重复元素,否则查找的元素下标可以不算唯一的二分答案二分答案时需要满足答案的有界性二分答案的思路:首先判断这个解是否为可行解,然后我们”认为“这个可行解的最优解,然后以这个可行解为标准去左(右)区间寻找最优解时间复杂......
  • 二分图的判定-染色法
    二分图如果一张无向图的N个节点可以分成A.B两个不相交的非空集合,并且同一集合内的点之间没有边相连,那么称该无向图为二分图(BipartiteGraph)。定理:二分图不存在奇环(长度为奇数的环)。因为每一条边都是从一个集合走到另一个集合,只有走偶数次才可能回到同一个集合。染色......
  • NOIP2024集训Day47 生成树+二分图
    NOIP2024集训Day47生成树+二分图B.[THUPC2022初赛]最小公倍树直接建边显然不行,考虑优化建边。对于两个点\(u,v\),\((u,v)\)的边权为\(\displaystyle\operatorname{lcm}(u,v)=\frac{u\timesv}{\gcd(u,v)}\),显然应该选择\(\gcd(u,v)\)尽可能大的点对连边,也就是......
  • JAV面试题答案——红黑树怎么保持平衡的
    红黑树根据规则通过旋转和节点染色这两种方式来保持平衡,这些操作是红黑树维持平衡的关键部分。1.旋转操作旋转操作是红黑树维持平衡的主要手段之一,它包括左旋和右旋两种基本操作。旋转操作通常在插入和删除操作中使用,以确保树的性质得以维护左旋将一个节点的右子树提升为其......
  • 软件测试面试中常见必问(一)内附答案
    一般面试都会按照简历当中我们写的技能或者项目进行提问,所以我们在简历当中一定要写自己能说上来的东西和对简历中的项目一定要有准备。另外,如果真的不知道就请坦诚相待,直说“不好意思,这里我不太清楚”就可以了,有的面试官也会当场告诉你答案。1.自我介绍虽然简历中都有信息,但是......
  • 大模型面试八股+答案,LLM-offer手到擒来!
    你是否也曾为面试大模型八股文而苦恼?别担心!今天我就来给你分享一些绝妙的面试技巧,让你轻松应对!......
  • 帝国cms网站忘记后台登录认证码、登录安全答案怎么办?
     忘记后台登录认证码解决方案:查看 e/class/config.php 文件中的 $do_loginauth 变量内容。步骤:通过FTP客户端连接到服务器。打开 e/class/config.php 文件。查找 $do_loginauth 变量。查看其内容,即为登录认证码。4.忘记后台登录安全答案解决方案:通过 p......
  • Codeforces Round 316 (Div. 2) D题 Tree Requests(二分,dfs,在线,前缀异或)
    题目链接CodeforcesRound316(Div.2)D题TreeRequests思路将262626个字母全部当作一个二进制数。将每个深度的结点按照dfs序放到一个vector里,同时记录每个vector......
  • 【前缀和+开区间二分】codeforces 1187 B. Letters Shop
    题意第一行,输入一个正整数\(n(1\leqn\leq2*10^5)\),代表字符串\(s\)的长度。第二行,输入一个字符串\(s\)。第三行,输入一个正整数\(m(1\leqm\leq5*10^4)\),代表接下来要输入的询问次数,对于每次询问:输入一个字符串\(t(1\leq|t|\leq2*10^5)\),并保证\(\sum_{i=1}^m......