复杂度与简单排序算法
时间复杂度
时间复杂度是常数操作地指标
数组:偏移量直接寻址搞定
链表:需要一个一个地去找,不能通过偏移量去找
常数操作地表达式,不要低阶项,也不要高阶项地系数,只保留高阶项,就是时间复杂度
复杂度越小越好
当复杂度一致地时候,需要拼常数项或者直接用程序运行走一把
func pro1() {
n := 1000
for i := 0; i < n; i++ {
n = 1 + 1
n = 1 * 1
n = 3 * 3
}
}
func pro2() {
n := 1000
for i := 0; i < n; i++ {
n = 1 | 1
n = 1 & 1
n = 3 ^ 3
}
}
常数操作
跟数据量无关地,固定时间地东西
加减乘除
数组
位运算
非常数操作
跟数据量有关地
底层api
链表
额外空间复杂度
额外有限几个变量就可以完成,复杂度为O(1)
如果需要开辟数组,则为O(n)
选择排序
每次在未排序地数组中找到一个最小的,与当前数交换位置
func selectsort(arr []int) []int {
temp := len(arr)
if temp < 2 || arr == nil { // 写程序前先考虑过滤杂项
return nil
}
for i := 0; i < temp; i++ {
minindex := i
for j := i + 1; j < temp; j++ {
if arr[minindex] > arr[j] {
minindex = j
}
}
arr[minindex], arr[i] = arr[i], arr[minindex]
}
return arr
}
prac1
package main
import "fmt"
func selectsort(arr []int) []int {
temp := len(arr)
if temp < 2 || arr == nil {
return nil
}
//每次在未排序的数组中找到一个最小的与当前值进行交换
// 当前值
// 与剩下值做对比
// 外循环每次取值,内循环每次跟剩下的值做对比
for i := 0; i < temp; i++ {
minIndex := i
for j := i + 1; j < temp; j++ {
if arr[minIndex] > arr[j] {
minIndex = j
}
}
arr[minIndex], arr[i] = arr[i], arr[minIndex]
}
return arr
}
//选择排序:每次在未排序的数组中找到一个最小的与当前值进行交换
func main() {
// arr := []int{2, 5, 6, 3, 1, 0}
arr := []int{5, 2}
fmt.Println(selectsort(arr))
}
时间复杂度:O(n^2)
一个for循环为o(n),嵌套for循环相乘即可
空间复杂度:O(1)
为了实现此算法,额外定义了temp,i,j,minindex,使用的空间有限,所以为O(1)
冒泡排序
每一次当前值跟后一个值做对比,当前值大于后一个值,就交换位置,简称沉底
func bubblesort(arr []int) []int {
temp := len(arr)
if temp < 1 || arr == nil {
return nil
}
for i := 0; i < temp; i++ {
for j := 0; j < temp-i-1; j++ {
if arr[j] > arr[j+1] {
arr[j], arr[j+1] = arr[j+1], arr[j]
}
}
}
return arr
}
practice1
package main
import "fmt"
func bubblesort(arr []int) []int {
temp := len(arr)
if temp < 2 || arr == nil {
return nil
}
//冒泡排序
//每一次将最大的一个值沉底
//拿每次值与剩下的值做对比,如果大了就交换
for i := 0; i < temp; i++ {
for j := 0; j < temp-i-1; j++ {
if arr[j] > arr[j+1] {
arr[j+1], arr[j] = arr[j], arr[j+1]
}
}
}
return arr
}
func main() {
arr := []int{2, 5, 3, 1, 6, 9}
fmt.Println(bubblesort(arr))
}
时间复杂度:一共需要n次循环,一共需要比较n次,所以时间复杂度为O(n^2)
空间复杂度:有限的几个变量,则为O(1)
位运算
无进位相加
能用位运算的就用位运算,别问为什么
a和b在内存里是两块独立的区域
a = 10
b = 10
查看a与b的地址
a := 10
b := 10
fmt.Println(&a, &b) // 0xc0000160a8 0xc0000160c0
// 思考
a := 10
b := 10
fmt.Println(&a, &b) //0xc0000aa078 0xc0000aa090
a, b = b, a
fmt.Println(&a, &b) //0xc0000aa078 0xc0000aa090
一种数
func printOddTimesNum1(arr []int) int {
value := 0
for _, eor := range arr {
value ^= eor
}
return value
}
func main() {
nums := []int{1, 3, 3, 2, 2, 6, 6}
fmt.Println(printOddTimesNum1(nums))
}
时间复杂度:O(n)
空间负载的:O(1)
两种数
func printOddTimesNum2(arr []int) (int, int) {
value := 0
for _, eor := range arr { //首先把这两个数找到
value ^= eor
}
// 取出一个数最右侧的1 将这个数&这个数取反在加1
rightone := value & (-value + 1)
for _, eor := range arr {
if (eor & rightone) != 0 { // 将尾数是1的拿出来
rightone ^= eor // 此操作可以拿到其中的一个数
}
}
return value ^ rightone, rightone
}
func main() {
nums := []int{1, 3, 3, 2, 2, 6, 6, 9}
fmt.Println(printOddTimesNum2(nums))
}
paactice1
package main
import "fmt"
/*
有个整型数组,时间O(N),空间O(1)
1):一种数奇数次,其他数偶数次,求奇数次的数
2):两种数奇数次,其他数偶数次,求奇数次的数
*/
func eventimeone(arr []int) int {
temp := len(arr)
if temp < 2 || arr == nil {
return -1
}
eor := 0
for _, i := range arr {
eor = eor ^ i
}
return eor
}
func eventimetwo(arr []int) int {
temp := len(arr)
if temp < 2 || arr == nil {
return -1
}
// 两种数奇,other 偶
// 将所有数^,得到这两个数的^值
// 将得到的这个值拿出最右边的1,在跟原数组进行^操作
// 如果
eor := 0
for _, i := range arr {
eor ^= i
}
fmt.Println("eor:=", eor)
//取出一个数最右侧的1
rightone := eor & (^eor + 1)
fmt.Println("rightone", rightone)
eorling := 0
for _, i := range arr {
if (rightone & i) != 0 {
eorling ^= i
}
}
fmt.Println(eorling, eor^eorling)
return eor
}
func printOddTimesNum2(arr []int) (int, int) {
value := 0
for _, eor := range arr { //首先把这两个数找到
value ^= eor
}
// 取出一个数最右侧的1 将这个数&这个数取反在加1
rightone := value & (^value + 1)
fmt.Println(rightone)
for _, eor := range arr {
if (eor & rightone) != 0 { // 将尾数是1的拿出来
rightone ^= eor // 此操作可以拿到其中的一个数
}
}
return value ^ rightone, rightone
}
func main() {
// arr := []int{1, 2, 2, 3, 3, 5, 5}
// fmt.Println(eventimeone(arr))
arrs := []int{1, 2, 3, 3, 5, 5, 6, 6}
fmt.Println(eventimetwo(arrs))
// fmt.Println(printOddTimesNum2(arrs))
}
时间复杂度:O(n + n) = O(n)
空间复杂度:O(1)
位运算要看八位
这个算法目前存在的问题是当奇数个数的数全是奇数的时候,有点问题,后面继续改进
插入排序
每一次向当前值往前到第一个,将小的值放在前面
func insertsort(arr []int) []int {
temp := len(arr)
if temp < 2 || arr == nil {
return nil
}
for i := 0; i < temp; i++ {
for j := i; j > 0; j-- {
if arr[j] < arr[j-1] {
arr[j], arr[j-1] = arr[j-1], arr[j]
}
}
}
return arr
}
practice1
package main
import "fmt"
func insertsort(arr []int) []int {
temp := len(arr)
if temp < 2 || arr == nil {
return nil
}
//插入排序
//每一次跟当前值前面的所有值做对比,小的放前面,大的不变
// 外循环每次的值
// 内循环每次跟前面所有值做对比
for i := 0; i < temp; i++ {
for j := i; j > 0; j-- {
if arr[j] < arr[j-1] {
arr[j], arr[j-1] = arr[j-1], arr[j]
}
}
}
return arr
}
func main() {
arr := []int{2, 5, 7, 3, 6}
fmt.Println(insertsort(arr))
}
二分法
有序数组中查找某个数是否存在
func bsexitone(arr []int, target int) int {
temp := len(arr)
if temp < 2 || arr == nil {
return -1
}
//有序数组中查找某一个数
// 采用二分的方法,那么首先应该定义二分在哪,然后分条件讨论
L := 0
R := temp
for L < R { //找到两个边界值,当边界值超出的时候,说明没找到
mid := L + (R-L)>>1 // 取数组的中点使用位运算
if arr[mid] == target { //如果刚好找到这个值,结束
return mid
// return arr[mid]
} else if arr[mid] < target { //如果target的值大于当前值,说明需要往右取
L = mid
} else { //如果target的值小于当前值,说明需要往左取
R = mid
}
}
return -1
}
有序数组中,找>=某个数最左侧的位置
局部最小值问题
对数器
递归
取中点用位运算
mid = L + (R-L) >> 1
master:特殊行为适用
归并排序
小和问题
逆序对
荷兰国旗
快排
1.0
2.0
3.0
堆
逻辑概念上是一颗完全二叉树
大根堆
小根堆
daily practice
二分查找:
func search(nums []int, target int) int {
if len(nums) < 1 || nums == nil {
return -1
}
L := 0
R := len(nums) - 1
mid := 0
for L <= R {
mid = L + (R-L)>>1
if nums[mid] > target {
R = mid - 1
} else if nums[mid] < target {
L = mid + 1
} else if nums[mid] == target {
return mid
}
}
return -1
}
时间复杂度:每一次都折半查找,比如一共查找八次,则就是2^n = 8,求n使用logn即可搞定
空间复杂度:寥寥几个变量,O(1)
移除元素:
package main
import "fmt"
func removeElement1(nums []int, val int) int {
temp := len(nums)
count := temp
newval := temp
for _, eor := range nums {
if val == eor {
temp--
} else {
nums = append(nums, eor)
count += 1
}
}
nums_count := len(nums[newval:])
for key, eor := range nums[newval:] {
nums[key] = eor
if nums[key] == val {
nums[key] = eor
}
}
fmt.Println(nums)
nums = nums[:nums_count]
return temp
}
func removeElement2(nums []int, val int) int {
temp := len(nums)
// count := temp
cc := temp
for _, eor := range nums {
if val != eor {
nums = append(nums, eor)
cc += 1
}
}
for key, eor := range nums {
if (key + temp) < cc {
eor = nums[key+temp]
} else {
eor = 0
}
fmt.Println(eor)
}
fmt.Println(nums)
return cc
}
func removeElement(nums []int, val int) int {
left := 0
for _, v := range nums { // v 即 nums[right]
if v != val {
nums[left] = v
left++
}
}
fmt.Println(nums)
return left
}
func main() {
nums := []int{0, 1, 2, 2, 3, 0, 4, 2}
val := 2
fmt.Println(removeElement(nums, val))
}
刚开始思路就想的太傻了,刚开始是这样想的
把与val不相同的一直往后面放,然后在将后面的跟前面的交换位置
直到我看了一眼答案,惊为天人
时间复杂度:O(1)
空间复杂度:O(1)
有序数组的平方:
思路没错但是没做出来版本
package main
import "fmt"
func sortedSquares(nums []int) []int {
temp := len(nums)
if temp < 1 || nums == nil {
return nil
}
// 时间复杂度要为O(n),所以选择,插入,冒泡都不可用,二分也不可用
// 因为是递增的,所以只需要考虑对半即可,也就是考虑负数这一块其实就ok
// 所以先准备两个数组,一个放负数,一个放正数,然后将复数和正数合并成为一个新数组
nums_z := []int{}
nums_f := []int{}
// 将正负数开始分类
for _, value := range nums {
if value < 0 {
nums_f = append(nums_f, value)
} else {
nums_z = append(nums_z, value)
}
}
// 将负数数组全部变为正数
if nums_f != nil {
for key, value := range nums_f {
nums_f[key] = -value
}
}
//合并数组
flag := true
nums_count_f := 0
nums_count_z := 0
num_acc := []int{}
for flag {
if nums_z != nil {
if nums_z[nums_count_z] > nums_f[nums_count_f] {
num_acc = append(num_acc, nums_f[nums_count_f])
nums_count_f += 1
}
} else {
flag = false
}
if nums_f != nil {
if nums_z[nums_count_z] > nums_f[nums_count_f] {
num_acc = append(num_acc, nums_f[nums_count_f])
nums_count_z += 1
}
} else {
flag = false
}
}
// 全部平方
for key, value := range num_acc {
nums[key] = value * value
}
return num_acc
}
func main() {
nums := []int{-4, -1, 0, 3, 10}
fmt.Println(sortedSquares(nums))
}
归并排序版本
func sortedSquares(arr []int) []int {
temp := len(arr)
if temp < 2 || arr == nil {
return nil
}
// 找出正数负数的位置
lastnum := -1
for i := 0; i < temp; i++ {
if arr[i] < 0 {
lastnum = i
}
}
// 根据lastnum也就是小于0的数求出边界值
ans := []int{}
// ans := make([]int, 0, temp)
for i, j := lastnum, lastnum+1; i >= 0 || j < temp; {
// 根据四个条件,先写第一个
// 当i也就是负数没有了的时候,数组只能存正数
if i < 0 {
ans = append(ans, arr[j]*arr[j])
j++
} else if j == temp { //只有负数,没有正数的时候,数组只能存负数
ans = append(ans, arr[i]*arr[i])
i--
} else if (arr[i] * arr[i]) < (arr[j] * arr[j]) { // 当不在两个边界值的时候,判断i和j哪个数小存哪个
ans = append(ans, arr[i]*arr[i])
i--
} else {
ans = append(ans, arr[j]*arr[j])
j++
}
}
return ans
}
惊人版
func sortedSquares(arr []int) []int {
temp := len(arr)
if temp < 2 || arr == nil {
return nil
}
// 不管正负了,直接双指针,一个从左走,一个从右走
i, j := 0, temp-1
// ans := []int{} 当使用这样定义的数组的时候,不能直接赋值
ans := make([]int, temp) //这样定义的数组可以直接赋值比如arr[3] = 3
// for pos := 0; pos < temp; pos++ {//因为走的是边界值,所以放的值从最大的开始,所以外循环要改
for pos := temp - 1; pos >= 0; pos-- { //因为走的是边界值,所以放的值从最大的开始,所以外循环要改
if k, v := arr[i]*arr[i], arr[j]*arr[j]; k > v {
// ans = append(ans, k)
ans[pos] = k
i++
} else {
ans[pos] = v
j--
}
}
return ans
}
长度最小的子数组:
螺旋矩阵 II:
参考资料
左神算法公开课