【重点!!!】【有序数组+二分搜索】315.计算右侧小于当前元素的个数

本文介绍了两种利用归并排序解决数组中逆序对问题的方法,分别是构造索引类和索引数组,详细展示了代码实现和计数过程。

题目
方法1:有序数组+二分搜索
方法2:归并排序
类似另一个经典题目:数组中的逆序对

Python

有序数组+二分搜索

最佳答案:参考
bisect库函数介绍:
在 Python 中,bisect_left 是 bisect 模块中的一个函数,用于在已排序的序列中快速找到元素的插入位置,以保持序列的有序性。

# 示例 1:基本用法
aa = [1, 3, 4, 4]
xx = 4
pos1 = bisect.bisect_left(aa, xx)
pos2 = bisect.bisect_right(aa, xx)

print(pos1)  # 输出 2
print(pos2)  # 输出 4
# 版本1:使用库函数bisect.bisect_left
class Solution:
    def countSmaller(self, nums: List[int]) -> List[int]:
        sort_list = []
        n = len(nums)
        res = [0] * n
        for i in range(n-1, -1, -1):
            idx = bisect.bisect_left(sort_list, nums[i])
            res[i] = idx
            sort_list.insert(idx, nums[i])
        
        return res
 
# 版本2:自己写二分搜索
class Solution:
    def countSmaller(self, nums: List[int]) -> List[int]:
        sort_list = []
        n = len(nums)
        res = [0] * n
        for i in range(n-1, -1, -1):
            # idx = bisect.bisect_left(sort_list, nums[i])
            idx = self.my_bisect_left(sort_list, nums[i])
            res[i] = idx
            sort_list.insert(idx, nums[i])
        
        return res

    def my_bisect_left(self, sort_list, target):
        l, r = 0, len(sort_list) - 1
        while l <= r:
            mid = (l + r) // 2
            if sort_list[mid] < target:
                l = mid + 1
            else:
                r = mid - 1
        return l

Java

法1:归并排序—构造索引类

这种写法更容易理解。

class Solution {
    List<Integer> ans = new ArrayList<>();
    int[] count;     // 记录题目中所求的count[i]
    Pair[] tmp;      // 临时数组, 存储一次归并过程中排序好的【两段有序的元素】

    public List<Integer> countSmaller(int[] nums) {
        int n = nums.length;
        Pair[] pairArray = new Pair[n];
        count = new int[n];
        tmp = new Pair[n];
        for (int i = 0; i < n; i++) {
            pairArray[i] = new Pair(nums[i], i);
        }
        mergeSort(pairArray, 0, n - 1);
        for (int i = 0; i < n; i++) {
            ans.add(count[i]);
        }
        return ans;
    }

    private void mergeSort(Pair[] pairArray, int start, int end) {
        if (start < end) {
            int mid = start + (end - start) / 2;
            mergeSort(pairArray, start, mid);  
            mergeSort(pairArray, mid + 1, end); 
            merge(pairArray, start, mid, end);  // 再将两个有序数组合并,只不过这里要承接统计count[i]的功能
        }
    }

    // 注意: 降序merge!!!
    private void merge(Pair[] pairArray, int start, int mid, int end) {
        int left = start;
        int right = mid + 1;
        int cur = left;

        for (int i = start; i <= end; ++i) {
            tmp[i] = pairArray[i];
        }

        while (left <= mid || right <= end) {
            if (left > mid) { // 这种情况无需再重复赋值, 直接break
                break;
            } else if (right > end) {
                pairArray[cur] = tmp[left];
                ++left;
            } else if (tmp[left].val > tmp[right].val) {
                count[tmp[left].idx] += end - right + 1; // 右半部分tmp[right, end]都 < nums[left]
                pairArray[cur] = tmp[left];
                ++left;
            } else { // tmp[left].val <= tmp[right].val
                pairArray[cur] = tmp[right];
                ++right;
            }
            ++cur;
        }
    }

    class Pair {
        public int val;
        public int idx;
        public Pair(int val, int idx) {
            this.val = val;
            this.idx = idx;
        }
    }
}

法2:归并排序—索引数组

必须掌握的基础算法!!!

class Solution {

    List<Integer> ans = new ArrayList<>();
    int[] index;    // 原数组的索引数组,存储着原数组中每个元素对应的下标
    int[] count;    // 记录题目中所求的count[i]
    int[] tmp;      // 临时数组, 存储一次归并过程中排序好的元素
    int[] tmpIndex; // 临时索引数组, 存储一次归并过程中排序好的元素对应原始索引

