Go语言实现多个方法解决10个经典数组问题(一)

目录

题目1:在数组中找唯一重复的元素(5种方法+1个扩展)

题目2:要求找出数组中的最大值和最小值(3种方法)

题目3:实现找出旋转数组指定区间的最小值(1种方法2个扩展)

题目5:数组长为N+2,2个数出现奇数次,找出它们(2种方法)

题目6:快速地求出该数组中第k小的数(3种方法1个扩展)

题目7:找出给定数组中两个元素的最短距离(2种方法)

题目8:找到三原组中三元组最短的距离(2种方法)

题目9:找出有序数组中绝对值最小的数(2种方法)

题目10:找出连续子数组和的最大值和位置(4种方法1个扩展)


题目1:在数组中找唯一重复的元素(5种方法+1个扩展)

package main

import (
	"fmt"
)

/*
数字1~1000放在含有1001个元素的数组中,
其中只有唯一的一个元素值重复,其他数字均只出现一次。
设计一个算法,将重复元素找出来,要求每个数组元素只能访问一次。
如果不使用辅助存储空间,能否设计一个算法实现?
*/

//FindDupByHas
//解法一:
//Hash法:时间复杂度:O(n),空间复杂度:O(n)
//方法功能:在数组中找唯一重复的元素
//输入参数:arr:数组对象
//返回值:重复元素的值,若无重复元素,返回-1
func FindDupByHas(arr []int) int {
	if arr == nil {
		return -1
	}
	data := map[int]bool{}
	for _, v := range arr {
		if _, ok := data[v]; ok {
			return v
		} else {
			data[v] = true
		}

	}

	return -1

}

//FindDupBySum
//解法二:
//求和法:时间复杂度:O(n),空间复杂度:O(1)
//方法功能:在数组中找唯一重复的元素
//输入参数:arr:数组对象
//返回值:重复元素的值,若无重复元素,返回-1
func FindDupBySum(arr []int) int {
	if arr == nil {
		return -1
	}
	sum := 0
	for _, v := range arr {
		sum += v
	}
	return sum - (((len(arr) + 1) * len(arr)) >> 2)

}

//FindDupByXOR
//方法三:
//异或法:时间复杂度:O(n),空间复杂度:O(1)
//方法功能:在数组中找唯一重复的元素
//输入参数:arr:数组对象
//返回值:重复元素的值,若无重复元素,返回-1
//以数组{1, 3, 4, 2, 5, 3}为例。
//(1^3^4^2^5^3)^(1^2^3^4^5)=(1^1)^(2^2)^(3^3^3)^(4^4)^(5^5)=0^0^3^0^0=3。
//异或有交换律,结合律,自反性(A^B^B=A)
func FindDupByXOR(arr []int) int {
	if arr == nil {
		return -1
	}

	result := 0
	for _, v := range arr {
		result ^= v
	}
	for i := 1; i <= len(arr); i++ {
		result ^= i
	}

	return result
}

//FindDupByMap
//方法四:
//映射法:时间复杂度:O(n),空间复杂度:O(1)
//方法功能:在数组中找唯一重复的元素
//输入参数:arr:数组对象
//返回值:重复元素的值,若无重复元素,返回-1
//思想:
/*
以数组array={1, 3, 4, 3, 5, 2}为例。
从下标0开始遍历数组,
(1)array[0]的值为1,说明没有被遍历过,接下来遍历下标为1的元素,
同时标记已遍历过的元素(变为相反数):array={-1, 3, 4, 3, 5, 2}。
(2)array[1]的值为3,说明没被遍历过,接下来遍历下标为3的元素,
同时标记已遍历过的元素:array={-1,-3, 4, 3, 5, 2}。
(3)array[3]的值为3,说明没被遍历过,接下来遍历下标为3的元素,
同时标记已遍历过的元素:array={-1,-3, 4, -3, 5, 2}。
(4)array[3]的值为-3,说明3已经被遍历过了,找到了重复的元素。

*/
func FindDupByMap(arr []int) int {
	if arr == nil {
		return -1
	}
	len := len(arr)
	index := 0

	for true {
		//arr的值是不会比下标大的
		if arr[index] >= len {
			return -1
		}
		//若遇到index的元素<0,就说明该元素已经被访问过了
		if arr[index] < 0 {
			break
		}

		//该元素第一次被访问,取反标记
		arr[index] = arr[index] * -1
		//下一次遍历arr[index]为下标对应的值
		index = arr[index] * -1

		if index >= len {
			fmt.Println("数组中有非法数字")
			return -1
		}

	}
	return index

}

//FindDupByLoop
//方法五:
//环形相遇法:时间复杂度:O(n),空间复杂度:O(1)
//方法功能:在数组中找唯一重复的元素
//输入参数:arr:数组对象
//返回值:重复元素的值,若无重复元素,返回-1
//思想:
/*
该方法就是采用类似于单链表是否存在环的方法进行问题求解。
“判断单链表是否存在环”是一个非常经典的问题,同时单链表可以采用数组实现,
此时每个元素值作为next指针指向下一个元素。
本题可以转化为“已知一个单链表中存在环,找出环的入口点”这种想法。
该题的关键在于,数组array的大小是n,而元素的范围是[1,n-1],
所以,array[0]不会指向自己,进而不会陷入错误的自循环。
如果元素的范围中包含0,则该题不可直接采用该方法。
*/

