【LeetCode】摆动排序 [M](数组)

news2025/8/12 7:42:25

280. 摆动排序 - 力扣(LeetCode)

一、题目

给你一个的整数数组 nums, 将该数组重新排序后使 nums[0] <= nums[1] >= nums[2] <= nums[3]... 
输入数组总是有一个有效的答案。

示例 1:

输入:nums = [3,5,2,1,6,4]
输出:[3,5,1,6,2,4]
解释:[1,6,2,5,3,4]也是有效的答案

示例 2:​​​​​​​

输入:nums = [6,6,5,6,3,8]
输出:[6,6,5,6,3,8]

提示:​​​​​​​

  • 1 <= nums.length <= 5 * 104
  • 0 <= nums[i] <= 104
  • 输入的 nums 保证至少有一个答案。

二、代码

class Solution {
    public void wiggleSort(int[] nums) {
        // 过滤无效参数
        if (nums == null || nums.length == 0) {
            return;
        }
        // 这道题需要先排序,因为这道题本意并不是说要将数组中的数据按照完美洗牌两两交错着重排一遍,而是说按照小大小大小大这样的间隔顺序排列,而我们的完美洗牌代码只是按照位置来两两交错,
        // 如果想要左到题目要求的小大小大小大这种按照大小交错,就需要先把数组搞成有序的,然后再调用我们的完美洗牌代码就可以了。
        // 还要注意的是这个是280. 摆动排序,这个题要求是nums[0] <= nums[1] >= nums[2] <= nums[3]... 是带等号的,所以用完美洗牌的代码可以求解,
        // 但是324. 摆动排序 II这道题是不带等号的,所以就还需要考虑将相等的数不能挨着,完美洗牌问题做不到这个,完美洗牌只是单纯的将位置来进行交错排列,但是无法根据它们的大小来调整顺序,所以完美洗牌代码是有可能将两个相等的数挨着的
        // 假设这个排序是额外空间复杂度O(1)的,当然系统提供的排序并不是,你可以自己实现一个堆排序
        Arrays.sort(nums);
        int n = nums.length;
        // 这道题本身并没有限制数组长度是否是偶数,而我们完美洗牌代码必须保证传入的数组长度是偶数才可以,所以下面还需要做数组长度的奇偶判断,做相应的处理
        // 偶数情况,因为力扣题目要求的完美问题是小大小大小大这样间隔的,但是如果我们的数组是递增的,用我们这个完美洗盘的代码搞出来的4 1 5 2 6 3,但是我们最后想要的其实是1 4 2 5 3 6
        // 所以偶数情况在用我们的代码完成完美洗牌后,还需要两两为一组,在组内交换两个数的位置,才是力扣这道题最后的答案。
        if ((n & 1) == 0) {
            // 先进行完美洗牌的前半部分和后半部分的交叉排序
            shuffle(nums, 0, n - 1);
            // 在按照两个为一组,在内部交换两数位置,这样得到的结果就符合本题要求了
            int temp;
            for (int i = 0; i < n; i+= 2) {
                temp = nums[i];
                nums[i] = nums[i + 1];
                nums[i + 1] = temp;
            }
        // 奇数情况   例如1 2 3 4 5,第一个位置的1保持不动,然后去对后面四个数做下标循环怼,因为后面四个是偶数,符合下标循环怼的要求,最后结果正好是 1 4 2 5 3,是我们要的答案
        } else {
            // 将1~n-1范围上做完美洗牌,下标0位置不动,最后的结果就是本题要的结果。
            shuffle(nums, 1, n - 1);
        }
    }
    /**
     * 完美洗牌问题的算法模板,这是整个代码的核心
     * nums:数组长度必须为偶数
     * 在nums[l...r]上做完美洗牌的调整(nums[l...r]范围上一定要是偶数个数字)
     */
    public void shuffle(int[] nums, int l, int r) {
        // 切成一块一块的解决,每一块的长度满足(3^k)-1
        // 如果此时r > l,就说明此时还有范围要搞(l==r有1个数,也就不需要再变动了,符合公式要求的最低长度也是2),当r和l错过去了,就说明已经完成全部位置的操作了
        while (r > l)  {
            // 当前要处理的r - l范围上的数据
            // 长度为n
            int n = r - l + 1;
            
            // 计算小于等于len并且是离n最近的,满足(3^k)-1的数
			// 也就是找到最大的k,满足3^k <= n+1
            int k = 1; // 初始k为1
            int base = 3; // 初始值
            // 保证3^k <= n+1
            // 要记住这个方法,就是求3次幂的时候直接用循环滚下去,利用之前求出来的结果,只需要再乘一个3就行了,这样效率可以更高一些,比每一轮都重新用Math.pow求快很多
            while (base * 3 - 1 <= n) {
                base *= 3;
                k++;
            }
            // 此时我们就先处理长度为base - 1长度的范围,至于剩下的长度留到后面的循环去弄。base - 1满足3^k -1
            // 下面这个流程就是将符合要求的前k个数移动到数组的最前面,下面的流程其实举个具体的例子或者直接看笔记就能明白了
			// 当前要解决长度为base-1的块,一半就是再除2
            int half = (base - 1) >> 1;
            // [L..R]的中点位置
            int mid = (l + r) >> 1;
            // 要旋转的左部分为[L+half...mid], 右部分为arr[mid+1..mid+half]
			// 注意在这里,arr下标是从0开始的
            rotate(nums, l + half, mid, mid + 1, mid + 1 + half - 1);

            // 旋转完成后,从l开始算起,长度为base-1的部分进行下标连续推
            // 从l位置开始,往右n的长度这一段,做下标循环怼
	        // 每一个环的起始位置依次为1,3,9...
            // 当前要处理数组的起始位置就是l,在后面算数组中真实下标时,都要加上l

            // 当前要处理数组的长度
            int len = base - 1;
            // trigger就是在我们结论中起始位置的下标,注意是从1开始的,如果想要求出来在数组中真实对应的下标,应该用start + trigger - 1(也就是用此时数组的左边界l加上trigger再减1,因为trigger是从1开始的,多算了一个)
            // 找到每一个出发位置trigger,一共k个(1、3、9...3^(k - 1))
            // 每一个trigger都进行下标连续推
            // 出发位置是从1开始算的,而数组下标是从0开始算的。
            // i是为了来控制求3^(k - 1),i会控制循环一共只会执行k次,但是因为trigger是从1开始的,所以最终其实只会有k-1个3相乘,也就得到了3 ^ (k - 1)
            for (int i = 0, trigger = 1; i < k; i++, trigger *= 3) {
                // 当前遍历到的在数组中真实的下标位置是l + trigger - 1
                // 我们要将nums[l + trigger - 1]放到下一个要去的位置,所以这里要将该位置的值记录一下
                int preValue = nums[l + trigger - 1];
                // 根据我们的结论公式,算出来下一个要在什么位置,注意这个位置并不是真实的数组下标位置
                int cur = modifyIndex(trigger, len);
                // 每一轮循环时trigger就相当于这一次下标循环怼的起始位置,只要是循环过程中下标再次回到trigger,就说明这个环已经遍历完一遍了
                while (cur != trigger) {
                    // 当前来到的位置在数组的真实下标l + cur - 1,(l就是当前处理范围的最左边界下标)
                    int tmp = nums[l + cur - 1];
                    // 将上一个位置的值放到当前位置上
                    nums[l + cur - 1] = preValue;
                    // 将当前位置的值作为下一轮的上一个位置的值,我们要将nums[cur + l - 1]放到下一个位置上去
                    preValue = tmp;
                    // 根据公式计算下一个位置
                    cur = modifyIndex(cur, len);
                }
                // 当cur == trigger时会跳出循环,但此时trigger位置的值还没有放,所以要将preValue赋值给当前环的起始位置l + cur  - 1
                nums[l + cur - 1] = preValue;
            }

            // 解决了前base-1的部分,剩下的部分继续处理,将要处理范围的左边界设置为l + base - 1,继续循环
            l = l + base - 1;
        }
    }

