剑offer - 5 -排序搜索

16 篇文章 0 订阅
11 篇文章 0 订阅

31. 下一个排列

在这里插入图片描述
下一个排列
问题描述
这道题是 LeetCode 31题。

“下一个排列”的定义是:给定数字序列的字典序中下一个更大的排列。如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。

我们可以将该问题形式化地描述为:给定若干个数字,将其组合为一个整数。如何将这些数字重新排列,以得到下一个更大的整数。如 123 下一个更大的数为 132。如果没有更大的整数,则输出最小的整数。

以 1,2,3,4,5,6 为例,其排列依次为:

123456
123465
123546

654321
可以看到有这样的关系:123456 < 123465 < 123546 < … < 654321。

算法推导
如何得到这样的排列顺序?这是本文的重点。我们可以这样来分析:

我们希望下一个数比当前数大,这样才满足“下一个排列”的定义。因此只需要将后面的「大数」与前面的「小数」交换,就能得到一个更大的数。比如 123456,将 5 和 6 交换就能得到一个更大的数 123465。
我们还希望下一个数增加的幅度尽可能的小,这样才满足“下一个排列与当前排列紧邻“的要求。为了满足这个要求,我们需要:
在尽可能靠右的低位进行交换,需要从后向前查找
将一个 尽可能小的「大数」 与前面的「小数」交换。比如 123465,下一个排列应该把 5 和 4 交换而不是把 6 和 4 交换
将「大数」换到前面后,需要将「大数」后面的所有数重置为升序,升序排列就是最小的排列。以 123465 为例:首先按照上一步,交换 5 和 4,得到 123564;然后需要将 5 之后的数重置为升序,得到 123546。显然 123546 比 123564 更小,123546 就是 123465 的下一个排列
以上就是求“下一个排列”的分析过程。

算法过程
标准的“下一个排列”算法可以描述为:

从后向前查找第一个相邻升序的元素对 (i,j),满足 A[i] < A[j]。此时 [j,end) 必然是降序
在 [j,end) 从后向前查找第一个满足 A[i] < A[k] 的 k。A[i]、A[k] 分别就是上文所说的「小数」、「大数」
将 A[i] 与 A[k] 交换
可以断定这时 [j,end) 必然是降序,逆置 [j,end),使其升序
如果在步骤 1 找不到符合的相邻元素对,说明当前 [begin,end) 为一个降序顺序,则直接跳到步骤 4
该方法支持数据重复,且在 C++ STL 中被采用。

class Solution {
    public void nextPermutation(int[] nums) {
        int n = 0;
        if(nums == null || (n = nums.length) == 0)  return;
        // 从后往前查找第一次出现 nums[i] < nums[i+1] 的位置
        int i = n-2;
        for(; i>=0 && nums[i] >= nums[i+1]; i--)    ;
        // if i == -1 nums 则整体逆序
        if(i >= 0){
            // 此时 nums[i+1, n-1] 降序, 查找其中比 大于nums[i] 的 最小的值,可以直接 从后往前 逆向找
            // 因为有序 可以练习一下二分查找
            int j = binarySearch(nums, i+1, n-1, nums[i]);
            // 交换 i j
            swap(nums, i, j);
        }
        // 此时 nums[i+1, n-1] 仍然降序,将其改为升序,只需要反转即可。
        reverse(nums, i+1, n-1);
    }

    // nums[left, right] 逆序,查找其中 > target 的 最大下标
    private int binarySearch(int[] nums, int left, int right, int target){
        while(left <= right){
            int mid = (left + right) >>> 1;
            if(nums[mid] > target){
                left = mid + 1; // 尝试再缩小区间
            }else{
                right = mid - 1;
            }
        }
        return right;
    }

    private void swap(int[] nums, int i, int j){
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }

    private void reverse(int[] nums, int i, int j){
        while(i < j){
            swap(nums, i++, j--);
        }
    }
}

摘自
可视化
以求 12385764 的下一个排列为例:

在这里插入图片描述

首先从后向前查找第一个相邻升序的元素对 (i,j)。这里 i=4,j=5,对应的值为 5,7:
在这里插入图片描述

然后在 [j,end) 从后向前查找第一个大于 A[i] 的值 A[k]。这里 A[i] 是 5,故 A[k] 是 6:

在这里插入图片描述

将 A[i] 与 A[k] 交换。这里交换 5、6:
在这里插入图片描述