func FindDupByLoop(arr []int) int {
	if arr == nil {
		return -1
	}
	slow := 0
	fast := 0

	//找到相遇点
	for ok := true; ok; ok = slow != fast {
		fast = arr[arr[fast]] //fast一次走两步
		slow = arr[fast]      //slow一次走一步
	}
	fast = 0
	//找到入口点
	for ok := true; ok; ok = slow != fast {
		slow = arr[slow]
		fast = arr[fast]
	}
	return fast

}

//FindDup
//扩展
//方法功能:对于一个给定的自然数N,有一个N+M个元素的数组,
//其中存放了小于等于N的所有自然数,求重复出现的自然数序列{X}。
//输入参数:arr:数组对象,num:会出现的重复的元素的个数。
//返回值:重复的元素组成的切片。
func FindDup(arr []int, num int) []int {
	res := []int{}
	if arr == nil {
		return []int{-1}
	}
	len := len(arr)
	index := arr[0]
	for true {
		if arr[index] < 0 {
			num--
			arr[index] = len - num
			res = append(res, index)
		}

		if num == 0 {
			return res
		}

		arr[index] *= -1
		index = arr[index] * -1

	}
	return res

}

func main() {
	arr := []int{1, 3, 4, 2, 5, 3}
	fmt.Println("Hash法")
	fmt.Println(FindDupByHas(arr))

	fmt.Println("求和法")
	fmt.Println(FindDupByHas(arr))

	fmt.Println("异或法")
	fmt.Println(FindDupByHas(arr))

	fmt.Println("映射法")
	fmt.Println(FindDupByHas(arr))

	fmt.Println("环形相遇法")
	fmt.Println(FindDupByHas(arr))

	arr1 := []int{1, 2, 3, 3, 3, 4, 5, 5, 5, 5, 6, 7, 6}
	fmt.Println("扩展")
	fmt.Println(FindDup(arr1, 6))

}

题目2:要求找出数组中的最大值和最小值(3种方法)

package main

import "fmt"

/*
给定数组a1, a2, a3, … an,要求找出数组中的最大值和最小值。
假设数组中的值两两各不相同。
*/

//GetMaxAndMinByBrute
//方法一:蛮力法
//方法分析:时间复杂度:O(n),空间复杂度:O(1)
//方法功能:要求找出数组中的最大值和最小值。
//输入参数:arr:对象数组
//返回值:对象数组中的最大值和最小值
func GetMaxAndMinByBrute(arr []int) (max, min int) {
	if arr == nil {
		return 0, 0
	}

	Max, Min := arr[0], arr[0]
	for _, v := range arr {
		if v < Min {
			Min = v
		}
		if v > Max {
			Max = v
		}
	}
	return Max, Min
}

//GetMaxAndMinByDvide
//方法二:分治法
//方法分析:时间复杂度:O(n),空间复杂度:O(1)
//方法功能:要求找出数组中的最大值和最小值。
//输入参数:arr:对象数组
//返回值:对象数组中的最大值和最小值
func GetMaxAndMinByDvide(arr []int) (max, min int) {
	if arr == nil {
		return 0, 0
	}
	len := len(arr)
	Max := arr[0]
	Min := arr[0]
	//两两分组,每组把较小的放在左边
	for i := 0; i < len-1; i += 2 {
		if arr[i] > arr[i+1] {
			arr[i], arr[i+1] = arr[i+1], arr[i]
		}
	}
	//在各组的左半部分寻找最小值
	for i := 0; i < len; i += 2 {
		if arr[i] < Min {
			Min = arr[i]
		}
	}

	//在各组的右半部分寻找最大值
	for i := 1; i < len; i += 2 {
		if arr[i] > Max {
			Max = arr[i]
		}
	}

	//如果数组中的元素是奇数个,最后一个元素为一组,需要特殊处理
	if len%2 == 1 {
		if Max < arr[len-1] {
			Max = arr[len-1]
		}
		if Min > arr[len-1] {
			Min = arr[len-1]
		}
	}

	return Max, Min

}

// GetMaxAndMinByRe
//方法三:递归法
//方法分析:时间复杂度:O(n),空间复杂度:O(1)
//方法功能:要求找出数组中的最大值和最小值。
//输入参数:arr:对象数组,l:所求最大最小值的起始位置,h:所求最大值最小值的终点位置
//返回值:对象数组中的最大值和最小值
func GetMaxAndMinByRe(arr []int, l, h int) (max, min int) {
	if arr == nil {
		return 0, 0
	}

	//求中点
	m := (l + h) >> 1

	//若l,h相遇那就返回其相遇点的值为结果。
	if l == h {
		max = arr[l]
		min = arr[l]
		return
	}
	//l与h相邻
	if l+1 == h {
		if arr[l] > arr[h] {
			max = arr[l]
			min = arr[h]
		} else {
			max = arr[h]
			min = arr[l]
		}
		return
	}

	//递归计算左半部分
	lmax, lmin := GetMaxAndMinByRe(arr, l, m)
	//递归计算右半部分
	rmax, rmin := GetMaxAndMinByRe(arr, m+1, h)

	if lmax > rmax {
		max = lmax
	} else {
		max = rmax
	}

	if lmin < rmin {
		min = lmin
	} else {
		min = rmin
	}

	return

}

