LeetCode刷题笔记【一】——Python

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/BreakingDawn0/article/details/102759572

#002.两数相加2019/10/15

Leetcode add-two-numbers

    题目考察单链表操作,包括获取链表节点和创建新的链表。

class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None

	
class Solution:
    def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
        """两数相加,返回由和的各位数字组成的链表"""
        sum_head = ListNode(0)
        sum_tail = sum_head
        carry = 0

        while l1 or l2:
            num1 = l1.val if l1 else 0
            num2 = l2.val if l2 else 0

            res = num1 + num2 + carry
            carry = 0 if res < 10 else 1
            sum_tail.next = ListNode(res % 10)
            sum_tail = sum_tail.next

            if l1:
                l1 = l1.next
            if l2:
                l2 = l2.next
        if carry == 1:
            sum_tail.next = ListNode(1)
        return sum_head.next

    链表由表头和节点组成,节点分为数据域和指针域,数据域中存贮数据元素,指针域存储下个结点的地址。获取单链表的节点要从头结点开始,依次获取下一个节点;创建链表可以采用尾插的方式,sum_head属性存储链表头结点,插入新节点ListNode时,当前链表尾节点的next属性改为ListNode对象。

    按照本题的代码模板,并没有链表类来实现链表的各种操作,最终的到的其实是一个next属性指向链表头结点的哑结点。

    实现一个单链表:

    1.创建节点类Node和链表类Linklist,Node类中包含val属性存储数据,next属性存储下个节点的地址;Linklist类中包含head属性存储头结点,和一系列链表操作方法。

    2.尾插节点从head开始取next属性,直到next=None到达当前链表的尾节点,将其next属性指向新节点的地址。

    3.获取节点val时,从head开始区next属性,直到到达索引位置,返回该节点出的val。

    4.删除节点时,需要将待删节点之前一个节点的next属性指向待删节点之后的一个节点,即可完成待删节点的删除;若删除头结点,只需将head属性指向head.next(即原链表的第二个节点)

    5.清空链表只需要令头结点head为None。

class Node:
    def __init__(self, val):
        self.val = val
        self.next = None


class LinkList():
    def __init__(self):
        self.head = None
        self.length = 0

    def is_empty(self):
        """链表为空?"""
        return self.length == 0

    def append(self, val_or_node):
        """尾插节点"""
        new_node = None
        if isinstance(val_or_node, Node):
            new_node = val_or_node
        else:
            new_node = Node(val_or_node)

        if self.is_empty():
            self.head = new_node
            self.length += 1
        else:
            node = self.head
            while node.next:
                node = node.next
            node.next = new_node
            self.length += 1

    def get_val(self, index: int):
        """获取指定索引的节点元素"""
        if type(index) is int:
            if index >= self.length or index < 0:
                print("Index is out of index")
                return
            else:
                node = self.head
                while index:
                    node = node.next
                return node.val
        else:
            print("Index is not int")
            return

    def del_node(self, index: int):
        """删除指定索引位置的节点"""
        if type(index) is int:
            if index >= self.length or index < 0:
                print("Index is out of index")
                return
            else:
                if index == 0:
                    self.head = self.head.next
                else:
                    node = self.head
                    while index > 1:
                        node = node.next
                        index -= 1
                    node.next = node.next.next
            self.length -= 1
            return
        else:
            print("Index is not int")

    def clear(self):
        """清空链表"""
        self.head = None
        self.length = 0
        print("Clear LinkList")


class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None


class Solution:
    def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
        """两数相加,返回由和的各位数字组成的链表"""
        sum_head = ListNode(0)
        sum_tail = sum_head
        carry = 0

        while l1 or l2:
            num1 = l1.val if l1 else 0
            num2 = l2.val if l2 else 0

            res = num1 + num2 + carry
            carry = 0 if res < 10 else 1
            sum_tail.next = ListNode(res % 10)
            sum_tail = sum_tail.next

            if l1:
                l1 = l1.next
            if l2:
                l2 = l2.next
        if carry == 1:
            sum_tail.next = ListNode(1)
        return sum_head.next

#005.最长回文子串2019/10/15

