LeetCode算法小抄--田忌赛马问题、游戏随机匹配机制问题

简介: LeetCode算法小抄--田忌赛马问题、游戏随机匹配机制问题

田忌赛马问题

870. 优势洗牌

给定两个大小相等的数组 nums1nums2nums1 相对于 nums2优势可以用满足 nums1[i] > nums2[i] 的索引 i 的数目来描述。

返回 nums1 的任意排列,使其相对于 nums2 的优势最大化。

思路:

给你输入两个长度相等的数组 nums1nums2,请你重新组织 nums1 中元素的位置,使得 nums1 的「优势」最大化。

如果 nums1[i] > nums2[i],就是说 nums1 在索引 i 上对 nums2[i] 有「优势」。优势最大化也就是说让你重新组织 nums1尽可能多的让 nums1[i] > nums2[i]

class Solution {
    public int[] advantageCount(int[] nums1, int[] nums2) {
        int n = nums1.length;
        // 给 nums2 降序排序
        PriorityQueue<int[]> maxpq = new PriorityQueue<>(
            (int[] pair1, int[] pair2)->{
                return pair2[1] - pair1[1];
            }
        );
        for(int i = 0; i< n; i++){
            maxpq.offer(new int[]{i, nums2[i]});
        }
        // 给 nums1 升序排序
        Arrays.sort(nums1);
        // nums1[left] 是最小值,nums1[right] 是最大值
        int left = 0, right = n - 1;
        int[] res = new int[n];
        while (!maxpq.isEmpty()) {
            int[] pair = maxpq.poll();
            // maxval 是 nums2 中的最大值,i 是对应索引
            int i = pair[0], maxval = pair[1];
            if (maxval < nums1[right]) {
                // 如果 nums1[right] 能胜过 maxval,那就自己上
                res[i] = nums1[right];
                right--;
            } else {
                // 否则用最小值混一下,养精蓄锐
                res[i] = nums1[left];
                left++;
            }
        }
        return res;
    }
}

带权重的随机选择算法

算法框架

class Solution {
    // 前缀和数组
    private int[] preSum;
    private Random rand = new Random();
    public Solution(int[] w) {
        int n = w.length;
        // 构建前缀和数组,偏移一位留给 preSum[0]
        preSum = new int[n + 1];
        preSum[0] = 0;
        // preSum[i] = sum(w[0..i-1])
        for (int i = 1; i <= n; i++) {
            preSum[i] = preSum[i - 1] + w[i - 1];
        }
    }
    public int pickIndex() {
        int n = preSum.length;
        // Java 的 nextInt(n) 方法在 [0, n) 中生成一个随机整数
        // 再加一就是在闭区间 [1, preSum[n - 1]] 中随机选择一个数字
        int target = rand.nextInt(preSum[n - 1]) + 1;
        // 获取 target 在前缀和数组 preSum 中的索引
        // 别忘了前缀和数组 preSum 和原始数组 w 有一位索引偏移
        return left_bound(preSum, target) - 1;
    }
    // 搜索左侧边界的二分搜索
    private int left_bound(int[] nums, int target) {
    int left = 0, right = nums.length - 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;
        }
    }
    // 判断 target 是否存在于 nums 中
    // 此时 target 比所有数都大,返回 -1
    if (left == nums.length) return -1;
    // 判断一下 nums[left] 是不是 target
    return nums[left] == target ? left : -1;
  }
}

528. 按权重随机选择[Cisco笔试题]

给你一个 下标从 0 开始 的正整数数组 w ,其中 w[i] 代表第 i 个下标的权重。

请你实现一个函数 pickIndex ,它可以 随机地从范围 [0, w.length - 1] 内(含 0w.length - 1)选出并返回一个下标。选取下标 i概率w[i] / sum(w)

  • 例如,对于 w = [1, 3],挑选下标 0 的概率为 1 / (1 + 3) = 0.25 (即,25%),而选取下标 1 的概率为 3 / (1 + 3) = 0.75(即,75%)。

思路:

1、根据权重数组 w 生成前缀和数组 preSum

2、生成一个取值在 preSum 之内的随机数,用二分搜索算法寻找大于等于这个随机数的最小元素索引。

3、最后对这个索引减一(因为前缀和数组有一位索引偏移),就可以作为权重数组的索引,即最终答案

前缀和数组中 0 本质上是个占位符

class Solution {
    private Random random = new Random();
    private int[] preSum;
    private int n;
    public Solution(int[] w) {
        this.n = w.length;
        // 前缀数组,下标0就是原数组w[0]
        preSum = new int[n];
        // 下标0就是原数组w[0]
        preSum[0] = w[0];
        for (int i = 1; i < n; i++) {
            preSum[i] = preSum[i-1] + w[i];
        }
    }
    public int pickIndex() {
        // 保证取到[1, preSum[n-1]]的闭区间
        int target = random.nextInt(preSum[n-1]) + 1;
        // right可以从n-1开始,也可以从n开始,对结果的正确性没有影响
        int left = 0, right = n;
        while (left < right) {
            int mid = left + (right - left) / 2;
            // 如果找到了,直接去当前的下标
            if (preSum[mid] == target) {
                left =mid;
                break;
            } else if (preSum[mid] > target) {
                // 向左找,因为当前mid的值大于target,可能是"第一个大于target"的值,所以不能丢弃mid
                // 如果mid的值不再需要了(最终不会取到现在的mid),那么就可以right=mid-1;
                right = mid;
            } else {
                // 向右找,因为当前的mid的值比target小,永远不会取到了。所以left=mid+1;
                left = mid + 1;
            }
        }
        // left代表的含义:
        // 当目标元素 target 不存在数组 nums 中时,搜索左侧边界的二分搜索的返回值可以做以下几种解读:
        // 1、返回的这个值是 nums 中大于等于 target 的最小元素索引。
        // 2、返回的这个值是 target 应该插入在 nums 中的索引位置。
        // 3、返回的这个值是 nums 中小于 target 的元素个数。
        return left;
    }
}