func main() {

	arr1 := []int{7, 3, 19, 40, 4, 7, 1}
	fmt.Println("方法一:蛮力法")
	max1, min1 := GetMaxAndMinByBrute(arr1)
	fmt.Println("max=", max1)
	fmt.Println("min=", min1)

	arr2 := []int{7, 40, 19, 3, 4, 7, 1}
	fmt.Println("方法二:分治法")
	max2, min2 := GetMaxAndMinByDvide(arr2)
	fmt.Println("max=", max2)
	fmt.Println("min=", min2)

	fmt.Println("方法三:递归法")
	max3, min3 := GetMaxAndMinByRe(arr1, 0, len(arr1)-1)
	fmt.Println("max=", max3)
	fmt.Println("min=", min3)

}

题目3:实现找出旋转数组指定区间的最小值(1种方法2个扩展)

package main

import "fmt"

/*
把一个有序数组最开始的若干个元素搬到数组的末尾,称为数组的旋转。
输入一个排好序的数组的一个旋转,输出旋转数组的最小元素。
例如数组{3, 4, 5, 1,2}为数组{1, 2, 3, 4, 5}的一个旋转,该数组的最小值为1。
*/

/*
分析:
取low和high,采用二分查找的思想,最终找到正确答案:
mid=(low+high)/2
(1)若arr[mid-1]>arr[mid],则arr[mid]为最小值。
(2)若arr[mid]>arr[mid+1],则arr[mid+1]为最小值。
(3)若arr[mid]<arr[low],则最小值在左半部分。
(4)若arr[mid]>arr[high],则最小值在右半部分。
(5)若arr[mid]=arr[low]=arr[high],则分别求左半部分的最小值lmid,右半部分最小值rmid,比较得出结果。
*/

//方法功能:实现找出旋转数组指定区间的最小值。
//输入值:arr:对象数组,low:前半部分的开始,high:后半部分的结束
//返回值:最小值
func getMinPara(arr []int, low, high int) int {
	if high < low { //若该数组是有序的,那第一个元素就是最小值
		return arr[0]
	}
	if low == high { //若只有一个元素,那当然该数为最小值
		return arr[low]
	}
	mid := low + ((low + high) >> 1)
	if arr[mid-1] > arr[mid] { //若arr[mid-1]>arr[mid],则arr[mid]为最小值。
		return arr[mid]
	}

	if arr[mid] > arr[mid+1] { //若arr[mid]>arr[mid+1],则arr[mid+1]为最小值。
		return arr[mid+1]
	}
	if arr[mid] < arr[low] { //若arr[mid]<arr[low],则最小值在左半部分。
		return getMinPara(arr, low, mid-1)
	}
	if arr[mid] > arr[high] { //若arr[mid]>arr[high],则最小值在右半部分。
		return getMinPara(arr, mid+1, high)
	} else {
		//若arr[mid]=arr[low]=arr[high],
		//则分别求左半部分的最小值lmid,右半部分最小值rmid,比较得出结果。
		lmin := getMinPara(arr, low, mid-1)
		rmin := getMinPara(arr, mid+1, high)
		if lmin < rmin {
			return lmin
		} else {
			return rmin
		}
	}

}

//方法功能:实现找出旋转数组的最小值。
//输入值:arr:对象数组
//返回值:最小值
func getMin(arr []int) int {

	if arr == nil {
		return -1
	} else {
		return getMinPara(arr, 0, len(arr)-1)
	}

}

//扩展一:如何实现旋转数组功能?
/*
分析:先将左右两个子数列逆序,然后拼接,最后逆序。
*/

//方法功能:逆序数组指定区间[low,high]
//输入参数:arr:对象数组,low:逆序开始位置,high:逆序结束位置
//返回值:无
func swap(arr []int, low, high int) {
	for ; low < high; low, high = low+1, high-1 {
		arr[low], arr[high] = arr[high], arr[low]
	}
}

//方法功能:实现旋转数组
//输入参数:arr:对象数组,div:旋转的数组
//返回值:无
func rotatearr(arr []int, div int) {
	if arr == nil || div <= 0 || div >= len(arr)-1 {
		return
	}
	//左边子序列逆序
	swap(arr, 0, div)
	//右边子序列逆序
	swap(arr, div+1, len(arr)-1)
	//整个序列逆序
	swap(arr, 0, len(arr)-1)
}

