LeetCode刷题记录:31~40

31.下一个排列

实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
必须原地修改,只允许使用额外常数空间。

示例:

以下是一些例子,输入位于左侧列,其相应输出位于右侧列。
1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1

思路:

本题的重点在于字典序算法,详情请见https://www.cnblogs.com/darklights/p/5285598.html,知道字典序算法后,按照算法实现程序即可。

代码:

class Solution(object):
    def nextPermutation(self, nums):
        """
        :type nums: List[int]
        :rtype: None Do not return anything, modify nums in-place instead.
        """
        if nums == []:
            return

        # 先检查nums是否为最大字典序
        max_flag = True
        for i in range(len(nums)-1):
            if nums[i] < nums[i+1]:
                max_flag = False
                break
        if max_flag:
            for i in range(len(nums)//2):
                reverse_index = len(nums) - 1 - i
                nums[i], nums[reverse_index] = nums[reverse_index], nums[i]
            return

        # 找到一般情况下的下一个字典序
        # 1.从右至左找到第一个左邻比右邻小的数
        for i in range(len(nums)-1, 0, -1):
            if nums[i-1] < nums[i]:
                # 2.从右至左找到第一个比nums[i-1]大的数
                for j in range(len(nums)-1, i-1, -1):
                    if nums[j] > nums[i-1]:
                        # 3.交换nums[i-1]和nums[j]
                        nums[j], nums[i-1] = nums[i-1], nums[j]
                        # 4.对nums[i-1]后面的数,从小到大排序
                        for k in range(i, len(nums)):
                            for m in range(i, len(nums) - (k - i) -1):
                                if nums[m] > nums[m+1]:
                                    nums[m], nums[m+1] = nums[m+1], nums[m]
                        return

分析:

时间复杂度:上面方法的性能瓶颈在于排序那一步,其他步骤的时间复杂度为O(n),使用归并排序等方法能使得总体时间复杂度降为O(nlogn), 由于是就地算法,如果可以用O(n)的空间,那么就使用桶排序,时间复杂度就降为O(n)
空间复杂度:O(1), 就地算法,只用了常数的额外空间

32.最长有效括号

给定一个只包含 ‘(’ 和 ‘)’ 的字符串,找出最长的包含有效括号的子串的长度。

示例:

示例 1:
输入: “(()”
输出: 2
解释: 最长有效括号子串为 “()”

示例 2:
输入: “)()())”
输出: 4
解释: 最长有效括号子串为 “()()”

思路:

使用两个栈结构进行有效括号统计,一个是"(",")"的栈结构,另一个是数字栈结构,用以记录某位置后面匹配的最长的有效括号数

代码:

class Solution(object):
    def longestValidParentheses(self, s):
        """
        :type s: str
        :rtype: int
        """
        
        # 先放入")"和0,避免边界情况
        s_stack = [")"]
        n_stack = [0]

        for parentheses in s:
            if parentheses == ")" and s_stack[-1] == "(": # 只有这种情况会特殊处理 
                s_stack.pop()
                # valid_num记录的是此位置后面有多长的有效括号,当此位置也是有效括号的一部分时,将valid_num + 2 加到前一位置。
                valid_num = n_stack.pop()
                n_stack[-1] += valid_num + 2
            else:
                s_stack.append(parentheses)
                n_stack.append(0)

        return max(n_stack)

分析:

时间复杂度:s中的每一个字符串最多操作O(1)次,那么总的时间复杂度为O(n)
空间复杂度:O(n),引入了栈结构进行储存字符

33.搜索旋转排序数组

假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。
你可以假设数组中不存在重复的元素。
你的算法时间复杂度必须是 O(log n) 级别。

示例:

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

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

思路:

时间复杂度规定为O(logn),说明需要使用类二分查找法,此题需要两步,第一步二分查找找到旋转点,第二步在有序数组中查找target
旋转数组轴点的特性是,数组中只有轴点的位置nums[pivor-1]>nums[pivor], nums[pivor]是最小点

代码:

class Solution(object):
    def search(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: int
        """
        if nums == []:
            return -1
        if len(nums) == 1:
            return 0 if nums[0] == target else -1

        # 先查找旋转点
        l = 0
        r = len(nums) - 1
        while l < r:
            # 升序数组不存在相等的情况
            mid = (l + r) // 2
            if nums[l] < nums[r]:
                break
            elif nums[l] <= nums[mid]: 
            # 注意这里的条件是nums[l] <= nums[mid]有一个=号,当l=mid时,说明l和r是相邻的,此时有两种情况,
            # 一种nums[l]<nums[r]此时之前的判断已经找到l是轴点,另一种nums[r]>nums[r]此时r为轴点,那么令l=l+1
                l = mid + 1
            else:
                r = mid

        # 进行二分查找
        pivor = l
        index_l = self.binary_search(nums[:pivor], target)
        if index_l >= 0:
            return index_l
        index_r = self.binary_search(nums[pivor:], target)
        if index_r >= 0:
            return index_r + pivor
        return -1

    def binary_search(self, nums, target):
        l = 0
        r = len(nums) - 1
        while l <= r:
            mid = (l + r) // 2
            if nums[mid] == target:
                return mid
            elif nums[mid] < target:
                l = mid + 1
            else:
                r = mid - 1
        return -1

分析:

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

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(object):
    def searchRange(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        start = self.search_start(nums, target)
        end = self.search_end(nums, target)
        return [start, end]

    def search_start(self, nums, target):
        # 找到最左边的target
        if nums == []:
            return -1

        l = 0
        r = len(nums) - 1

        while l < r:
            mid = (l + r) >> 1
            if nums[mid] >= target:
                r = mid
            else:
                l = mid + 1
        if nums[l] == target:
            return l
        return -1

    def search_end(self, nums, target):
        # 找到最右边的target
        if nums == []:
            return -1
        
        l = 0
        r = len(nums) - 1

        while l < r:
            mid = (l + r + 1) >> 1    # 注意这里是(l + r + 1) >> 1保证mid取到中间靠右的位置
            if nums[mid] <= target:
                l = mid
            else:
                r = mid - 1
        if nums[l] == target:
            return l
        return -1

分析:

时间复杂度:两个二分查找,O(logn)
空间复杂度:O(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

思路:

使用二分查找,查找target,没有找到的话,最后比较nums[l]和target的关系,nums[l]<target,则target的插入位置是l,否则为l+1

代码:

class Solution(object):
    def searchInsert(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: int
        """
        if nums == []:
            return 0

        l = 0
        r = len(nums) - 1

        while l < r:
            mid = (l + r) >> 1
            if nums[mid] == target:
                return mid
            elif nums[mid] < target:
                l = mid + 1
            else:
                r  = mid -1

        if nums[l] < target:
            return l + 1
        return l

分析:

时间复杂度:主体为二分查找,时间复杂度为O(logn)
空间复杂度:O(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 存在, 因此这个数独是无效的。

说明:
一个有效的数独(部分已被填充)不一定是可解的。
只需要根据以上规则,验证已经填入的数字是否有效即可。
给定数独序列只包含数字 1-9 和字符 ‘.’ 。
给定数独永远是 9x9 形式的。

思路:

验证数独是否有效比较简单,验证每个位置是否符合数独的三个条件即可(或者验证每行,列和方框是否符合条件即可)

代码:

class Solution(object):
    def isValidSudoku(self, board):
        """
        :type board: List[List[str]]
        :rtype: bool
        """
        # 分别验证行,列和方格是否符合数独条件
        for row in board:
            valid = self.is_valid(row)
            if valid == False:
                return False
        
        # 注意列的列表表达式
        for i in range(len(board)):
            column = [board[row][i] for row in range(9)]
            valid = self.is_valid(column)
            if valid == False:
                return False

        # 注意每个小方格的列表表达式
        for i in range(3):
            for j in range(3):
                sub_board = [board[3*i+n][3*j+m] for n in range(3) for m in range(3)]
                valid = self.is_valid(sub_board)
                if valid == False:
                    return False

        return True

    def is_valid(self, nums):
        num_times = {}
        for num in nums:
            if num != ".":
                if num in num_times:
                    return False
                else:
                    num_times[num] = 1
        return True

分析:

时间复杂度:9*9的方格,时间复杂度是常数级别的。O(1)
空间复杂度:O(1)

37.解数独

编写一个程序,通过已填充的空格来解决数独问题。

一个数独的解法需遵循如下规则:
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
空白格用 ‘.’ 表示。

示例:

在这里插入图片描述
在这里插入图片描述
Note:
给定的数独序列只包含数字 1-9 和字符 ‘.’ 。
你可以假设给定的数独只有唯一解。
给定数独永远是 9x9 形式的。

思路:

使用回溯法求解数独,一次填充每一个位置,对于每一个位置从1填充至9。当填充到每一位置时,如果数字有效,填充下一位置(从1到9),如果下一位置都无效,下一位置重置为".",改变当前位置填充值。

代码:

class Solution(object):
    def solveSudoku(self, board):
        """
        :type board: List[List[str]]
        :rtype: None Do not return anything, modify board in-place instead.
        """
        start = self.get_start(board)   # 获得第一个"."位置
        next = self.get_next(board) # 下一个"."位置,是一个位置关系字典

        self.solve(board, loc=start, next=next) # 回溯法


    def solve(self, board, loc, next):
        for num in range(1, 10):
            board[loc[0]][loc[1]] = str(num)
            if self.is_valid(board, loc):   # 检查num填入loc是否有效
                if loc in next.keys():      # 有效的情况下,loc在next.keys()中,填充下一位置。
                    next_loc = next[loc]
                    if self.solve(board, next_loc, next):
                        return board
                    else:
                        board[next_loc[0]][next_loc[1]] = "."   # 这步很重要,下一位置所有数字均无效,则重置为".",返回上级
                else:   # 有效的情况下,loc不在next.keys()中,说明loc是最后一个"."位置,数独完成。
                    return True


    def is_valid(self, board, loc):
        row_num_dict = {}
        for row_num in board[loc[0]]:
            if row_num != ".":
                if row_num in row_num_dict:
                    return False
                else:
                    row_num_dict[row_num] = True

        column_num_dict = {}
        for column_num in [board[j][loc[1]] for j in range(9)]:
            if column_num != ".":
                if column_num in column_num_dict:
                    return False
                else:
                    column_num_dict[column_num] = True

        sub_board_num_dict = {}
        sub_board_i = loc[0] // 3
        sub_board_j = loc[1] // 3
        for sub_board_num in [board[sub_board_i * 3 + n][sub_board_j * 3 + m] for n in range(3) for m in range(3)]:
            if sub_board_num != ".":
                if sub_board_num in sub_board_num_dict:
                    return False
                else:
                    sub_board_num_dict[sub_board_num] = True
        return True

    def get_start(self, board):
        for row in range(9):
            for column in range(9):
                if board[row][column] == ".":
                    return (row, column)

    def get_next(self, board):
        next = {}
        pre_loc = ""
        for row in range(9):
            for column in range(9):
                if board[row][column] == ".":
                    cur_loc = (row, column)
                    if pre_loc:
                        next[pre_loc] = cur_loc
                    pre_loc = cur_loc
        return next

分析:

时间复杂度:回溯法的时间复杂度很高,具体级别不是很清楚,解数独是一个NP难的问题
空间复杂度:O(1), 储存一个board和常数级别的字典即可

38.报数

报数序列是一个整数序列,按照其中的整数的顺序进行报数,得到下一个数。其前五项如下:

  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个数是第n-1个数的读音,每个读连续相同的数字段,例如,数列第一项不是1吗,那第二项就报第一项的有1个1,输出11,然后第三项就在第二项的基础上报数,第二项是11,第三项不就是2个1么,然后输出21。。。

代码:

class Solution(object):
    def countAndSay(self, n):
        """
        :type n: int
        :rtype: str
        """
        nums = []
        if n <= 0:
            return None

        # 构建长度为n的nums,最后的输出结果为nums[-1]
        nums.append("1")
        for i in range(n-1):
            nums.append(self.say(nums[-1]))

        return nums[-1]

    def say(self, num):
        say_num = ""

        # 读num, 一次读取连续相同的数字字符
        pre_char = ""
        count = 0
        for char in num:
            if pre_char == "" or char == pre_char:
                count += 1
            else:
                say_num += str(count) + pre_char
                count = 1
            pre_char = char

        say_num += str(count) + pre_char  # 写入最后一串相同数字
        return say_num

分析:

时间复杂度:这题的时间复杂度不好论证,我的猜测是至少为O(n2)以上
空间复杂度:O(n2)以上

39.组合总和

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。

说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。

示例:

示例 1:
输入: candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]

示例 2:
输入: candidates = [2,3,5], target = 8,
所求解集为:
[
[2,2,2,2],
[2,3,3],
[3,5]
]

思路:

本题需要使用递归的办法,类似于第17题电话号码组合,递归的关键是控制好start,做到不重不漏。

代码:

class Solution(object):
    def combinationSum2(self, candidates, target):
        """
        :type candidates: List[int]
        :type target: int
        :rtype: List[List[int]]
        """
        result = []
        if candidates == []:
            return result

        candidates = sorted(candidates) # 对数组进行排序
        for i in range(len(candidates)):    # 第一重循环,start从0到len(candidates)-1
            self.get_candidate(candidates, target, start=i, sub=[], result=result)

        return result

    def get_candidate(self, candidates, target, start, sub, result):
        ele = candidates[start] # ele是目前可用的最小的元素
        # 递归基
        if ele > target:
            return
        elif ele == target:
            if sub + [ele] not in result:   # 去重
                result.append(sub + [ele])  # 使用sub + [ele],不能用sub.append(ele), sub.append(ele)没有返回值
            return
        elif ele < target:
            # 第n重循环,新的start是从start到len(candidates)-1
            for i in range(start, len(candidates)):
                if candidates[i] <= target - ele: # 提前终止无效的迭代
                    self.get_candidate(candidates, target - ele, i, sub + [ele], result)
                else:
                    break

分析:

时间复杂度:O(n3), 对于1到n这么一个candidate,target为n,start为1是会有n2种情况,start可以为1到n,所以总的时间复杂度为n3
空间复杂度:O(n3)

40.组合总和 II

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。

说明:
所有数字(包括目标数)都是正整数。
解集不能包含重复的组合。

示例:

示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]