    // 完美洗牌问题的公式结论,这个记住即可,不用管他的证明
    // 当前来到index位置,当前进行下标循环堆的数组长度是n,返回要将index位置的数据移动到哪个下标上
    public int modifyIndex(int index, int n) {
        // 分段函数,根据index不同来返回不同的下一个位置的下标
        if (index <= n / 2) {
            return index * 2;
        } else {
            return (index - n / 2) * 2 - 1;
        }
    }

    // 将l1~r1和l2~r2两个部分做整体交换
    // 这两个部分是连续的,即r1 + 1 = l2
    public void rotate(int[] nums, int l1, int r1, int l2, int r2){
        // 先对这两个部分自己内部做逆序
        reverse(nums, l1, r1);
        reverse(nums, l2, r2);
        // 然后再把这两个部分的整体进行逆序
        reverse(nums, l1, r2);
    }
    // 对数组nums内的l~r范围进行逆序
    public void reverse(int[] nums, int l, int r) {
        int temp;
        while (l < r) {
            temp = nums[l];
            nums[l++] = nums[r];
            nums[r--] = temp; 
        }
    }
}

三、解题思路 

一个位置要去哪儿是一个简单公式可以确定的。

这里我们就用R1L1R2L2这种形式为基础,来给一个公式。

左半部分的数要去2*i位置;右半部分的数要去(i - N / 2)* 2 - 1。规定下标从1开始。

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

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