//扩展二:
/*
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回-1。
你可以假设数组中不存在重复的元素。
你的算法时间复杂度必须是O(logn) 级别。
*/
/*
@title    search
@description   寻找一个旋转后的升序数组中的目标元素的下标,若没有,返回-1
@auth      voyager       2019/6/18   16:43
@param     nums  []int   "输入的旋转后的数组"   target int "需要查找的元素"
@return    int      "数组下标"

@思路:
对于有序数组的元素查找,可以首先考虑二分查找方法:
二分查找:low,mid,high
1.若mid在前面的较小的有序子数组中:nums[low]<=nums[mid]<nums[high]
2.若mid在后面较大的有序子数组中:nums[low]>nums[mid],nums[high]>=nums[mid]

也就是说:
当nums[mid]==target,返回mid
nums[mid]>nums[low]的时候,mid在前半区:
    若nums[mid]<target,low=mid+1
    若nums[mid]>target,这时候target可能落在前半区中low和mid之间:
        若target>nums[low],high=mid-11

nums[mid]<nums[low]的时候,mid在后半区:
    若nums[mid]<target,这时候可能落在mid和high之前,:
        若target<nums[high],low=mid+1
    若nums[mid]>target,low=mid
*/
func search(nums []int, target int) int {

	if len(nums) == 0 {
		return -1
	}

	if len(nums) == 1 {
		if nums[0] == target {
			return 0
		}
		return -1
	}

	low := 0
	high := len(nums) - 1

	for low <= high {
		mid := low + (high-low)>>1
		if nums[mid] == target { //当nums[mid]==target,返回mid
			return mid
		} else if nums[mid] > nums[low] { //nums[mid]>nums[low]的时候,mid在前半区:
			if target < nums[mid] && target >= nums[low] { //若nums[mid]>target,这时候target可能落在前半区中low和mid之间:
				high = mid - 1
			} else { //若nums[mid]<target,low=mid+1
				low = mid + 1
			}
		} else if nums[mid] < nums[low] { //nums[mid]<nums[low]的时候,mid在后半区:
			if nums[mid] < target && target <= nums[high] { //若nums[mid]<target,这时候可能落在mid和high之前:
				low = mid + 1
			} else { //若nums[mid]>target,low=mid+1
				high = mid - 1
			}
		} else { //nums[mid]=nums[low],那mid必定与low和mid其中一个相等
			if mid == low {
				low++
			}
			if mid == high {
				high--
			}
		}

	}

	return -1

}

func main() {
	fmt.Println("找出最小值")
	arr := []int{5, 6, 1, 2, 3, 4}
	fmt.Println(getMin(arr))
	arr = []int{1, 1, 0, 1}
	fmt.Println(getMin(arr))

	fmt.Println("数组旋转")
	arr1 := []int{1, 2, 3, 4, 5}
	rotatearr(arr1, 2)
	fmt.Println(arr1)
}

题目4:寻找数组序列中缺失的整数(2种方法)

package main

import "fmt"

/*
给定一个由n-1个整数组成的未排序的数组序列,
其元素都是1~n中的不同的整数。请写出一个寻找数组序列中缺失整数的线性时间算法。
*/

//方法一:累加求和
//方法分析:这种方法的时间复杂度为O(n)
//方法功能:寻找数组序列中缺失的整数
//输入参数:arr:对象数组
//返回值:返回缺失的值是多少

func getNum(arr []int) int {
	if arr == nil { //若对象数组为空,则返回-1
		return -1
	}
	sum := 0
	for _, v := range arr { //求对象数组的和
		sum += v
	}
	n_sum := ((1 + len(arr) + 1) * (len(arr) + 1)) >> 1 //求1-n的和
	return n_sum - sum

}

//方法二:异或法
//方法分析:这种方法的时间复杂度为O(n)
//方法功能:寻找数组序列中缺失的整数
//输入参数:arr:对象数组
//返回值:返回缺失的值是多少
func getNumByXor(arr []int) int {
	if arr == nil {
		return -1
	}

	res := 0 //初始值为0,对结果不影响,因为0与任何数异或都等于该数本身。
	for _, v := range arr {
		res ^= v
	}
	for i := 1; i <= len(arr)+1; i++ {
		res ^= i
	}

	return res

}

func main() {

	arr := []int{1, 4, 3, 2, 7, 5}
	fmt.Println("方法一:累加求和")
	fmt.Println(getNum(arr))

	fmt.Println("方法二,异或法")
	fmt.Println(getNumByXor(arr))
}

题目5:数组长为N+2,2个数出现奇数次,找出它们(2种方法)

package main

import "fmt"

/*
数组中有N+2个数,其中,N个数出现了偶数次,
2个数出现了奇数次(这两个数不相等),请用O(1)的空间复杂度,找出这两个数。
注意:不需要知道具体位置,只需要找出这两个数。
*/

//方法一:hash法
//方法功能:数组中有N+2个数,2个数出现了奇数次(这两个数不相等,其它出现偶数次),找出这两个数。
//输入参数:arr:对象数组
//返回值:出现次数为奇数的元素祖成的切片
func getNumByHash(arr []int) []int {
	if arr == nil {
		return []int{-1, -1}
	}
	res := []int{}          //存放输出结果
	data := map[int]int{}   //用来记录每个元素出现的次数为奇数还是偶数
	for _, v := range arr { //遍历数组
		if _, ok := data[v]; ok { //若data中出现过,就取反(用异或实现)
			data[v] ^= 1
		} else { //若data中没出现过,就赋值为1
			data[v] = 1
		}
	}

	for _, v := range arr { //找到key值为1的元素,添加到输出结果中
		if data[v] == 1 {
			res = append(res, v)
		}
	}

	return res

}

//方法二:异或法
//方法功能:数组中有N+2个数,2个数出现了奇数次(这两个数不相等,其它出现偶数次),找出这两个数。
//输入参数:arr:对象数组
//返回值:出现次数为奇数的元素祖成的切片

/*
假设这两个出现奇数次的数分别为a与b,根据异或运算的性质,
将二者异或运算的结果记为c,由于a与b不相等,
所以,c的值自然也不会为0,此时只需知道c对应的二进制数中某一个位为1的位数N,
例如,十进制数44可以由二进制0010 1100表示,此时可取N=2或者3,或者5,
然后将c与数组中第N位为1的数进行异或,异或结果就是a,b中一个,
然后用c异或其中一个数,就可以求出另外一个数了。
通过上述方法为什么就能得到问题的解呢?其实很简单,
因为c中第N位为1表示a或b中有一个数的第N位也为1,
假设该数为a,那么,当将c与数组中第N位为1的数x进行异或时,
也就是将x与a外加上其他第N位为1的出现过偶数次的数进行异或,化简即为x与a异或,结果即为a。
c异或a,即将为b
*/

