Leetcode题解 0010期

(不知不觉连续刷Leetcode也有10天了,这种东西就得坚持,坚持才是胜利。想起以前刷OJ时的那段生活了。另外相比于一个人孤零零的刷题,我觉得和一群人(至少有两个)刷题会更有效率,这就和健身一个道理,找1~2个人,你不想去的时候他拉你,他不想去的时候你拉他,这样一点点扶持下去,就能养成习惯,并健好身体。所以这里也不用给自己多说,只需两个字:加油)

因为前面有几道题偷机耍滑了,所以在这里需要重新写一下,避免自己良心过不去。

0010题 正则表达式匹配【Regular Expression Matching】

题目相关参见:https://blog.csdn.net/bright_silmarillion/article/details/80556553

偷机耍滑原因:
1. 因为Python有re库,直接非法使用外部链接库,通过该题
使用re正则库的简短代码:

import re
class Solution:
    def isMatch(self, s, p):
        res = re.match(p, s)
        if not res: return False
        if res.group() == s:
            return True
        else:
            return False

解题思路:
1.一开始的思路是从前往后扫,然后处理*号;后来发现提示相关话题中有回溯算法,改为从后往前扫,结果仍然有几组数据过不去,说明直接扫描是错误的,肯定不仅仅是简单的回溯。
但是扫描肯定是要扫描的,随后发现应该记录扫描位置,因为*号实在是有太多选择,所以再多出两个list储存扫描下表用来之后枚举,即可完成任务。

注意一下几组数据:
① “aaa”, “a*a”
② “aaa”, “aa*a”
③ “”,”a*”

Python不使用外部库的暴力扫描代码:

class Solution:
    def isMatch(self, s, p):
        p_list = list()
        ri = list()
        rj = list()
        s_len = len(s)
        p_len = len(p)
        i, j = 0, 0
        while i < s_len or j < p_len:
            if j + 1 < p_len and p[j+1] == "*":
                p_list.append(p[j])
                ri.append(i)
                j += 2
                rj.append(j)
            elif i < s_len and j < p_len and (p[j] == "." or s[i] == p[j]):
                i += 1
                j += 1
            elif len(p_list) != 0:
                k = ri[-1]
                if k < s_len and (p_list[-1] == "." or p_list[-1] == s[k]):
                    k += 1
                    i = k
                    ri[-1] = k
                    j = rj[-1]
                else:
                    p_list.pop()
                    ri.pop()
                    rj.pop()
            else:
                break

        return (i == s_len and j == p_len)

2.这并不是最优的解法,因为要反复回溯搜索尝试,所以时间复杂度无法估量,然而,题目另外一个提示就是动态规划,花了很长时间琢磨了一下动态转移方程,发现规律:

class Solution:
    def isMatch(self, s, p):
        s_len = len(s)
        p_len = len(p)
        d = [[False for col in range(p_len+1)] for row in range(s_len+1)]
        d[0][0] = True
        for i in range(s_len+1):
            for j in range(1, p_len+1):
                if j > 1 and p[j-1]=="*":
                    d[i][j] = d[i][j-2] or (i!=0 and d[i-1][j] and (s[i-1]==p[j-2] or p[j-2]=='.'))
                else:
                    d[i][j] = i!=0 and d[i-1][j-1] and (s[i-1] == p[j-1] or p[j-1]=='.')

        return d[s_len][p_len]

3.可以看出这里使用了二维list来进行动态规划,就像打了一张表一样,实际上我们可以采用一维List完成这项动归,放上目前最优思路:

class Solution:
    def isMatch(self, s, p):
        s_len = len(s)
        dp = [False]*(s_len+1)
        dp[s_len] = True
        i = len(p) - 1
        while i >= 0:
            if p[i] == '*':
                for j in range(s_len-1, -1, -1):
                    dp[j] = dp[j] or (dp[j + 1] and (p[i - 1] == '.' or p[i - 1] == s[j]))
                i -= 1
            else:
                for j in range(s_len):
                    dp[j] = dp[j + 1] and (p[i] == '.' or p[i] == s[j])
                dp[s_len] = False
            i -= 1

        return dp[0]

实际上用Python中比较速度在某种程度上是完全没有意义的事情,因为Python中的许多函数都是通过底层代码C/C++完成的,就比如用Python直接编写一个归并排序,和直接用sort函数,肯定后者比前者要快许多。像这道题,使用C++编写同样的代码,只需要4ms,而Python最优的解法仍需60ms。所以在Leetcode刷题并不是要追求绝对速度,而是要追求思路和Robust,因为这些仿佛都是面试所需要的。


0013题 罗马数字转整数【Roman to Integer】

题目相关参见:https://blog.csdn.net/bright_silmarillion/article/details/80562178

偷机耍滑原因:
1. 直接使用打表方式暴力破解
2. 因为是简单题目而且又是模拟题所以不重视
3. 因为前一道题感觉有点感觉,这题没感觉很不爽

解题思路:
反向打表,然后比较即可,题目简单是简单,但是不能偷机耍滑!

class Solution:
    def romanToInt(self, s):
        d = {
                'I' : 1,
                'V' : 5,
                'X' : 10,
                'L' : 50,
                'C' : 100,
                'D' : 500,
                'M' : 1000,
            }
        n = len(s)
        res = 0
        idx = 0
        while idx < n:
            if idx < n-1 and d[s[idx]] < d[s[idx+1]]:
                res -= d[s[idx]]
                res += d[s[idx+1]]
                idx += 2
            else:
                res += d[s[idx]]
                idx += 1

        return res

0024题 两两交换链表中的节点【Swap Nodes in Pairs】

