力扣leetcode刷题记录

在一个上班摸鱼的下午,抱着练练算法和go语言基础的想法打开了力扣,并且决定坚持每天一题,于是有了这篇笔记

1.两数之和

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

你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。

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

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

示例 2:

输入:nums = [3,2,4], target = 6
输出:[1,2]

示例 3:

输入:nums = [3,3], target = 6
输出:[0,1]

方法一:哈希表

开局第一题,啥都不会,问了kimi老师,知道了哈希表这个东西,那么思路清晰:

依次访问数组的元素并和目标数计算差值,然后查看哈希表中是否有这个差值,没有就将当前元素推入哈希表,然后判断下一个...

代码实现:

func twoSum(nums []int, target int) []int {
    numMap := make(map[int]int)
    for i, num := range nums {
        cha := target - num 
        if j, ok := numMap[cha]; ok {
            return []int{j, i}
        }
        numMap[num] = i
    }
    return []int{}
}

时间复杂度o(n),空间复杂度o(1)

9.回文数

给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false

回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。

  • 例如,121 是回文,而 123 不是。

示例 1:

输入:x = 121
输出:true

示例 2:

输入:x = -121
输出:false
解释:从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。

示例 3:

输入:x = 10
输出:false
解释:从右向左读, 为 01 。因此它不是一个回文数。

方法一:转字符串+双指针

这里首先想到的是转换为字符串再继续,那么双指针法是一个很好的选择,从两头到中间来判断,省去了反转字符串的环节,并且双指针的效率也更高,另外可以将特殊情况直接判断,比如负数、0、10的倍数,代码实现:

func isPalindrome(x int) bool {
    if x < 0 {
        return false
    }
    s := strconv.Itoa(x)
    left, right := 0, len(s)-1
    for left < right {
        if s[left] != s[right] {
            return false
        }
        left ++
        right --
    }
    return true
}

时间复杂度o(n),空间复杂度o(1)

方法二:反转数字

不转换成字符串的条件下反转数字然后判断是否相等,很明显这种方法的时间复杂度更高,但可以拿来练练手:

func isPalindrome(x int) bool {
    if x < 0 && x%10 == 0 {
        return false
    }
    if x == 0 {
        return true
    }
    renum := 0
    tmp := x
    for x > 0 {
        renum = renum*10 + x%10
        x /= 10
    }
    if renum == tmp {
        return true
    }
    return false
}

时间复杂度O(Log10(N)),空间复杂度o(1)

13.罗马数字转整数

罗马数字包含以下七种字符: IVXLCDM

字符          数值
I             1
V             5
X             10
L             50
C             100
D             500
M             1000

例如, 罗马数字 2 写做 II ,即为两个并列的 1 。12 写做 XII ,即为 X + II27 写做 XXVII, 即为 XX + V + II

通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:

  • I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
  • X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。
  • C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。

给定一个罗马数字,将其转换成整数。

方法一:哈希表+正负判断

这里参考了0x3f师傅的思路,我们通过观察可以得出当左边的数小于右边则需要右减左,即相当于左边数字为负,右边数字为正,那么我们可以按照这个逻辑判断出每个罗马字符对应数字的正负,最后求和即可:

func romanToInt(s string) int {
    rom := map[byte]int{
        'I':1,
        'V':5,
        'X':10,
        'L':50,
        'C':100,
        'D':500,
        'M':1000,
    }
    re := 0
    n := len(s)
    for i := 0; i+1 < n; i++ {
        if rom[s[i]] < rom[s[i+1]] {
            re -= rom[s[i]]
        } else {
            re += rom[s[i]]
        }
    }
    return re + rom[s[n-1]]
}

时间复杂度O(n),空间复杂度O(1)

28.找出字符串中第一个匹配项的下标

给你两个字符串 haystackneedle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1

示例 1:

输入:haystack = "sadbutsad", needle = "sad"
输出:0
解释:"sad" 在下标 0 和 6 处匹配。
第一个匹配项的下标是 0 ,所以返回 0 。

示例 2:

输入:haystack = "leetcode", needle = "leeto"
输出:-1
解释:"leeto" 没有在 "leetcode" 中出现,所以返回 -1 。

提示:

  • 1 <= haystack.length, needle.length <= 104
  • haystackneedle 仅由小写英文字符组成

方法一:官方库

不讲码德的方法,直接使用官方的strings.Index(haystack, needle),一句话解决:

import "strings"
func strStr(haystack string, needle string) int {
    return strings.Index(haystack, needle)
}