下面这道题目和上面👆一模一样

剑指 Offer II 071. 按权重生成随机数

给定一个正整数数组 w ,其中 w[i] 代表下标 i 的权重(下标从 0 开始),请写一个函数 pickIndex ,它可以随机地获取下标 i,选取下标 i 的概率与 w[i] 成正比。

例如,对于 w = [1, 3],挑选下标 0 的概率为 1 / (1 + 3) = 0.25 (即,25%),而选取下标 1 的概率为 3 / (1 + 3) = 0.75(即,75%)。

也就是说,选取下标 i 的概率为 w[i] / sum(w)

class Solution {
    private Random random = new Random();
    private int[] preSum;
    private int n;
    public Solution(int[] w) {
        this.n = w.length;
        // 前缀数组,下标0就是原数组w[0]
        preSum = new int[n];
        // 下标0就是原数组w[0]
        preSum[0] = w[0];
        for (int i = 1; i < n; i++) {
            preSum[i] = preSum[i-1] + w[i];
        }
    }
    public int pickIndex() {
        // 保证取到[1, preSum[n-1]]的闭区间
        int target = random.nextInt(preSum[n-1]) + 1;
        // right可以从n-1开始,也可以从n开始,对结果的正确性没有影响
        int left = 0, right = n;
        while (left < right) {
            int mid = left + (right - left) / 2;
            // 如果找到了,直接去当前的下标
            if (preSum[mid] == target) {
                left =mid;
                break;
            } else if (preSum[mid] > target) {
                // 向左找,因为当前mid的值大于target,可能是"第一个大于target"的值,所以不能丢弃mid
                // 如果mid的值不再需要了(最终不会取到现在的mid),那么就可以right=mid-1;
                right = mid;
            } else {
                // 向右找,因为当前的mid的值比target小,永远不会取到了。所以left=mid+1;
                left = mid + 1;
            }
        }
        // left代表的含义:
        // 当目标元素 target 不存在数组 nums 中时,搜索左侧边界的二分搜索的返回值可以做以下几种解读:
        // 1、返回的这个值是 nums 中大于等于 target 的最小元素索引。
        // 2、返回的这个值是 target 应该插入在 nums 中的索引位置。
        // 3、返回的这个值是 nums 中小于 target 的元素个数。
        return left;
    }
}


相关文章
|
1月前
|
算法
Leetcode第45题(跳跃游戏II)
这篇博客文章讨论了如何使用贪心算法解决LeetCode第45题“跳跃游戏II”,目的是找到使用最少跳跃次数到达数组末尾的策略。
74 8
Leetcode第45题(跳跃游戏II)
|
1月前
|
算法
Leetcode 初级算法 --- 数组篇
Leetcode 初级算法 --- 数组篇
38 0
|
3月前
|
算法
LeetCode第55题跳跃游戏
LeetCode第55题"跳跃游戏"的解题方法,通过记录当前最远可达到的位置并判断每个位置是否可达以及能否到达末尾,有效解决了跳跃至数组末尾的可行性问题。
LeetCode第55题跳跃游戏
|
9天前
|
存储 算法 Java
leetcode算法题-有效的括号(简单)
【11月更文挑战第5天】本文介绍了 LeetCode 上“有效的括号”这道题的解法。题目要求判断一个只包含括号字符的字符串是否有效。有效字符串需满足左括号必须用相同类型的右括号闭合,并且左括号必须以正确的顺序闭合。解题思路是使用栈数据结构,遍历字符串时将左括号压入栈中,遇到右括号时检查栈顶元素是否匹配。最后根据栈是否为空来判断字符串中的括号是否有效。示例代码包括 Python 和 Java 版本。
|
1月前
|
算法
每日一道算法题(Leetcode 20)
每日一道算法题(Leetcode 20)
23 2
|
1月前
Leetcode第55题(跳跃游戏)
LeetCode第55题“跳跃游戏”要求判断在一个非负整数数组中,从第一个位置出发,是否能够到达最后一个位置,其中每个位置的元素代表可跳跃的最大长度。
27 0
|
1月前
|
人工智能 算法 Java
【搜索算法】数字游戏(C/C++)
【搜索算法】数字游戏(C/C++)
|
3月前
|
人工智能 算法 Java
LeetCode经典算法题:井字游戏+优势洗牌+Dota2参议院java解法
LeetCode经典算法题:井字游戏+优势洗牌+Dota2参议院java解法
50 1
|
3月前
|
算法
LeetCode第45题跳跃游戏 II
LeetCode第45题"跳跃游戏 II"的解题方法,通过一次循环和选择每个位置的最大可跳距离,有效减少了跳跃次数,简化了问题。
|
3月前
|
算法
互动游戏解决遇到问题之基于射线投射寻路算法的问题如何解决
互动游戏解决遇到问题之基于射线投射寻路算法的问题如何解决