题目相关参见:https://blog.csdn.net/bright_silmarillion/article/details/80616139

偷机耍滑原因:
1. 不愿意用Python编写链表题目

解题思路:
之前也说到了,我们关键是练习思路来的,虽然用Python编写链表题很痛苦,但这也同时说明了我自己对链表的恐惧和不熟练的心态,所以我必须正视自己这方面的弱点。

class Solution:
    def swapPairs(self, head):
        if head == None or head.next == None:
            return head
        new_head = head.next
        new_tail = head

        head = head.next.next
        new_head.next = new_tail
        new_tail.next = None

        while head != None and head.next != None:
            p1 = head
            p2 = head.next
            head = head.next.next
            new_tail.next = p2
            p2.next = p1
            new_tail = p1
            new_tail.next = None

        if head != None:
            new_tail.next = head
            new_tail = head
            new_tail.next = None

        return new_head

发现解法里有一个特别厉害的循环交换式代码,很不错,贴在这里,每次翻到这个博客时都学习一遍:

class Solution:
    def swapPairs(self, head):
        if head==None:
            return head
        elif head.next==None:
            return head
        else:
            temp = head.next
            head.next = self.swapPairs(temp.next)
            temp.next = head
            return temp

0025题 k个一组翻转链表【Reverse Nodes in k-Group】

题目:
给出一个链表,每 k 个节点一组进行翻转,并返回翻转后的链表。
k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么将最后剩余节点保持原有顺序。

说明 :

  • 你的算法只能使用常数的额外空间。
  • 你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例 :

给定这个链表:1->2->3->4->5

当 k = 2 时,应当返回: 2->1->4->3->5

当 k = 3 时,应当返回: 3->2->1->4->5

题目相对严谨

需要注意:
1. 链表题,小心驶得万年船
2. 注意交换的时候要额外小心以及如果剩余不够k个数的情况

解题思路:
其实与两两交换相同,无伤大雅,链表题反正都是难题(,post代码

class Solution:
    def __init__(self):
        self.h = None
        self.t = None

    def reverseList(self):
        if self.h == None: return
        h = t = None
        p = self.h
        while p != None:
            self.h = self.h.next
            if h == None:
                h = t = p
                t.next = None
            else:
                p.next = h
                h = p

            p = self.h

        self.h = h
        self.t = t

    def reverseKGroup(self, head, k):
        if head == None or head.next == None or k < 2:
            return head
        cnt = 0
        h = t = None
        while head != None:
            self.h = self.t = None
            if head == None: 
                cnt = 0
            else:
                cnt = 0
                p = head
                while cnt < k and p != None:
                    head = head.next
                    if self.t == None:
                        self.h = self.t = p
                    else:
                        self.t.next = p
                        self.t = p

                    self.t.next = None
                    cnt += 1
                    p = head

            if cnt >= k:
                self.reverseList()
            if t == None:
                h = self.h
            else:
                t.next = self.h

            t = self.t

        head = h
        return head

0033题 搜索旋转排序数组【Search in Rotated Sorted Array】

题目:

假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。

你可以假设数组中不存在重复的元素。

你的算法时间复杂度必须是 O ( l o g n ) 级别。

示例:

输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4

输入: nums = [4,5,6,7,0,1,2], target = 3
输出: -1

题目相对严谨

二分查找题目无需注意太多

解题思路:
1. 这题很明显,先找见pivot旋转点, O ( l o g n ) ,然后二分 O ( l o g n ) ,或者不找旋转点,就从中间进去,然后看左右大小,然后重新二分,相当于把找旋转点和查找合到一起。
2. 注意之前也说道,这种题目是考察思路的,所以不要用index或者find之类的内部函数

题目相对简单,故不post代码


0034题 搜索范围【Search for a Range】

题目:
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
你的算法时间复杂度必须是 O ( l o g n ) 级别。
如果数组中不存在目标值,返回 [-1, -1]。

示例:

输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]

输入: nums = [5,7,7,8,8,10], target = 6
输出: [-1,-1]

题目相对严谨

需要注意:
1. 如果数只出现一个的话,应该返回两个相同的数

解题思路:
1. 如果可以用Python的大招,我愿意使用下面这段代码:

class Solution:
    def searchRange(self, nums, target):
        try:
            return [nums.index(target), len(nums)-1-nums[::-1].index(target)]
        except:
            return [-1, -1]
  1. 但是不行,所以我们得迎头编写二分查找,代码如下:
class Solution:
    def searchLeft(self, nums, target, left, right):
        while left <= right:
            mid = (left + right)//2
            if nums[mid] < target:
                left = mid + 1
            else:
                right = mid - 1

        return left


    def searchRange(self, nums, target):
        n = len(nums)
        idx1 = self.searchLeft(nums, target, 0, n-1)
        idx2 = self.searchLeft(nums, target+1, 0, n-1) - 1

        if idx1 < n and nums[idx1] == target:
            return [idx1, idx2]
        else:
            return [-1, -1]

这里巧妙运用了寻找target+1的方法确定上界,同时也完成了下一道题的任务。


0035题 搜索插入位置【Search Insert Position】

题目:
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素。

示例:

输入: [1,3,5,6], 5
输出: 2

输入: [1,3,5,6], 2
输出: 1

输入: [1,3,5,6], 7
输出: 4

输入: [1,3,5,6], 0
输出: 0

题目相对严谨

无需注意太多

解题思路:上一道题已经完成了这道题的任务,故无需post代码。

明天一期会附上0030、0032题的思路分析以及新的题目。

猜你喜欢

转载自blog.csdn.net/bright_silmarillion/article/details/80632159