相关文章

基于SpringBoot的篮球竞赛预约平台

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SpringBoot 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;…

新知实验室 腾讯云实时音视频 RTC WEB端初识

这里写目录标题前言初识产品产品介绍基础功能高级功能扩展功能快速上手位置创建源码下载源码文档写入密钥使用调试区域前言 当前时代是信息行业飞速发展的时代&#xff0c;万物都在朝物联网方向转化。而人作为一个意识体&#xff0c;也正在通过互联网&#xff0c;认识一个全新…

01-10-Hadoop-HA-概述

01-Hadoop-HA-概述&#xff1a; HA 1&#xff09;所谓HA&#xff08;High Available&#xff09;&#xff0c;即高可用&#xff08;7*24小时不中断服务&#xff09;。 2&#xff09;实现高可用最关键的策略是消除单点故障。HA严格来说应该分成各个组件的HA机制&#xff1a;H…

【学习笔记14】JavaScript的循坏语句

一、while循坏 1、解释说明 while (循环结束条件) { 循环体}// 1. 初始化var num 1; // 2. 循环结束条件 num < 5while (num < 5) { // 3. 循环体console.log(1);// 4. 改变自身, 不写还能执行, 但是是死循环, 电脑死机速度num }2、课堂案例 2.1 计算1到100的…

【学习笔记15】JavaScript的函数

一、函数 笔记首发 &#xff08;一&#xff09;什么是函数 &#x1f644; 前端的函数, 与数学的函数, 完全是两个概念&#x1f644; 可以粗暴的理解为 一个盒子&#x1f644; 当一段需要多次使用的复杂代码段, 我们可以把它放在(抽离)一个盒子中(就是函数)&#x1f644;在需要…

【math】利用Cardano方法对一元三次方程求解及python实现

文章目录【参考】【问题描述】求解一元三次方程【代码实现】现成的包 cardano_method根据公式编写求解代码【总结】【参考】 用Cardano方法求解三次方程介绍cardano方法求解下载cardano方法包x^310求解问题、三次方程反函数问题Micorsoft-Math-solver 微软数学工具WolframAlph…

《Transformers自然语言处理系列教程》第1章:Transformers 介绍

2017年,谷歌的研究人员发表了一篇论文,提出了一种用于序列建模的新型神经网络架构。这种架构被称为Transformer,在机器翻译质量和训练成本方面都优于递归神经网络(RNNs)。 与此同时,一种名为ULMFiT的有效迁移学习方法表明,在一个非常大和多样化的语料库上,训练长短期记…

Kotlin拿Android本地视频缩略图

本文主要讨论如下三个问题&#xff1a; 如何拿到本地视频&#xff1f;怎么拿视频缩略图&#xff1f;缩略图如何压缩&#xff1f; 1 如何拿到本地视频&#xff1f; 1.1 定义数据结构 先定义媒体信息数据结构MediaInfo&#xff0c;以及视频信息数据结构VideoInfo。 open class…

我参加NVIDIA Sky Hackathon 训练文件的路径设置

各变量的作用 KEY 对应的是 NVIDIA ngc 的那个网站上面生成的那个 keyGPU 的索引&#xff0c; 这个一般不需要修改&#xff0c; 因为大家只有一块 GPU用户实验目录&#xff0c; 这个文件夹用于存放后续过程产生的一系列的文件数据下载目录&#xff0c; 存放数据 本地工程目录&a…

Java并发编程实战读书笔记二

