向灵神学算法第二课:基础快刷

按照灵神的建议,基础快刷的内容是这里面的几十道题:编程基础0到1。历经十天也是终于刷完了。

本篇就来总结一下这里面值得探讨或学习的题目。
第一题:1768. 交替合并字符串
这题虽然简单,但我第一次做的时候就是没搞清楚循环的逻辑,导致最终连正确结果都没做出来。实际上,他两个字符串只需满足有一个里面不为空,就可以继续遍历。因此首先是选while循环,其次判断循环的条件是if 第一个字符串不为空 or 第二个字符串不为空, 在这个if里面 再去判断谁不为空。对于当时没做出来这题的我来说,这个判断方式属于是查漏补缺了。完整代码如下:

class Solution: # Python3
    def mergeAlternately(self, word1: str, word2: str) -> str:
        ans = []

        i, m, n = 0, len(word1), len(word2)
        
        while i < m or i < n:
            if i < m:
                ans.append(word1[i])
            if i < n:
                ans.append(word2[i])
            i += 1
        
        return "".join(ans)

时空复杂度都是O(m+n)的

第二题:389. 找不同
这题印象极其深刻,因为是第一次用位运算秒杀题目。因为题目是把一个字符串打乱顺序之后再额外加一个字母,所以利用异或就可以把相同的字母消除掉,无论是什么顺序。同时也学会了ord()是把字符串转换为ascii码,然后chr()是把ascii值转换回字符串。

class Solution: # Python3
    def findTheDifference(self, s: str, t: str) -> str:
        k, ans = s + t, 0
        for ch in k:
            ans ^= ord(ch)
        return chr(ans)
        

时空复杂度都是O(n)的,n是两个字符串长度之和。

第三题:28. 找出字符串中第一个匹配项的下标
这题我还是选择先用最好想的方法做出来,KMP算法以后再学吧哈哈。
那还是比较好想的了,就是遍历长串,遇到和目标串第一个字母相同的就开始遍历后面的部分是否相同,如果都相同就是找到了,如果有不相同的就break掉,然后继续下一个字母的遍历。完整代码如下:

class Solution: # Python3
    def strStr(self, haystack: str, needle: str) -> int:
        # haystack 里 有 needle
        if(len(haystack) < len(needle)): 
            return -1

        for i in range(len(haystack)):
            if haystack[i] == needle[0]:
                k = i
                for j in range(len(needle)):
                    if k == len(haystack) or haystack[k] != needle[j]:
                        break
                    k += 1
                else:
                    return i

        return -1

时间复杂度:O(m * n)。 因为遍历长串是m,然后对于m遍历的每个结果都可能会把目标串遍历一遍,也就是n。所以是m * n。
空间复杂度:O(1)

第四题:242. 有效的字母异位词
用一个数组来存每个字母的出现次数,然后遍历另一个数组的时候把统计的次数剪掉,最后看数组里的次数是不是都是0。全部代码如下:

class Solution: # Python3
    def isAnagram(self, s: str, t: str) -> bool:
        cnt = [0] * 26
        for c in s:
            cnt[ord(c) - ord('a')] += 1
        for c in t:
            cnt[ord(c) - ord('a')] -= 1
        return all(c == 0 for c in cnt)

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

第五题:283. 移动零
双指针:non_0指针表示的是非零元素所放的位置,current是遍历每个元素,然后如果元素非零,就把元素放在non_0的位置,这样遍历完所有元素之后就会把非零元素按照原本的先后次序放在数组里了,然后后面的都是0了。完整代码如下:

class Solution: # Python3
    def moveZeroes(self, nums: List[int]) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        non_0, current = 0, 0
        for current in range(len(nums)):
            if nums[current] != 0:
                nums[current], nums[non_0] = nums[non_0], nums[current]
                non_0 += 1
        

时间复杂度:O(n)。
空间复杂度:O(1)。

第六题:66. 加一
这题主要是判断尾巴后面的数是否是9,因为如果是9就得进位,如果不是9就直接加一就好。

class Solution: # Python3
    def plusOne(self, digits: List[int]) -> List[int]:

        for i in range(len(digits) - 1, -1, -1):
            if digits[i] < 9:
                digits[i] += 1
                return digits
            else: 
                digits[i] = 0
        return [1] + digits