leetcode longest-palindromic-substring

    题目考察字符串操作,对于最长回文字串问题可以采用中间扩展算法求解。 

class Solution:
    def longestPalindrome(self, s: str) -> str:
        """返回字符串s中的最长回文子串"""
        str_start, str_end = 0, 0
        if (s == None) or (len(s) < 1):
            return ''
        for index in range(len(s)):
            str_len1 = get_around_center(s, index, index)
            str_len2 = get_around_center(s, index, index + 1)
            str_len = max(str_len1, str_len2)
            if str_len > str_end - str_start:
                str_start = index - int((str_len - 1) / 2)
                str_end = index + int(str_len / 2)
        return s[str_start:str_end + 1]


def get_around_center(string, left_index, right_index) -> int:
    """中心扩展 返回回文子串长度"""
    left, right = left_index, right_index
    while (left >= 0) and (right < len(string)) \
            and (string[left] == string[right]):
        left -= 1
        right += 1
    return right - left - 1

中心扩展:

   由于回文子串是镜像对称的,因此可以每次循环选则一个中心,同时向左和向右扩展索引,判断左右索引对应的字符是否相同。这里特别需要注意,对于一个长度为n的字符串,有2n-1个中心,这是因为有回文串可能有奇数个字符或偶数的字符,如图所示。

#006.字符串转换整数2019/10/16

Leetcode string-to-integer-atoi

    题目考察正则表达式在字符串处理中的的应用(第一次解题时使用了暴力解法)

class Solution:
    def myAtoi(self, str: str) -> int:
        curr_index = 0  # 当前字符索引
        res = 0  # 结果
        sign = 1  # 符号,1正号,-1负号

        # 获得第一个非空字符索引
        while curr_index < len(str) and str[curr_index] == ' ':
            curr_index += 1
        if curr_index == len(str):
            return 0
        if str[curr_index] == '-':
            sign = -1
            curr_index += 1
        elif str[curr_index] == '+':
            sign = 1
            curr_index += 1
        while (curr_index < len(str)) and (str[curr_index] >= '0') \
                and (str[curr_index] <= '9'):
            res = res * 10 + int(str[curr_index])
            curr_index += 1
            if res >= 2147483648:
                if sign == 1:
                    return 2147483647
                elif sign == -1:
                    return -2147483648
        return res * sign

    在处理较复杂的字符串索引和匹配问题时,用暴力解法需要很多循环和if条件语句,这不是一种好的方法。计算机科学中正则表达式语言是一种专门用于字符串处理的语言,它使用一种数学算法来解决计算机程序中的文本检索,匹配等问题(re 模块使 Python 语言拥有全部的正则表达式功能)。

    检索:通过正则表达式,从字符串中获取我们想要的部分

    匹配:判断给定的字符串是否符合正则表达式的过滤逻辑

    可以理解为正则表达式表述了一个字符串的书写规则,如:判断用户输入的密码是否合法,判断用户输入的邮箱格式是否合法。

    正则表达式就是由普通字符以及特殊字符组成(成为元字符)的文字模式。该模式描述在查找文字主体时待匹配的一个或多个字符串。

    下面是整理的一些元字符及示例:

元字符

描述

\

