力扣算法学习计划打卡:第二天

977有序数组的平方,排序算法复习,189轮转数组

https://leetcode-cn.com/study-plan/algorithms/?progress=lv45wk7

有序数组的平方

在这里插入图片描述

排序复习

插入排序

插入排序是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

算法描述
  1. 把待排序的数组分成已排序和未排序两部分,初始的时候把第一个元素认为是已排好序的。
  2. 从第二个元素开始,在已排好序的子数组中寻找到该元素合适的位置并插入该位置。
  3. 重复上述过程直到最后一个元素被插入有序子数组中。

由于只需要找到不大于当前数的位置而并不需要交换,因此,直接插入排序是稳定的排序方法。

算法实现
func sortedSquares(nums []int) []int {
    ans:=make([]int,len(nums))
    ans[0]=nums[0]*nums[0]
    var i,j int
    for i=1;i<len(nums);i++ {
        temp:=nums[i]*nums[i]
        for j=i-1;j>=0;j--{
            if temp < ans[j]{
                ans[j+1]=ans[j]
            }else{
                break
            }
        }
        ans[j+1]=temp
    }
    return ans
}
冒泡排序

冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

算法描述
  1. 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
  3. 针对所有的元素重复以上的步骤,除了最后一个;
  4. 重复步骤1~3,直到排序完成。

在相邻元素相等时,它们并不会交换位置,所以,冒泡排序是稳定排序。

算法实现
func sortedSquares(nums []int) []int {
    ans:=make([]int,len(nums))
    var i,j int
    n:=len(nums)
    for k,val:=range nums{
        ans[k]=val*val
    }
    for i=n-1;i>0;i-- {
        for j=0;j<i;j++{
            if ans[j]>ans[j+1]{
                temp:=ans[j]
                ans[j]=ans[j+1]
                ans[j+1]=temp
            }
        }
    }
    return ans
}

在数据完全有序的时候展现出最优时间复杂度,为O(n)。其他情况下,几乎总是O( n 2 n^2 n2)。因此,算法在数据基本有序的情况下,性能最好。

要使算法在最佳情况下有O(n)复杂度,需要做一些改进,增加一个flag的标志,当前一轮没有进行交换时,说明数组已经有序,没有必要再进行下一轮的循环了,直接退出。

func sortedSquares(nums []int) []int {
    ans:=make([]int,len(nums))
    var i,j int
    n:=len(nums)
    for k,val:=range nums{
        ans[k]=val*val
    }
    for i=n-1;i>0;i-- {
        flag:=false
        for j=0;j<i;j++{
            if ans[j]>ans[j+1]{
                temp:=ans[j]
                ans[j]=ans[j+1]
                ans[j+1]=temp
                flag=true
            }
        }
        if(flag==false){
            break
        }
    }
    return ans
}
选择排序
算法描述

选择排序是一种简单直观的排序算法,它也是一种交换排序算法,和冒泡排序有一定的相似度,可以认为选择排序是冒泡排序的一种改进。

  1. 在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
  2. 从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
  3. 重复第二步,直到所有元素均排序完毕。

用数组实现的选择排序是不稳定的,用链表实现的选择排序是稳定的。

不过,一般提到排序算法时,大家往往会默认是数组实现,所以选择排序是不稳定的。

算法实现
func sortedSquares(nums []int) []int {
    ans:=make([]int,len(nums))
    var i,j,temp int
    n:=len(nums)
    for k,val:=range nums{
        ans[k]=val*val
    }
    for i=0;i < n-1;i++ {
        min:=i
        for j=i+1;j<n;j++{
            if ans[j]<ans[min]{
                min=j
            }
        }
        if min!=i{
            temp=ans[min]
            ans[min]=ans[i]
            ans[i]=temp
        }
    }
    return ans
}
归并排序

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。

算法描述

因为我们在遇到相等的数据的时候必然是按顺序“抄写”到辅助数组上的,所以,归并排序同样是稳定算法。

递归法(Top-down)
  1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
  2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置
  3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
  4. 重复步骤3直到某一指针到达序列尾
  5. 将另一序列剩下的所有元素直接复制到合并序列尾
迭代法(Bottom-up)

原理如下(假设序列共有n个元素):

  1. 将序列每相邻两个数字进行归并操作,形成ceil(n/2)个序列,排序后每个序列包含两/一个元素
  2. 若此时序列数不是1个则将上述序列再次归并,形成ceil(n/4)个序列,每个序列包含四/三个元素
  3. 重复步骤2,直到所有元素排序完毕,即序列数为1