时间复杂度O(n),空间复杂度O(1)

方法二:暴力匹配

很基础的方法,遍历字符串,并逐个对比是否一致,值得注意的是,当起始位置到字符串结尾长度小于needle长度,则不可能存在,若needle是空字符串,则一定存在(另外注意循环边界):

func strStr(haystack string, needle string) int {
    lenh := len(haystack)
    lenn := len(needle)
    if lenn == 0 {
        return 0
    }
    if lenn > lenh {
        return -1
    }
    for i := 0; i <= lenh - lenn ; i++ {
        for j := 0;j < lenn; j++ {
            if haystack[i+j] != needle[j] {
                break
            }
            if j == lenn - 1 {
                return i
            }
        }
    }
    return -1
}

时间复杂度O(lenh∗lenn),空间复杂度O(1)

方法三:KMP

这个是看官方题解学到的,coding思路一眼看过去已经晕了,这是我目前遇到最难的一个思路了:

KMP 算法是一个快速查找匹配串的算法,它的作用其实就是本题问题:如何快速在「原字符串」中找到「匹配字符串」,放个链接https://www.zhihu.com/question/21923021/answer/281346746

func strStr(haystack string, needle string) int {
    lenh, lenn := len(haystack), len(needle)
    if lenn == 0 {
        return 0
    }
    next := make([]int, lenn)
    next[0] = -1
    for i, j:= 0, -1; i < lenn - 1; {
        if j == -1 || needle[i] == needle[j] {
            i++
            j++
            next[i] = j 
        } else {
            j = next[j]
        }
    }
    for i, j := 0,0; i<lenh && j<lenn; {
        if j == -1 || haystack[i] == needle[j] {
            i++
            j++
        } else {
            j = next[j]
        }
        if j == lenn {
            return i - j
        }
    }
    return -1
}

时间复杂度O(lenh+lenn),空间复杂度O(lenn)

54.螺旋矩阵

给你一个 mn 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。

示例 1:

img

输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]

示例 2:

img

输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]

方法一:遍历

按照上、右、下、左四条边的顺序遍历,注意边界判断:

func spiralOrder(matrix [][]int) []int {
    if len(matrix)==0 || len(matrix[0])==0 {
        return nil
    }
    m, n := len(matrix), len(matrix[0])
    u, d := 0, m - 1
    r, l := n-1, 0
    res := make([]int, 0, m*n)
    for r>=l && u<=d {
        for i:= l; i<=r; i++{
            res = append(res,matrix[u][i])
        }
        u++
        for i:=u;i<=d; i++{
            res = append(res, matrix[i][r])
        }
        r--
        if u<=d {
            for i:=r; i>=l; i--{
                res = append(res, matrix[d][i])
            }
            d--
        }
        if l<=r {
            for i:=d;i>=u; i--{
                res = append(res, matrix[i][l])
            }
            l++
        }
    }
    return res
}

时间复杂度O(nm),空间复杂度O(nm)

58.最后一个单词的长度

给你一个字符串 s,由若干单词组成,单词前后用一些空格字符隔开。返回字符串中 最后一个 单词的长度。

单词 是指仅由字母组成、不包含任何空格字符的最大。

示例 1:

输入:s = "Hello World"
输出:5
解释:最后一个单词是“World”,长度为 5。

示例 2:

输入:s = "   fly me   to   the moon  "
输出:4
解释:最后一个单词是“moon”,长度为 4。

示例 3:

输入:s = "luffy is still joyboy"
输出:6
解释:最后一个单词是长度为 6 的“joyboy”。

方法一:反向遍历

可以从字符串最后一个字节开始判断,先遍历到不为空的字节,再开始遍历到为空的字节,中间的字节数就是长度:

func lengthOfLastWord(s string) int {
    n, m := len(s) - 1, 0
    for s[n] == ' ' {
        n--
    }
    for n >= 0 && s[n] != ' ' {
        n--
        m++
    }
    return m
}

时间复杂度O(n),空间复杂度O(1)

66.加一

给定一个表示 大整数 的整数数组 digits,其中 digits[i] 是整数的第 i 位数字。这些数字按从左到右,从最高位到最低位排列。这个大整数不包含任何前导 0

将大整数加 1,并返回结果的数字数组。

示例 1:

输入:digits = [1,2,3]
输出:[1,2,4]
解释:输入数组表示数字 123。
加 1 后得到 123 + 1 = 124。
因此,结果应该是 [1,2,4]。

