算法day2打卡

算法day2

  • 977.有序数组的平方
  • 209.长度最小的子数组
  • 59.螺旋矩阵II

977.有序数组的平方

拿到这个题就想到了暴力算法,先把数组的每个元素平方之后再用个排序。
这种方法最快时间复杂度只能达到o(nlogn),本题的目标是达到o(n)

暴力解法

import "sort"
func sortedSquares(nums []int) []int {
    for i:=0;i<len(nums);i++{
        nums[i]*=nums[i]
    }

    sort.Slice(nums,func (i,j int) bool{
        return nums[i]<nums[j] 
    })

    return nums
}

在学go语言时这个库函数我用的不多,这里做个详细的学习:
sort.Slice()可以根据自定义规则对切片进行排序,并且特别适合用于元素类型不是基本类型(int,string),可以对结构体进行排序。

sort.Slice()用法

例1:

sort.Slice(nums,func (i,j int) bool{
        return nums[i]<nums[j] 
    })

这个例子是对int切片进行排序
sort.Slice()需要两个参数,nums是待排序的数组,后面的匿名函数是比较函数,需要接收两个索引i和j,并返回一个bool值。表示在排序中是否将所有i的元素排在索引j之前。这个匿名函数体定义了排序的方式。
当nums[i]

例2:
这个例子是对结构体切片排序

type Person struct {
    Name string
    Age  int
}

people := []Person{
    {"Alice", 30},
    {"Bob", 25},
    {"Clara", 35},
}

sort.Slice(people, func(i, j int) bool {
    return people[i].Age < people[j].Age
})

双指针解法

解法思路:利用题目的性质,有序数组,数组中元素平方,在一般情况下,平方后的最小值都在中间,两端可能取到最大值。
因此设置left和right指针指向两端,进行比较后构造目标切片,两个指针不断往中间靠拢。最后扫描完所有元素,当left和right相遇后错开就停止。
时间0(n)

func sortedSquares(nums []int) []int {
    i,j:=0,len(nums)-1
    result := make([]int,len(nums))
    k:=len(nums)-1
    for i<=j{
        if nums[i]*nums[i]>nums[j]*nums[j]{
            result[k] = nums[i]*nums[i]
            i++
            k--
        }else{
            result[k] = nums[j]*nums[j]
            j--
            k--
        }
    }
    return result
}

209.长度最小的子数组

两个for循环暴力解法

我最开始想的就是这么做的,一个for循环负责元素起点,另一个for循环在第一个for循环的指向进行累加判断。
这个就是我第一次写出来的代码,纯粹是按这个想法去写。

func minSubArrayLen(target int, nums []int) int {
    l := len(nums)
    testnum :=0
    for k:=0;k<l;k++{
        testnum+=nums[k]
    }
    if testnum < target{
        return 0
    }

    result :=0
    for i:=0;i<l;i++{
        temp:=0
        count:=0
        for j:=i;j<l;j++{
            if temp>=target{
                break
            }
            temp+=nums[j]
            count++
        }
        if result ==0 {
            result = count
        }else if count<result && temp >=target{
            result = count
        }
        
    }

    return result
}

