leetcode 1-10 总结

61 篇文章 0 订阅
54 篇文章 0 订阅

1. Two Sum

方法1:暴力群举

没学算法前,觉得只要暴力能解决的问题就不叫问题,于是幼稚的认为这样就可以:

class Solution:
    def twoSum(self, nums, target):
        for i in range(len(nums) - 1):
            for j in range(i + 1, len(nums)):
                if nums[i] + nums[j] == target:
                    return [i, j]

至少想到了边界条件,没有错误的输出, O(n^2)没什么。

方法二:二分查找

后来学了算法后,觉得可以改善:

class Solution: # wrong in leetcode
    def twoSum(self, nums, target):
        def binary_search(arr1, num):
            arr = arr1.copy()
            # arr  changed arr1 not change, need to find the value of arr1 idx
            arr.sort()
            i, j = 0, len(arr) - 1
            while i <= j:
                mid = round((i + j) / 2)
                if arr[mid] < num:
                    i = mid + 1
                elif arr[mid] > num:
                    j = mid - 1
                else:
                    return arr1.index(arr[mid])
            return -1
        # binary search changed the idx
        for i in range(len(nums)):
            res = target - nums[i]
            if res != nums[i]:
                idx = binary_search(nums, res)
                if idx != -1:
                    return [i, idx]
            else:
                nums[i] = res -100000
                idx = binary_search(nums, res)
                if idx != -1:
                    return [i, idx]

这时已经达到O(nlogn)了,勉强可以接受,但这种算法也是自己花了几个小时, 考虑到各种边界条件写出了了,虽然能通过,但在真实的面试中,20分钟绝对写不出来。自能是当作练习,以后决不不用该类算法,因为自己都无法保证下次可以写出来。

方法三:双指针

双指针是极力推荐的一种算法,虽然对于本体是大材小用,但如果变态的面试官就要求O(nlogn)的时间, 这种方法至少满足了条件,也是一种好的算法思想。通过对排好序的数组高低指针的不断接近,最终达到理想的输出。

class Solution: # need to think about the sort changed the idx
    def twoSum(self, nums, target):
        nums_final = nums.copy()
        nums.sort() # nlog(n)
        # binary search to find the idx from both side
        # shift the right and left pointer, because, if nums[left] + nums[right] > target
        # means nums[right] is too big, need to have a smaller, right -= 1
        left, right = 0, len(nums) - 1
        while right > left:
            if nums[left] + nums[right] > target:
                right -= 1
            elif nums[left] + nums[right] < target:
                left += 1
            else:
                if nums[left] != nums[right]:
                    return [nums_final.index(nums[left]), nums_final.index(nums[right])]
                else:
                    x = nums_final.index(nums[left])
                    nums_final[x] = -100000 # in case nums[left] = nums[right]
                    return [x, nums_final.index(nums[right])]
方法四:哈希表

哈希表,就不用都说了,通过牺牲空间换取时间,最好不过了。

class Solution:
    def twoSum(self, nums, target):
        d = {}
        for idx, val in enumerate(nums):
            res = target - val
            if res in d:
                return [d[res],idx]
            else:
                d[val] = idx
  • nums = [2,7,11,15]
  • target = 9
  • 1st loop: d = {}, idx = 0, val = 2, res = 7, 7 is not in d = {}, d = {2: 0}
  • 2nd loop: d = {2: 0}, idx = 1, val = 7, res = 2, 2 is in d = {2: 0}, return [d[2], 1], d[2] = 0
    这里已经达到了O(n), 是最理想的算法,以后此类提可以用这种思想。
    In order to get less than O(n^2) time complexity, we need to prepare a dict, increase a bit memory, but decreased time complexity
    一道算法题绝不是学了某一个方法,而是在不断的总结到底需要用什么样的算法。比如顺便还学习了二分法:
def binary_search(arr, num):
    i, j = 0, len(arr) - 1
    while i <= j:
        mid = (i + j + 1) // 2
        if arr[mid] < num:
            i = mid + 1
        elif arr[mid] > num:
            j = mid - 1
        else:
            return mid
    return -1
binary_search([2,7,11,15,16], 16)

2. Add Two Numbers