func getNumXor(arr []int) []int {
	if arr == nil {
		return []int{-1, -1}
	}
	res := []int{} //记录结果值
	position := uint(0)
	result := 0
	for _, v := range arr { //计算目标值的异或结果,也就是c
		result ^= v
	}

	tmpresult := result

	//找出异或结果中一个位值为1的位置下标(如1100,位置为1的下标为2或者3)
	for i := result; i&1 == 0; i = i >> 1 {
		position++
	}

	//异或的结果c与所有第position位为1的数异或,结果一定为两个结果中的一个
	for _, v := range arr {
		if (v>>position)&1 == 1 {
			result ^= v
		}
	}

	res = append(res, result)           //result即为其中一个值
	res = append(res, tmpresult^result) //用c与result异或就能得出另一个值

	return res
}

func main() {

	arr := []int{3, 5, 6, 6, 5, 7, 2, 2}
	fmt.Println("方法一:Hash法")
	fmt.Println(getNumByHash(arr))

	fmt.Println("方法二:异或法")
	fmt.Println(getNumXor(arr))

}

题目6:快速地求出该数组中第k小的数(3种方法1个扩展)

package main

import (
	"fmt"
	"math"
)

/*
给定一个整数数组,如何快速地求出该数组中第k小的数。
假如数组为{4,0,1,0,2,3},那么第3小的元素是1。
*/

//方法一:排序法
//方法功能:给定一个整数数组,如何快速地求出该数组中第k小的数
//输入参数:arr:数组对象,begin:排序开始下标,end:排序终止下标,k:第k小的数
//返回值:第k小的数
//排序算法实现(https://www.topgoer.cn/docs/goalgorithm/goalgorithm-1cm6b09u217l1)
func findSmallKByquick(arr []int, begin, end, k int) int {
	quickSort(arr, begin, end)
	return arr[k-1]
}

//方法功能:快速排序
//输入参数:arr:数组对象,begin:排序开始下标,end:排序终止下标
func quickSort(arr []int, begin, end int) {
	if begin < end {
		i := begin + 1
		j := end
		for i < j {
			if arr[i] > arr[begin] {
				arr[i], arr[j] = arr[j], arr[i]
				j--
			} else {
				i++
			}
		}
		if arr[i] > arr[begin] {
			i--
		}
		arr[begin], arr[i] = arr[i], arr[begin]
		quickSort(arr, begin, i-1)
		quickSort(arr, i+1, end)
	}

}

//方法二:部分排序法
//方法功能:给定一个整数数组,如何快速地求出该数组中第k小的数
//输入参数:arr:数组对象,begin:排序开始下标,end:排序终止下标,k:第k小的数
//返回值:第k小的数
func findsmallBySeletSort(arr []int, begin, end, k int) int {
	if begin > end {
		return -1
	}
	for i := 0; i < k; i++ {
		minx := arr[i] //最小数
		minindex := i  //最小数的下标
		for j := i + 1; j < len(arr)-1; j++ {
			if arr[j] < minx {
				minx = arr[j]
				minindex = j
			}
		}
		if arr[minindex] < arr[i] {
			arr[i], arr[minindex] = arr[minindex], arr[i]
		}

	}
	return arr[k-1]
}

//方法三:类快速排序法
//方法功能:给定一个整数数组,如何快速地求出该数组中第k小的数
//输入参数:arr:数组对象,begin:排序开始下标,end:排序终止下标,k:第k小的数
//返回值:第k小的数
func findSmallKByQuick1(arr []int, begin, end, k int) int {
	if begin > end {
		return -1
	}
	i := begin
	j := end
	for i < j {
		if arr[i] > arr[begin] {
			arr[i], arr[j] = arr[j], arr[i]
			j--
		} else {
			i++
		}
	}
	if arr[i] > arr[begin] {
		i--
	}
	arr[begin], arr[i] = arr[i], arr[begin]
	if (i - begin) == k-1 {
		return arr[i]
	} else if (i - begin) > k-1 {
		//fmt.Println("前半段:+", "i:", i)
		return findSmallKByQuick1(arr, begin, i-1, k)
	} else {
		//fmt.Println("后半段:----", "i:", i, "-----k-(i-begin)-1:", k-(i-begin)-1)
		return findSmallKByQuick1(arr, i+1, end, k-(i-begin)-1)
	}

}

//扩展:在O(n)时间复杂度内查找数组中前三名。
//方法功能:在O(n)时间复杂度内查找数组中前三名。
//输入参数:arr:数组对象
//返回值:前3大的数的切片
func findTop3(arr []int) []int {
	if arr == nil || len(arr) < 3 {
		return []int{}
	}
	r1 := math.MinInt64
	r2 := math.MinInt64
	r3 := math.MinInt64
	for _, v := range arr {
		if v > r1 {
			r3 = r2
			r2 = r1
			r1 = v
		} else if v < r1 && v > r2 {
			r3 = r2
			r2 = v
		} else if v > r3 && v < r2 {
			r3 = v
		}
	}

	return []int{r1, r2, r3}
}
func main() {

	k := 3
	arr := []int{4, 0, 1, 0, 2, 3}

	fmt.Println("方法一:排序法(快速排序实现)")
	fmt.Println("第", k, "小的值为", findSmallKByquick(arr, 0, len(arr)-1, k))

	fmt.Println("方法二:部分排序法(选择排序实现)")
	fmt.Println("第", k, "小的值为", findsmallBySeletSort(arr, 0, len(arr)-1, k))

	fmt.Println("方法三:类快速排序法")
	fmt.Println("第", k, "小的值为", findSmallKByQuick1(arr, 0, len(arr)-1, k))

	arr1 := []int{4, 7, 1, 2, 3, 5, 3, 6, 3, 2}
	fmt.Println("扩展:在O(n)时间复杂度内查找数组中前三名。")
	fmt.Println("arr中前三大的值为", findTop3(arr1))
}