这时 [j,end) 必然是降序,逆置 [j,end),使其升序。这里逆置 [7,5,4]:
在这里插入图片描述

因此,12385764 的下一个排列就是 12386457。

最后再可视化地对比一下这两个相邻的排列(橙色是蓝色的下一个排列):
在这里插入图片描述

162.递归迭代二分寻找峰值

算法题:给定一个数组,求峰值(比左边一个大,比右边一个大),暴力O(n)很显然,但是有logn的。我想到了一部分,如果两边都不是峰值,那么峰值一定在中间。这是可以二分的。但是我没敢说,因为太紧张了我无法证明。归根结底是我对二分理解得不够深入,之前遇到的二分都是排好序的数组的二分,这次一个没排序的也可以二分让我大开眼界。本题凉
在这里插入图片描述
普通线性查找:
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;

/***********************************递归二分查找*******************************************/
int helper(int *a,int l,int r){
	if(l == r) return l;
	int mid = (l + r)/2;
	if(a[mid] > a[mid+1]) return helper(a,l,mid);
	return helper(a,mid+1,r);
}

int peak1(int *a, int len){
	return helper(a,0,len-1);
}
/***********************************递归二分查找*******************************************/

/***********************************迭代二分查找*******************************************/
int peak2(int *a, int l, int r){
	while(l < r){
		int mid = (l+r)/2;
		if(a[mid]> a[mid+1]) r = mid;
		else l = mid+1;
	}
	return r;
}
/***********************************迭代二分查找*******************************************/
int main(){
	int a[]= {1};
	int len = sizeof(a)/sizeof(a[0]);
	cout << peak1(a, len)<<endl;
	cout << peak2(a, 0, len-1)<<endl;
}

在这里插入图片描述

40.最小的k个数

快速排序:

参考链接:https://blog.csdn.net/nrsc272420199/article/details/82587933

package com.nrsc.sort;

public class QuickSort {
	public static void main(String[] args) {
		int[] arr = { 49, 38, 65, 97, 23, 22, 76, 1, 5, 8, 2, 0, -1, 22 };
		quickSort(arr, 0, arr.length - 1);
		System.out.println("排序后:");
		for (int i : arr) {
			System.out.println(i);
		}
	}

	private static void quickSort(int[] arr, int low, int high) {

		if (low < high) {
			// 找寻基准数据的正确索引
			int index = getIndex(arr, low, high);

			// 进行迭代对index之前和之后的数组进行相同的操作使整个数组变成有序
			quickSort(arr, 0, index - 1);
			quickSort(arr, index + 1, high);
		}

	}

	private static int getIndex(int[] arr, int low, int high) {
		// 基准数据
		int tmp = arr[low];
		while (low < high) {
			// 当队尾的元素大于等于基准数据时,向前挪动high指针
			while (low < high && arr[high] >= tmp) {
				high--;
			}
			// 如果队尾元素小于tmp了,需要将其赋值给low
			arr[low] = arr[high];
			// 当队首元素小于等于tmp时,向前挪动low指针
			while (low < high && arr[low] <= tmp) {
				low++;
			}
			// 当队首元素大于tmp时,需要将其赋值给high
			arr[high] = arr[low];

		}
		// 跳出循环时low和high相等,此时的low或high就是tmp的正确索引位置
		// 由原理部分可以很清楚的知道low位置的值并不是tmp,所以需要将tmp赋值给arr[low]
		arr[low] = tmp;
		return low; // 返回tmp的正确位置
	}
}


堆排序

https://www.cnblogs.com/chengxiao/p/6129630.html
a.将无序序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;
b.将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;
c.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。

  • 复杂度O(nlogk):topk问题:
    • 现在有n个数,设计算法得到前k大的数。(k<n)
    • 先 取列表前k个元素建立一个小根堆。堆顶就是目前第k大的数(最小的数)。
    • 再 依次向后遍历原列表,对于列表中的元素,如果小于堆顶,则忽略该元素;如果大于堆顶,则将堆顶更换为该元素,并且对堆进行依次调整。
    • 最后 遍历列表所有元素后,倒序弹出堆顶。
  • 升序用大顶堆,降序用小顶堆
#include<iostream>
using namespace std;

void print(int arr[],int len){
	for(int i=0;i<len;i++){
		cout<<arr[i]<<" ";
	}
	cout<<endl;
}