示例 2:

输入:digits = [4,3,2,1]
输出:[4,3,2,2]
解释:输入数组表示数字 4321。
加 1 后得到 4321 + 1 = 4322。
因此,结果应该是 [4,3,2,2]。

示例 3:

输入:digits = [9]
输出:[1,0]
解释:输入数组表示数字 9。
加 1 得到了 9 + 1 = 10。
因此,结果应该是 [1,0]。

方法一:遍历+判断

重点在于考虑进位问题,那么可以设计一套逻辑:从最后一位向前判断是否为9,不为9就直接加一然后return结果,为9就继续判断下一位,如果均为9,则make一个第0位为1,其他为0的的新数组即可:

func plusOne(digits []int) []int {
    n := len(digits)
    for i := n-1; i >= 0; i-- {
        if digits[i] != 9{
            digits[i]++
            return digits
        }
        digits[i] = 0
    }
    digits = make([]int, n+1)
    digits[0] = 1
    return digits
}

283.移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

请注意 ,必须在不复制数组的情况下原地对数组进行操作。

示例 1:

输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]

示例 2:

输入: nums = [0]
输出: [0]

方法一:快慢指针

双指针法,使用快慢指针,均从左边出发,快指针遍历元素,如果非零,元素与慢指针位置交换,慢指针右移,为0则跳过继续遍历:

func moveZeroes(nums []int)  {
    fast, slow, n:= 0, 0, len(nums)
    for ;fast < n;fast++ {
        if nums[fast] != 0 {
            nums[slow], nums[fast] = nums[fast], nums[slow]
            slow++
        }
    }
}

时间复杂度O(n),空间复杂度O(1)

242.有效字母异位词

给定两个字符串 st ,编写一个函数来判断 t 是否是 s

示例 1:

输入: s = "anagram", t = "nagaram"
输出: true

示例 2:

输入: s = "rat", t = "car"
输出: false

方法一:哈希表

首先想到的是哈希表:

func isAnagram(s string, t string) bool {
    count := make(map[byte]int)
    if  len(s)!=len(t) {
        return false
    }
    for i:= 0; i < len(s); i++ {
        count[s[i]]++
    }
    for j:= 0; j < len(t); j++ {
        if count[t[j]]==0 {
            return false
        }
        count[t[j]]--
    }
    return true
}

389.找不同

给定两个字符串 st ,它们只包含小写字母。

字符串 t 由字符串 s 随机重排,然后在随机位置添加一个字母。

请找出在 t 中被添加的字母。

示例 1:

输入:s = "abcd", t = "abcde"
输出:"e"
解释:'e' 是那个被添加的字母。

示例 2:

输入:s = "", t = "y"
输出:"y"

方法一:哈希表

这里第一个想到的是使用哈希表,遍历s存储每个字母出现次数,再便利t查询字母出现次数,只要存在则次数减一,为0则为被添加的字母:

func findTheDifference(s string, t string) byte {
	charCount := make(map[byte]int)
    for i := 0; i < len(s); i++ {
        charCount[s[i]]++
    }
    for j := 0; j < len(t); j++ {
        if charCount[t[j]] == 0 {
            return t[j]
        }
        charCount[t[j]]--
    }
    return 0
}

时间复杂度O(n),空间复杂度O(1)

方法二:ascii码计算

这个方法是问AI问出来的,也是第一次看见这个解法,惊为天人好吧,我怎么就没想到(其实因为题目说全小写,没考虑这个了,这个方法连分大小写的情况也能秒),直接转ascii求和,然后计算前后差值即可:

func findTheDifference(s string, t string) byte {
    ascii1, ascii2 := 0,0
    for i:= 0; i < len(s); i++ {
        ascii1 += int(s[i])
    }
    for j:= 0; j < len(t); j++ {
        ascii2 += int(t[j])
    }
    res := ascii2 - ascii1
    return byte(res)
}

时间复杂度O(n),空间复杂度O(1)

459.重复的子字符串

给定一个非空的字符串 s ,检查是否可以通过由它的一个子串重复多次构成。

示例 1:

输入: s = "abab"
输出: true
解释: 可由子串 "ab" 重复两次构成。

示例 2:

输入: s = "aba"
输出: false

示例 3:

输入: s = "abcabcabcabc"
输出: true
解释: 可由子串 "abc" 重复四次构成。 (或子串 "abcabc" 重复两次构成。)

方法一:拼接判断