题目7:找出给定数组中两个元素的最短距离(2种方法)

package main

import (
	"fmt"
	"math"
)

/*
给定一个数组,数组中含有重复元素,给定两个数字num1和num2,
求这两个数字在数组中出现的位置的最小距离。
*/

//方法一:蛮力法
//方法性能:时间复杂度O(n^2)
//方法功能:找出给定数组中两个元素的最短距离
//输入参数:arr:对象数组,num1:第一个元素,num2:第二个对象
//返回值:num1和num2的最短距离
func getMinDistance(arr []int, num1, num2 int) int {
	if arr == nil {
		return -1
	}

	mindistance := math.MaxInt64
	distance := 0
	for i, a := range arr {
		if a == num1 {
			for j, b := range arr {
				if b == num2 {
					distance = int(math.Abs(float64(i - j)))
					if distance < mindistance {
						mindistance = distance
					}
				}
			}
		}
	}
	return mindistance
}

//方法一:动态规划
//方法性能:时间复杂度O(n)
//方法功能:找出给定数组中两个元素的最短距离
//输入参数:arr:对象数组,num1:第一个元素,num2:第二个对象
//返回值:num1和num2的最短距离

func getMinDistanceByDyplan(arr []int, num1, num2 int) int {
	if arr == nil {
		return -1
	}
	lastpos1 := -1
	lastpos2 := -1

	mindistance := math.MaxInt64
	distance := 0
	for i, v := range arr {
		if v == num1 {
			lastpos1 = i
			if lastpos2 >= 0 {
				distance = int(math.Abs(float64(i - lastpos2)))
				if distance < mindistance {
					mindistance = distance
				}
			}
		}

		if v == num2 {
			lastpos2 = i
			if lastpos1 >= 0 {
				distance = int(math.Abs(float64(i - lastpos1)))
				if distance < mindistance {
					mindistance = distance
				}
			}
		}
	}
	return mindistance
}

func main() {
	arr := []int{4, 5, 6, 4, 7, 4, 6, 4, 7, 8, 5, 6, 4, 3, 10, 8}
	num1 := 4
	num2 := 8

	fmt.Println("方法一:蛮力法")
	fmt.Println(getMinDistance(arr, num1, num2))

	fmt.Println("方法二:动态规划法")
	fmt.Println(getMinDistanceByDyplan(arr, num1, num2))

}

题目8:找到三原组中三元组最短的距离(2种方法)

package main

import (
	"fmt"
	"math"
)

/*
已知三个升序整数数组a[l],b[m]和c[n],请在三个数组中各找一个元素,使得组成的三元组距离最小。
三元组距离的定义是:假设a[i]、b[j]和c[k]是一个三元组,
那么距离为:Distance=max(|a[i]-b[j]|,|a[i]-c[k]|,|b[j]-c[k]|),
请设计一个求最小三元组距离的最优算法。
*/

//方法一:蛮力法
//方法性能:O(l*m*n)
//方法功能:找到三个升序数组元素组成的三原组中三元组最短的距离
//输入参数:arr1,arr2,arr3:三个输组对象
//返回值:三原组的最短距离
func getMinDistanceByViolence(arr1, arr2, arr3 []int) int {
	mindistance := math.MaxInt64
	distance := 0
	for _, a := range arr1 {
		for _, b := range arr2 {
			for _, c := range arr3 {
				distance = int(math.Max(math.Max(math.Abs(float64(a-b)), math.Abs(float64(a-c))), math.Abs(float64(b-c))))
				if distance < mindistance {
					mindistance = distance
				}
			}
		}
	}

	return mindistance
}

//方法二:最小距离法
//方法性能:O(l+m+n)
//方法功能:找到三个升序数组元素组成的三原组中三元组最短的距离
//输入参数:arr1,arr2,arr3:三个输组对象
//返回值:三原组的最短距离

/*
假设当前遍历到这三个数组中的元素分别为ai,bi,ci,并且ai<=bi<=ci,
此时它们的距离肯定为Di=ci-ai,那么接下来可以分如下三种情况讨论:
(1)如果接下来求ai,bi,ci+1的距离,由于ci+1>=ci,
此时它们的距离必定为Di+1=ci+1-ai,显然Di+1>=Di,因此,Di+1不可能为最小距离。
(2)如果接下来求ai,bi+1,ci的距离,由于bi+1>=bi,如果bi+1<=ci,
此时它们的距离仍然为Di+1=ci-ai;如果bi+1>ci,那么此时它们的距离为Di+1=bi+1-ai,
显然Di+1>=Di,因此,Di+1不可能为最小距离。
(3)如果接下来求ai+1,bi,ci的距离,
此时它们的距离Di+1=max(ci-ai+1, ci-bi),显然Di+1<Di,因此,Di+1有可能是最小距离。

综上所述,在求最小距离的时候只需要考虑第三种情况即可。
*/

