(最新版2022版)剑指offer之排序题解

news2025/4/9 5:28:27

(最新版2022版)剑指offer之排序题解

      • JZ3数组中重复的数字
      • JZ51 数组中的逆序对
      • JZ40 最小的K个数
      • JZ41 数据流中的中位数


JZ3数组中重复的数字

在这里插入图片描述

思路:

既然数组长度为nnn只包含了0到n−1n-1n−1的数字,那么如果数字没有重复,这些数字排序后将会与其下标一一对应。那我们就可以考虑遍历数组,每次检查数字与下标是不是一致的,一致的说明它在属于它的位置上,不一致我们就将其交换到该数字作为下标的位置上,如果交换过程中,那个位置已经出现了等于它下标的数字,那肯定就重复了。

具体做法:

  • step 1:遍历数组,遇到数组元素与下标相同的不用管。
  • step 2:遇到数组元素与下标不同,就将其交换到属于它的位置,交换前检查那个位置是否有相同的元素,若有则重复。
  • step 3:遍历结束完全交换也没重复,则返回-1.
import java.util.*;


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     *
     * @param numbers int整型一维数组
     * @return int整型
     */
    public int duplicate (int[] numbers) {
        // write code here
        for (int i = 0; i < numbers.length; i++) {
            //下标和对应的下标元素值不等,则交换
            while (i != numbers[i]) {
                //交换前,i位置元素已经等于numbers[i]位置元素
                if (numbers[i] == numbers[numbers[i]]) {
                    return numbers[i];
                }

                int temp = numbers[numbers[i]];
                numbers[numbers[i]] = numbers[i];
                numbers[i] = temp;
            }

        }
        return -1;
    }
}

JZ51 数组中的逆序对

在这里插入图片描述

思路:
因为我们在归并排序过程中会将数组划分成最小为1个元素的子数组,然后依次比较子数组的每个元素的大小,依次取出较小的一个合并成大的子数组。

//取中间
int mid = (left + right) / 2;
//左右划分合并
merge(divide(left, mid, data, temp), divide(mid + 1, right, data, temp));

这里我们也可以用相同的方法划分,划分之后相邻一个元素的子数组就可以根据大小统计逆序对,而不断往上合并的时候,因为已经排好序了,我们逆序对可以往上累计。我们主要有以下三个阶段。

具体做法:

  • step 1: 划分阶段:将待划分区间从中点划分成两部分,两部分进入递归继续划分,直到子数组长度为1.
  • step 2: 排序阶段:使用归并排序递归地处理子序列,同时统计逆序对,因为在归并排序中,我们会依次比较相邻两组子数组各个元素的大小,并累计遇到的逆序情况。而对排好序的两组,右边大于左边时,它大于了左边的所有子序列,基于这个性质我们可以不用每次加1来统计,减少运算次数。
  • step 3: 合并阶段:将排好序的子序列合并,同时累加逆序对。
public class Solution {
    int count = 0;
    public int InversePairs(int [] array) {
        if (array == null || array.length <= 1) {
            return 0;
        }

        //先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间
        int[] temp = new int[array.length];
        mergeSort(array, 0, array.length - 1, temp);
        return count;
    }

    private void mergeSort(int[] array, int left, int right, int[] temp) {
        if (left == right) {
            return;
        }

        int mid = left + (right - left) / 2;
        //左边归并排序,使得左子序列有序
        mergeSort(array, left, mid, temp);
        //右边归并排序,使得右子序列有序
        mergeSort(array, mid + 1, right, temp);
        //将两个有序子数组合并操作
        merge(array, left, mid, right, temp);
    }

    //mid代表左半边的最后一个元素位置
    private void merge(int[] array, int left, int mid, int right, int[] temp) {
        //指向左边的第一个位置
        int i = left;
        //指向右边的第一个位置
        int j = mid + 1;
        //指向临时数组的第一个位置
        int k = 0;

        while (i <= mid && j <= right) {
            if (array[i] > array[j]) {
                temp[k++] = array[j++];
                count = (count + mid - i + 1) % 1000000007;
            } else {
                temp[k++] = array[i++];
            }
        }

        //将左边剩余元素填充进temp中
        while (i <= mid) {
            temp[k++] = array[i++];
        }

        //将右序列剩余元素填充进temp中
        while (j <= right) {
            temp[k++] = array[j++];
        }

        k = 0;
        //将temp中的元素全部拷贝到原数组中
        while (left <= right) {
            array[left++] = temp[k++];
        }
    }
}

