开学~ 每日学习继续开始

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

相关推荐
计算机学姐11 分钟前
基于Python的高校成绩分析管理系统
开发语言·vue.js·后端·python·mysql·pycharm·django
wclass-zhengge43 分钟前
SpringCloud篇(服务拆分 / 远程调用 - 入门案例)
后端·spring·spring cloud
A_cot1 小时前
一篇Spring Boot 笔记
java·spring boot·笔记·后端·mysql·spring·maven
tryCbest2 小时前
java8之Stream流
java·后端
白总Server2 小时前
JVM 处理多线程并发执行
jvm·后端·spring cloud·微服务·ribbon·架构·数据库架构
@sinner2 小时前
【Spring Boot 入门五】Spring Boot中的测试 - 确保应用质量
spring boot·后端·log4j
江梦寻3 小时前
解决SLF4J: Class path contains multiple SLF4J bindings问题
java·开发语言·spring boot·后端·spring·intellij-idea·idea
LightOfNight3 小时前
Redis设计与实现第9章 -- 数据库 总结(键空间 过期策略 过期键的影响)
数据库·redis·后端·缓存·中间件·架构
每天写点bug3 小时前
golang 常用的占位符 %w, %v, %s
开发语言·后端·golang
鸡鸭扣3 小时前
springboot苍穹外卖实战:五、公共字段自动填充(aop切面实现)+新增菜品功能+oss
java·spring boot·后端