LeetCode(一):Python实现1-100题

开始刷题啦,打算先刷前100道题,从简单——中等——困难的顺序刷。根据前100题的经验判断100题-200题是否要全部刷。

还是中等的题有些难度,至少一天三道。加油\^-^/    39/100完成度

值得再看一遍的题:14,26,38,53,70,88,100

不需要再看的:1 34 36


1. 两数之和

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

你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。

示例:

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        # 检测输入列表不为空
        if not nums:
            return None
        
        d = dict()
        for i,val in enumerate(nums):
            if val in d.keys():
                return [d[val], i]
            d[target - val] = i
        return None

 

2. 两数相加

给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。

如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。

您可以假设除了数字 0 之外,这两个数都不会以 0 开头。

示例:

输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807

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

class Solution:
    def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
        re = ListNode(0)
        r=re
        carry=0
        while(l1 or l2):
            x= l1.val if l1 else 0
            y= l2.val if l2 else 0
            s=carry+x+y
            carry=s//10
            r.next=ListNode(s%10)
            r=r.next
            if(l1!=None):l1=l1.next
            if(l2!=None):l2=l2.next
        if(carry>0):
            r.next=ListNode(1)
        return re.next 

 

3. 无重复字符的最长子串

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:    输入: "abcabcbb"    输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:    输入: "bbbbb"    输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 3:    输入: "pwwkew"    输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。

请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

 双指针代表滑动区间:快指针记录要添加的元素。慢指针指向要缩减的元素。

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        # 双指针代表滑动区间的左右坐标
        n = len(s)
        se = set()
        slow = 0
        max_len = cur_len = 0
        for fast in range(n):
            cur_len += 1
            while s[fast] in se:
                se.remove(s[slow])
                cur_len -= 1
                slow += 1
            max_len = max(max_len,cur_len)
            se.add(s[fast])
        return max_len

 

5. 最长回文子串

暴力解法:最长回文子串即颠倒后相同的字符串。使用暴力的滑动窗口解法,首先判断整个字符串是否是回文子串,若是返回整个字符串。若不是再取长度-1,再判断是否是回文子串,若是返回,不是长度继续-1,反复循环,直到长度为1停止。长度为1直接返回字符串首位即得到最长回文子串。

时间复杂度:取得所有子串需要两层循环,将子串翻转还需要O(N),一共需要时间复杂度O(N^3)

class Solution:
    def longestPalindrome(self, s: str) -> str:
        # 滑动窗口暴力解法
        n = len(s)
        if n <= 1: return s 

        for i in range(n,1,-1):
            for left in range(0,n-i+1):
                a = s[left:left+i]
                b = a[::-1]
                if  a == b:
                    return a
        return s[0]

中心扩散法

遍历每一个索引,以这个索引为中心,利用“回文串”中心对称的特点,往两边扩散,看最多能扩散多远。

在这里要注意一个细节:回文串在长度为奇数和偶数的时候,“回文中心”的形式是不一样的。

奇数回文串的“中心”是一个具体的字符,例如:回文串 "aba" 的中心是字符 "b";
偶数回文串的“中心”是位于中间的两个字符的“空隙”,例如:回文串串 "abba" 的中心是两个 "b" 中间的那个“空隙”。

我们可以设计一个方法,兼容以上两种情况:

1、如果传入重合的索引编码,进行中心扩散,此时得到的回文子串的长度是奇数;

2、如果传入相邻的索引编码,进行中心扩散,此时得到的回文子串的长度是偶数。

时间复杂度:枚举“中心位置”时间复杂度为 O(N),从“中心位置”扩散得到“回文子串”的时间复杂度为 O(N),因此时间复杂度可以降到 O(N^2)。

class Solution:
    def longestPalindrome(self, s: str) -> str:
        size = len(s)
        if size < 2:
            return s

        # 至少是 1
        max_len = 1
        res = s[0]

        for i in range(size):
            palindrome_odd, odd_len = self.__center_spread(s, size, i, i)
            palindrome_even, even_len = self.__center_spread(s, size, i, i + 1)

            # 当前找到的最长回文子串
            cur_max_sub = palindrome_odd if odd_len >= even_len else palindrome_even
            if len(cur_max_sub) > max_len:
                max_len = len(cur_max_sub)
                res = cur_max_sub

        return res

    def __center_spread(self, s, size, left, right):
        """
        left = right 的时候,此时回文中心是一个字符,回文串的长度是奇数
        right = left + 1 的时候,此时回文中心是一个空隙,回文串的长度是偶数
        """
        i = left
        j = right

        while i >= 0 and j < size and s[i] == s[j]:
            i -= 1

 