算法实现
func mergesort(result_left,result_right []int) []int{
    result := make([]int, 0)
	i, j := 0, 0
	l, r := len(result_left), len(result_right)
	for i<l && j<r{
		if result_left[i] > result_right[j]{
			result = append(result, result_right[j])
			j++
		}else {
			result = append(result, result_left[i])
			i++
		}
	}
	result = append(result, result_right[j:]...)
	result = append(result, result_left[i:]...)
    return result
}
func merge(ans []int) []int {
    var mid int
    var result,result_left,result_right []int
    
    if len(ans)<2{
        return ans
    }

    mid=len(ans)/2
    result_left=merge(ans[0:mid])
    result_right=merge(ans[mid:])
    result=mergesort(result_left,result_right)
    
    return result
}
func sortedSquares(nums []int) []int {
    ans:=make([]int,len(nums))
    for k,v:=range nums{
        ans[k]=v*v
    }
    ans=merge(ans)
    return ans
}
快排

快速排序是一个知名度极高的排序算法,其对于大数据的优秀排序性能和相同复杂度算法中相对简单的实现使它注定得到比其他算法更多的宠爱。

算法描述
  1. 从数列中挑出一个元素,称为"基准"(pivot)
  2. 重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任何一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
  3. 递归地(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序。
算法实现
func sort(arr []int, low int, high int) {
    if high <= low {
        return
    }
    pivot:= partition(arr, low, high)
    sort(arr, low, pivot-1)
    sort(arr, pivot+1, high)
}
func partition(arr []int, low int, high int) int {
    pivot:=arr[low]
    for low<high {
        for arr[high]>=pivot&& low<high {
            high--
        }
        arr[low]=arr[high]
        for arr[low] <= pivot&&low<high {
            low++
        }
        arr[high]=arr[low]
    }
    arr[low]=pivot
    return low
}
func sortedSquares(nums []int) []int {
    ans:=make([]int,len(nums))
    for k,v:=range nums{
        ans[k]=v*v
    }
    sort(ans,0,len(nums)-1)
    return ans
}
go语言调库

sort库中Ints函数
func Ints(a []int)
Ints函数将a排序为递增顺序

func sortedSquares(nums []int) []int {
    ans:=make([]int,len(nums))
    for i,val:=range nums{
        ans[i]=val*val
    }
    sort.Ints(ans)
    return ans
}

双指针

显然,如果数组中的所有数都是非负数,那么将每个数平方后,数组仍然保持升序;如果数组中的所有数都是负数,那么将每个数平方后,数组会保持降序。

这样一来,如果我们能够找到数组中负数与非负数的分界线,那么就可以用类似「归并排序」的方法了。

func sortedSquares(nums []int) []int {
    n := len(nums)
    //因存在全为正数的情况
    lastNegIndex := -1
    for i := 0; i < n && nums[i] < 0; i++ {
        lastNegIndex = i
    }

    ans := make([]int, 0, n)
    for i, j := lastNegIndex, lastNegIndex+1; i >= 0 || j < n; {
        //全为正数
        if i < 0 {
            ans = append(ans, nums[j]*nums[j])
            j++
        } else if j == n {
        	//全为负数
            ans = append(ans, nums[i]*nums[i])
            i--
        } else if nums[i]*nums[i] < nums[j]*nums[j] {
            ans = append(ans, nums[i]*nums[i])
            i--
        } else {
            ans = append(ans, nums[j]*nums[j])
            j++
        }
    }

    return ans
}

同样地,我们可以使用两个指针分别指向位置 0 和 n-1,每次比较两个指针对应的数,选择较大的那个逆序放入答案并移动指针。这种方法无需处理某一指针移动至边界的情况。

轮转数组

在这里插入图片描述

额外数组

我们可以使用额外的数组来将每个元素放至正确的位置。
用 n 表示数组的长度,我们遍历原数组,将原数组下标为 i 的元素放至新数组下标为 ( i + k )   m o d   n (i+k)\bmod n (i+k)modn的位置,最后将新数组拷贝至原数组即可。

func rotate(nums []int, k int)  {
    newNums := make([]int, len(nums))
    for i, v := range nums {
        newNums[(i+k)%len(nums)] = v
    }
    copy(nums, newNums)
}
数组翻转

该方法基于如下的事实:当我们将数组的元素向右移动 k次后,尾部 k   m o d   n k\ mod\ n k mod n 个元素会移动至数组头部,其余元素向后移动 k   m o d   n k\ mod\ n k mod n个位置。

该方法为数组的翻转:我们可以先将所有元素翻转,这样尾部的 k   m o d   n k\ mod\ n k mod n 个元素就被移至数组头部,然后我们再翻转 [ 0 , k   m o d   n − 1 ] [0, k\bmod n-1] [0,kmodn1]区间的元素和 [ k   m o d   n , n − 1 ] [k\bmod n, n-1] [kmodn,n1] 区间的元素即能得到最后的答案。

func reverse(a []int) {
    for i, n := 0, len(a); i < n/2; i++ {
        a[i], a[n-1-i] = a[n-1-i], a[i]
    }
}

func rotate(nums []int, k int) {
    k %= len(nums)
    reverse(nums)
    reverse(nums[:k])
    reverse(nums[k:])
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值