排序
1. 比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序。
2. 非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。
1. 选择排序(Selection Sort)
选择排序(Selection-sort)是一种简单直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
时间复杂度 O(N^2)
function selectionSort(arr) {
varlen = arr.length;
varminIndex, temp;
for(vari = 0; i < len - 1; i++) {
minIndex = i;
for(varj = i + 1; j < len; j++) {
// 寻找最小的数
if(arr[j] < arr[minIndex]) {
// 将最小数的索引保存
minIndex = j;
}
}
temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
returnarr;
}
2. 插入排序(Insertion Sort)
插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
时间复杂度 O(N^2)
function insertionSort(arr) {
varlen = arr.length;
varpreIndex, current;
for(vari = 1; i < len; i++) {
preIndex = i - 1;
current = arr[i];
while(preIndex >= 0 && arr[preIndex] > current) {
arr[preIndex + 1] = arr[preIndex];
preIndex--;
}
arr[preIndex + 1] = current;
}
returnarr;
}
3. 冒泡排序(Bubble Sort)
冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
时间复杂度 O(N^2)
function bubbleSort(arr) {
varlen = arr.length;
for(vari = 0; i < len - 1; i++) {
for(varj = 0; j < len - 1 - i; j++) {
// 相邻元素两两对比
if(arr[j] > arr[j+1]) {
// 元素交换
vartemp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = temp;
}
}
}
returnarr;
}
4. 堆排序(Heap Sort)
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
时间复杂度 O(NlogN)
class Solution {
public int[] sortArray(int[] nums) {
PriorityQueue<Integer> q = new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
// 小根堆
return o1-o2;
}
});
int[] ans = new int[nums.length];
for (int i = 0; i < nums.length; i++) {
q.add(nums[i]);
}
int index = 0;
while (!q.isEmpty()) {
ans[index] = q.poll();
index++;
}
return ans;
}
}
5. 快速排序(Quick Sort)
快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
时间复杂度 O(NlogN)
class Solution {
public int[] sortArray(int[] nums) {
//快排
this.quickSort(nums,0,nums.length - 1);
return nums;
}
public void quickSort(int[] nums,int k, int left, int right) {
if (left >= right) return;
// 交换数据
int mid = sort(nums,left,right);
// 排序左边
quickSort(nums,k,left,mid);
// 排序右边
quickSort(nums,k,mid + 1,right);
}
// 随机选一个数S,根据这个数把小于S的放左边,大于S放右边
public int sort(int[] nums, int left, int right){
// 防止出现选择的数都是最小或最大,每次取一个随机数
int mid = left + (int)(Math.random() * (right - left));
int l = left;
int r = right;
// 选择的位置的值
int val = nums[mid];
while (l <= r) {
// 小于选择的值,直接l向右移动
while (nums[l] < val) l++;
// 大于选择的值,直接r向左移动
while (nums[r] > val) r--;
if(r == l) break;
// 符合区间条件直接交换左右的值
if (l <= r){
int swap = nums[r];
nums[r] = nums[l];
nums[l] = swap;
l++;
r--;
}
}
// 最后返回最后left和right相等中间的元素的下标
return r;
}
}
6. 归并排序(Merge Sort)
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
时间复杂度 O(NlogN)
class Solution {
public int[] sortArray(int[] nums) {
this.sort(nums,0,nums.length - 1);
return nums;
}
// 归并排序
public void sort(int[] nums,int left, int right) {
// 终止条件
if (left >= right) return;
int mid = (left + right) >> 1;
// 左边
sort(nums,left,mid);
// 右边
sort(nums,mid + 1,right);
// 合并
merge(nums,left,mid,right);
}
public void merge(int[] nums, int left, int mid, int right) {
// 数组长度
int length = right - left + 1;
int[] ans = new int[length];
int l = left;
int r = mid + 1;
int index = 0;
// 归并排序进行 双数组合并
for (int i = 0; i < ans.length; i++ ) {
// 当l等于mid+1 或者 (l<=mid && r<=right && nums[l] > nums[r]) 都是 r++
// 每次只用考虑一个方向的判断,其他进入elese
if(l > mid || (r <= right && nums[l] > nums[r])){
ans[i] = nums[r++];
}else{
// 其他的
ans[i] = nums[l++];
}
}
// 拷贝临时数组到原数组
for (int i = 0; i < ans.length; i++ ) {
nums[left + i] = ans[i];
}
}
}
7. 计数排序(Counting Sort)
计数排序不是基于比较的排序算法,其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数,并且这个范围不是太大,如果范围超过一定的长度,那么res数组就会很大,内存开销很大。
计数排序对于元素值在正整数区间是可行的,对于负数还需要另外的考虑
时间复杂度 O(M + N) N为元素个数 , M为元素值区间
class Solution {
public int[] sortArray(int[] nums) {
int max = Integer.MIN_VALUE;
for (int i = 0; i < nums.length; i++) {
max = Math.max(nums[i],max);
}
int[] res = new int[max + 1];
for (int i = 0; i < nums.length; i++) {
res[nums[i]]++;
}
int index = 0;
for (int i = 0; i < res.length; i++) {
while(res[i]-->0)nums[index++] = i;
}
return nums;
}
}
- 寻找旋转排序数组中的最小值 II
class Solution {
public int findMin(int[] nums) {
int left = 0,right = nums.length - 1;
while (left < right){
int mid = (left + right) >> 1;
// 如果mid == right 则最小值肯定在mid和right之间
// 则right = right - 1;右节点向做移动一个数
if( nums[mid] == nums[right]){
right = right - 1;
}else if (nums[mid] < nums[right]){
right = mid ;
}else{
left = mid + 1;
}
}
System.out.println(left + ";" + right);
return nums[left];
}
}
- 寻找峰值
/* 思维逻辑
1. 三分查询-lmid 和 rmid
- 如果lmid < rmid 即峰值肯定在lmid的右侧 即区间 [lmid , r]
- 如果lmid > rmid 单调递减了,那么峰值肯定在左侧,即[l,rmid]
2. 尽可能的缩短lmid和rmid的值
3. 本题的数组元素为int,那么可以lmid = (left + right)>>1;rmid就可以为 lmid + 1;
4. 如果数组元素为double类型那么lmid = (left + right)>>1;rmid就可以为 lmid + 0.01;
*/
class Solution {
public int findPeakElement(int[] nums) {
if (nums.length == 1) {}
int left = 0, right = nums.length - 1;
while (left < right){
int lmid = (left + right) >> 1;
int rmid = lmid + 1;
// lmid < rmid 即单调递增 取区间为 [lmid + 1,right]
if (nums[lmid] <= nums[rmid]){
left = lmid + 1;
}else{
// lmid > rmid 即单调递减 取区间为 [l,rmid - 1]
right = rmid - 1;
}
// System.out.println(mid +";"+left);
}
return left;
}
}
- 分割数组的最大值
/* 思维逻辑
1. 提取题目信息:
- 一个非负整数数组 nums 和一个整数 m
- 将这个数组分成 m 个非空的连续子数组
- 设计一个算法使得这 m 个子数组各自和的最大值最小
2. 把nums数组分成m个连续子数组,并且求出所有子数组的和中最大值
3. 然后有很多种分成连续子数组的方式,求其中最大值最小的一次分法
4. 那么猜想一个数S满足此前的条件
5. 直接贪心算法可以分成多少个组,只要小于等于当前的子数组m个数即可符合要求
6. 此题解就转变为寻找第一个满足条件的数S
7. 二分的区间[数组最大值,数组的总和]
8. 从这个区间寻找到第一个满足条件的数S
*/
class Solution {
public int splitArray(int[] nums, int m) {
// 二分的最大值查找
int sum = 0;
int left = 0;
for (int i = 0; i < nums.length;i++) {
sum += nums[i];
// 起始位置为数组中最大的值
left = Math.max(left, nums[i]);
}
int right = sum;
// 二分猜想一个数
while (left < right) {
int mid = (left + right) >> 1;
if (isFind(nums,m,mid)) {
right = mid;
}else {
left = mid + 1;
}
}
return left;
}
// 假设一个数S,这个数S是各个数组最大和中最小的值
// 那么 每个子数组的和都小于S
public boolean isFind(int[] nums, int m, int S) {
// 初始为1
int count = 1;
int sum = 0;
// 贪心的把数组的元素相加
for (int i = 0; i < nums.length; i++) {
// 如果当前元素+sum大于等于S,即应该分割新数组,并且数组数count++
if (sum + nums[i] <= S) {
sum += nums[i];
}else{
// i元素为下一个数组的头元素,sum为下一个数组的初始值
sum = nums[i];
count++;
}
}
// 计算分割的数组count < m时:[7,2,5,10,8] m=2
// ex : S为33
// 那么count=1,那么分一个数组就满足条件,把这个数组分成两个数组也能满足条件
return count <= m;
}
}
- 数组的相对排序
class Solution {
public int[] relativeSortArray(int[] arr1, int[] arr2) {
return this.sortArray(arr1,arr2);
}
// 计数
public int[] sortArray(int[] nums,int[] arr2) {
int max = 1000;
int[] res = new int[max + 1];
for (int i = 0; i < nums.length; i++) {
res[nums[i]]++;
}
int index = 0;
int[] ans = new int[nums.length];
for (int i = 0; i < arr2.length; i++) {
while(res[arr2[i]]-->0) ans[index++] = arr2[i];
}
for (int i = 0; i <= max; i++) {
while(res[i]-->0)ans[index++] = i;
}
return ans;
}
}