    //入口
    public List<Integer> countSmaller(int[] nums) {
        int n = nums.length;
        index = new int[n];
        count = new int[n];
        tmp = new int[n];
        tmpIndex = new int[n];
        for (int i = 0; i < n; i++) {
            index[i] = i;
        }
        mergeSort(nums, 0, n - 1);
        for (int i = 0; i < n; i++) {
            ans.add(count[i]);
        }
        return ans;
    }

    private void mergeSort(int[] nums, int start, int end) {
        if (start < end) {
            int mid = start + (end - start) / 2;
            mergeSort(nums, start, mid);  
            mergeSort(nums, mid + 1, end); 
            merge(nums, start, mid, end);  // 再将两个有序数组合并,只不过这里要承接统计count[i]的功能
        }
    }

    // 注意: 降序merge!!!
    private void merge(int[] nums, int start, int mid, int end) {
        int left = start;
        int right = mid + 1;
        int cur = left;

        while (left <= mid && right <= end) {
            if (nums[left] > nums[right]) {
                count[index[left]] += end - right + 1; // 右半部分小于nums[left]元素的数目
                tmpIndex[cur] = index[left];           // 记录元素位置的改变
                tmp[cur] = nums[left];
                ++left;
            } else {
                tmp[cur] = nums[right];
                tmpIndex[cur] = index[right];
                ++right;
            }
            ++cur;
        }
        while (left <= mid) {
            tmp[cur] = nums[left];
            tmpIndex[cur] = index[left];
            ++left;
            ++cur;
        }
        while (right <= end) {
            tmp[cur] = nums[right];
            tmpIndex[cur] = index[right];
            ++right;
            ++cur;
        }
        for (int i = start; i <= end; i++) {
            nums[i] = tmp[i];
            index[i] = tmpIndex[i];
        }
    }
}
<think>我们计划实现一个二分查找程序,输入格式如下: 第一行:数组元素个数 n 第二行:n个有序的整数(假设数组已经有序,因为二分查找要求数组有序) 第三行:要查找的目标元素 target 输出格式: 如果找到目标元素,输出“该元素是数组中的第x个元素”,其中x是从1开始的索引(即第一个元素是第1个元素) 如果未找到,输出“未查找到该元素” 注意:二分查找要求数组有序,如果输入数组无序,需要先排序。但题目未说明数组有序,通常题目会给定有序数组。为了严谨,我们可以先检查数组是否有序,或者先排序再查找。但根据输入样例格式,第二行是数组元素,第三行是要查找的元素,一般题目会给定有序数组。如果题目没有给定有序,那么我们需要在程序中进行排序。 但是,根据问题描述,输入样例格式并没有明确数组有序,所以我们需要考虑排序。然而,问题中提到“根据给定的输入样例格式”,通常这类题目都是给定有序数组。因此,我们先按照有序数组处理,如果用户输入无序,我们可以在程序中进行排序,但是要注意题目要求输出的是原数组中的位置?还是排序后的位置? 问题描述中说“输出格式为找到元素时显示元素是数组中的第几个元素”,这里的“第几个”通常是指原数组中的位置(索引+1)。但是,如果数组无序,我们排序后位置就变了。所以,有两种情况: 1. 题目保证输入数组有序,那么我们直接查找,输出位置(原位置)。 2. 题目没有保证有序,那么我们需要先排序,但是排序后元素的位置变化了,那么输出的位置就是排序后的位置?但是这样就不符合“原数组中的第几个”了。 仔细阅读问题描述,输入样例格式并没有说明数组有序,但二分查找的前提是有序。所以我们需要澄清:题目要求的是在排序前的位置还是排序后的位置? 然而,根据问题描述,输出的是“数组中的第几个元素”,这个数组应该是原始输入数组。因此,如果原始数组无序,我们排序后打乱了原始顺序,就无法得到原始位置。 因此,我们假设输入数组是有序的。如果用户输入无序,则二分查找可能找不到,或者结果错误。但题目没有要求处理无序情况,所以我们按照有序数组处理。 如果题目要求能够处理无序数组,那么我们需要先排序,但是排序后元素的位置已经不是原始位置了。所以,为了解决这个问题,我们可以将原始数组的索引一起排序?这样复杂度就增加了。考虑到问题描述中并没有要求保留原始位置,而且输入样例格式也没有提及无序情况,我们按照有序数组处理。 步骤: 1. 读取第一行整数n。 2. 读取第二行,用空格分割得到n个整数,存储在数组arr中。 3. 读取第三行,目标元素target。 4. 使用二分查找在数组arr中查找target。 5. 如果找到,输出位置(注意:题目要求第几个元素,即索引+1,且从1开始计数)。 6. 如果未找到,输出未找到。 二分查找实现注意事项: - 循环条件:left <= right - 中间值计算:mid = left + (right - left) / 2 避免溢出 - 比较arr[mid]和target,调整左右边界 但是,问题描述中并没有说明数组是升序还是降序?通常升序较多,但题目没有说明。所以我们需要判断数组的排序方式吗? 我们可以先判断数组的排序方式:比较第一个元素和最后一个元素,如果第一个小于最后一个,则是升序,否则可能是降序或者相等。但这样不够严谨(比如整个数元素都相等?)。 另一种方式:我们实现一个升序的二分查找,如果用户输入降序,我们可以先反转数组,但是这样位置就变了。所以,为了简单,我们假设数组是升序的。如果题目要求降序,我们可以再写一个降序的二分查找?但是问题没有说明。 因此,我们假设数组是升序的。如果用户输入降序数组,则二分查找会失败。根据常见的编程题,一般会指定升序。所以,我们按升序处理。 如果用户输入无序,那么二分查找可能出错,但题目没有要求处理这种情况。 代码实现步骤: 读取输入: 使用Scanner读取输入。 二分查找函数: 参数:数组,目标值 返回:目标值在数组中的索引(0-based),如果未找到返回-1。 然后,如果返回的索引>=0,则输出“该元素是数组中的第(索引+1)个元素”,否则输出“未查找到该元素”。 示例: 输入: 5 1 3 5 7 9 5 输出:该元素是数组中的第3个元素 输入: 5 1 3 5 7 9 10 输出:未查找到该元素 代码实现: </think>根据您的需求,我将实现一个Java二分搜索程序,输入输出格式完全符合要求。以下是完整代码和详细说明: ```java import java.util.Arrays; import java.util.Scanner; public class BinarySearch { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); // 读取数组长度 int n = scanner.nextInt(); int[] arr = new int[n]; // 读取数组元素 for (int i = 0; i < n; i++) { arr[i] = scanner.nextInt(); } // 读取目标值 int target = scanner.nextInt(); // 执行二分查找 int result = binarySearch(arr, target); // 格式化输出结果 if (result != -1) { System.out.println("该元素是数组中的第" + (result + 1) + "个元素"); } else { System.out.println("未查找到该元素"); } } private static int binarySearch(int[] arr, int target) { int left = 0; int right = arr.length - 1; while (left <= right) { int mid = left + (right - left) / 2; // 防止整数溢出 if (arr[mid] == target) { return mid; // 找到目标值,返回索引 } else if (arr[mid] < target) { left = mid + 1; // 目标在右半区 } else { right = mid - 1; // 目标在左半区 } } return -1; // 未找到 } } ``` ### 程序说明 1. **输入处理**: - 第一行:读取整数`n`(数组长度) - 第二行:读取`n`个整数组成数组 - 第三行:读取目标值`target` 2. **二分查找核心逻辑**: - 初始化指针`left=0`, `right=n-1` - 循环条件:`while (left <= right)` - 中间位置计算:`mid = left + (right - left)/2`(避免整数溢出) - 比较`arr[mid]`与`target`: - 相等 → 返回当前索引 - 小于 → 移动左边界`left = mid + 1` - 大于 → 移动右边界`right = mid - 1` 3. **输出规范**: - 找到元素:输出从1开始的位置(索引+1) - 未找到:提示未查找到 ### 示例执行 **输入样例**: ``` 5 10 20 30 40 50 30 ``` **输出结果**: ``` 该元素是数组中的第3个元素 ``` **输入样例**: ``` 4 1 3 5 7 6 ``` **输出结果**: ``` 未查找到该元素 ``` ### 注意事项 1. **数组有序前提**:二分搜索要求输入数组**必须有序**,程序未包含排序功能 2. **时间复杂度**:$O(\log n)$,比线性搜索$O(n)$更高效 3. **边界处理**: - 使用`left + (right - left)/2`而非`(left+right)/2`避免整数溢出 - 循环结束条件`left <= right`确保边界元素被检查 4. **输出位置**:程序输出的是元素在数组中的**实际位置**(从1开始计数) 此实现严格遵循二分搜索的标准算法规范,并通过双指针夹逼策略高效定位目标元素[^1][^2]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值