开学~ 每日学习继续开始

8.31任务

1.听一套听力题,细听错误的片段

2.记单词100个

3.go语言基础学习(go语言菜鸟之旅)

4.刷leetcode算法题3道,做好总结

LeetCode面试经典150题 第5题 多数元素

题目描述

给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数大于⌊ n/2 ⌋的元素。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

ini 复制代码
示例 1:
输入:nums = [3,2,3]
输出:3

示例 2:
输入:nums = [2,2,1,1,1,2,2]
输出:2

进阶:尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。

思路

读题,其实就是让我们找数组中占半数以上的自然数,并将其输出。第一个想法是类似于每当有俩个不一样的数,就抵消,最后留下的一定是最多的数

怎么实现?考虑引入多个计数器?感觉有点麻烦

然后转换思路,因为要占半数,所以直接将数组排序, 取最中间的那个数一定是多数的那个元素

第一次写的代码是这样的。。。

go 复制代码
func majorityElement(nums []int) int {
    lens := len(nums)
    nums=sort.Ints(nums)
    return nums[lens/2]
}

报错Line 3: Char 10: sort.Ints(nums) (no value) used as value (solution.go)

错误原因:

sort.Ints(nums) 并不返回值,因此不能直接将其赋值给 nums。

sort.Ints(nums) 会直接修改切片 nums,而不是返回一个新的排序后的切片。

修改代码:

go 复制代码
func majorityElement(nums []int) int {
    lens := len(nums)
    sort.Ints(nums)
    
    return nums[lens/2]
}

还有一个易错点

注意是sort.Ints 而不是.Strings 。

因为本题的数组是Int类型的

官方解答--摩尔投票法

前面提到的排序是最简单实现的,但是时间复杂度比较高。

接下来是官方解答,竟然和我一开始的想法一样(乐),学学他们是怎么实现的。

时间复杂度:O(n),仅遍历一次数组

空间复杂度:O(1),没有使用额外空间

摩尔投票法(Boyer--Moore majority vote algorithm),也被称作「多数投票法」,算法解决的问题是:如何在任意多的候选人中(选票无序),选出获得票数最多的那个。

算法可以分为两个阶段:

对抗阶段:分属两个候选人的票数进行两两对抗抵消

计数阶段:计算对抗结果中最后留下的候选人票数是否有效

思路

将当前票数最多的候选人与其获得的(抵消后)票数分别存储在 major 与 count 中。

当我们遍历下一个选票时,判断当前 count 是否为零:

若 count == 0,代表当前 major 空缺,直接将当前候选人赋值给 major,并令 count++

若 count != 0,代表当前 major 的票数未被完全抵消,因此令 count--,即使用当前候选人的票数抵消 major 的票数(妙哇!)

看着思路自己写一遍代码。

go 复制代码
func majorityElement(nums []int) int {
   leng := len(nums)
   count :=1
   major :=nums[0]
   for i:=1 ; i<leng ; i++{ 
      if nums[i]!=major{
         count--
         if count==0{
         major=nums[i]
         count++
         }
      }else{
         count++
      }   
}
return major
}

过了,但是 时间击败 66.68%使用 Go 的用户 内存击败 54.40%使用 Go 的用户。呃呃呃,看看他们的解答代码。

go 复制代码
func majorityElement(nums []int) int {
    major := 0
    count := 0

    for _, num := range nums {
        if count == 0 {
            major = num
        }
        if major == num {
            count += 1
        } else {
            count -= 1
        }
    }
    
    return major
}

总结问题: 第一:循环写的不够简洁!

第二:流程应该是先判断是否为0,然后再根据是否相等来加减。我是先判断是否相等,然后若不相等减去,然后判断是否为0。

嘶,我好像写的不对哇。比如数组是1,2,1 。 刚开始major是1,然后判断第2个元素,是2 ,此时count--,为0.会直接将2赋给major???!

总结思考

在判断大多数元素的时候,可以采用对抗的方法,将不同的数俩俩抵消,最后剩下的数一定是大多数元素。。。此时不得不想一下,如果只是要求你求出多数元素,并没有一定到一半以上,该咋做。。。比如数组为1,1,1,2,2,3,3.用我们这个做法得到的是3,但实际却是1。

这就是摩尔投票法后面计数阶段应该做的事情。 代码如下:

go 复制代码
  count = 0
    for _, num := range nums {
        if num == candidate {
            count++
        }
    }
    
    if count > len(nums) / 2 {
        return candidate
    }

