4. 寻找两个正序数组的中位数

给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。

示例 1:

输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2

示例 2:

输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/median-of-two-sorted-arrays
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

本题对Go语言来说,主要用到Go语言数组的知识:

  1. 数组的声明与初始化

    1
    2
    3
    // 声明一个数组arr
    var arr [3] int
    arr := []int{1,2,3}
  2. 访问数组元素,与C语言十分类似。

    1
    b := arr[1]
  3. 函数中使用数组:

    这一点和之前学习的略有不同,Go语言中,[2]int 与 []int 类型是不同的,也就是说,当形参为[]int时,传递类型为[2]int的数组是无法编译通过的。例如下面代码是不合法的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    package main

    func main() {
    var arr [3] int
    arr2 := []int{1,2,3}
    // 错误:cannot use arr (type [3]int) as type []int in argument to printArray1
    printArray1(arr)
    // 错误:cannot use arr2 (type []int) as type [3]int in argument to printArray2
    printArray2(arr2)
    }

    func printArray1(arr []int) {
    for _, v := range arr {
    println(v)
    }
    }
    func printArray2(arr [3]int) {
    for _, v := range arr {
    println(v)
    }
    }
  4. 动态长度数组

    1
    2
    length := 3
    arr := make([]int, length)

回到题目上,我们使用4中方法来求解。

解法1: 快速合并两个正序数组,然后求合并后数组的中位数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/**
* 解法1: 通过两个下标p1, p2,快速将两个数组合并为一个长度为(m+n)的大数组;
若(m+n)为奇数,则下标(m+n)/2位置数即为中位数;
若(m+n)为偶数,则下标(m+n)/2 - 1与(m+n)/2的平均数为中位数
*/
func findMedianSortedArrays(nums1 []int, nums2 []int) float64 {
mergeArray := mergeArray(nums1, nums2)
for _, v := range mergeArray {
println(v)
}
length := len(mergeArray)
if length % 2 == 0 {
// 偶数
return float64((mergeArray[length/2-1] + mergeArray[length/2]))/2
} else {
// 奇数
return float64(mergeArray[length/2])
}

}

/**
* 合并字符串
*/
func mergeArray(nums1 []int, nums2 []int) []int {
length1 := len(nums1)
length2 := len(nums2)
// 错误1:non-constant array bound length1 + length2
// 原代码: mergeArray := [length1 + length2]int
mergeArray := make([]int, length1 + length2)
mergeArrayP := 0
p1 := 0
p2 := 0
for ; p1 < length1 || p2 < length2 ; {
if p1 >= length1 {
mergeArray[mergeArrayP] = nums2[p2]
p2++
mergeArrayP ++
continue
}

if p2 >= length1 {
mergeArray[mergeArrayP] = nums1[p1]
p1++
mergeArrayP ++
continue
}

if nums1[p1] < nums2[p2] {
mergeArray[mergeArrayP] = nums1[p1]
p1 ++
mergeArrayP ++
} else {
mergeArray[mergeArrayP] = nums2[p2]
p2 ++
mergeArrayP ++
}
}
return mergeArray
}

解法2:不合并数组,仅通过两个数组的指针移动。与解法1十分类似,此处便不写代码了。

解法3:将题目理解为“寻找两个有序数组中的第k小的数,其中 k 为 (m+n)/2(m+n)/2 或 (m+n)/2+1

解法4:划分数组,这算是一种通过数学方法来降低时间控件复杂度的方法了。详解见力扣官网。


Go语言力扣刷题系列文章 |Go主题月

  1. Go语言力扣刷题-两数之和|Go主题月

  2. Go语言力扣刷题-两数相加|Go主题月

  3. Go语言力扣刷题-无重复字符的最长子串|Go主题月

  4. Go语言力扣刷题-寻找两个正序数组的中位数|Go主题月

3. 无重复字符的最长子串

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

1
2
3
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

我们使用两种解法来解决,一种暴力求解,毕竟遍历是最基础的一种操作方式。另外一种通过map来实现的滑动窗口求解。

遍历所有子串,输出所有子串的最长长度。我们采用的遍历方式是所有子串的起始位置,所有可能长度的子串。

对于我初学Go语言,如果要解决这个问题,需要学到以下知识内容:

  1. 如何获取字符串中某个位置的字符:string(s[1])
  2. 如何获取某个字符串的子字符串: s[i:j],s[:j],s[i:]。涉及到切片的知识,如果有Python基础,会比较好理解。并且,经过测试,此切片只针对英文字符有效,即utf-8单个字节切片。
  3. 如何获取字符串的长度: len([]rune(s))
  4. Go处理字符串是否存在官方工具包:import strings