我一开始想到的是KMP但是很难想,然后AI给出了一个很不错的思路,把字符串重复两次拼接,然后去掉首尾字节,再在这个字符串中寻找是否有原字符串·,若存在则可以重复构成,那么代码实现很简单,一个Contains和一个切片就能搞定:

func repeatedSubstringPattern(s string) bool {
    return strings.Contains((s+s)[1:len(s+s)-1], s)
}

时间复杂度O(n),空间复杂度O(n)

657.机器人能否返回原点

在二维平面上,有一个机器人从原点 (0, 0) 开始。给出它的移动顺序,判断这个机器人在完成移动后是否在 (0, 0) 处结束

移动顺序由字符串 moves 表示。字符 move[i] 表示其第 i 次移动。机器人的有效动作有 R(右),L(左),U(上)和 D(下)。

如果机器人在完成所有动作后返回原点,则返回 true。否则,返回 false

注意:机器人“面朝”的方向无关紧要。 “R” 将始终使机器人向右移动一次,“L” 将始终向左移动等。此外,假设每次移动机器人的移动幅度相同。

示例 1:

输入: moves = "UD"
输出: true
解释:机器人向上移动一次,然后向下移动一次。所有动作都具有相同的幅度,因此它最终回到它开始的原点。因此,我们返回 true。

示例 2:

输入: moves = "LL"
输出: false
解释:机器人向左移动两次。它最终位于原点的左侧,距原点有两次 “移动” 的距离。我们返回 false,因为它在移动结束时没有返回原点。

方法一:坐标+switch

用横纵坐标xy来记录位置,switch来控制移动时的坐标变化即可:

func judgeCircle(moves string) bool {
    x, y := 0, 0
    for _, d := range moves {
        switch d {
            case 'R':
                x++
            case 'L':
                x--
            case 'U':
                y++
            case 'D':
                y--
        }
    }
    if x == 0 && y == 0 {
        return true
    }
    return false
}

时间复杂度O(n),空间复杂度O(1)

682.棒球比赛

你现在是一场采用特殊赛制棒球比赛的记录员。这场比赛由若干回合组成,过去几回合的得分可能会影响以后几回合的得分。

比赛开始时,记录是空白的。你会得到一个记录操作的字符串列表 ops,其中 ops[i] 是你需要记录的第 i 项操作,ops 遵循下述规则:

  1. 整数 x - 表示本回合新获得分数 x
  2. "+" - 表示本回合新获得的得分是前两次得分的总和。题目数据保证记录此操作时前面总是存在两个有效的分数。
  3. "D" - 表示本回合新获得的得分是前一次得分的两倍。题目数据保证记录此操作时前面总是存在一个有效的分数。
  4. "C" - 表示前一次得分无效,将其从记录中移除。题目数据保证记录此操作时前面总是存在一个有效的分数。

请你返回记录中所有得分的总和。

示例 1:

输入:ops = ["5","2","C","D","+"]
输出:30
解释:
"5" - 记录加 5 ,记录现在是 [5]
"2" - 记录加 2 ,记录现在是 [5, 2]
"C" - 使前一次得分的记录无效并将其移除,记录现在是 [5].
"D" - 记录加 2 * 5 = 10 ,记录现在是 [5, 10].
"+" - 记录加 5 + 10 = 15 ,记录现在是 [5, 10, 15].
所有得分的总和 5 + 10 + 15 = 30

示例 2:

输入:ops = ["5","-2","4","C","D","9","+","+"]
输出:27
解释:
"5" - 记录加 5 ,记录现在是 [5]
"-2" - 记录加 -2 ,记录现在是 [5, -2]
"4" - 记录加 4 ,记录现在是 [5, -2, 4]
"C" - 使前一次得分的记录无效并将其移除,记录现在是 [5, -2]
"D" - 记录加 2 * -2 = -4 ,记录现在是 [5, -2, -4]
"9" - 记录加 9 ,记录现在是 [5, -2, -4, 9]
"+" - 记录加 -4 + 9 = 5 ,记录现在是 [5, -2, -4, 9, 5]
"+" - 记录加 9 + 5 = 14 ,记录现在是 [5, -2, -4, 9, 5, 14]
所有得分的总和 5 + -2 + -4 + 9 + 5 + 14 = 27

示例 3:

输入:ops = ["1"]
输出:1

方法一:switch操作切片

逻辑很简单,一个switch就能实现,关键点在于对切片的处理逻辑,需要考虑到使用下标遍历切片时不能操作切片,应当用索引遍历,使用切片长度代替下标:

func calPoints(operations []string) int {
    a := make([]int, 0, len(operations))
    score := 0
    for _ , op := range operations {
        switch op {
            case "+":
                sum := a[len(a) - 1]+a[len(a) - 2]
                score += sum
                a = append(a, sum)
            case "D":
                doublenum := 2*a[len(a)-1]
                score += doublenum
                a = append(a, doublenum)
            case "C":
                score -= a[len(a)-1]
                a = a[:len(a)-1]
            default:
                sco,_ := strconv.Atoi(op)
                a = append(a, sco)
                score += sco
        }
    }
    return score
}

时间复杂度O(n),空间复杂度O(n)

709.转换成小写字母

给你一个字符串 s ,将该字符串中的大写字母转换成相同的小写字母,返回新的字符串。

示例 1:

输入:s = "Hello"
输出:"hello"

示例 2:

输入:s = "here"
输出:"here"

示例 3:

输入:s = "LOVELY"
输出:"lovely"

方法一:内置方法

直接用golang的内置函数即可

func toLowerCase(s string) string {
    return strings.ToLower(s)
}

时间复杂度O(n),空间复杂度O(n)

方法二:ASCII码

首先知道:

  • 大写字母 A - Z 的 ASCII 码范围为 [65,90]
  • 小写字母 a - z 的 ASCII 码范围为 [97,122]

所以大写字母的ascii码加32即为小写字母

func toLowerCase(s string) string {
    runes := []rune(s)
    for i ,ch := range runes {
        if ch <='Z' && ch >= 'A' {
            runes[i] += 32
        }
    }
    return string(runes)
}

时间复杂度O(n),空间复杂度O(n)

896.单调数列

如果数组是单调递增或单调递减的,那么它是 单调

如果对于所有 i <= jnums[i] <= nums[j],那么数组 nums 是单调递增的。 如果对于所有 i <= jnums[i] >= nums[j],那么数组 nums 是单调递减的。

当给定的数组 nums 是单调数组时返回 true,否则返回 false

示例 1:

输入:nums = [1,2,2,3]
输出:true

示例 2:

输入:nums = [6,5,4,4]
输出:true

示例 3:

输入:nums = [1,3,2]
输出:false

方法一:常规遍历

按照题目描述的方法来遍历判断即可:

func isMonotonic(nums []int) bool {
    n := len(nums)
    if n < 3{
        return true
    }
    i := 0
    if nums[i] <= nums[n-1] {
        for ; i+1 < n;i++ {
            if nums[i] > nums[j] {
                return false
            }
        } 
    }
    if nums[i] > nums[n-1] {
        for ; i+1 < n;i++{
            if nums[i] < nums[j] {
                return false
            }
        } 
    }
    return true
}

时间复杂度O(n),空间复杂度O(1)

1041.困于环中的机器人

在无限的平面上,机器人最初位于 (0, 0) 处,面朝北方。注意:

  • 北方向 是y轴的正方向。
  • 南方向 是y轴的负方向。
  • 东方向 是x轴的正方向。
  • 西方向 是x轴的负方向。

机器人可以接受下列三条指令之一:

  • "G":直走 1 个单位
  • "L":左转 90 度
  • "R":右转 90 度

机器人按顺序执行指令 instructions,并一直重复它们。

只有在平面中存在环使得机器人永远无法离开时,返回 true。否则,返回 false

示例 1:

输入:instructions = "GGLLGG"
输出:true
解释:机器人最初在(0,0)处,面向北方。
“G”:移动一步。位置:(0,1)方向:北。
“G”:移动一步。位置:(0,2).方向:北。
“L”:逆时针旋转90度。位置:(0,2).方向:西。
“L”:逆时针旋转90度。位置:(0,2)方向:南。
“G”:移动一步。位置:(0,1)方向:南。
“G”:移动一步。位置:(0,0)方向:南。
重复指令,机器人进入循环:(0,0)——>(0,1)——>(0,2)——>(0,1)——>(0,0)。
在此基础上,我们返回true。

示例 2:

输入:instructions = "GG"
输出:false
解释:机器人最初在(0,0)处,面向北方。
“G”:移动一步。位置:(0,1)方向:北。
“G”:移动一步。位置:(0,2).方向:北。
重复这些指示,继续朝北前进,不会进入循环。
在此基础上,返回false。

示例 3:

输入:instructions = "GL"
输出:true
解释:机器人最初在(0,0)处,面向北方。
“G”:移动一步。位置:(0,1)方向:北。
“L”:逆时针旋转90度。位置:(0,1).方向:西。
“G”:移动一步。位置:(- 1,1)方向:西。
“L”:逆时针旋转90度。位置:(- 1,1)方向:南。
“G”:移动一步。位置:(- 1,0)方向:南。
“L”:逆时针旋转90度。位置:(- 1,0)方向:东方。
“G”:移动一步。位置:(0,0)方向:东方。
“L”:逆时针旋转90度。位置:(0,0)方向:北。
重复指令,机器人进入循环:(0,0)——>(0,1)——>(- 1,1)——>(- 1,0)——>(0,0)。
在此基础上,我们返回true。

方法一:模拟

经过模拟可知,完成循环后,在原点或不朝北都是会原地转圈的,所以用0123表示方向,然后用两个数组记录每个方向在x和y轴上的位移即可:

func isRobotBounded(instructions string) bool {
    dirx := []int{0,1,0,-1}
    diry := []int{1,0,-1,0}
    x, y, dir := 0,0,0
    for _, c := range instructions {
        switch c {
            case 'G':
                x += dirx[dir]
                y += diry[dir]
            case 'L':
                dir = (dir+3)%4
            case 'R':
                dir = (dir+1)%4
        }
    }
    return (x==0 && y==0)||dir != 0
}

时间复杂度O(n),空间复杂度O(1)

1275.找出井字棋的获胜者

井字棋 是由两个玩家 AB3 x 3 的棋盘上进行的游戏。井字棋游戏的规则如下:

  • 玩家轮流将棋子放在空方格 (' ') 上。
  • 第一个玩家 A 总是用 'X' 作为棋子,而第二个玩家 B 总是用 'O' 作为棋子。
  • 'X''O' 只能放在空方格中,而不能放在已经被占用的方格上。
  • 只要有 3 个相同的(非空)棋子排成一条直线(行、列、对角线)时,游戏结束。
  • 如果所有方块都放满棋子(不为空),游戏也会结束。
  • 游戏结束后,棋子无法再进行任何移动。

给你一个数组 moves,其中 moves[i] = [rowi, coli] 表示第 i 次移动在 grid[rowi][coli]。如果游戏存在获胜者(AB),就返回该游戏的获胜者;如果游戏以平局结束,则返回 "Draw";如果仍会有行动(游戏未结束),则返回 "Pending"

你可以假设 moves有效(遵循 井字棋 规则),网格最初是空的,A 将先行动。

示例 1:

img

输入:moves = [[0,0],[2,0],[1,1],[2,1],[2,2]]
输出:"A"
解释:"A" 获胜,他总是先走。

示例 2:

img

输入:moves = [[0,0],[1,1],[0,1],[0,2],[1,0],[2,0]]
输出:"B"
解释:"B" 获胜。

示例 3:

img

输入:moves = [[0,0],[1,1],[2,0],[1,0],[1,2],[2,1],[0,1],[0,2],[2,2]]
输出:"Draw"
解释:由于没有办法再行动,游戏以平局结束。

主要实现两功能:计算当前玩家、记录和检验每一个三连路径上的棋子数:

func tictactoe(moves [][]int) string {
    a := [2][8]int{}
    for rd, qi := range moves {
        h, l := qi[0],qi[1]
        round := rd & 1
        a[round][h]++
        a[round][l+3]++
        if h == l {
            a[round][6]++
        }
        if h + l ==2 {
            a[round][7]++
        }
        if a[round][h] == 3 || a[round][l+3] == 3 || a[round][6] == 3 || a[round][7] == 3 {
            if round == 0 {
                return "A"
            }
            if round == 1 {
                return "B"
            }
        }
        if rd == 8 {
            return "Draw"
        }
    }
    return "Pending"
}

时间复杂度O(n),空间复杂度O(1)

1502.判断能否形成等差数列

给你一个数字数组 arr

如果一个数列中,任意相邻两项的差总等于同一个常数,那么这个数列就称为 等差数列

如果可以重新排列数组形成等差数列,请返回 true ;否则,返回 false

示例 1:

输入:arr = [3,5,1]
输出:true
解释:对数组重新排序得到 [1,3,5] 或者 [5,3,1] ,任意相邻两项的差分别为 2 或 -2 ,可以形成等差数列。

示例 2:

输入:arr = [1,2,4]
输出:false
解释:无法通过重新排序得到等差数列。

方法一:排序+差值判断