6. Z 字形变换 

将一个给定字符串根据给定的行数,以从上往下、从左到右进行 Z 字形排列。

比如输入字符串为 "LEETCODEISHIRING" 行数为 3 时,排列如下:

L     C      I     R
E T O E  S  I  I G
E    D     H     N
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"LCIRETOESIIGEDHN"。
示例 1:

输入: s = "LEETCODEISHIRING", numRows = 3
输出: "LCIRETOESIIGEDHN"
示例 2:

输入: s = "LEETCODEISHIRING", numRows = 4
输出: "LDREOEIIECIHNTSG"
解释:

L       D       R
E   O E    I   I
E C    I   H  N
T       S       G

思路: 按行访问

首先访问 行 0 中的所有字符,接着访问 行 1,然后 行 2,依此类推...

对于所有整数 k,

行 0中的字符位于索引 k(2⋅numRows−2) 处;

行 numRows−1 中的字符位于索引k(2⋅numRows−2)+numRows−1 处;

内部的行i中的字符位于索引 k(2⋅numRows−2)+i 以及(k+1)(2⋅numRows−2)−i 处;

时间复杂度:O(n),每个索引被访问一次。

空间复杂度:O(n)

class Solution:
    def convert(self, s: str, numRows: int) -> str:
        n = len(s)
        if n<=numRows or numRows<=1: return s
        space = [i for i in range((numRows-1)*2,0,-2)]
        res = ''
        for i in range(numRows):
            if i==0 or i==numRows-1:
                res += s[i::space[0]]
            else:
                j = i
                flag = True
                while j < n:
                    res += s[j]
                    j += space[i] if flag else space[0]-space[i]
                    flag = not flag
        return res

思路:用一个二维列表记录元素,内部每一个列表代表一行,只要遍历字符串,将相应字符放置在正确位置,最后按行遍历二维列表得到的字符串即是Z字变换的返回值。

从左到右迭代 s,将每个字符添加到合适的行。可以使用当前行和当前方向这两个变量对合适的行进行跟踪。

只有当我们向上移动到最上面的行或向下移动到最下面的行时,当前方向才会发生改变。

时间复杂度:O(n)

空间复杂度:O(n)

class Solution:
    def convert(self, s: str, numRows: int) -> str:
        n = len(s)
        if n<=numRows or numRows<=1: return s
        
        rows = list()
        for i in range(min(numRows,len(s))):
            rows.append([])
        curRow = 0
        goingDown = False

        for c in s:
            rows[curRow].append(c)
            if curRow==0 or curRow==numRows-1: goingDown = not goingDown
            curRow += 1 if goingDown else -1
        
        # rows = [['s','a','g'],['a','b','g'],['y','a','i']]
        res = ''
        for row in rows:
            res += ''.join(row)

        return res

 

 

7. 整数反转

给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。

输入: 123    输出: 321

输入: -123   输出: -321

输入: 120    输出: 21
注意:假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为 [−231,  231 − 1]。请根据这个假设,如果反转后整数溢出那么就返回 0。

class Solution:
    def reverse(self, x: int) -> int:
        if x<0:
            flag = -1
            x *= flag
        else: 
            flag = 1
        count = 0
        while x:
            count = count * 10 + x % 10
            x = x // 10
        count *= flag
        if count < -2**31 or count >2**31-1:
            return 0
        return count

思路:想要将数字倒置,也就是让最低位变为最高位,次最低位变成次最高位,首先将符号先存储好,每次将数字的个位与高位分开,每次将个位与先前拿到的数*10相加,这样不断循环完成数字位数的翻转。注意条件溢出时返回0。

Python还可将数字直接转换为字符串,再将结果用切片翻转,很好想但速度不快。

 

9. 回文数

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

示例 1:    输入: 121    输出: true
示例 2:    输入: -121    输出: false
解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
示例 3:    输入: 10    输出: false
解释: 从右向左读, 为 01 。因此它不是一个回文数。

class Solution:
    def isPalindrome(self, x: int) -> bool:
        x = str(x)
        y = x[::-1]
        if x == y:
            return True
        else:
            return False

 

12. 整数转罗马数字 

罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。

字符          数值
I             1
V             5
X             10
L             50
C             100
D             500
M             1000
例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做  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。
给定一个整数,将其转为罗马数字。输入确保在 1 到 3999 的范围内。