func getMinDistanceByDyc(arr1, arr2, arr3 []int) int {
	lena := len(arr1) //arr1的长度
	lenb := len(arr2) //arr2的长度
	lenc := len(arr3) //arr3的长度
	i := 0            //arr1的下标
	j := 0            //arr2的下标
	h := 0            //arr3的下标
	distance := 0
	mindistance := math.MaxInt64
	for true {

		distance = int(math.Max(math.Max(math.Abs(float64(arr1[i]-arr2[j])), math.Abs(float64(arr1[i]-arr3[h]))), math.Abs(float64(arr2[j]-arr3[h]))))
		if distance < mindistance {
			mindistance = distance
		}
		min := int(math.Min(math.Min(float64(arr1[i]), float64(arr2[j])), float64(arr3[h])))
		if min == arr1[i] {
			i++
			if i >= lena {
				break
			}
		}

		if min == arr2[j] {
			j++
			if j >= lenb {
				break
			}
		}

		if min == arr3[h] {
			h++
			if i >= lenc {
				break
			}
		}

	}
	return mindistance
}

func main() {
	a := []int{3, 4, 5, 7, 15}
	b := []int{10, 12, 14, 16, 17}
	c := []int{20, 21, 23, 24, 37, 30}

	fmt.Println("方法一:蛮力法")
	fmt.Println("最小距离为", getMinDistanceByViolence(a, b, c))

	fmt.Println("方法二:最小距离法")
	fmt.Println("最小距离为", getMinDistanceByDyc(a, b, c))

}

题目9:找出有序数组中绝对值最小的数(2种方法)

package main

import (
	"fmt"
	"math"
)

/*
有一个升序排列的数组,数组中可能有正数、负数或0,求数组中元素的绝对值最小的数。
例如,数组{-10,-5,-2, 7, 15, 50},该数组中绝对值最小的数是-2。
*/

//方法一:蛮力法
//方法性能:时间复杂度O(n),空间复杂度O(1)
//方法功能:找出有序数组中绝对值最小的数
//输入参数:arr:数组对象
//返回值:绝对值最小的数

func findMinAbsByViolence(arr []int) int {
	if arr == nil {
		return -1
	}
	min := arr[0]
	for _, v := range arr {
		if int(math.Abs(float64(v))) < int(math.Abs(float64(min))) {
			min = v
		}
	}
	return min
}

//方法二:数学性质法
//方法性能:时间复杂度O(nlogn),空间复杂度O(1)
//方法功能:找出有序数组中绝对值最小的数
//输入参数:arr:数组对象
//返回值:绝对值最小的数
func findMinAbsByMath(arr []int) int {
	if arr == nil {
		return -1
	}
	if arr[0] >= 0 {
		return arr[0]
	}
	if arr[len(arr)-1] <= 0 {
		return arr[len(arr)-1]
	}
	min := 0
	if arr[0] < 0 {
		low := 0
		high := len(arr) - 1

		for low < high {
			mid := low + ((high - low) >> 1)
			if arr[mid] == 0 {
				min = arr[mid]
				break
			}
			if arr[mid+1] == 0 {
				min = arr[mid+1]
				break
			}
			if arr[mid-1] == 0 {
				min = arr[mid-1]
				break
			}
			if arr[mid] < 0 && arr[mid+1] > 0 {
				if int(math.Abs(float64(arr[mid]))) < int(math.Abs(float64(arr[mid+1]))) {
					min = arr[mid]
					break
				} else {
					min = arr[mid+1]
					break
				}
			}
			if arr[mid] < 0 && arr[mid+1] < 0 {
				low = mid + 1
			}

			if arr[mid] > 0 && arr[mid-1] < 0 {
				if int(math.Abs(float64(arr[mid]))) < int(math.Abs(float64(arr[mid-1]))) {
					min = arr[mid]
					break
				} else {
					min = arr[mid-1]
					break
				}
			}
			if arr[mid] > 0 && arr[mid-1] > 0 {
				high = mid - 1
			}

		}

	}
	return min
}

func main() {
	arr := []int{-10, -5, -2, 7, 15, 50}

	fmt.Println("顺序比较法")
	fmt.Println("绝对值最小的数为:", findMinAbsByViolence(arr))

	fmt.Println("数学性质法")
	fmt.Println("绝对值最小的数为:", findMinAbsByMath(arr))
}

题目10:找出连续子数组和的最大值和位置(4种方法1个扩展)

package main

import (
	"fmt"
	"math"
)

/*
一个有n个元素的数组,这n个元素既可以是正数也可以是负数,
数组中连续的一个或多个元素可以组成一个连续的子数组,
一个数组可能有多个这种连续的子数组,求子数组和的最大值。
例如:对于数组{1,-2, 4, 8,-4, 7,-1,-5}而言,
其最大和的子数组为{4, 8,-4, 7},最大值为15。
*/