void swap(int arr[],int i,int j){
	int temp=arr[i];
	arr[i] = arr[j];
	arr[j] = temp;
}

void adjustHeap(int arr[],int i,int len){
	int temp=arr[i]; //先取出当前元素i
	for(int k=2*i+1;k<len;k=2*k+1){ //从i结点的左子结点开始,也就是2i+1处开始
		if(k+1<len && arr[k]<arr[k+1]){  //如果左子结点小于右子结点,k指向右子结点
			k++;
		}
		if(arr[k]>temp){ //如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)
			arr[i] = arr[k];
			i=k;
		}else{
			break;
		}
	}	
	arr[i] = temp; //将temp值放到最终的位置
}

void sort(int arr[],int len){
	
	//1.构建大顶堆
	for(int i=len/2-1;i>=0;i--){ //由下至上从最后一个非叶子节点开始创建堆 
		adjustHeap(arr,i,len);
		print(arr,len); 
	}
	
	//2.调整堆结构+交换堆顶元素与末尾元素
	for(int j=len-1;j>=0;j--){
		swap(arr,0,j); //将堆顶元素与末尾元素进行交换
		adjustHeap(arr,0,j); //重新对堆进行调整
		print(arr,len);
	}
}
int main(){
	int arr[10] = {9,8,7,6,5,4,3,2,1,0};
	int len = sizeof(arr)/sizeof(arr[0]);
	sort(arr,len);
	return 0;
}

快速排序(递归)

复杂度为O(nlogn)
逆序最坏复杂度变成了O(n^2)
不稳定,是冒泡排序的跳跃
分治核心,归并排序也是分治核心

package com.nrsc.sort;

public class QuickSort {
	public static void main(String[] args) {
		int[] arr = { 49, 38, 65, 97, 23, 22, 76, 1, 5, 8, 2, 0, -1, 22 };
		quickSort(arr, 0, arr.length - 1);
		System.out.println("排序后:");
		for (int i : arr) {
			System.out.println(i);
		}
	}

	private static void quickSort(int[] arr, int low, int high) {

		if (low < high) {
			// 找寻基准数据的正确索引
			int index = getIndex(arr, low, high);

			// 进行迭代对index之前和之后的数组进行相同的操作使整个数组变成有序
			quickSort(arr, 0, index - 1);
			quickSort(arr, index + 1, high);
		}

	}

	private static int getIndex(int[] arr, int low, int high) {
		// 基准数据
		int tmp = arr[low];
		while (low < high) {
			// 当队尾的元素大于等于基准数据时,向前挪动high指针
			while (low < high && arr[high] >= tmp) {
				high--;
			}
			// 如果队尾元素小于tmp了,需要将其赋值给low
			arr[low] = arr[high];
			// 当队首元素小于等于tmp时,向前挪动low指针
			while (low < high && arr[low] <= tmp) {
				low++;
			}
			// 当队首元素大于tmp时,需要将其赋值给high
			arr[high] = arr[low];

		}
		// 跳出循环时low和high相等,此时的low或high就是tmp的正确索引位置
		// 由原理部分可以很清楚的知道low位置的值并不是tmp,所以需要将tmp赋值给arr[low]
		arr[low] = tmp;
		return low; // 返回tmp的正确位置
	}
}

归并排序

稳定
时间复杂度 O(nlogn)

#include<iostream>
#include<vector>
using namespace std;

void print(int arr[],int len){
	for(int i=0;i<len;i++){
		cout<<arr[i]<<" ";
	}
	cout<<endl;
}

void merge(int arr[],int l,int mid,int r){
	int *help = new int(r-l+1);
	int p=l,q=mid+1;
	int i=0;
	while(p<=mid && q<=r){
		help[i++] = arr[p]<arr[q]?arr[p++]:arr[q++]; 
	}
	while(p<=mid) help[i++] = arr[p++];
	while(q<=r) help[i++] = arr[q++];
	
	for(int j=0;j<r-l+1;j++){
		arr[l+j] = help[j];
	}
}
void MergeSort(int arr[],int l,int r){
	if(l<r){
		int mid = (l+r)/2;
		MergeSort(arr,l,mid);
		MergeSort(arr,mid+1,r);
		merge(arr,l,mid,r);
	}
}

int main(){
	int arr[10] = {9,8,7,6,5,4,3,2,1,0};
	int len = sizeof(arr)/sizeof(arr[0]);
	MergeSort(arr,0,len-1);
	print(arr,len);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值