示例 1:    输入: 3    输出: "III"
示例 2:    输入: 4    输出: "IV"
示例 3:    输入: 9    输出: "IX"
示例 4:    输入: 58    输出: "LVIII"    解释: L = 50, V = 5, III = 3.
示例 5:    输入: 1994
输出: "MCMXCIV"    解释: M = 1000, CM = 900, XC = 90, IV = 4.

class Solution:
    def intToRoman(self, num: int) -> str:
        val_list = [1000,900,500,400,100,90,50,40,10,9,5,4,1]
        lab_list = ['M','CM','D','CD','C','XC','L','XL','X','IX','V','IV','I']

        res = ''
        cur = 0
        while num != 0:
            while val_list[cur] <= num:
                num -= val_list[cur]
                res += lab_list[cur]
            else:cur += 1
        return res

 

13. 罗马数字转整数

罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。

字符          数值
I             1
V             5
X             10
L             50
C             100
D             500
M             1000
例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做  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。
给定一个罗马数字,将其转换成整数。输入确保在 1 到 3999 的范围内。

示例 1:    输入: "III"    输出: 3
示例 2:    输入: "IV"    输出: 4
示例 3:    输入: "IX"    输出: 9
示例 4:    输入: "LVIII"    输出: 58   解释: L = 50, V= 5, III = 3.
示例 5:    输入: "MCMXCIV"    输出: 1994      解释: M = 1000, CM = 900, XC = 90, IV = 4.

class Solution:
    def romanToInt(self, s: str) -> int:
        outcome = 0
        val = {'I':1,'V':5,'X':10,'L':50,'C':100,'D':500,'M':1000}
        for i in range(len(s)-1):
            if val[s[i]] < val[s[i+1]]:
                outcome -= val[s[i]]
            else:
                outcome += val[s[i]]
        return outcome + val[s[-1]]

 

14. 最长公共前缀

编写一个函数来查找字符串数组中的最长公共前缀。

如果不存在公共前缀,返回空字符串 ""。

示例 1:   输入: ["flower","flow","flight"]      输出: "fl"
示例 2:    输入: ["dog","racecar","car"]    输出: ""
解释:     输入不存在公共前缀。
说明:    所有输入只包含小写字母 a-z 。

class Solution:
    def longestCommonPrefix(self, strs: List[str]) -> str:
        if len(strs) == 0: return ''
        if len(strs) == 1: return strs[0]

        s = strs[0]
        for i in range(1,len(strs)):
            while not strs[i].startswith(s):
                s = s[:-1]
                if s == '':
                    return ''
        return s

思路:先得到 flower 和 flow 的共同前缀,为 flow ,再与 flight 找前缀为 fl

 

15. 三数之和

给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例:

给定数组 nums = [-1, 0, 1, 2, -1, -4],

