数组--排序、搜索、双指针索引

数组的题目有一些是涉及到合并、搜索和索引。其中有一部分思想其实也包含在排序算法里面。所以这里先由几道数组的题目抛砖引玉,分析排序算法。

  1. 分治合并:归并排序、求pow函数
  2. 搜索:二分法搜索,注意left、right区间的取法
  3. 索引双指针:三数之和

分治合并

合并两个有序的数组

合并两个有序的数组

输入:[4,5,6],[1,2,3]
返回值:[1,2,3,4,5,6]
说明:A数组为[4,5,6],B数组为[1,2,3],后台程序会预先将A扩容为[4,5,6,0,0,0],B还是为[1,2,3],m=3,n=3,传入到函数merge里面,然后请同学完成merge函数,将B的数据合并A里面,最后后台程序输出A数组

这个合并就是归并排序里的归并的思想,使用三指针。当然归并还可以另外建一个新的数组进行归并。

func merge(A []int, m int, B []int, n int) {
	// write code here
	i, j, k := m-1, m+n-1, n-1
	for i >= 0 && k >= 0 {
		if A[i] <= B[k] {
			A[j] = B[k]
			j--
			k--
		} else {
			A[j] = A[i]
			j--
			i--
		}
	}
	for k >= 0 {
		A[j] = B[k]
		k--
		j--
	}
}

归并排序

先分后合的思想。分的部分就是按中点一直递归切分直到左右数组都只剩一个元素后,而合的部分也是递归地合并左右两块。思路和合并两个有序的数组的题目一样。递归结束即合并完成,排好序。
在这里插入图片描述

func MySort(arr []int) []int {
	// write code here
	if len(arr) == 0 {
		return nil
	}
	return mergeSort(arr)
}

func mergeSort(arr []int) []int {
	if len(arr) <= 1 {
		return arr
	}
	mid := len(arr) / 2
	left := mergeSort(arr[:mid])
	right := mergeSort(arr[mid:])
	return merge(left, right)
}

func merge(left, right []int) []int {
	result := make([]int, 0, len(left)+len(right))
	l, r, i := 0, 0, 0
	for {
		if l >= len(left) {
			result = append(result, right[r:]...)
			return result
		}
		if r >= len(right) {
			result = append(result, left[l:]...)
			return result
		}
		if left[l] < right[r] {
			result = append(result, left[l])
			l++
			i++
		} else {
			result = append(result, right[r])
			r++
			i++
		}
	}
}
复杂度与稳定性

归并排序花费的时间递推式:

  • T(n) = 2 ∗ T(n/2) + O(n)
  • T(1) = O(1)
  • T(n) / n = T(n/2)/(n/2) + O(1)

根据递推式计算复杂度: 令 Sn = T(n) / n

  • S(1) = O(1)
  • Sn = S(n/2) + O(1) = S(n/4) + O(2) = S(n/8) + O(3) = S(n/2k) + O(k) = S(1) + O(logn) = O(logn)
  • Tn = n ∗ Sn = O(nlogn)

由于归并排序总是平均分割子列,所以

  • 最好、最坏时间复杂度都 O(nlogn)
  • 归并排序属于稳定排序
  • 归并排序的空间复杂度是 O(n/2 + logn) = O(n) n /2 用于临时存放左侧数组, logn 是因为递归调用

Pow(x, n)

使用分治算法求,否则会超时。需要判断一下正负的情况以及奇偶的情况。

func myPow(x float64, n int) float64 {
	switch {
	case n == 1:
		return x
	case n == 0:
		return 1
	}
	var minus bool
	var result float64
	if n < 0 {
		minus = true
		n = -n
	}
	isOdd := n & 1
	half := myPow(x, n/2)
	half = half * half
	if isOdd == 1 {
		result = x * half
	} else {
		result = half
	}
	if minus {
		return 1 / result
	}
	return result
}

二分搜索

数组有序包含重复元素

数组部分有序不包含重复元素

数组部分有序包含重复元素

部分内容来自知乎 https://zhuanlan.zhihu.com/p/141480088

需要分析中点、终止条件、左右区间移位。

  • 首先中点取:mid = left + ((right -left) >> 1) 是左中的取法。数组长度为奇数而言,是处于正中间的,对于长度为偶数而言,是中间偏左的。
  • 终止条件配合左右区间移位有两种写法:
    1.当target是在左闭右闭区间里,for(left<=right) 时:mid=right-1
    2.当target是在左闭右开区间里,for(left<right) 时:mid=right

查找左边界

从right开始往mid收,有 mid=rightfor(left<right)

  • 数组有序,但包含重复元素
  • 数组部分有序,且不包含重复元素
  • 数组部分有序,且包含重复元素

1.数组有序,但包含重复元素
例如数组 [3,3,3] ,nums[mid] > targetnums[mid] == target都往左收

class Solution {
    public int search(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] < target) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        return nums[left] == target ? left : -1;
    }
}

2.数组部分有序,且不包含重复元素
与第一类的情况一样
3.数组部分有序,且包含重复元素
nums[mid] == target时无法确定是否直接往左收,只能right--

class Solution {
    public int search(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] < target) {
                left = mid + 1;
            } else if (nums[mid] > target) {
                right = mid;
            } else {
                right--;
            }
        }
        return nums[left] == target ? left : -1;
    }
}
在排序数组中查找数字

在排序数组中查找数字 I

示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: 2
示例 2:
输入: nums = [5,7,7,8,8,10], target = 6
输出: 0

分为左边界和右边界两部分查找。 注意查右边界的时候是 mid=left,此时中点需要设定为右中mid = left + ((right -left) >> 1)+1

func search(nums []int, target int) int {
    if len(nums) == 0{
        return 0
    }
    left,right := 0,len(nums)-1
    for left<right{
        mid := left+(right-left)/2
        if nums[mid]<target{
            left = mid+1
        } else{
            right=mid
        }
    }
    if nums[left] != target{
        return 0
    }
    leftIndex := left

    left,right = 0,len(nums)-1
    for left<right{
        mid := left+(right-left)/2+1
        if nums[mid]>target{
            right = mid-1
        } else{
            left=mid
        }
    }
    if nums[right] != target{
        return 0
    }
    return right-leftIndex+1
}

索引双指针

三数之和

复杂度可降低到O(N^2),先对数组进行排序,然后三个指针进行搜索,如下图。i从0到len(nums),然后计算nums[i],nums[l],nums[r]是否和为0 。 是的话l 、r往里面收一个。否则看大小收缩l或r。
这里需要注意两处去重的地方:

  1. i往后移动时,若碰到重复则继续移动
  2. l、r同时往里收缩的时候,若遇到相等的则继续收,且要满足(left<right)
    请添加图片描述
func threeSum(nums []int) [][]int {
    result := make([][]int,0)
    sort.Ints(nums)
    right := len(nums)-1
    var left int
    for i:=0;i<=len(nums)-3;i++{
        left = i+1
        right = len(nums)-1
        if i>0 && nums[i]==nums[i-1]{
            continue
        }
        for left<right{
            if nums[i]+nums[left]+nums[right]==0{
                result=append(result,[]int{nums[i],nums[left],nums[right]})
                for left<right && nums[left]==nums[left+1]{
                    left++
                }
                for left<right && nums[right]==nums[right-1]{
                    right--
                }
                left++
                right--
            } else if  nums[i]+nums[left]+nums[right]<0{
                left++
            } else{
                right--
            }
        }
    }
    return result
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值