进行验证,不符合就重新再去找。

然后找了一个类似的题229. 多数元素 II - 力扣(LeetCode)

需要把大于n/3的数都找出来,做法大概就是设置俩个判断点,中间思路和此题一样,最后对两者进行验证,输出符合条件的。

其他大于n/x的,就设置x-1个判断点即可。

直接贴代码:

ini 复制代码
func majorityElement(nums []int) []int {
    candidate1, candidate2 := 0, 0
    count1, count2 := 0, 0
    for _, num := range nums {
        if candidate1 == num {
            count1++
        } else if candidate2 == num {
            count2++
        } else if count1 == 0 {
            candidate1 = num
            count1 = 1
        } else if count2 == 0 {
            candidate2 = num
            count2 = 1
        } else {
            count1--
            count2--
        }
    }
    // 验证两个候选元素的出现次数是否满足要求
    count1, count2 = 0, 0
    for _, num := range nums {
        if num == candidate1 {
            count1++
        } else if num == candidate2 {
            count2++
        }
    }
    result := []int{}
    if count1 > len(nums) / 3 {
        result = append(result, candidate1)
    }
    if count2 > len(nums) / 3 {
        result = append(result, candidate2)
    }
    
    return result
}

打表

上述后面的计数阶段,还是停留在告诉你占了多少分之1,如果啥都没说,就让你求最大值。就可以用打表的思想。

代码如下

go 复制代码
func majorityElement(nums []int) int {
    var statMap = make(map[int]int)
    for _, v := range nums {
        statMap[v]++
    }

    maxCount := 0
    maxElement := 0
    for k, v := range statMap {
        if v > maxCount {
            maxCount = v
            maxElement = k
        }
    }
    
    return maxElement
}

这种方法就是最易想到的方法,非常滴朴实无华。

面试经典150题 第六题 轮转数组

题目描述

给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。

ini 复制代码
示例 1:
输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右轮转 1 步: [7,1,2,3,4,5,6]
向右轮转 2 步: [6,7,1,2,3,4,5]
向右轮转 3 步: [5,6,7,1,2,3,4]

示例 2:
输入:nums = [-1,-100,3,99], k = 2
输出:[3,99,-1,-100]
解释: 
向右轮转 1 步: [99,-1,-100,3]
向右轮转 2 步: [3,99,-1,-100]
 

进阶:

尽可能想出更多的解决方案,至少有 三种 不同的方法可以解决这个问题。

你可以使用空间复杂度为 O(1) 的 原地 算法解决这个问题吗?

思路

题目很好懂,就是看k是多少,然后把数组依次向右移动相应k位。

最开始的想法: 看k是多少,然后把数组最后的k位放到一个新数组里面,然后老数组依次移动k位,最后在把新数组里面的数按顺序放到老数组里面。

代码如下:

css 复制代码
func rotate(nums []int, k int)  {
    arry := make([]int,k)
    leng := len(nums)
    if leng <= 1 {
        return
    }
    k %= leng
    
    for i:=0;i< k;i++{
        arry[i] = nums[leng-i-1]
    }
    
    //得逆序来放
    for z:= leng-k-1 ; z>=0;z--{
        nums[z+k] = nums[z]
    }

    for j:=0;j<k;j++{
        nums[j]=arry[k-j-1]
    }
}

代码解释:

kotlin 复制代码
    if leng <= 1 {
        return
    }
    k %= leng

这一块是因为我后面的代码对数组长度为1的时候,没有做特定判断,会报错。 有一说一,上面代码写的可读性一坨。。。。

然后继续想有没有啥其他的解决方法:

还是看看官方解答吧,悲

官方解答---数组翻转

该方法基于如下的事实:当我们将数组的元素向右移动 k 次后,尾部 k mod n 个元素会移动至数组头部,其余元素向后移动 k mod n 个位置。

该方法为数组的翻转:我们可以先将所有元素翻转,这样尾部的 k mod n 个元素就被移至数组头部,然后我们再翻转 [0,k mod n−1]区间的元素和 [k mod n,n−1] 区间的元素即能得到最后的答案。(确实可以)

代码如下:

css 复制代码
func reverse(a []int) {
    for i, n := 0, len(a); i < n/2; i++ {
        a[i], a[n-1-i] = a[n-1-i], a[i]
    }
}

func rotate(nums []int, k int) {
    k %= len(nums)
    reverse(nums)
    reverse(nums[:k])
    reverse(nums[k:])
}

翻转函数实现,是根据中心点对称,将其交换