这个写法前19个例子都能过,但是leetcode更新例子了
会在这个例子爆时间
1000000000
[10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,10000,1000…
所以不用在这个暴力算法浪费太多时间。由于是两个for,所以这里时间是o(n^2)的复杂度

滑动窗口做法(双指针)

func min(a, b int)int{
    if a > b{
        return b
    }else{
        return a
    }
}


func minSubArrayLen(target int, nums []int) int {
    result :=len(nums)+1
    sum:=0
    count:=0
    for i,j:=0,0;j<len(nums);j++{
        sum+=nums[j]
        for sum >= target{
            count = j-i+1
            result = min(count,result)
            sum-=nums[i]
            i++
        }
    }
    if result == len(nums)+1{
        return 0
    }else{
        return result
    }
}

该算法的时间复杂度是O(N),一个for循环做了两个for循环的事。
思路:滑动窗口也就是双指针,这个窗口和上面暴力维护的窗口道理是一样的,但是维护的思路是不一样的。暴力算法维护的双指针是起始指针移动,然后终止指针再往后维护区间。
滑动窗口是终止指针向后移动,当满足条件之后,其实指针再往后移动。

代码中有两个关键点是我写滑动窗口时做错的

for sum >= target{
            count = j-i+1
            result = min(count,result)
            sum-=nums[i]
            i++
        }

这个地方第一次写的时候我不用for循环用了if,这就是对这个过程理解的还不够。
例子:target=100,nums={1,1,1,1,1,1,100}
如果用if就会出现问题,用if我的这个i++就只会移动一次,然后直接到下次循环j又往后移动了。
用循环,这个i就会不断地向后移动,这个滑动窗口才是想要的。

其次就是特判:数组所有元素之和小于target,这个时候要返回0
当时虽然写了,但是我觉得我写的太垃圾了,硬生生又去搞了个求和特判。主要是对过程理解的不够,这里特判应该这么写:当你全部元素求和都小于target,那么里面那个内层for循环是根本进不去的,此时这个result就还是保持原值不变,而且这个result本身就是一个不可能取到的值。所以这样特判就写出来了。

一点改进:虽然说影响不大,这个result初始值:这里一开始我写的math.MaxInt32,虽然说都一样,既然为了取不到,那可以写的更好理解一点,在result能取到的最大值+1即可。所以这里我后来改成了len(nums)+1


59.螺旋矩阵II

本题没什么算法,就是一个模拟题。当时遇到的难点就是边界的处理,和这个转的过程我的脑子就很乱。
所以本题的关键:
1.转多少圈
2.边界如何处理
3.转圈的时候行和列的维护
4.当n为奇数时中间会剩一个位置

func generateMatrix(n int) [][]int {
    startx,starty := 0,0
    count:=1
    loop:=n/2
    offset := 1
    res := make([][]int,n)
    for k:=0;k<n;k++{
        res[k] = make([]int,n)
    }
    for loop>0{
        i,j:=startx,starty
        for ;j<n-offset;j++{
            res[i][j]=count
            count++
        }

        for ;i<n-offset;i++{
            res[i][j]=count
            count++
        }

        for ;j>starty;j--{
            res[i][j]=count
            count++
        }

        for ;i>startx;i--{
            res[i][j]=count
            count++
        }

        offset++
        startx++
        starty++
        loop--
    }

    if n%2 == 1{
        res[n/2][n/2]=count
    }

    return res
}

这个题要模拟出来就要理清楚这个过程。
就是从0,0这个位置开始转圈,在这个正方形每个要处理的边上都必须按同一个规则来,如图所示算法day2打卡_第1张图片
每条边上的区间处理都是按左闭右开来处理,按这样来处理代码就会很好写,此时对于边界的处理,此时的边界,我们输入的是n,那么这个外圈1这个地方的边界就应该是n-offset,也就是3-1=2,此时i<2就是刚好处理前面两个格子。对于2这条边也是n-offset,3这条边的边界调节就用startx,4这条边的边界就是starty。
在转圈的过程中,这个二维切片,就统一用i,j代表行列进行维护。
转完一圈之后,要进入内圈,这个时候循环圈数loop–,offset也要相应的往内部缩一格offset++,起始位置都要往内缩一格startx++,starty++,这些都是总结出来的。

最后就是一个特判,就拿上面这个图举例子,转的圈loop = n/2 = 1;那这样内部就会剩下一个格子,这个格子就直接特殊处理赋值。


写的时候的问题
1.go语言的二维数组和c++的初始化方式还是有点区别的,我一开始在这里就卡住了。

res := make([][]int,n)
    for k:=0;k<n;k++{
        res[k] = make([]int,n)
    }

第一个是先进行第一步分配内存,然后再进一步对内部声明切片

2.关于go库函数,没用min()函数,这个函数要自己写。

你可能感兴趣的:(算法,数据结构,go)