满足要求的三元组集合为:
[
  [-1, 0, 1],
  [-1, -1, 2]

思路:很容易想到三层循环嵌套,但是复杂度O(n^3) ,一看就不是很容易。而且此题[-1,0,1,2,-1,-4,1]与[-1,0,1,2,-1,-4]答案相同,不免看出如果按原顺序查找[-1,0,1(第三位的1)],[-1,0,1(第7位的1)]会造成输出过多,那么我们想把有相同的元素的变为一个呢,显然[-1,-1,2]这个答案就证明不能这么做。那么很容易想出想要位置不影响输出结果,就需要首先让数组进行排序。 [-1,0,1,2,-1,-4]--》[-4,-1,-1,0,1,2]

算法流程:

  • 特判,对于数组长度 n,如果数组为 null或者数组长度小于 3,返回 []。
  • 对数组进行排序。
  • 遍历排序后数组:

若 nums[i]>0:因为已经排序好,所以后面不可能有三个数加和等于 0,直接返回结果。

对于重复元素:跳过,避免出现重复解

令左指针 L=i+1,右指针 R=n-1,当 L<R 时,执行循环:

        当 nums[i]+nums[L]+nums[R]==0,执行循环,判断左界和右界是否和下一位置重复,去除重复解。并同时将 L,R移到下一位一置,寻找新的解

        若和大于 0,说明 nums[R] 太大,R 左移

        若和小于 0,说明 nums[L] 太小,L 右移

时间复杂度:O(n^2),数组排序O(NlogN),遍历数组 O(n),双指针遍历O(n),总体O(NlogN)+O(n)∗O(n) =O(n^2)
空间复杂度:O(1)O(1)

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        n = len(nums)
        if(not nums or n<3):
            return []

        res = []
        nums.sort()

        for i in range(n):

            if(nums[i]>0):
                return res

            if i == 0 or nums[i] > nums[i-1]:
                l = i+1
                r = n-1
                while l < r:
                    s = nums[i] + nums[l] + nums[r]
                    if s == 0:
                        res.append([nums[i],nums[l],nums[r]])
                        l += 1
                        r -= 1
                        while l < r and nums[l] == nums[l-1]:
                            l += 1
                        while l < r and nums[r] == nums[r+1]:
                            r -= 1
                    elif s > 0:
                        r -= 1  # 右边坐标左移一位
                    else:
                        l += 1  # 左边坐标右移一位
        return res

 

16. 最接近的三数之和

给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。

例如,给定数组 nums = [-1,2,1,-4], 和 target = 1.

与 target 最接近的三个数的和为 2. (-1 + 2 + 1 = 2).

思路:与15题相同,首先排序,但此题

class Solution:
    def threeSumClosest(self, nums: List[int], target: int) -> int:
        n = len(nums)
        if not nums or n<3:
            return 

        nums.sort()  # [-4,-1,1,2] target=1
        res = abs(sum(nums[0:3]) - target)
        s_out = sum(nums[0:3])

        for i in range(n):  
            if i==0 or nums[i] > nums[i-1]:
                l = i+1
                r = n-1
                while l < r:
                    s = nums[i] + nums[l] +nums[r]
                    if s == target:
                        return target
                    elif s > target:
                        if s-target < res:
                            res = s-target
                            s_out = s
                        r -= 1
                        while l<r and nums[r] == nums[r+1]:
                            r -= 1
                    else:
                        if target -s < res:
                            res = target -s 
                            s_out = s
                        l += 1
                        while l<r and nums[l] == nums[l-1]:
                            l += 1
        return s_out

 

17. 电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

示例:

输入:"23"   输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
说明:尽管上面的答案是按字典序排列的,但是你可以任意选择答案输出的顺序。

class Solution(object):
    def letterCombinations(self, digits):
        """
        动态规划
        dp[i]: 前i个字母的所有组合
        由于dp[i]只与dp[i-1]有关,可以使用变量代替列表存储降低空间复杂度
        :type digits: str
        :rtype: List[str]
        """
        if not digits:
            return []
        d = {'2': 'abc', '3': 'def', '4': 'ghi', '5': 'jkl',
             '6': 'mno', '7': 'pqrs', '8': 'tuv', '9': 'wxyz'}
        n = len(digits)
        dp = [[] for _ in range(n)]
        dp[0] = [x for x in d[digits[0]]]
        for i in range(1, n):
            dp[i] = [x + y for x in dp[i - 1] for y in d[digits[i]]]
        return dp[-1]

    def letterCombinations2(self, digits):
        """
        使用变量代替上面的列表
        降低空间复杂度
        :type digits: str
        :rtype: List[str]
        """
        if not digits:
            return []
        d = {'2': 'abc', '3': 'def', '4': 'ghi', '5': 'jkl',
             '6': 'mno', '7': 'pqrs', '8': 'tuv', '9': 'wxyz'}
        n = len(digits)
        res = ['']
        for i in range(n):
            res = [x + y for x in res for y in d[digits[i]]]
        return res

    def letterCombinations3(self, digits):
        """
        递归
        :param digits:
        :return:
        """
        d = {'2': 'abc', '3': 'def', '4': 'ghi', '5': 'jkl',
             '6': 'mno', '7': 'pqrs', '8': 'tuv', '9': 'wxyz'}
        if not digits:
            return []
        if len(digits) == 1:
            return [x for x in d[digits[0]]]
        return [x + y for x in d[digits[0]] for y in self.letterCombinations3(digits[1:])]

 

20. 有效的括号

给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。

有效字符串需满足:

左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。

示例 1:    输入: "()"         输出: true
示例 2:    输入: "()[]{}"     输出: true
示例 3:     输入: "(]"        输出: false
示例 4:     输入: "([)]"      输出: false
示例 5:    输入: "{[]}"       输出: true

class Solution:
    def isValid(self, s: str) -> bool:
        if s == '':return True
        d = {'}':'{', ')':'(', ']':'['}
        l = list()
        for i in s:
            if i in d:
                top = l.pop() if l else '#'
                if top != d[i]:
                    return False               
            else:
                l.append(i)

        return not l

思路:栈

 

21. 合并两个有序链表

将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 

示例:

输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4

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

class Solution:
    def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
        if not l1:return l2
        if not l2:return l1
        cu1,cu2 = l1,l2
        cur = ListNode(0)
        head = cur
        while cu1 and cu2:
            if cu1.val <= cu2.val:
                node = ListNode(cu1.val)  
                cu1 = cu1.next
            else:
                node = ListNode(cu2.val)
                cu2 = cu2.next
            cur.next = node
            cur = cur.next
        if cu1: cur.next = cu1
        if cu2: cur.next = cu2
        return head.next

思路:双指针遍历,构建新列表。

空间复杂度:O(m+n)

时间复杂度:O(m+n)

 

26. 删除排序数组中的重复项

给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。

不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。

示例 1:

给定数组 nums = [1,1,2], 

函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。 

你不需要考虑数组中超出新长度后面的元素。

示例 2:

给定 nums = [0,0,1,1,1,2,2,3,3,4],

函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。

你不需要考虑数组中超出新长度后面的元素。

方法一:快慢指针

class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        if nums == []: return 0
        slow = 0
        for fast in range(1,len(nums)):
            if nums[fast] != nums[slow]:
                slow += 1
                nums[slow] = nums[fast]
        return slow+1

思想:用一个慢指针记录新列表尾部,用快指针遍历直到发现不等于尾部的,将此元素添加到尾部。

执行用时 :36 ms

内存消耗 :14.4 MB

方法二:反向遍历 

def removeDuplicates(nums):
    for num_index in range(len(nums)-1, 0, -1):
        if nums[num_index] == nums[num_index-1]:
            nums.pop(num_index)
    return len(nums)

思想:从nums的最后一个开始遍历, 然后与前一个进行对比,如果相等,,则删除该位置的数;不等, 则继续往前遍历

执行用时:120ms

内存消耗:15.6MB

 

27. 移除元素

给定一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,返回移除后数组的新长度。

不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

示例 1:

给定 nums = [3,2,2,3], val = 3,

函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。

你不需要考虑数组中超出新长度后面的元素。

示例 2:

给定 nums = [0,1,2,2,3,0,4,2], val = 2,

函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。

注意这五个元素可为任意顺序。

你不需要考虑数组中超出新长度后面的元素。

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        if nums == [] or val is None:
            return len(nums)
        slow = 0
        for fast in range(len(nums)):
            if nums[fast] != val:
                nums[slow] = nums[fast]
                slow += 1
        return slow

 思想:快慢指针

 

28. 实现 strStr()

给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回  -1。

示例 1:     输入: haystack = "hello", needle = "ll"    输出: 2

示例 2:      输入: haystack = "aaaaa", needle = "bba"     输出: -1

说明:

当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。

对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与C语言的 strstr() 以及 Java的 indexOf() 定义相符。

class Solution:
    def strStr(self, haystack: str, needle: str) -> int:
        return haystack.find(needle)

34. 在排序数组中查找元素的第一个和最后一个位置

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

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

如果数组中不存在目标值,返回 [-1, -1]。

示例 1:   输入: nums = [5,7,7,8,8,10], target = 8   输出: [3,4]
示例 2:   输入: nums = [5,7,7,8,8,10], target = 6   输出: [-1,-1]

class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        # 二分查找,找到扩散
        if not nums:return [-1,-1]
        n = len(nums)

        low = 0
        high = n-1
        while low <= high:
            mid = (low+high)>>1
            if nums[mid] == target:
                start = end = mid
                while start>0 and nums[start-1]==target:
                    start -= 1
                while end<n-1 and nums[end+1]==target:
                    end += 1
                return [start,end]
            elif nums[mid] > target:
                high = mid - 1
            else:
                low = mid + 1
        return [-1,-1]

35. 搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

你可以假设数组中无重复元素。

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

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

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

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

class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        if target < nums[0]:return 0    # 查找元素小于列表最小值
        if target > nums[-1]:return len(nums)  # 查找元素大于列表最大值
        # 查找元素肯定可以在列表中找到适当位置
        for i in range(len(nums)):  
            # 正好找到该元素,返回索引
            if nums[i] == target:
                return i
            # 前面元素小于查找元素,后面元素大于查找元素,返回后面元素索引
            if nums[i] < target and nums[i+1]> target:
                return i+1

36. 有效的数独

判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。

数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。

上图是一个部分填充的有效的数独。

数独部分空格内已填入了数字,空白格用 '.' 表示。

示例 1:输入:
[
  ["5","3",".",".","7",".",".",".","."],
  ["6",".",".","1","9","5",".",".","."],
  [".","9","8",".",".",".",".","6","."],
  ["8",".",".",".","6",".",".",".","3"],
  ["4",".",".","8",".","3",".",".","1"],
  ["7",".",".",".","2",".",".",".","6"],
  [".","6",".",".",".",".","2","8","."],
  [".",".",".","4","1","9",".",".","5"],
  [".",".",".",".","8",".",".","7","9"]
]
输出: true
示例 2:输入:
[
  ["8","3",".",".","7",".",".",".","."],
  ["6",".",".","1","9","5",".",".","."],
  [".","9","8",".",".",".",".","6","."],
  ["8",".",".",".","6",".",".",".","3"],
  ["4",".",".","8",".","3",".",".","1"],
  ["7",".",".",".","2",".",".",".","6"],
  [".","6",".",".",".",".","2","8","."],
  [".",".",".","4","1","9",".",".","5"],
  [".",".",".",".","8",".",".","7","9"]
]
输出: false      解释: 除了第一行的第一个数字从 5 改为 8 以外,空格内其他数字均与 示例1 相同。但由于位于左上角的 3x3 宫内有两个 8 存在, 因此这个数独是无效的。

思路:判断行有没有重复的、判断列有没有重复的、判断每个3x3方格有没有重复的

class Solution:
    def isValidSudoku(self, board: List[List[str]]) -> bool:
        if not board or len(board)!=9 or len(board[0])!=9:
            return False
        # 判断行有没有重复的
        for row in board:
            l = []
            for i in row:
                if i == '.':continue
                if i not in l: l.append(i)
                else: return False
        # 判断列有没有重复的
        for j in range(9):
            l=[]
            for i in range(9):
                if board[i][j] == '.':continue
                if board[i][j] not in l:l.append(board[i][j])
                else:return False
        # 判断3x3方格有没有重复的
        for i in (0,3,6):
            for j in (0,3,6):
                l=[]
                for x in range(i,i+3):
                    for y in range(j,j+3):
                        if board[x][y] == '.':continue
                        if board[x][y] not in l:l.append(board[x][y])
                        else:return False

        return True

 

38. 外观数列

「外观数列」是一个整数序列,从数字 1 开始,序列中的每一项都是对前一项的描述。前五项如下:

1.     1
2.     11
3.     21
4.     1211
5.     111221

1 被读作  "one 1"  ("一个一") , 即 11。

11 被读作 "two 1s" ("两个一"), 即 21。

21 被读作 "one 2",  "one 1" ("一个二" ,  "一个一") , 即 1211。

给定一个正整数 n(1 ≤ n ≤ 30),输出外观数列的第 n 项。

注意:整数序列中的每一项将表示为一个字符串。

示例 1:   输入: 1    输出: "1"

示例 2:     输入: 4       输出: "1211"

解释:当 n = 3 时,序列是 "21",其中我们有 "2" 和 "1" 两组,"2" 可以读作 "12",也就是出现频次 = 1 而 值 = 2;类似 "1" 可以读作 "11"。所以答案是 "12" 和 "11" 组合在一起,也就是 "1211"。

class Solution:
    def countAndSay(self, n: int) -> str:
        if n == 1: return '1'
        pre = self.countAndSay(n-1)  # pre存放前一个字符串

        res = []
        count = 1
        for idx in range(len(pre)):

            if idx == 0 :
                count = 1

            elif pre[idx] != pre[idx -1]:
                res.append(count)
                res.append(pre[idx-1])
                count = 1
            elif pre[idx] == pre[idx-1]:
                count +=1

            if idx == len(pre) - 1:
                res.append(count)
                res.append(pre[idx])
                
        return ''.join(str(i) for i in res)

思路:
使用递归的方法,这道题其实跟斐波那契数列有点类似,都是基于上一项的结果得出下一项的结果,因此适合使用递归来解决问题。

因为确定了n=1时的值,所以基线条件就可以设置为 n <= 1 时返回。

在后面的循环里,思路是当遇到的字符串与上一个相等的时候,计数器+1,否则结束计数,并将当前结果添加到res变量中,并且计数器

复到1。比较需要注意的是边际条件,在字符串开头跟结尾的时候做了单独的处理。

处理字符串开头的时候

if idx == 0 :
    count = 1

处理字符串末尾的时候

if idx == len(pre) - 1:
    res.append(count)
    res.append(pre[idx])

 

53. 最大子序和

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例:    输入: [-2,1,-3,4,-1,2,1,-5,4],    输出: 6

解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

进阶:

如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        max_sum = nums[0]
        for i in range(1,len(nums)):
            if nums[i-1] > 0:
                nums[i] += nums[i-1]
            max_sum = max(max_sum, nums[i])
        return max_sum

思想:动态规划

  • 时间复杂度:\mathcal{O}(N)O(N)。只遍历了一次数组。
  • 空间复杂度:\mathcal{O}(1)O(1),使用了常数的空间。

 

58. 最后一个单词的长度

给定一个仅包含大小写字母和空格 ' ' 的字符串 s,返回其最后一个单词的长度。

如果字符串从左向右滚动显示,那么最后一个单词就是最后出现的单词。

如果不存在最后一个单词,请返回 0 。

说明:一个单词是指仅由字母组成、不包含任何空格的 最大子字符串。

示例:

输入: "Hello World"

输出: 5

class Solution:
    def lengthOfLastWord(self, s: str) -> int:
        if s is None or len(s) == 0:
            return 0
        s = s.rstrip()
        words = s.split(' ')
        return len(words[-1])

 

66. 加一

给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。

最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。

你可以假设除了整数 0 之外,这个整数不会以零开头。

示例 1:   输入: [1,2,3]     输出: [1,2,4]

解释: 输入数组表示数字 123。

示例 2:   输入: [4,3,2,1]      输出: [4,3,2,2]

解释: 输入数组表示数字 4321。

class Solution:
    def plusOne(self, digits: List[int]) -> List[int]:
        # 尝试最笨的算法
        for i in range(len(digits)-1,-1,-1):
            if digits[i] + 1 == 10:
                digits[i] = 0
            else:
                digits[i] = digits[i] + 1
                return digits
        a = [1]
        a.extend(digits)
        return a

执行用时 :32 ms, 在所有 Python3 提交中击败了86.63%的用户

内存消耗 :13.4 MB, 在所有 Python3 提交中击败了34.26%的用户

class Solution:
    def plusOne(self, digits: List[int]) -> List[int]:
        # 转换成数字
        digits = [str(i) for i in digits]  
        num = int(''.join(digits))  
        num += 1
        return list(map(eval, list(str(num))))

思路:转换为数字进行加和再转成列表,显然时间复杂度高于上面,但是我们可以用此题复习一下类型转换

首先将,数字列表转为字符列表。注:数字列表需要先变为字符列表才能进一步使用.join方法

list1 = [1,2,3,4]
list2 = [str(i) for i in list1] 

# list2 = ['1','2','3','4']

其次,将字符列表连接成字符串。

str = ''.join(['1','2','3','4'])

# str = '1234'

再将数字变成字符列表。注:数字不能直接转换成列表

list3 = list(str(1234))

# list3 = ['1','2','3','4']

最后,将字符列表转换为数字列表

list4 = list(map(eval,['1','2','3','4']))
list5 = list(map(int,['1','2','3','4']))

# list4 = list5 = [1,2,3,4]

 

67. 二进制求和

给定两个二进制字符串,返回他们的和(用二进制表示)。

输入为非空字符串且只包含数字 1 和 0。

示例 1:    输入: a = "11", b = "1"     输出: "100"

示例 2:     输入: a = "1010", b = "1011"       输出: "10101"

class Solution:
    def addBinary(self, a: str, b: str) -> str:
        if a == '': return b
        if b == '': return a
        return '{:b}'.format(int(a, 2) + int(b, 2))

 

69. x 的平方根

实现 int sqrt(int x) 函数。

计算并返回 x 的平方根,其中 x 是非负整数。

由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。

示例 1:   输入: 4    输出: 2

示例 2:   输入: 8    输出: 2

说明: 8 的平方根是 2.82842...,  由于返回类型是整数,小数部分将被舍去。

import math
class Solution:
    def mySqrt(self, x: int) -> int:
        return math.trunc(math.sqrt(x))

 

70. 爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

注意:给定 n 是一个正整数。

示例 1:  输入: 2    输出: 2

解释: 有两种方法可以爬到楼顶。1.  1 阶 + 1 阶      2.  2 阶

示例 2:   输入: 3    输出: 3

解释: 有三种方法可以爬到楼顶。1.  1 阶 + 1 阶 + 1 阶       2.  1 阶 + 2 阶          3.  2 阶 + 1 阶

暴力法:超出时间限制

class Solution:
    def climbStairs(self, n: int) -> int:
        if n == 0: return 0
        if n == 1: return 1
        if n == 2: return 2
        return self.climbStairs(n-1) + self.climbStairs(n-2)

动态规划

class Solution:
    def climbStairs(self, n: int) -> int:
        if n <= 1: return 1
        l = [0] * (n+1)   # l=[0 for i in range(n+1)]
        l[1] = 1
        l[2] = 2
        for i in range(3,n+1):
            l[i] = l[i-1] + l[i-2]
        return l[n]

 

83. 删除排序链表中的重复元素

给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。

示例 1:   输入: 1->1->2    输出: 1->2

示例 2:   输入: 1->1->2->3->3   输出: 1->2->3

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

class Solution:
    def deleteDuplicates(self, head: ListNode) -> ListNode:
        # 直接在链表上修改
        if head is None: return None
        post = head
        while post.next:
            cur = post
            post = post.next
            if cur.val == post.val:
                cur.next = post.next
                post = cur
        return head

 

88. 合并两个有序数组

给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。

说明:

初始化 nums1 和 nums2 的元素数量分别为 m 和 n。

你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。

示例:    输入:nums1 = [1,2,3,0,0,0],m = 3,nums2 = [2,5,6],n = 3

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

自创最笨写法:找到元素该插入的位置,将其它元素后移

class Solution:
    def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
        if n <= 0: return
        if m <= 0: 
            m = n
            for i in range(m):
                nums1[i] = nums2[i] 
            return 
        start = 0
        for j in range(n):
            for i in range(start,m):
                if nums1[i] <= nums2[j]: # 插入值大于当前值
                    continue
                else: # 插入到目前位置
                    z = m
                    while z > i:
                        nums1[z] = nums1[z-1]
                        z -= 1
                    nums1[i] = nums2[j]
                    m += 1
                    break
            if i == m-1:
                nums1[m] = nums2[j]
                m += 1
            start = i

合并后排序

class Solution(object):
    def merge(self, nums1, m, nums2, n):
        nums1[:] = sorted(nums1[:m] + nums2)

方法二 : 双指针 / 从前往后

一般而言,对于有序数组可以通过 双指针法 达到O(n + m)的时间复杂度。

最直接的算法实现是将指针p1 置为 nums1的开头, p2为 nums2的开头,在每一步将最小值放入输出数组中。

由于 nums1 是用于输出的数组,需要将nums1中的前m个元素放在其他地方,也就需要 O(m) 的空间复杂度。

class Solution(object):
    def merge(self, nums1, m, nums2, n):
        nums1_copy = nums1[:m] 
        nums1[:] = []
        p1 = 0 
        p2 = 0
        
        while p1 < m and p2 < n: 
            if nums1_copy[p1] < nums2[p2]: 
                nums1.append(nums1_copy[p1])
                p1 += 1
            else:
                nums1.append(nums2[p2])
                p2 += 1
        if p1 < m: 
            nums1[p1 + p2:] = nums1_copy[p1:]
        if p2 < n:
            nums1[p1 + p2:] = nums2[p2:]

方法三 : 双指针 / 从后往前

方法二已经取得了最优的时间复杂度O(n + m),但需要使用额外空间。这是由于在从头改变nums1的值时,需要把nums1中的元素存放在其他位置。

如果我们从结尾开始改写 nums1 的值又会如何呢?这里没有信息,因此不需要额外空间。

这里的指针 p 用于追踪添加元素的位置。

class Solution(object):
    def merge(self, nums1, m, nums2, n):
        p1 = m - 1
        p2 = n - 1
        p = m + n - 1
        
        while p1 >= 0 and p2 >= 0:
            if nums1[p1] < nums2[p2]:
                nums1[p] = nums2[p2]
                p2 -= 1
            else:
                nums1[p] =  nums1[p1]
                p1 -= 1
            p -= 1
        
        nums1[:p2 + 1] = nums2[:p2 + 1]

 

100. 相同的树

给定两个二叉树,编写一个函数来检验它们是否相同。

如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。

示例 1:

输入:       1          1
               / \         / \
             2   3     2   3

        [1,2,3],   [1,2,3]

输出: true
示例 2:

输入:      1        1
              /           \
            2             2

        [1,2],     [1,null,2]

输出: false
示例 3:

输入:       1          1
               / \         / \
             2   1     1   2

        [1,2,1],   [1,1,2]

输出: false

 递归:最简单的策略是使用递归。首先判断 p 和 q 是不是 None,然后判断它们的值是否相等。若以上判断通过,则递归对子结点做同样操作。

class Solution:
    def isSameTree(self, p, q):
        if not p and not q:
            return True
        if not q or not p:
            return False
        if p.val != q.val:
            return False
        return self.isSameTree(p.right, q.right) and self.isSameTree(p.left, q.left)

 复杂度分析

时间复杂度 : O(N),其中 N 是树的结点数,因为每个结点都访问一次。

空间复杂度 : 最优情况(完全平衡二叉树)时为 O(log(N)),最坏情况下(完全不平衡二叉树)时为 O(N),用于维护递归栈。

 

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值