第五章 基础构建模块 5.1 同步容器类 5.1.1 同步容器类的问题 如下&#xff0c;如果list含有10个元素&#xff0c;线程A调用getLast的同时线程B调用deleteLast&#xff0c;那么getLast可能会报ArrayIndexOutOfBoundsException 改为如下方式能确保size和get一致 Vector迭代也…

【795. 区间子数组个数】

来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 给你一个整数数组 nums 和两个整数&#xff1a;left 及 right 。找出 nums 中连续、非空且其中最大元素在范围 [left, right] 内的子数组&#xff0c;并返回满足条件的子数组的个数。 生成的测试用例…

微信小程序| 用小程序复刻微信Wechat

&#x1f4cc;个人主页&#xff1a;个人主页 ​&#x1f9c0; 推荐专栏&#xff1a;小程序开发成神之路 --【这是一个为想要入门和进阶小程序开发专门开启的精品专栏&#xff01;从个人到商业的全套开发教程&#xff0c;实打实的干货分享&#xff0c;确定不来看看&#xff1f; …

新的趋势:From Big to Small and Wide data

新的趋势&#xff1a;From Big to Small and Wide data 所以&#xff0c;在这个时候&#xff0c;作为率先提出要做 MySQL 开源 HTAP 数据库的 StoneDB&#xff0c;想要稍微冷静一下。 不是说我们不做 HTAP 了&#xff0c;而是有了一个新的思路。这个思路&#xff0c;也同样来…

【模型训练】YOLOv7车辆三类别检测

YOLOv7车辆三类别检测 1、车辆三类别检测模型训练2、模型评估3、模型和数据集下载网盘链接1、本项目采用YOLOv7算法实现对车辆三类别检测,在几千多张车辆三类别数据集中训练得到,我们训练了YOLOv7、,所有指标都是在同一个验证集上得到; 2、目标类别数:3;类别名:car、bus…

【蓝桥杯选拔赛真题29】python堆砖块 青少年组蓝桥杯python 选拔赛STEMA比赛真题解析

目录 python堆砖块 一、题目要求 1、提示信息 1、编程实现 2、输入输出

WindowsServer域控的安装与卸载

搭建域服务器 1.安装域控 打开服务器管理器, 点击右上角的管理, 选择添加角色和功能 一直点击下一步,直到选择服务器角色处, 勾选Active Directory域服务器 一直下一步&#xff0c;然后点击安装 安装完毕后将此服务器提升为域控制器 自行设置DSRM的密码, 后面一直点击下一步直…

【优化调度】遗传算法求解公交车调度排班优化问题【含Matlab源码 2212期】

⛄ 一、 遗传算法简介 1 引言 公交排班问题是城市公交调度的核心内容,是公交调度人员、司乘人员进行工作以及公交车辆正常运行的基本依据。行车时刻表是按照线路的当前客流量情况,确定发车频率,提供线路车辆的首、末车时间。它是公交企业对社会的承诺,决定着为乘客服务的水平,…

2023-2028年中国花炮行业市场供需与投资预测分析报告

本报告由锐观咨询重磅推出&#xff0c;对中国花炮行业的发展现状、竞争格局及市场供需形势进行了具体分析&#xff0c;并从行业的政策环境、经济环境、社会环境及技术环境等方面分析行业面临的机遇及挑战。还重点分析了重点企业的经营现状及发展格局&#xff0c;并对未来几年行…

【Java 设计模式】简单工厂模式 静态工厂模式

简单工厂模式 & 静态工厂模式1 简单工厂模式1.1 角色1.2 点咖啡案例1.2.1 类图1.2.2 实现1.3 优点1.4 缺点2 静态工厂模式2.1 代码变动2.2 优点1 简单工厂模式 简单工厂模式并不属于 23 种设计模式。 1.1 角色 抽象产品&#xff1a;定义产品的规范&#xff0c;描述产品的…

相控阵天线(七):常规平面阵列分布(矩形阵列、三角栅格、六边形阵列和圆形阵列)

目录简介矩形栅格平面阵列三角栅格平面阵列六边形阵列圆形平面阵列空心平面阵列简介 常见的平面阵有一些基本类型&#xff0c;按照栅格形式可以进行以下划分&#xff1a;矩形栅格、三角形栅格、同心圆环和椭圆环栅格等&#xff1b;按照边界形式可以进行以下划分&#xff1a;矩…