最简单的方法就是排序后用差来判断,另外当数组长度小于2时一定不为等差:

func canMakeArithmeticProgression(arr []int) bool {
    if len(arr)<2{
        return false
    }
    sort.Ints(arr)
    complement := arr[1] - arr[0]
    for i := 1; i<len(arr)-1; i++ {
        if arr[i] + complement != arr[i+1] {
            return false
        }
    }
    return true
}

时间复杂度 O(nlog n),空间复杂度 O(1)

方法二:算差值+哈希表

如果还想要更快,这个方法是个很好的思路,首先遍历得到极值和存储出现次数的哈希表,然后通过计算公差,不为整数就false,接下来通过最小值和公差来算元素并进入哈希表查询,查询不到则false(缺点是空间复杂度高):

func canMakeArithmeticProgression(arr []int) bool {
    n := len(arr)
    if n < 2 {
        return false
    }
    max, min := arr[0], arr[0]
    nums := make(map[int]int)
    for i:=0; i < n; i++ {
        if arr[i] > max {
            max = arr[i]
        }
        if arr[i] < min {
            min = arr[i]
        }
        nums[arr[i]]++
    }
    if (max - min)%(n-1) != 0 {
        return false
    }
    step := (max - min)/(n-1)
    for i:=0; i < n-1; i++{
        if nums[min] == 0 {
            return false
        }
        nums[min]--
        min += step
    }
    return true
}

时间复杂度O(n),空间复杂度O(n)

1572.矩阵对角线元素的和

给你一个正方形矩阵 mat,请你返回矩阵对角线元素的和。

请你返回在矩阵主对角线上的元素和副对角线上且不在主对角线上元素的和。

示例 1:

img

输入:mat = [[1,2,3],
         [4,5,6],
         [7,8,9]]
输出:25
解释:对角线的和为:1 + 5 + 9 + 3 + 7 = 25
请注意,元素 mat[1][1] = 5 只会被计算一次。

示例 2:

输入:mat = [[1,1,1,1],
         [1,1,1,1],
         [1,1,1,1],
         [1,1,1,1]]
输出:8

示例 3:

输入:mat = [[5]]
输出:5

先把两个对角线的遍历相加,然后判断lenth是否为奇数,为奇数则减去中间的数即可:

func diagonalSum(mat [][]int) int {
    sum := 0
    n := len(mat)
    mid := n/2
    for i:=0;i < n;i++ {
        sum += mat[i][i]+mat[i][n-1-i]
    }
    if n&1 == 1 {
        sum -= mat[mid][mid]
    }
    return sum
}

时间复杂度O(n),空间复杂度O(1)

1672.最富有客户的资产总量

给你一个 m x n 的整数网格 accounts ,其中 accounts[i][j] 是第 i 位客户在第 j 家银行托管的资产数量。返回最富有客户所拥有的 资产总量

客户的 资产总量 就是他们在各家银行托管的资产数量之和。最富有客户就是 资产总量 最大的客户。

示例 1:

输入:accounts = [[1,2,3],[3,2,1]]
输出:6
解释:
第 1 位客户的资产总量 = 1 + 2 + 3 = 6
第 2 位客户的资产总量 = 3 + 2 + 1 = 6
两位客户都是最富有的,资产总量都是 6 ,所以返回 6 。

示例 2:

输入:accounts = [[1,5],[7,3],[3,5]]
输出:10
解释:
第 1 位客户的资产总量 = 6
第 2 位客户的资产总量 = 10 
第 3 位客户的资产总量 = 8
第 2 位客户是最富有的,资产总量是 10

示例 3:

输入:accounts = [[2,8,7],[7,1,3],[1,9,5]]
输出:17

方法一:遍历

没什么好说的,就是遍历、比较、求最值一把梭哈:

func maximumWealth(accounts [][]int) int {
    max := 0
    for _, cust := range accounts {
        sum := 0
        for _, money := range cust {
            sum += money
        }
        if sum > max {
            max = sum
        }
    }
    return max
}

1768.交替合并字符串

给你两个字符串 word1word2 。请你从 word1 开始,通过交替添加字母来合并字符串。如果一个字符串比另一个字符串长,就将多出来的字母追加到合并后字符串的末尾。

返回 合并后的字符串

示例 1:

输入:word1 = "abc", word2 = "pqr"
输出:"apbqcr"
解释:字符串合并情况如下所示:
word1:  a   b   c
word2:    p   q   r
合并后:  a p b q c r

示例 2:

输入:word1 = "ab", word2 = "pqrs"
输出:"apbqrs"
解释:注意,word2 比 word1 长,"rs" 需要追加到合并后字符串的末尾。
word1:  a   b 
word2:    p   q   r   s
合并后:  a p b q   r   s

示例 3:

输入:word1 = "abcd", word2 = "pq"
输出:"apbqcd"
解释:注意,word1 比 word2 长,"cd" 需要追加到合并后字符串的末尾。
word1:  a   b   c   d
word2:    p   q 
合并后:  a p b q c   d

方法一:for循环

相当简单的一个题目了,首先想到的就是循环,然后用指针指定字符串的每个byte进行拼接,然后判断条件为指针不超过字符串长度:

func mergeAlternately(word1 string, word2 string) string {
   s := 0
   res := ""
   for s < len(word1) || s < len(word2) {
    if s < len(word1) {
        res += string(word1[s])
    }
    if s < len(word2) {
        res += string(word2[s])
    }
    s++
   }
   return res
}

时间复杂度O(n),空间复杂度O(len(word1) + len(word2))

1822.数组元素积的符号

已知函数 signFunc(x) 将会根据 x 的正负返回特定值:

  • 如果 x 是正数,返回 1
  • 如果 x 是负数,返回 -1
  • 如果 x 是等于 0 ,返回 0

给你一个整数数组 nums 。令 product 为数组 nums 中所有元素值的乘积。

返回 signFunc(product)

示例 1:

输入:nums = [-1,-2,-3,-4,3,2,1]
输出:1
解释:数组中所有值的乘积是 144 ,且 signFunc(144) = 1

示例 2:

输入:nums = [1,5,0,2,-3]
输出:0
解释:数组中所有值的乘积是 0 ,且 signFunc(0) = 0

示例 3:

输入:nums = [-1,1,-1,1,-1]
输出:-1
解释:数组中所有值的乘积是 -1 ,且 signFunc(-1) = -1

方法一:

也是一个很基础的逻辑判断:数组中有0就返回0,然后看负数的数量,为奇数则返回-1,否则返回1:

func arraySign(nums []int) int {
    count := 0
    for i:= 0; i < len(nums); i++ {
        if nums[i] == 0 {
            return 0
        }
        if nums[i] < 0 {
            count++
        }
    }
    if count%2 == 0 {
        return 1
    }
    return -1
}

时间复杂度O(n),空间复杂度O(1)

3516.找到最近的人

给你三个整数 xyz,表示数轴上三个人的位置:

  • x 是第 1 个人的位置。
  • y 是第 2 个人的位置。
  • z 是第 3 个人的位置,第 3 个人 不会移动

第 1 个人和第 2 个人以 相同 的速度向第 3 个人移动。

判断谁会 到达第 3 个人的位置:

  • 如果第 1 个人先到达,返回 1 。
  • 如果第 2 个人先到达,返回 2 。
  • 如果两个人同时到达,返回 0

根据上述规则返回结果。

示例 1:

输入: x = 2, y = 7, z = 4

输出: 1

解释:

  • 第 1 个人在位置 2,到达第 3 个人(位置 4)需要 2 步。
  • 第 2 个人在位置 7,到达第 3 个人需要 3 步。

由于第 1 个人先到达,所以输出为 1。

示例 2:

输入: x = 2, y = 5, z = 6

输出: 2

解释:

  • 第 1 个人在位置 2,到达第 3 个人(位置 6)需要 4 步。
  • 第 2 个人在位置 5,到达第 3 个人需要 1 步。

由于第 2 个人先到达,所以输出为 2。

示例 3:

输入: x = 1, y = 5, z = 3

输出: 0

解释:

  • 第 1 个人在位置 1,到达第 3 个人(位置 3)需要 2 步。
  • 第 2 个人在位置 5,到达第 3 个人需要 2 步。

由于两个人同时到达,所以输出为 0。

方法一:比较绝对值

思路很简单直接计算差的绝对值即可,值得注意的是go的内置方法math.Abs中的参数必须是float64类型:

func findClosest(x int, y int, z int) int {
    dxz, dyz := int(math.Abs(float64(z-x))), int(math.Abs(float64(z-y)))
    if dxz > dyz {
        return 2
    }
    if dxz < dyz {
        return 1
    }
    return 0
}

时间复杂度O(1),空间复杂度O(1)

posted @ 2025-09-07 18:37  Yuy0ung  阅读(54)  评论(0)    收藏  举报