将下一个字符标记为一个特殊字符、或一个原义字符、或一个向后引用、或一个八进制转义符。例如,“\\n”匹配\n。“\n”匹配换行符。序列“\\”匹配“\”而“\(”则匹配“(”。即相当于多种编程语言中都有的“转义字符”的概念。

^

匹配输入字符串的开始位置。如果设置了RegExp对象的Multiline属性,^也匹配“\n”或“\r”之后的位置。

$

匹配输入字符串的结束位置。如果设置了RegExp对象的Multiline属性,$也匹配“\n”或“\r”之前的位置。

*

匹配前面的子表达式任意次。例如,zo*能匹配“z”,“zo”以及“zoo”。*等价于{0,}。

+

匹配前面的子表达式一次或多次(大于等于1次)。例如,“zo+”能匹配“zo”以及“zoo”,但不能匹配“z”。+等价于{1,}。

?

匹配前面的子表达式零次或一次。例如,“do(es)?”可以匹配“do”或“does”中的“do”。?等价于{0,1}。

?

当该字符紧跟在任何一个其他限制符(*,+,?,{n},{n,},{n,m})后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串“oooo”,“o+?”将匹配单个“o”,而“o+”将匹配所有“o”。

.

匹配除“\r\n”之外的任何单个字符。要匹配包括“\r\n”在内的任何字符,请使用像“[\s\S]”的模式。

x|y

匹配x或y。例如,“z|food”能匹配“z”或“food”或"zood"(此处请谨慎)。“(z|f)ood”则匹配“zood”或“food”。

[xyz]

字符集合。匹配所包含的任意一个字符。例如,“[abc]”可以匹配“plain”中的“a”。

[^xyz]

负值字符集合。匹配未包含的任意字符。例如,“[^abc]”可以匹配“plain”中的“plin”。

[a-z]

字符范围。匹配指定范围内的任意字符。例如,“[a-z]”可以匹配“a”到“z”范围内的任意小写字母字符。

注意:只有连字符在字符组内部时,并且出现在两个字符之间时,才能表示字符的范围; 如果出字符组的开头,则只能表示连字符本身.

[^a-z]

负值字符范围。匹配任何不在指定范围内的任意字符。例如,“[^a-z]”可以匹配任何不在“a”到“z”范围内的任意字符。

\b

匹配一个单词边界,也就是指单词和空格间的位置(即正则表达式的“匹配”有两种概念,一种是匹配字符,一种是匹配位置,这里的\b就是匹配位置的)。例如,“er\b”可以匹配“never”中的“er”,但不能匹配“verb”中的“er”。

\B

匹配非单词边界。“er\B”能匹配“verb”中的“er”,但不能匹配“never”中的“er”。

\d

匹配一个数字字符。等价于[0-9]。

\D

匹配一个非数字字符。等价于[^0-9]。

\f

匹配一个换页符。等价于\x0c和\cL。

\n

匹配一个换行符。等价于\x0a和\cJ。

\r

匹配一个回车符。等价于\x0d和\cM。

\s

匹配任何不可见字符,包括空格、制表符、换页符等等。等价于[ \f\n\r\t\v]。

\S

匹配任何可见字符。等价于[^ \f\n\r\t\v]。

\t

匹配一个制表符。等价于\x09和\cI。

\v

匹配一个垂直制表符。等价于\x0b和\cK。

\w

匹配包括下划线的任何单词字符。类似但不等价于“[A-Za-z0-9_]”,这里的"单词"字符使用Unicode字符集。

\W

匹配任何非单词字符。等价于“[^A-Za-z0-9_]”。

|

将两个匹配条件进行逻辑“或”(Or)运算。例如正则表达式(him|her) 匹配"it belongs to him"和"it belongs to her",但是不能匹配"it belongs to them."。注意:这个元字符不是所有的软件都支持的。

+

匹配1或多个正好在它之前的那个字符。例如正则表达式9+匹配9、99、999等。注意:这个元字符不是所有的软件都支持的。

?

匹配0或1个正好在它之前的那个字符。注意:这个元字符不是所有的软件都支持的。

 

    题目的正则表达式解法如下:

class Solution:
    def myAtoi(self, s: str) -> int:
        return max(
            min(int(*re.findall('^[\+\-]?\d+', s.lstrip())), 2 ** 31 - 1), -2 ** 31)

    分析:

    ^ 匹配字符串开头

    \+\- 表示加减字符

    [] 匹配方括号内的任一个字符,[\+\-]即匹配加号或减号

    ? 前一个字符可有可无

    \d 匹配数字字符0~9

    + 匹配前面的一个或多个

    整个pattern = '^[\+\-]?\d+'表示从字符串开始,匹配加号或减号(可以没有),并匹配数字字符0~9,直到匹配结果不是数字字符。

 

    re.findall(pattern, string) 返回string中所有与pattern(正则表达式)不重叠匹配项的列表

    *将re.findall返回的列表打散为元素(如res=[1, 3], *res输出为1 3),同样*也用于将元祖、字典、字符串打散

    int()将结果转为整型并用max(),min()防止越界,满足题目要求(实际上python中不存在32位int类型,不会有越界的问题)

#010.正则表达式匹配2019/10/16

Leetcode regular-expression-matching

    题目考察对正则表达式的理解(*和.),以及递归思想的理解。

   

class Solution:
    def isMatch(self, s: str, p: str) -> bool:
        # 以规则p是否为空,可以分为两种情况
        if len(p) == 0:
            return not len(s)
        # 递归问题考虑好当下(第一个字符能否匹配),其余交给递归
        first_match = len(s) != 0 and p[0] in [s[0], '.']

        # 考虑到若p的第一个字符之后是*,会存在两种情况
        if len(p) >= 2 and p[1] == '*':
            # p[1]是*,且s与p首字符不匹配,那么跳过p的*,继续用s与剩余的p匹配
            # p[1]是*,且s与p首字符匹配,那么从s的下一个字符与p匹配
            return self.isMatch(s, p[2:]) \
                   or first_match and self.isMatch(s[1:], p)
        # 若p的第一个字符之后不是*,则只要首字符匹配,就匹配s和p的剩余字符
        else:
            return first_match and self.isMatch(s[1:], p[1:])

    递归包括递归条件(什么情况函数调用自己)和基线条件(什么情况停止调用自己)。

 

#007.整数反转2019/10/17

Leetcode reverse-integer

    题目考察数字界限、对算数运算符操作的理解以及弹出和推入数字的方法(最开始的想法是数字转字符列表弹出推入)

class Solution:
    def reverse(self, x: int) -> int:
        val, res = abs(x), 0
        # x正负
        sign = 1 if x >= 0 else 0
        # 弹出和推入数字
        while val > 0:
            res = res * 10 + val % 10
            if (res > 2147483647 and sign == 1) \
                    or (res > 2147483648 and sign == 0):
                return 0
            val //= 10
        return res if sign == 1 else -res

    弹出x最后一位,推入res的后面就可以实现x和res相反,在不使用辅助堆栈和数组的情况下,可以使用数学方法,利用取模和取整运算实现这一过程。

    需要注意的是Python中,取整运算支持浮点数,是向下取整(而不是C中的向0取整),并且//才是取整运算,而/在Python2.x与3.x中的结果不同。

    向下取整:-5//2=-3,5/2=2,C向0取整:-5/2=-2,5/2=2

    Python中 2.x中x/y,x、y为整数,返回整数;x、y有浮点数,返回浮点数;3.x中不论x、y是整数或浮点数都会返回浮点数。而Python2.x和3.x中x//y的结果一致,x、y为整数返回整数,x、y有浮点数返回浮点数。

    5 / -2

    #2.X 商:-3

    #3.X 商:-2.5

 

    5.0 / -2

    #2.X 商:-2.5

    #3.X 商:-2.5

 

    -5 // 2

    #2.X 商:-3

    #3.X 商:-3

 

    -5.0 // 2

    #2.X 商:-3.0 

    #3.X 商:-3.0

#012.整数转罗马数字2019/10/17

Leetcode integer-to-roman

    题目考察贪心算法的基本思想

class Solution:
    def intToRoman(self, num: int) -> str:
        roma_val = (('M', 1000), ('CM', 900), ('D', 500), ('CD', 400),
                    ('C', 100), ('XC', 90), ('L', 50), ('XL', 40),
                    ('X', 10), ('IX',9), ('V', 5), ('IV', 4), ('I', 1))
        res = ''
        for roma, val in roma_val:
            # 贪心选择
            while num >= val:
                res += roma
                num -= val
        return res

    贪心算法以迭代的方式作出相继的贪心选择,每作一次贪心选择就将所求问题简化为规模更小的子问题。对于一个具体问题,要确定它是否具有贪心选择性质,必须证明每一步所作的贪心选择的局部最优解最终导致问题的整体最优解。

    在本题中,将数字转化为罗马数字的过程,就是将roma_val中的数字作为分解因子,用尽可能少的分解因子去分解一个整数的过程。每次循环中,都在剩余数字中选择尽可能大的数字去作为分解因子(贪心法则)。

#001.两数之和2019/10/18

Leetcode two-sum

    本题考查列表及其操作。如果使用字典模拟哈希表可以极大的提高效率。

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        for index in range(len(nums)):
            if target - nums[index] in nums[index + 1:]:
                return [index, nums.index(target - nums[index], index + 1)]

    python列表的index(obj)方法可以从列表中找出某个值第一个匹配项的索引位置。

    上述方法本质是暴力解法,虽然通过切片减少了一部分循环,但效率仍比较低。通过Python中的字典实现哈希表(一个用于存储Key-Value键值对的集合)结构,可以提高执行效率。

    为了找到列表元素对应的索引,可以用enumerate()函数,它用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标。

    seq = ['one', 'two', 'three']

    for index, element in enumerate(seq):

   

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        hashmap = {}
        for index in range(len(nums)):
            # 用字典创建哈希表
            if hashmap.get(target - nums[index]) is not None:
                return [hashmap.get(target - nums[index]), index]
            hashmap[nums[index]] = index

#013.罗马数字转整数2019/10/18

Leetcode roman-to-integer

    题目考察字符串处理,这里可以用查表的方法实现转化。

class Solution:
    def romanToInt(self, s: str) -> int:
        hashmap = {'I': 1, 'IV': 4, 'V': 5, 'IX': 9, 'X': 10, 'XL': 40, 'L': 50,
                   'XC': 90, 'C': 100, 'CD': 400, 'D': 500, 'CM': 900,
                   'M': 1000}
        res, index = 0, 0
        while index < len(s):
            if hashmap.get(s[index:index + 2]) is not None:
                res += hashmap[s[index:index + 2]]
                index += 2
            else:
                res += hashmap[s[index]]
                index += 1
        return res

    为什么这里用字典创建表,而在#012题中不用?因为#012题创建表时是按值从大到小的顺序排列,为了便于接下来从头到尾顺序访问。而这里如果用列表或元组创建表,接下来查找罗马数字就需要遍历,时间复杂复杂度是O(n);而用字典创建表,通过键(待查找的罗马数字)可以直接访问其值,不需要遍历,时间复杂度是O(1)。

#021.合并两个有序链表2019/10/19

Leetcode merge-two-sorted-lists

    题目考察链表操作和递归

class Solution:
    def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
        if l1 and l2:
            if l1.val < l2.val:
                l1.next = self.mergeTwoLists(l1.next, l2)
            else:
                temp = l1
                l1 = l2
                l2 = temp
                l1.next = self.mergeTwoLists(l1.next, l2)

        return l1 or l2

    再次对递归进行一次理解,引用一位博主的例子:

    递归:你打开面前这扇门,看到屋里面还有一扇门(这门可能跟前面打开的门一样大小(静),也可能门小了些(动)),你走过去,发现手中的钥匙还可以打开它,你推开门,发现里面还有一扇门,你继续打开,。。。, 若干次之后,你打开面前一扇门,发现只有一间屋子,没有门了。 你开始原路返回,每走回一间屋子,你数一次,走到入口的时候,你可以回答出你到底用这钥匙开了几扇门。 递归就是有去(递去)有回(归来)。

    具体来说,为什么可以”有去“?

    这要求递归的问题需要是可以用同样的解题思路来回答除了规模大小不同其他完全一样的问题。

    为什么可以”有回“?

    这要求这些问题不断从大到小,从近及远的过程中,会有一个终点,一个临界点,一个baseline,一个你到了那个点就不用再往更小,更远的地方走下去的点,然后从那个点开始,原路返回到原点。

    本例中,将问题转化为“l1,l2当前头节点元素值较小的是l1吗”这样一个子问题,接着从链表下一个节点开始继续解决这个子问题(递),直到到达任一链表的哑节点(终止条件),从结尾开始,将返回的链表添加到上一节点的next属性中,直到返回头结点(归)。

 

    另外,Python中变量交换值有一种特殊的方式,不需要使用中间变量:x,y=y,x即可交换x,y的值。它的机制是首先构造一个元组(y, x),然后构造另一个元组(x, y),接着用元组(y, x)赋值给(x, y),元组赋值过程从左到右,依次进行。

猜你喜欢

转载自blog.csdn.net/BreakingDawn0/article/details/102759572