示例 2:
输入: candidates = [2,5,2,1,2], target = 5,
所求解集为:
[
[1,2,2],
[5]
]

思路:

与上题十分类似,区别在于每个数只能用一次,那么第n重循环的新start起点应该为start+1

代码:

class Solution(object):
    def combinationSum2(self, candidates, target):
        """
        :type candidates: List[int]
        :type target: int
        :rtype: List[List[int]]
        """
        result = []
        if candidates == []:
            return result

        candidates = sorted(candidates) # 对数组进行排序
        for i in range(len(candidates)):    # 第一重循环,start从0到len(candidates)-1
            self.get_candidate(candidates, target, start=i, sub=[], result=result)

        return result

    def get_candidate(self, candidates, target, start, sub, result):
        ele = candidates[start] # ele是目前可用的最小的元素
        # 递归基
        if ele > target:
            return
        elif ele == target:
            if sub + [ele] not in result:   # 去重
                result.append(sub + [ele])  # 使用sub + [ele],不能用sub.append(ele), sub.append(ele)没有返回值
            return
        elif ele < target:
            # 第n重循环,新的start是从start+1到len(candidates)-1
            for i in range(start+1, len(candidates)):
                if candidates[i] <= target - ele: # 提前终止无效的迭代
                    self.get_candidate(candidates, target - ele, i, sub + [ele], result)
                else:
                    break

分析:

时间复杂度:O(n3), 对于1到n这么一个candidate,target为n,start为1是会有n2种情况,start可以为1到n,所以总的时间复杂度为n3
空间复杂度:O(n3)

猜你喜欢

转载自blog.csdn.net/weixin_40014471/article/details/89674499