目录
题目3:实现找出旋转数组指定区间的最小值(1种方法2个扩展)
题目5:数组长为N+2,2个数出现奇数次,找出它们(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)
}