数组--分治合并、搜索、索引
数组的题目有一些是涉及到合并、搜索和索引。其中有一部分思想其实也包含在排序算法里面。所以这里先由几道数组的题目抛砖引玉,分析排序算法。
- 分治合并:归并排序、求pow函数
- 搜索:二分法搜索,注意left、right区间的取法
- 索引双指针:三数之和
分治合并
合并两个有序的数组
输入:[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=right
与 for(left<right)
- 数组有序,但包含重复元素
- 数组部分有序,且不包含重复元素
- 数组部分有序,且包含重复元素
1.数组有序,但包含重复元素
例如数组 [3,3,3] ,nums[mid] > target
和nums[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;
}
}
在排序数组中查找数字
示例 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。
这里需要注意两处去重的地方:
- i往后移动时,若碰到重复则继续移动
- 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
}