JZ40 最小的K个数

在这里插入图片描述

思路:

要找到最小的k个元素,只需要准备k个数字,之后每次遇到一个数字能够快速的与这k个数字中最大的值比较,每次将最大的值替换掉,那么最后剩余的就是k个最小的数字了。

如何快速比较k个数字的最大值,并每次替换成较小的新数字呢?我们可以考虑使用优先队列(大根堆),只要限制堆的大小为k,那么堆顶就是k个数字的中最大值,如果需要替换,将这个最大值拿出,加入新的元素就好了。

//较小元素入堆
if(q.peek() > input[i]){
	q.poll();
	q.offer(input[i]);
}

具体做法:

  • step 1:利用input数组中前k个元素,构建一个大小为k的大顶堆,堆顶为这k个元素的最大值。
  • step 2:对于后续的元素,依次比较其与堆顶的大小,若是比堆顶小,则堆顶弹出,再将新数加入堆中,直至数组结束,保证堆中的k个最小。
  • step 3:最后将堆顶依次弹出即是最小的k个数。
import java.util.*;

public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> list = new ArrayList<>();
        if (input.length == 0 || k == 0) {
            return list;
        }

        // o1 - o2为小顶堆,o2-o1为大顶堆
        PriorityQueue<Integer> queue = new PriorityQueue<>((o1, o2) -> o2 - o1);

        for (int i = 0; i < input.length; i++) {
            queue.add(input[i]);
            if (queue.size() > k) {
                queue.poll();
            }
        }

        while (!queue.isEmpty()) {
            list.add(0, queue.poll());
        }
        return list;
    }
}

JZ41 数据流中的中位数

在这里插入图片描述
思路:

除了插入排序,我们换种思路,因为插入排序每次要遍历整个已经有的数组,很浪费时间,有没有什么可以找到插入位置时能够更方便。

我们来看看中位数的特征,它是数组中间个数字或者两个数字的均值,它是数组较小的一半元素中最大的一个,同时也是数组较大的一半元素中最小的一个。那我们只要每次维护最小的一半元素和最大的一半元素,并能快速得到它们的最大值和最小值,那不就可以了嘛。这时候就可以想到了堆排序的优先队列。

具体做法:

  • step 1:我们可以维护两个堆,分别是大顶堆min,用于存储较小的值,其中顶部最大;小顶堆max,用于存储较大的值,其中顶部最小,则中位数只会在两个堆的堆顶出现。
  • step 2:我们可以约定奇数个元素时取大顶堆的顶部值,偶数个元素时取两堆顶的平均值,则可以发现两个堆的数据长度要么是相等的,要么奇数时大顶堆会多一个。
  • step 3:每次输入的数据流先进入大顶堆排序,然后将小顶堆的最大值弹入大顶堆中,完成整个的排序。
  • step 4:但是因为大顶堆的数据不可能会比小顶堆少一个,因此需要再比较二者的长度,若是小顶堆长度小于大顶堆,需要从大顶堆中弹出最小值到大顶堆中进行平衡。
import java.util.*;
public class Solution {
    //小顶堆,存放较大的数
    PriorityQueue<Integer> min = new PriorityQueue<>();
    //大顶堆,存放较小的数
    PriorityQueue<Integer> max = new PriorityQueue<>((o1, o2) -> o2 - o1);
    public void Insert(Integer num) {
        //要加入的数为第奇数个
        if(max.size() == min.size()){
            max.add(num);
            min.add(max.poll());
        //要加入的数为第偶数个
        }else{
            min.add(num);
            max.add(min.pol1l());
        }
    }

    public Double GetMedian() {
        if(max.size() == min.size()){
            return (min.peek() + max.peek()) / 2.0;
        }else{
            return min.peek() * 1.0;
        }
    }
    
}

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

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

相关文章

qt C++中指针自动释放内存及程序中的内存操作、管理