时空复杂度均为:O(n)。前者是把List遍历一遍,后者是最坏情况下要创建一个长度为n+1的列表。

第七题:1822. 数组元素积的符号
这题我一开始想的就是算出结果再判断正负,但是实际上只需用0和1就好,每次乘一个负数时就把1变成-1,这样就可以不用算乘积就可以直接算符号。完整代码如下:

class Solution: # Python3
    def arraySign(self, nums: List[int]) -> int:
        
        ans = 1
        for num in nums:
            if num == 0:
                return 0
            if num < 0:
                ans = -ans
        return ans

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

第八题:896. 单调数列
这题也是想了很久的方法也没找到合适的范围来限制所有情况,于是看解答了:用两个bool类型变量来判断是否是升或者降序列,如果两者还有True就说明是单调序列,如果两个都是False就说明不是单调序列。完整代码如下:

class Solution: # Python3
    def isMonotonic(self, nums: List[int]) -> bool:
        if len(nums) == 1:
            return True
        
        is_increasing = True
        is_decreasing = True

        for i in range(1, len(nums)):
            if nums[i] < nums[i-1]:
                is_increasing = False
            if nums[i] > nums[i-1]:
                is_decreasing = False
        return is_increasing or is_decreasing

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

第九题:58. 最后一个单词的长度
从后往前,先把后面的空格遍历完,然后再开始计数非空格的地方,直到下一个空格。

class Solution: # Python3
    def lengthOfLastWord(self, s: str) -> int:
        n = len(s)
        count = 0
        i = n - 1

        while i >= 0 and s[i] == ' ':
            i -= 1
        
        while i >= 0 and s[i] != ' ':
            count += 1
            i -= 1
        
        return count

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

第十题:709. 转换成小写字母
这题 一行就结束,但是包含的是很多句话的意思。 先看代码再解释:

class Solution: # Python3
    def toLowerCase(self, s: str) -> str:
        return "".join(chr(asc | 32) if 65 <= (asc := ord(ch)) <= 90 else ch for ch in s)

首先是对s中的字符取ascii码并赋值给asc, (这里利用的是 := 海象运算符), 然后判断这个ascii码是否在大写字母范围以内,如果是在范围以内,则 把ascii值 或 上32 就变成了小写字母的ascii码,然后用chr()变成字符串,再join到字符串里。
换句话重新说一下就是,遍历字符串s,如果是小写字母就直接加入到结果集,如果是大写字母就转换为小写然后加入结果集。大写字母怎么变成小写字母呢?那就是先获取ascii码,然后或32得到对应小写字母的ascii,再用chr()变成小写字母。
时间复杂度:O(n)。毕竟都遍历了一遍嘛
空间复杂度:O(n)。最后生成的是一个长为n的字符串

第十一题:1275. 找出井字棋的获胜者
这题一开始想的比较复杂,要判断各种情况,感觉应该会有更简单的方式。但是后来看到一种很妙的方法,是得判断各种情况,但是思路非常清晰:
首先用row、col、diag、anti_diag变量来记录行、列、斜线的占领情况,以便后面方便判断是否有人赢的棋局。
随后每下一步棋 就是根据二维数组的数据来 把结果填入到行、列、斜线中,然后判断行、列、斜线是否达到了3。同时看是哪个玩家正在下棋。
如果有到了3的,就说明当前的人赢了,因为我们是在当前玩家下完一步后里面统计并判断。
如果一直都没有人赢,就看下完步骤时,棋局是否填满。

这题我觉得很妙的地方有亮点:1. 它用player的数值在1和-1之间切换来实现玩家的转变。2. 他把胜利条件定为行、列、斜线的和为3,这是巧妙运用了第一点,因为只要在一个行、列、斜线上既有玩家A的棋,又有玩家B的棋,则在程序里就会表现为绝对值不可能到3.

class Solution: # Python3
    def tictactoe(self, moves: List[List[int]]) -> str:
        # count for wins
        row = [0] * 3
        col = [0] * 3
        diag = 0
        anti_diag = 0

        # player A = 1, player B = -1
        player = 1 

        for move in moves:
            r, c = move

            row[r] += player
            col[c] += player

            if r == c:
                diag += player
            if r + c == 2:
                anti_diag += player

            # check for wins
            if (abs(row[r]) == 3 or
                abs(col[c]) == 3 or 
                abs(diag) == 3 or
                abs(anti_diag) == 3):
                return "A" if player == 1 else "B"
            
            # switch player
            player *= -1

        if len(moves) == 9:
            return "Draw"
        else:
            return "Pending"