前期写的代码太多的重复,虽然可以运行,但可读性太差,写了太多的重复,后来经过不多的改善,最终达到了理想的状态。

# Definition for singly-linked list.
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next
class Solution:
    def addTwoNumbers(self, l1, l2):
        """
        :type l1: ListNode
        :type l2: ListNode
        :rtype: ListNode
        """
        # prepare a dummy node, can be any value
        dummy = ListNode(0)
        # temp pointed to ListNode(0)
        temp = dummy
        # carry is defined 0
        carry = 0
        # if there is value in l1 and l2
        while l1 and l2:
            val1 = l1.val
            val2 = l2.val
            if (val1 + val2 + carry >= 10):
                dummy.next = ListNode((val1 + val2 + carry) % 10)
                dummy = dummy.next
                carry  = 1
            else:
                dummy.next = ListNode(val1 + val2 + carry)
                dummy = dummy.next
                carry  = 0
            l1 = l1.next
            l2 = l2.next
        
        # if l1 is empty first
        if not l1:
            while l2:
                val2 = l2.val
                if (val2 + carry >= 10):
                    dummy.next = ListNode((val2 + carry) % 10)
                    dummy = dummy.next
                    carry  = 1
                else:
                    dummy.next = ListNode(val2 + carry)
                    dummy = dummy.next
                    carry  = 0
                l2 = l2.next
        
        # if l2 is empty first
        if not l2:
            while l1:
                val1 = l1.val
                if (val1 + carry >= 10):
                    dummy.next = ListNode((val1 + carry) % 10)
                    dummy = dummy.next
                    carry  = 1
                else:
                    dummy.next = ListNode(val1 + carry)
                    dummy = dummy.next
                    carry  = 0
                l1 = l1.next
                    
        if carry == 1:
            dummy.next = ListNode(1)
        return temp.next

改善后, 代码由50多行变成了16行,而且是完全一样的逻辑。改善后的更加符合算法的逻辑,容易固化。

class Solution:
    def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
        p = dummy = ListNode()
        carry = 0
        while l1 or l2:
            if l2 and l1:
                val = l1.val + l2.val + carry
            elif l1:
                val = l1.val + carry
            else:
                val = l2.val + carry
            p.next = ListNode(val % 10)
            p = p.next
            if l1: l1 = l1.next
            if l2: l2 = l2.next
            carry = val // 10
        if carry: p.next = ListNode(1)
        return dummy.next
Single Linked List plus
  • 1 Prepare a dummy
  • 2 Use a carry
  • 3 the result is a new linked list

3. Longest Substring Without Repeating Characters

方法一:暴力
class Solution:
    def lengthOfLongestSubstring(self, s):
        if s == '':
            return 0
        final = []
        for i in range(len(s)):
            arr = []
            arr.append(s[i])
            for j in range(i + 1, len(s)):
                if s[j] not in arr:
                    arr.append(s[j])
                else:
                    break
            final.append(arr)
        return max([len(item) for item in final])
方法二:滑动窗口

虽然能运行,但没有必要用数组存储,应为不是求字符串,是求长度。

class Solution: # 126s, 111s
    def lengthOfLongestSubstring(self, s):
        if s == '':
            return 0
        temp = []
        res = []
        for i in range(len(s)):
            if s[i] not in temp:
                temp.append(s[i])
            else:
                res.append(temp)
                temp = temp[temp.index(s[i]) + 1:]
                temp.append(s[i])
        res.append(temp)
        return max([len(i) for i in res])
方法三:优化后滑动窗口

求长度,可以直接定义整型变量,时间和空间均为最优。但是s[right] not in s[left: right],需要消耗大量时间,可以用哈希表处理。

longest, left, right = 1, 0, 1
        if len(s) < 2:
            return len(s)
        while right < len(s):
            if s[right] not in s[left: right]:
                longest = max(longest, right - left + 1)
            else:
                left = s.index(s[right], left, right) + 1
            right += 1
        return longest
方法四:滑动窗口+哈希表

原本认为使用set后时间会加快,结果变差,可能set增加删除消耗时间太多,不如list的直接访问,不需要额外空间。总之后两种算法都是O(n^2).