程序加载到内存后代码存储到代码区&#xff0c;并将全局变量、静态变量初始化到全局/静态内存区&#xff0c;然后会分配2M左右的栈内存区用于存储局部变量&#xff0c;并在运行时根据需要可以在堆内存区(空闲内存区及硬盘的虚拟内存区)申请空间。 程序可使用的内存分区↓ 各基…

C++之Hello World

概览 编程语言历史 机器语言:00110101…最初始的计算机内部语言,不同机器使用的语言甚至不同 汇编语言:利用简单符号(a DB 7H…)对机器语言进行了一定的抽象,增加了可读性,更加人性化.在一定程度上仍然依赖硬件,属于低级的语言 高级语言:使用文字通过编译器被翻译为机器语言…

Vue中 引入使用 element-resize-detector 监听 Dom 元素 宽度、高度 变化

1. 前言 很多做pc端平台的小伙伴都遇到过这样一个问题&#xff1a;在做侧边栏菜单时会有一个收缩和展开的一个功能&#xff0c;在伸缩的过程中右边的页面的宽度就会随之改变。我上网查了查 &#xff0c;也动手试了试 window.onresize ()>{}。却不尽人意&#xff0c;因为它…

SVM 超平面计算例题

SVM Summary Example Suppose the dataset contains two positive samples x(1)[1,1]Tx^{(1)}[1,1]^Tx(1)[1,1]T andx(2)[2,2]Tx^{(2)}[2,2]^Tx(2)[2,2]T, and two negative samples x(3)[0,0]Tx^{(3)}[0,0]^Tx(3)[0,0]T and x(4)[−1,0]Tx^{(4)}[-1,0]^Tx(4)[−1,0]T. Please…

MySQL纯代码复习

前言 本文章是用于总结尚硅谷MySQL教学视频的记录文章&#xff0c;主要用于复习&#xff0c;非商用 原视频连接&#xff1a;https://www.bilibili.com/video/BV1iq4y1u7vj/?p21&spm_id_frompageDriver&vd_sourcec4ecde834521bad789baa9ee29af1f6c https://www.bilib…

C#重启 --- 枚举

第一部分 --- 枚举 枚举类型的本质其实就是在给整型数据加标签&#xff0c;当编译器遇到枚举类型标签的时候&#xff0c;编译器会自动获取标签对应的整型数据&#xff08;默认从0开始由上往下递增&#xff09; 枚举类型的使用方法&#xff1a; 1.枚举类型的类型名是由我们自己…

四.STM32F030C8T6 MCU开发之利用 TIM1+ADC1+DMA1 实现5路(3路外部电压模拟信号+内部2路信号)采集

四.STM32F030C8T6 MCU开发之利用 TIM1ADC1DMA1 实现5路&#xff08;3路外部电压模拟信号内部2路信号&#xff09;采集 文章目录四.STM32F030C8T6 MCU开发之利用 TIM1ADC1DMA1 实现5路&#xff08;3路外部电压模拟信号内部2路信号&#xff09;采集0.总体功能概述ADC 简介1.ADC硬…

数据结构《LinkeList 双向链表》

LinkeList LinkeList 的低层是由双向链表结构组成的&#xff0c;所有元素都是存放到单独的节点当中&#xff0c;通过地址引用将节点串联起来 因此在任意位置插入或删除元素时&#xff0c;都不在需要移动元素&#xff0c;效率较高 下面是双向链表的结构图&#xff1a; 在集合框…

【从零开始游戏开发】静态资源优化 | 全面总结 |建议收藏

你知道的越多&#xff0c;你不知道的越多 &#x1f1e8;&#x1f1f3;&#x1f1e8;&#x1f1f3;&#x1f1e8;&#x1f1f3; 点赞再看&#xff0c;养成习惯&#xff0c;别忘了一键三连哦 &#x1f44d;&#x1f44d;&#x1f44d; 文章持续更新中 &#x1f4dd;&#x1f4dd;…

C++智能指针

文章目录一、智能指针的目的和基本原理二、不带引用计数的智能指针2.1 auto_ptr2.2 scoped_ptr2.3 unique_ptr三、带引用计数的智能指针3.1 shared_ptr3.2 weak_ptr一、智能指针的目的和基本原理 一般new出来的对象会用普通指针引用&#xff0c;此时申请的堆上的资源需要我们手…