在查找Go资料的过程中,遇到一个十分陌生的关键字:rune。个人理解,可以把rune当做是Go中处理中文字符一个工具。

当然,最终解决并没有全部用到,例如没有用到strings包。单并不影响我学习这些go语言的知识。

暴力求解方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
func lengthOfLongestSubstring(s string) int {
max := 0
var runeS = []rune(s)
length := len(runeS)
for i :=0; i<length; i += 1 {
for j := i+1; j <= length; j += 1 {
temp := runeS[i:j]
same := hasSame(string(temp))
if !same {
if max < (j - i) {
max = j-i
}
}
}
}
return max
}

/**
* 判断一个字符串中是否有重复字符
*/
func hasSame(s string) bool {
sMap := make(map[string]int) // 字符到index的映射表
sr := []rune(s)
for index, v := range sr {
_, ok := sMap[string(v)]
if ok {
return true
} else {
sMap[string(v)] = index
}
}
return false
}

最终在力扣上的运行结果是”超出时间限制”,所以最终是否有逻辑问题也无从验证了,欢迎小伙伴指正!

滑动窗口求解代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func lengthOfLongestSubstring(s string) int {
strMap := make(map[rune]int)
runeS := []rune(s)
length := len(runeS)
max := 0
// start作为窗口左侧下标,end作为窗口右侧下标
for start, end:= 0, 0; end < length; end ++ {
index, ok:= strMap[runeS[end]]
if ok {
// 关键处1:end处字符已出现过
start = maxNum(index, start)
}
max = maxNum(max, end - start + 1)
strMap[runeS[end]] = end + 1
// fmt.Printf("after, is testing %s, start = %d, end = %d, index=%d, max = %d\n", string(runeS[end]), start, end, index, max)
}
return max
}

func maxNum(a int, b int) int {
if a < b {
return b
} else {
return a
}
}

这段代码个人一直未能理解的地方在于注释标注的关键处1:一开始我理解index肯定是大于start的,但是index实际上表示的是窗口右侧字符之前出现过的下标,而此时左侧窗口可能已经超过那处字符。

滑动窗口值得仔细品味!!

第一次编写Go链表指针,顺利通过编译,开森!!!

2. 两数相加

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

1
2
3
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/add-two-numbers
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

首次编译通过的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {
if l1 == nil {
return l2
}
if l2 == nil {
return l1
}
var head = new(ListNode)
var movePoint = head
head.Val = 0
carry := 0
l1point:=l1
l2point:=l2
for ; l1point != nil || l2point != nil; {
sum := 0
if l1point != nil {
sum += l1point.Val
}
if l2point != nil {
sum += l2point.Val
}
sum += carry
movePoint.Val = sum % 10
carry = sum / 10
movePoint.Next = new(ListNode)
movePoint = movePoint.Next

l1point = l1point.Next
l2point = l2point.Next
}
if carry > 0 {
movePoint.Val = carry
} else {
/*第一处: 假如链表节点依次为a->b->c->d,并且movePoint指向d,此时令movePoint=nil并不会将最后的接点移除,而应设c.Next = nil*/
movePoint = nil
}

return head
}

遇到的问题:

  1. 编译通过:嗯,第一次写go 链表,就编译通过,开森!
  2. 测试用例未通过:[2,4,3] + [5,6,4],正确结果应为 [7, 0, 8],但运行结果为 [7, 0, 8, 0],即代码标注的第一处:链表去除节点方式错误

新增tail节点,移动指针始终在tail节点的下一个节点,修正第一处错误后,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {
if l1 == nil {
return l2
}
if l2 == nil {
return l1
}
var head = new(ListNode)
var movePoint = head
var tail = head
head.Val = 0
carry := 0
l1point:=l1
l2point:=l2
for ; l1point != nil || l2point != nil; {
sum := 0
if l1point != nil {
sum += l1point.Val
}
if l2point != nil {
sum += l2point.Val
}
sum += carry
movePoint.Val = sum % 10
carry = sum / 10
movePoint.Next = new(ListNode)
tail = movePoint
movePoint = movePoint.Next

/*第二处错误:当len(l1)与len(l2)不等时,必然报错*/
l1point = l1point.Next
l2point = l2point.Next
}
if carry > 0 {
movePoint.Val = carry
tail = movePoint
} else {
tail.Next = nil
}

return head
}

测试用例OK,但提交运行报错:[9,9,9,9,9,9,9]+ [9,9,9,9],报错信息如下:

1
**Line 38: panic: runtime error: invalid memory address or nil pointer dereference**

程序逻辑错误:循环的条件是两条所给的链表均不为空,因此获取下一个节点时应判空。或者在获取数值时就判空。二次修改后,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
type ListNode struct {
Val int
Next *ListNode
}

func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {
if l1 == nil {
return l2
}
if l2 == nil {
return l1
}
var head = new(ListNode)
var movePoint = head
var tail = head
head.Val = 0
carry := 0
l1point:=l1
l2point:=l2
for ; l1point != nil || l2point != nil; {
sum := 0
if l1point != nil {
sum += l1point.Val
/*此处向后移动节点l1point*/
l1point = l1point.Next
}
if l2point != nil {
sum += l2point.Val
/*此处向后移动节点l2point*/
l2point = l2point.Next
}
sum += carry
movePoint.Val = sum % 10
carry = sum / 10
movePoint.Next = new(ListNode)
tail = movePoint
movePoint = movePoint.Next
}
if carry > 0 {
/*进位保留*/
movePoint.Val = carry
tail = movePoint
} else {
/*舍弃最后一个节点*/
tail.Next = nil
}
return head
}

感觉代码略繁琐,经过查看Go的基础语法,将部分语句移动到for的初始化语句(init版块)与循环执行语句(post版块),修改后的最终版:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {
if l1 == nil {
return l2
}
if l2 == nil {
return l1
}
var head = new(ListNode)
var movePoint = head
var tail = head
head.Val = 0
carry := 0
for l1point, l2point :=l1, l2; l1point != nil || l2point != nil; tail, movePoint = movePoint, movePoint.Next {
sum := 0
if l1point != nil {
sum += l1point.Val
l1point = l1point.Next
}
if l2point != nil {
sum += l2point.Val
l2point = l2point.Next
}
sum += carry
movePoint.Val = sum % 10
carry = sum / 10
movePoint.Next = new(ListNode)
}
if carry > 0 {
movePoint.Val = carry
tail = movePoint
} else {
tail.Next = nil
}

return head
}

原发于掘金:https://juejin.cn/post/6942846978107637767

编程语言是写给计算机的语言,一定与人类之间的语言有些许的共通之处,也都将是一项未来生活必不可少的基本技能。

快速学习一项技能的几个关键点:

  1. 明确的目标:切实可行的驱动。
  2. 快速获得反馈: 知道自己做的对或者不对,及时纠正。
  3. 创造深的理解:能够使用或者进行说明。
  4. 高强度的学习:刻意练习与广泛练习。

刷题学语言正有这样的特点:

  1. 明确、清晰而无歧义的问题,即实现目标。
  2. 在线或本地的编译器,清晰的编译错误或测试用例错误,便于及时纠正语法、程序逻辑错误。
  3. 题目一般有一定难度,需要深刻的理解。
  4. 力扣题目经典,值得去做这样的练习,仅仅是思维练习也是有很多好处的。

当然,在刷题之前过一遍Go的基础语法还是很有必要的。

从最简单的题目出发:

1. 两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/two-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

结题思路是:通过(值->索引)的映射表,来快速查找是否存在目标值。

我的Go首次执行:当然编译没有通过o(╥﹏╥)o

1
2
3
4
5
6
7
8
9
10
11
func twoSum(nums []int, target int) []int {
var numsMap map[int]int
for i:=0; i <= len(nums); i++ {
val, ok := numsMap[target - nums[i]]
if (ok) {
return {i, val}
}
numsMap[nums[i]] = i
}
return nil
}

这里面有几个错误:

  1. go中map需要初始化才可以使用

  2. 返回的数组不能这样写

    1
    {i, val}
  3. 当然,最后返回nil还是空数组也纠结了好久

修正后的代码:

1
2
3
4
5
6
7
8
9
10
11
func twoSum(nums []int, target int) []int {
numsMap := make(map[int]int)
for i:=0; i <= len(nums); i++ {
val, ok := numsMap[target - nums[i]]
if (ok) {
return []int{i, val}
}
numsMap[nums[i]] = i
}
return nil
}

进一步,使用range的语法修改后:

1
2
3
4
5
6
7
8
9
10
11
func twoSum(nums []int, target int) []int {
numsMap := make(map[int]int)
for index, value := range nums {
val, ok := numsMap[target - value]
if (ok) {
return []int{index, val}
}
numsMap[nums[index]] = index
}
return nil
}

涉及Go知识:数组,集合map,range, nil等用法。

本博文起始与看到一篇博文:

硅谷王川发现犯错误后,人性所致,会马上会陷入一种深深的懊悔的情绪,懊悔说如果我当初怎么怎么做就好了。

而且因为这种懊悔的痛苦,一般不愿去自揭疮疤,不愿去总结经验,而往往容易采用鸵鸟策略,不去想不去看。而且也不高兴周边的亲人或者好友提这件事,因为很痛。

这样表面可以回避痛苦,但其实非常愚蠢,因为没有从错误中学到任何教训。

一个非常有效的做法,是拿着手术刀解剖自己,把自己的思维过程决策过程写出来,为什么当初会犯这样的错误,现在意识到错误了,现在有了新的信息和认知了,如何尽快采取行动,把未来的利益最大化。

只有写出来,才能把逻辑一条条理顺,才能使逻辑内化,让大脑真正认同,克服自身错误的直觉。不写出来,实际上是自己口服但心不服的。

普通人发财的最大障碍不在于不勤奋,而是在于缺乏第一时间直面自身错误的勇气和行动。

评论续:还有一类错误,是事物的发展大幅度超过预期。这时候不能够甘于沾沾自喜,而要迅速评估认知的哪个方面低估了事态的发展,如何在不增加风险的前提下,冷静调整战略,扩大战果。更有甚者,也许这种发展昭示着整个底层价值体系的变迁,对价值和利益的评判标准都在发生翻天覆地的改变,必须迅速跟上。

评论续:用纸写下来、经常回顾对照

评论续:发现错误就是感知到在应用的手段下期望(目标)和现实的差距很大。“差距”引发的情绪的唯一有利的作用是报错。首先检验真实目标是否与期望相符,手段与目标是否匹配,再检验手段的执行过程和时机是否有瑕疵,最后检验手段从【手段集】中被选择的过程以及其他手段被放弃的过程。

  1. 高中的一句错话造成的误解

    (1) 为什么会说那句话

    ​ 这是高中时候的一件事,当时的情况是这样的,我因为初中基础好,经常被前后左右的同学问问题,给他们讲题。其实我本身并不反感这件事,尽管问题这件事的人员和频率已经快频繁到影响我自己的学习和作业进度了。后面的女同学说,“你怎么什么都会啊”,我当时脑子空空的,不禁夸,还是个害羞的小伙子,没想好说什么,什么都不说又显得太尴尬,就摆摆手,“谦虚”地说,“我啥都不会”,说完就后悔了。当时真的就是谦虚一下下啊,没别的任何想法。

    ​ 当时我已经意识到这句话会让周围的人都产生误解,尤其是经常问问题的那几个同学,“这人怕不是说我问问题影响他学习吧!”,她/他们当时的内心OS大概是这个样子的吧~!

    (2) 说了之后为什么没有及时弥补

    ​ 说了之后第一反应是想弥补的,说点什么别的啥,搞笑啊或者别的什么,又怕和女生说这些不太好,比较大一刚开学,还都不是很熟悉。急的都快出汗了,最终还是什么都没说~~

    (3) 以后如何避免

    没想好说什么,不要因为需要说些话而说些话,不要怕沉默的尴尬。

    ​ 题外话:当时刚上高中,从初中数学到高中数学的跨度比较大,很多人都不太适应,觉得学不会学不懂,我也一样。鉴于“我数学很好”和身为数学课代表的自尊心,当时每天晚上回到宿舍都会拿着当时的错题本过一遍,看看错哪里了,这个对我晚上熟悉当天的学习内容很有帮助,这个习惯是从一个初中同学那里学到的,只是后来因为懒惰,没有坚持下去。

  2. 大学没有好好学习基础知识

  3. 不懂主动追求异性

    已婚的我还是不写这个模块了-_-||

  4. 大学入门安卓之后没有继续深入的学习

  5. 研究生没有形成良好的学习和科研习惯

  6. 工作伊始,没有持续保持良好的工作习惯

一种是通过vue-router/activated保存和恢复组件的整体滚动条位置,一种是通过deactivated/activated保存组件内部某个组件的滚动条位置

阅读全文 »

  1. 需求讨论时,不能从全局思考,会导致最终在编码时出现很多未知的问题,需要去返回去重新讨论

  2. 需求讨论时,如果能用流程图绘制,应该会少遗漏一些东西

  3. 闭环的思考问题:不能仅仅想到当前这一个点,还要想到这个点之后,这个点之前,这个点关联的点

    ​ 例如:一个显示界面,界面上的每段文字关联的逻辑,界面的下一个界面关联的逻辑,这个界面逻辑执行完毕后的其他业务操作逻辑。