时间复杂度:O(n)
空间复杂度:O(1)。指运用了有限的额外变量

第十二题:1041. 困于环中的机器人
这题主要学习的有两点:1. 定义方向数组,这样就方便在判断指令后快速给不同方向赋值。同时也可以利用%4来改变方向。2. 数学结论:只有机器人既没回到原点,又方向没变,才会是不在循环内,否则一定在循环内。就不证明了。完整代码如下:

class Solution: # Python3
    def isRobotBounded(self, instructions: str) -> bool:
        direction = [(0, 1), (-1, 0), (0, -1), (1, 0)]
        cur_direction = 0
        x, y = 0, 0

        for move in instructions:
            if move == "G":
                dx, dy = direction[cur_direction]
                x += dx
                y += dy
            elif move == "L":
                cur_direction = (cur_direction - 1) % 4
            elif move == "R":
                cur_direction = (cur_direction + 1) % 4
        
        return (x, y) == (0, 0) or cur_direction != 0

时间复杂度:O(n)
空间复杂度:O(1)。 还是有限个数的变量。所以O(1)

第十三题:54. 螺旋矩阵
这题就是按照题意去做就好了,关键就在于如何去控制边界条件。我才用的是最好理解的办法:拿四个变量分别控制四个方向的边界。剩下的就是注意遍历的细节了,例如row和col分别代表什么,在某一方向的遍历里他们的数值是放在行还是列里?完整代码如下:

class Solution: # Python3
    def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
        if not matrix or not matrix[0]:
            return []
        
        m, n = len(matrix), len(matrix[0])
        top, bottom = 0, m
        left, right = 0, n
        result = []

        while top < bottom and left < right:
            for col in range(left, right):
                result.append(matrix[top][col])
            top += 1
            
            for row in range(top, bottom):
                result.append(matrix[row][right - 1])
            right -= 1

            if top < bottom:
                for col in range(right - 1, left - 1, - 1):
                    result.append(matrix[bottom - 1][col])
                bottom -= 1
            
            if left < right:
                for row in range(bottom - 1, top - 1, -1):
                    result.append(matrix[row][left])
                left += 1

        return result

时空复杂度均为O(m * n),前者因为二维数组被遍历一遍;后者因为结果集合存放的个数为m * n。

第十四题:73. 矩阵置零
这题有点意思,我一开始没做出来。后来看题解恍然大悟。
首先我们看看第一行第一列里是否有0,并且用两个布尔变量来表示。然后接下来遍历内部数据,如果找到一个0,就把它的行和列分别在 第一行、第一列 对应的位置表示为0。这样哪些行哪些列需要置为0就十分清晰了。最后根据之前存的两个布尔值来判断是否需要将第一行、第一列也置为0。 完整代码如下:

class Solution: # Python3
    def setZeroes(self, matrix: List[List[int]]) -> None:
        """
        Do not return anything, modify matrix in-place instead.
        """
        if not matrix or not matrix[0]:
            return
        m, n = len(matrix), len(matrix[0])
        isFirstRowZero = False
        isFirstColZero = False

        # check first row
        for i in range(n):
            if matrix[0][i] == 0:
                isFirstRowZero = True
                break
        
        # check first col
        for j in range(m):
            if matrix[j][0] == 0:
                isFirstColZero = True
                break

        # use the first row and col to record 0
        for i in range(1, m):
            for j in range(1, n):
                if matrix[i][j] == 0:
                    matrix[i][0] = 0
                    matrix[0][j] = 0
        
        # set rows to 0 
        for i in range(1, m):
            if matrix[i][0] == 0:
                for j in range(1, n):
                    matrix[i][j] = 0
        
        # set cols to 0
        for i in range(1, n):
            if matrix[0][i] == 0:
                for j in range(1, m):
                    matrix[j][i] = 0
        
        if isFirstRowZero:
            for i in range(n):
                matrix[0][i] = 0

        if isFirstColZero:
            for j in range(m):
                matrix[j][0] = 0