乐趣国学—品读《弟子规》中的“泛爱众”之道(上篇)

前言 “泛爱众”就是以广泛的爱心对待社会大众。人类生活是以爱心为纽带&#xff0c;没有爱心&#xff0c;人类生活就太痛苦不堪了。这个爱心从哪里来的&#xff1f;这个爱心就是孝心。孝道&#xff0c;正是培养爱心的第一步&#xff0c;一个连父母都不爱的人决不会真心爱他人&…

19.Feign 的工程化实例:eureka,ribbon,feign,hystrix(springcloud)

项目模型 项目结构 本实例创建model都是通过maven手动创建&#xff0c;依赖进行手动导入&#xff0c;好处是比使用springboot模板创建更加灵活&#xff0c;更方便的进行父子模块的管理。 1.创建父项目feign-project 2.对父项目feign-project的pom.xml&#xff0c;进行手动导入依…

Linux基础内容(10)—— 进程概念

目录 1.冯诺依曼体系结构 ​编辑1.冯诺依曼体系特点 2.cpu运算原理 3.数据传输 2.操作系统 1.操作系统管理的真相 2.操作系统与硬件的交互方式 3.操作系统与用户的交互方式 1.系统调用接口 2.用户对系统调用的使用 3.进程 1.进程的概念 2.Linux中的进程 3.与进程…

基于DJYOS的SPI驱动编写指导手册

1.贡献者列表 深圳市秦简计算机系统有限公司DJYOS驱动开发团队。 2.概述 DJYOS的DjyBus总线模型为IIC、SPI之类的器件提供统一的访问接口&#xff0c;SPIBUS模块是DjyBus模块的一个子模块&#xff0c;为SPI器件提供统一的编程接口&#xff0c;实现通信协议层与器件层的分离。…

Python 考试练习题 2

一、选择题 1、下列是 python 合法标识符的是&#xff08; B&#xff09;。 A. 2variable B. variable2 C. $anothervar D. if 2、在 python 中字符串的表示方式是&#xff08;D &#xff09;。 A.采用单引号包裹 B.采用双引号包裹 C.采用三重单引号包裹 D.ABC 都是 3、设有…

【浅学Linux】动态库与静态库的封装与使用

朋友们好&#xff0c;这里简单介绍一下LINUX学习中关于动态库与静态库的理解&#xff0c;以及站在封装和使用的角度去介绍是如何封装的&#xff1f;如何使用的&#xff1f; 文章目录一&#xff1a;动态库与静态库的理解二&#xff1a;静态库2.1&#xff1a;静态库的使用2.2&…

Sprite Editor

1、SpriteEditor SpriteEditor是精灵图片编辑器 它主要用于编辑2D游戏开发中使用的Sprite精灵图片 它可以用于编辑 图集中提取元素&#xff0c;设置精灵边框&#xff0c;设置九宫格&#xff0c;设置轴心&#xff08;中心&#xff09;点等等功能 2、Single图片编辑 Sprite Ed…

Docker原生网络、自定义网络、Docker容器通信、跨主机容器网络

Docker原生网络、自定义网络、Docker容器通信、跨主机容器网络Docker原生网络bridgeHostnoneDocker自定义网络自定义bridgeoverlaymacviandocker network所有基本命令Docker容器通信双冗余机制跨主机容器网络一些遗留错误解决错误1错误2错误3错误4Docker原生网络 docker安装时…

如何搭建node_exporter

如何搭建node_exporter 1.观看条件 1.假设你已经看过上一篇文章 《如何搭建普罗米修斯 Prometheus》 2.假设你已经会搭建普罗米修斯&#xff08;promethus&#xff09; 3.上面两个假设&#xff0c;只要满足一个。那你看这篇文章就没什么压力了 2.node_exporter是啥 node_…

UI自动化测试之selenium工具(浏览器窗口的切换)

目录 前言 方法 实例 ①示例1 ②示例2 附加知识 结语 前言 1、在浏览网页的时候&#xff0c;有时点击一个链接或者按钮&#xff0c;会弹出一个新的窗口。这类窗口也被称之为句柄&#xff08;一个浏览器窗口的唯一标识符&#xff0c;通过句柄实现不同浏览器窗口之间的切…