//方法一:蛮力法
//方法性能:时间复杂度:O(n^3)空间复杂度:O(1)
//方法功能:找出数组中连续子数组和的最大值
//输入参数:arr:对象数组
//返回值:连续子数组和的最大值
func findMaxSubArraySumByViolence(arr []int) int {
	if arr == nil {
		return -1
	}
	maxSubArraySum := math.MinInt64
	len := len(arr)
	for i := 0; i < len; i++ {
		for j := i; j < len; j++ {
			thisSum := 0
			for k := i; k < j; k++ {
				thisSum += arr[k]
			}
			if thisSum > maxSubArraySum {
				maxSubArraySum = thisSum
			}
		}
	}
	return maxSubArraySum
}

//方法二:改进蛮力法
//方法性能:时间复杂度:O(n^2)空间复杂度:O(1)
//方法功能:找出数组中连续子数组和的最大值
//输入参数:arr:对象数组
//返回值:连续子数组和的最大值
func findMaxSubArraySumByViolencepro(arr []int) int {
	if arr == nil {
		return -1
	}
	len := len(arr)
	maxSubArray := math.MinInt64
	for i := 0; i < len; i++ {
		thisSum := 0
		for k := i; k < len; k++ {
			thisSum += arr[k]
			if thisSum > maxSubArray {
				maxSubArray = thisSum
			}
		}
	}
	return maxSubArray
}

//方法三:动态规划法
//方法性能:时间复杂度:O(n)空间复杂度:O(n)
//方法功能:找出数组中连续子数组和的最大值
//输入参数:arr:对象数组
//返回值:连续子数组和的最大值
/*
首先可以根据数组的最后一个元素arr[n-1]与最大子数组的关系分为以下三种情况讨论:
(1)最大子数组包含arr[n-1],即最大子数组以arr[n-1]结尾。
(2)arr[n-1]单独构成最大子数组。
(3)最大子数组不包含arr[n-1],那么求arr[1…n-1]的最大子数组可以转换为求arr[1…n-2]
的最大子数组。
通过上述分析可以得出如下结论:
假设已经计算出子数组arr[1…i-2]的最大的子数组和All[i-2],
同时也计算出arr[0…i-1]中包含arr[i-1]的最大的子数组和为End[i-1]。
则可以得出如下关系:All[i-1]=max{End[i-1],arr[i-1],All[i-2]}。
*/

func findMaxSubArraySumByDyn(arr []int) int {
	if arr == nil {
		return -1
	}
	n := len(arr)
	End := make([]int, n) //包含本元素的最大子数组和
	All := make([]int, n) //当前元素之前的最大子数组和
	End[0] = arr[0]
	All[0] = arr[0]

	for i := 1; i < n; i++ {
		End[i] = int(math.Max(float64(End[i-1]+arr[i]), float64(arr[i])))
		All[i] = int(math.Max(float64(All[i-1]), float64(End[i])))
	}
	return All[n-1]

}

//方法四:动态规划法改进
//方法性能:时间复杂度:O(n)空间复杂度:O(1)
//方法功能:找出数组中连续子数组和的最大值
//输入参数:arr:对象数组
//返回值:连续子数组和的最大值
func findMaxSubArraySumByDynPro(arr []int) int {
	if arr == nil {
		return -1
	}
	n := len(arr)
	End := arr[0] //包含本元素的最大子数组和
	All := arr[0] //当前元素之前的最大子数组和

	for i := 1; i < n; i++ {
		End = int(math.Max(float64(End+arr[i]), float64(arr[i])))
		All = int(math.Max(float64(All), float64(End)))
	}
	return All

}

//扩展:在知道子数组最大值后,如何才能确定最大子数组的位置?
//方法性能:时间复杂度:O()空间复杂度:O()
//方法功能:找出数组中连续子数组和的最大值和位置
//输入参数:arr:对象数组
//返回值:连续子数组和的最大值maxsum,开始位置beigin,结束位置end
/*
为了得到最大子数组的位置,首先介绍另外一种计算最大子数组和的方法。
在上例的方法三中,通过对公式End[i] = max(End[i-1]+arr[i],arr[i])的分析可以看出,
当End[i-1]<0时,End[i]=array[i],其中End[i]表示包含array[i]的子数组和,
如果某一个值使得End[i-1]<0,那么就从arr[i]重新开始。
可以利用这个性质非常容易地确定最大子数组的位置。
*/

func maxSubArrayEx(arr []int) (maxsum, begin, end int) {
	if arr == nil {
		return
	}
	maxsum = math.MinInt64
	nsum := 0
	nstart := 0
	for i, v := range arr {
		if nsum < 0 {
			nsum = v
			nstart = i
		} else {
			nsum += v
		}
		if nsum > maxsum {
			maxsum = nsum
			begin = nstart
			end = i
		}
	}
	return
}

func main() {
	arr := []int{1, -2, 4, 8, -4, 7, -1, -5}

	fmt.Println("方法一:蛮力法")
	fmt.Println("连续最大和为:", findMaxSubArraySumByViolence(arr))

	fmt.Println("方法二:改进蛮力法")
	fmt.Println("连续最大和为:", findMaxSubArraySumByViolencepro(arr))

	fmt.Println("方法三:动态规划法")
	fmt.Println("连续最大和为:", findMaxSubArraySumByDyn(arr))

	fmt.Println("方法四:动态规划法改进")
	fmt.Println("连续最大和为:", findMaxSubArraySumByDynPro(arr))

	fmt.Println("扩展")
	masum, begin, end := maxSubArrayEx(arr)
	fmt.Println("连续最大和为:", masum)
	fmt.Println("开位置与结束位置为:", begin, "-", end)

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值