时间复杂度:O(m * n)。遍历二维数组
空间复杂度:O(1)。

第十五题:976. 三角形的最大周长
这题巧妙的点就在于在排好序后,从后往前遍历时,满足条件的第一个就一定是最大的值。

class Solution: # Python3
    def largestPerimeter(self, nums: List[int]) -> int:
        nums.sort()
        n = len(nums)
        for i in range(n - 1, 1, -1):
            if nums[i-2] + nums[i-1] > nums[i]:
                return nums[i-2] + nums[i-1] + nums[i]
        return 0

时间复杂度:O(n * log n)。 因为排序
空间复杂度:O(1)

第十六题:1232. 缀点成线
这题的收获在于,用向量的方式是做乘法毕竟结果,可以避免除法带来的 精度上的问题 以及 处理除数为0的麻烦。

class Solution: # Python3
    def checkStraightLine(self, coordinates: List[List[int]]) -> bool:
        # 方向向量
        x1 = coordinates[1][0] - coordinates[0][0]
        y1 = coordinates[1][1] - coordinates[0][1]

        for coordinate in coordinates:
            x2, y2 = coordinate
            x3 = x2 - coordinates[0][0]
            y3 = y2 - coordinates[0][1]
            if(x1 * y3 != x3 * y1):
                return False
        return True

时空复杂度: O(n)的,O(1)

第十七题:50. Pow(x, n)
本题主要学到的是灵神发布的 快速幂, 我第一次是按常规解法但是如果乘积次数太多且位数太多就会超时,因此快速幂十分有必要。

快速幂原理

针对此题的小解惑

以下就是快速幂的核心代码:

while n: # 从低到高位 逐个枚举n的比特位
            if n & 1:
                ans *= x
            x *= x
            n >>= 1

解释就是 n & 1是看n二进制的最后一位是否为1,是的话就 ans *=,不是的话就不乘。然后判断一位之后都要移一位,且x要做一次幂运算。这样就是利用快速幂的原理了。
完整代码如下:

class Solution: # Python3
    def myPow(self, x: float, n: int) -> float:
        if x == 0:
            return 0.0

        if n < 0:
            n = -n
            x = 1/x
        
        ans = 1
        while n: # 从低到高位 逐个枚举n的比特位
            if n & 1:
                ans *= x
            x *= x
            n >>= 1
        
        return ans

时间复杂度:O(log n)。因为每次右移一位,就是相对于n除以2,看n能被2除几次就是 log n。
空间复杂度:O(1)

第十八题:206. 反转链表
经典中的经典,考中之考!
搞清楚每个指针每次在指什么东西就好了,流程并不难:
pre是指向前一个节点的, cur是指向当前节点,nxt是临时节点用来临时保存cur.next的值。

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next

class Solution: # Python3
    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        pre = None
        cur = head
        while cur:
            nxt = cur.next
            cur.next = pre
            pre = cur
            cur = nxt
        return pre

时空复杂度:O(n)。O(1)。

第十九题:2. 两数相加
本题学到了1. 递归的用法:把一个问题划分为相同的但是范围更小的问题就适合用递归。2. 当L1和L2不确定谁长时,可以写成 如果L2更长就把它交换到L1,这样可以使代码简洁一点,因为后面可以避免一些不必要的判断。3. 剩下的就是两个的值加上进位carry等于总和了,然后总和s取模就是留在当前位置的数,总和s // 10 就是进位。

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next

class Solution: # Python3
    def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode], carry = 0) -> Optional[ListNode]:
        if l1 is None and l2 is None:
            return ListNode(carry) if carry != 0 else None
        
        if l1 is None:
            l1, l2 = l2, l1
        s = carry + l1.val + (l2.val if l2 else 0)
        l1.val = s % 10
        l1.next = self.addTwoNumbers(l1.next, l2.next if l2 else None, s // 10)
        return l1

时空复杂度均为O(n)。前者是长度更长的列表;后者是递归需要O(n)的栈空间。

希望对学习有所帮助!接下来应该是专题篇章了,即每次都是同一类型的题。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,265评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,078评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,852评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,408评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,445评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,772评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,921评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,688评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,130评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,467评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,617评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,276评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,882评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,740评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,967评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,315评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,486评论 2 348

推荐阅读更多精彩内容