longest, left, right = 0, 0, 0
        hash_set = set()
        while right < len(s):
            if s[right] not in hash_set:
                hash_set.add(s[right])
                right += 1
                longest = max(longest, right - left)
            else:
                hash_set.remove(s[left])
                left += 1
        return longest

4. Median of Two Sorted Arrays

方法1:排序

O((m+n)*log(m+n)), 不满足要求, 这可是难度级别的题,不可能比容易的题都简单。

class Solution:
    def findMedianSortedArrays(self, nums1, nums2):
        nums1.extend(nums2)
        nums1.sort()
        if len(nums1) % 2 == 1:
            return nums1[len(nums1) // 2]
        return (nums1[len(nums1) // 2 - 1] + nums1[len(nums1) // 2]) / 2
方法2:归并数组

O(m+n), 不满足要求

class Solution:
    def findMedianSortedArrays(self, nums1, nums2):
        nums1.extend(nums2)
        nums1.sort()
        if len(nums1) % 2 == 1:
            return nums1[len(nums1) // 2]
        return (nums1[len(nums1) // 2 - 1] + nums1[len(nums1) // 2]) / 2
方法3:D & Q 分治

O(log(m+n)), 重来没用过在两个数组中同时用二分查找,更多的难点是边界条件,另外在

  • return self.findMedianSortedArrays(nums2, nums1)
    这条语句上不太理解返回结果,一度怀疑自己的指针理解有问题,函数理解有问题,总之始终要保持nums1的长度小于nums2,否则会出现index out of range.
class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        if len(nums1) > len(nums2):
            return self.findMedianSortedArrays(nums2, nums1)
        x, y = len(nums1), len(nums2)
        low, high = 0, x
        while low <= high:
            partitionX = (low + high) // 2
            partitionY = (x + y + 1) // 2 - partitionX
            
            maxLeftX = -float('inf') if partitionX == 0 else nums1[partitionX - 1]
            minRightX = float('inf') if partitionX == x else nums1[partitionX]
            maxLeftY = -float('inf') if partitionY == 0 else nums2[partitionY - 1]
            minRightY = float('inf') if partitionY == y else nums2[partitionY]
            
            if maxLeftX <= minRightY and maxLeftY <= minRightX:
                if (x + y) % 2 == 1:
                    return max(maxLeftX, maxLeftY)
                else:
                    return (max(maxLeftX, maxLeftY) + min(minRightX, minRightY)) / 2
            elif maxLeftX > minRightY:
                high = partitionX - 1
            else:
                low = partitionX + 1

5. Longest Palindromic Substring

方法1: 暴力

n^3

class Solution:
    """
    T = O(n^3)
    """
    def Palindrome(self, List): # check from outside to middle
        for i in range(len(List)):
            if List[i] != List[len(List) - i - 1]:
                return False
        return True
    def longestPalindrome(self, s):
        # 1 put all the palindromic substrings in a container list
        # 2 choose the longest
        L_str = list(s)
        if len(L_str) == "":
            return ""
        L_new = [] # for all the palindromic substrings
        for i in range(len(L_str)):
            for j in range(i, len(L_str)):
                if Solution.Palindrome(self, L_str[i:j + 1]) == True:
                    L_new.append(L_str[i:j + 1])
        l = max([len(item) for item in L_new]) # need only one max
        for item in L_new:
            if len(item) == l:
                return "".join(item)
方法2:双指针
class Solution:
    """
    T = O(n^2) is better, but there is nlog(n) method
    two case: odd and even of res
    from center to edge
    """
    def longestPalindrome(self, s):
        res = ''
        length = 0
        for i in range(len(s)):
            # odd case
            l, r = i, i
            while l >= 0 and r < len(s) and s[l] == s[r]:
                if (r - l + 1) > length:
                    res = s[l: r + 1]
                    length = r - l + 1
                l -= 1
                r += 1
            # even case
            l, r = i, i + 1
            while l >= 0 and r < len(s) and s[l] == s[r]:
                if (r - l + 1) > length:
                    res = s[l: r + 1]
                    length = r - l + 1
                l -= 1
                r += 1
        return res
方法3:双指针+函数

用函数优化代码,因为有复用,另外检查回型文,如果指针重中间出发,可以大大减少运算量。

class Solution:
    def longestPalindrome(self, s: str) -> str:
        def helper(l, r):
            while l >= 0 and r < len(s) and s[r] == s[l]:
                l -= 1
                r += 1
            return s[l+1: r]
        
        res = ""
        for i in range(len(s)):
            test = helper(i, i)
            if len(test) > len(res): res = test
            test = helper(i, i + 1)
            if len(test) > len(res): res = test
        return res

6. ZigZag Conversion

方法:利用bucket sort 的原理

想了几天没想出来,想得到一个特定模式,但没找到,其实用bucket的原理很容易解决。Z字形的字符串按照顺序放在bucket里,然后把bucket连接起来,考虑边界条件如何放置,需要用到一个flip变量,控制如何放。
Input: s = “PAYPALISHIRING”, numRows = 4
Output: “PINALSIGYAHRPI”
Explanation:
P --------- I — – - N
A - — L–S —I —G
Y – A — H -R
P ----------I
1.如果numRows = 4,代表有四排序列, 可以设定四个木桶
res[0], 放置第1行字母 P I N
res[1], 放置第2行字母 A L S I G
res[2], 放置第3行字母 Y A H R
res[3], 放置第4行字母 P I
2. 输入按照翻转的Z字形,
第1个字母P,放在res[0]里面
第2个字母A,放在res[1]里面
第3个字母Y,放在res[2]里面
第4个字母p,放在res[3]里面, 当前木桶的下标逐步加 1
第5个字母A,放在res[2]里面,此时木桶的下标逐步加减1
要想得到这种情况,需要一个flip来翻转 +1 和-1
每当遍历到第一个或最后一个木桶,改变flip符号。这样可以满足所有的木桶正确存储,然后把木桶里的字符连接起来
最后把木桶依次合并, 输出。

		'''
        time: O(n+k < n + n) = O(n), n is number of char in str and k is number of rows
        space: O(n)
        '''
        # numRows >= len(s) does not influence res, but saves space
        if numRows == 1 or numRows >= len(s):
            return s
        
        flip = -1
        row = 0
        res = [[] for i in range(numRows)]
        # iterate through string
        for c in s:
            res[row].append(c)
            if row == 0 or row == numRows - 1:
                flip *= -1
            row += flip
        
        # consolidate res
        for i in range(len(res)):
            res[i] = "".join(res[i])
        return "".join(res)

7. Reverse Integer

方法:数学

7.1. 也没什么好的方法,这种题应该经常做,固定的方法逆序一个数字,难点是需要考虑边界条件与负数的逆序处理。
7.2. 这里的函数reverse_pos_int(num)是翻转一个正数或0的方法,若是负数翻转,那么先变为正数,翻转后加上符号
7.3. 最后检查输出条件

class Solution:
    def reverse(self, x: int) -> int:
        def reverse_pos_int(num):
            reversed_num = 0
            while num:
                remainder = num % 10
                reversed_num = reversed_num * 10 + remainder
                num //= 10
            return reversed_num
        if x >= 0 and reverse_pos_int(x) <= 2 ** 31 -1:
            return reverse_pos_int(x)
        elif x < 0 and -reverse_pos_int(-x) >= -2 ** 31:
            return -reverse_pos_int(-x)
        else:
            return 0

8. String to Integer (atoi)

method: 按照题目要求top-down approach
class Solution:
    def myAtoi(self, s: str) -> int:
        # 1 strip leading and ending space
        s = s.strip() 
        signs = ['-', '+']
        res = ''
        # 2 check for sign and numbers and stop condition
        for idx, char in enumerate(s):
            if char in signs and idx == 0:
                res += char
                continue
            if char.isnumeric():
                res += char
            else:
                break
        # 3 check 0 condition
        if not res or res in signs:
            return 0
        # 4 return 3 cases
        res = max(min(int(res), 2**31 - 1), -2**31)
        return res

9. Palindrome Number

方法:

第5题的子问题,又和第7题的解题方法一样,做题顺序应该是9 -> 5, 7

1 用数字逆序处理,第7题方法
class Solution:
    def isPalindrome(self, x: int) -> bool:
        check_num = x
        new_num = 0
        if x < 0:
            return False
        while x >0:
            remainder = x % 10
            new_num = (new_num*10)+remainder
            x = x //10
        if new_num == check_num:
            return True
        else:
            return False
2 第5题子问题解法
class Solution:
    def isPalindrome(self, x: int) -> bool:
        s = str(x)
        def helper(l, r):
            while l >= 0 and r < len(s) and s[l] == s[r]:
                l -= 1
                r += 1
            if l < 0 and r >= len(s):
                return True
            return False
        
        mid = (len(s) - 1) // 2
        if len(s) % 2 == 1:
            return helper(mid, mid)
        return helper(mid, mid + 1)

如果有要求:
Follow up: Could you solve it without converting the integer to a string?
那么用方法一

10. Regular Expression Matching

方法1: 暴力2^n
class Solution:
    def isMatch(self, s: str, p: str) -> bool:
        # top-down memo
        
        def dfs(i, j):
            if i >= len(s) and j >= len(p):
                return True
            if j >= len(p):
                return False
            # match can be true or false, aa == a. or aa != ba
            match = i < len(s) and (s[i] == p[j] or p[j] == ".")
            # what if aa and a*
            if (j + 1) < len(p) and p[j + 1] == "*":
                return (dfs(i, j + 2) or       # don't use *
                (match and dfs(i + 1, j)))     # use *
            
            if match:
                return dfs(i + 1, j + 1)
            
            return False
        
        return dfs(0, 0)   
方法2:DP
class Solution:
    def isMatch(self, s: str, p: str) -> bool:
        # use dp
        dp = [[False for j in range(len(p) + 1)] for i in range(len(s) + 1)]
        dp[0][0] = True
        # for a*b* cases
        for i in range(1, len(p) + 1):
            if p[i-1] == "*" and dp[0][i - 2]:
                dp[0][i] = True
        
        for i in range(1, len(s) + 1):
            for j in range(1, len(p) + 1):
                if p[j-1] == "." or p[j-1] == s[i-1]:
                    dp[i][j] = dp[i-1][j-1]
                elif p[j-1] == "*":
                    # if cases like a and b*, then delete b* because of * function
                    if p[j-2] != s[i-1] and p[j-2] != ".":
                        dp[i][j] = dp[i][j-2]
                    # if cases like ...a and ...a*: three cases 0, 1 or many a
                    else:
                        dp[i][j] = dp[i][j-2] or dp[i][j-1] or dp[i-1][j]
                else:
                    dp[i][j] = False
        return dp[-1][-1]
        return res

修改后代码便于理解:DP
如果遇到*,
比如:p1: a和 p2: aa*, 那么此时当p2指向时,如果向左移动2,那么检查a和a是否相等,如果相等那么dp[i][j] = dp[i][j-2] = True,就不用再做处理,如果不为真,
检查
使a为一个或多个的情况
比如:p1: ba和 p2: ba*, 满足dp[i][j] = dp[i][j-1], ba=ba
比如:p1: baaaa和 p2: ba*, 满足dp[i][j] = dp[i-1][j],
baaa和ba*, -> baa和ba* -> ba和ba*, 直到满足条件,而baaa和ba*, -> baa和ba* -> ba和ba这些已经存在DP里,只需要看baaa和ba,故而满足dp[i][j] = dp[i-1][j]就可以了。

class Solution:
    def isMatch(self, s: str, p: str) -> bool:
        # use dp
        dp = [[False for j in range(len(p) + 1)] for i in range(len(s) + 1)]
        dp[0][0] = True
        # for a*b* cases
        for i in range(1, len(p) + 1):
            if p[i-1] == "*" and dp[0][i - 2]:
                dp[0][i] = True
        
        for i in range(1, len(s) + 1):
            for j in range(1, len(p) + 1):
                if p[j-1] == "." or p[j-1] == s[i-1]:
                    dp[i][j] = dp[i-1][j-1]
                elif p[j-1] == "*":
                    dp[i][j] = dp[i][j-2]
                    if dp[i][j-2] == False and (p[j-2] == s[i-1] or p[j-2]== "."):
                        dp[i][j] = dp[i][j-1] or dp[i-1][j]
                else:
                    dp[i][j] = False
        return dp[-1][-1]

通过前期和后期刷题对比发现,之前只要能运行就行,后来更多的考虑时间与空间。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值