实现代码不难,其实主要是一个数学的思想,没考虑到三次翻转。

go语言面试题(第一天)

资料来自第一天 · Go语言中文文档-面试题 (topgoer.com)

下面这段代码输出什么

go 复制代码
package main

 import (
     "fmt"
 )

 func main() {
     defer_call()
 }

func defer_call() {
    defer func() { fmt.Println("打印前") }()
    defer func() { fmt.Println("打印中") }()
    defer func() { fmt.Println("打印后") }()
    panic("触发异常")
}

主要考察的应该是 defer 的应用

如果让我做的话我应该会答: 打印前 打印中 触发异常 打印后

因为defer是延后输出 ,但不是都把他推到最后,而是在下一个函数或语句运行后,在输出。

答案

令人感慨,全都错了 。。

go 复制代码
  打印后
  打印中
  打印前
  panic: 触发异常

参考解析:defer 的执行顺序是后进先出。当出现 panic 语句的时候,会先按照 defer 的后进先出的顺序执行,最后才会执行panic

错误原因: defer 没有理解对,而且不知道panic语句的作用。。。go基础不牢,悲()

defer就用堆栈的形式来理解吧,后进先出。

panic介绍

panic 是一个 Go 内置函数,它用来停止当前常规控制流并启动 panicking(运行时恐慌)过程。 当函数 F 调用 panic 函数时,函数 F 的执行停止,函数 F 中已进行了求值的 defer 函数都将得到正常执行,然后函数 F 将控制权返还给其调用者。

用例子来说吧

go 复制代码
func main(){
    fmt.Println("c")
    defer func(){ // 必须要先声明defer,否则不能捕获到panic异常
        fmt.Println("d")
        if err:=recover();err!=nil{
            fmt.Println(err) // 这里的err其实就是panic传入的内容
        }
        fmt.Println("e")
    }()

    f() //开始调用f
    fmt.Println("f") //这里开始下面代码不会再执行
}

func f(){
    fmt.Println("a")
    panic("异常信息")
    fmt.Println("b") //这里开始下面代码不会再执行
    fmt.Println("f")
}
输出结果:

c
a
d
异常信息
e

1.在panic后面的代码就不会在执行了

2.顺序是

一、执行c

二、到defer,先放到堆栈中,继续下面代码运行

三、调用函数f,输出到a就停止(因为panic以后就不会在执行代码)

四、此时就会把堆栈中的函数运行完(不会在运行输出f了)

个人理解 代码的运行其实是从堆栈中调用的过程,不过一般都是先进先出,但是到defer是先进后出。然后到panic执行完以后,就不会在读取代码了。但是此时堆栈中的命令还是会继续运行完。

最后!!

正确使用 Panic

禁止\] 使用 panic 用于正常的错误处理。应该使用 error 和多个返回值。 例:(写fabric链码经常用到的) ```go f, err := os.Open("file.txt") if err != nil { // handle error here } // do stuff with f here ``` panic 只适合用于那些严重影响程序运行的错误。参考[Effective Go - The Go Programming Language](https://link.juejin.cn?target=https%3A%2F%2Fgo.dev%2Fdoc%2Feffective_go%23errors "https://go.dev/doc/effective_go#errors")

相关推荐
Vfw3VsDKo36 分钟前
Maui 实践:Go 接口以类型之名,给 runtime 传递方法参数
开发语言·后端·golang
是真的小外套2 小时前
第十五章:XXE漏洞攻防与其他漏洞全解析
后端·计算机网络·php
ybwycx3 小时前
SpringBoot下获取resources目录下文件的常用方法
java·spring boot·后端
小陈工4 小时前
Python Web开发入门(十一):RESTful API设计原则与最佳实践——让你的API既优雅又好用
开发语言·前端·人工智能·后端·python·安全·restful
小阳哥AI工具4 小时前
Seedance 2.0使用真人参考图生成视频的方法
后端
IeE1QQ3GT4 小时前
使用ASP.NET Abstractions增强ASP.NET应用程序的可测试性
后端·asp.net
Full Stack Developme5 小时前
SpringBoot多线程池配置
spring boot·后端·firefox
sxhcwgcy7 小时前
SpringBoot 使用 spring.profiles.active 来区分不同环境配置
spring boot·后端·spring
稻草猫.8 小时前
Spring事务操作全解析
java·数据库·后端·spring
希望永不加班9 小时前
SpringBoot 整合 MongoDB
java·spring boot·后端·mongodb·spring