Leetcode 21-40 Python解法

21. 合并两个有序链表

简单

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

示例 1:

img

输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

示例 2:

输入:l1 = [], l2 = []
输出:[]

示例 3:

输入:l1 = [], l2 = [0]
输出:[0]

提示:

  • 两个链表的节点数目范围是 [0, 50]
  • -100 <= Node.val <= 100
  • l1l2 均按 非递减顺序 排列

Python做法

class Solution:
    def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:

        def mergeTwoListsHelper(list1,list2):
            cur_l1 = list1
            cur_l2 = list2
            l_new_head = ListNode()
            cur_l_new = l_new_head
            while cur_l1 and cur_l2:
                # 当l1 and l2存在时一直迭代

                # 比较l1和l2的大小,如果谁小就将其作为新链表的元素加入,并l = l.next
                if cur_l1.val <= cur_l2.val:
                    cur_l_new.next = cur_l1
                    cur_l1 = cur_l1.next
                else:
                    cur_l_new.next = cur_l2
                    cur_l2 = cur_l2.next
                
                cur_l_new = cur_l_new.next
                
            cur_l_new.next = cur_l1 if cur_l1 else cur_l2
            
            return l_new_head.next

    
        new_list  = mergeTwoListsHelper(list1,list2)
        return new_list

---

22. 括号生成

中等

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

示例 1:

输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]

示例 2:

输入:n = 1
输出:["()"]

提示:

  • 1 <= n <= 8

深度优先搜索

class Solution:
    def generateParenthesis(self, n: int):
        def dfs(depth_left,depth_right):
            global ans,path
            if depth_left == 0 and depth_right == 0:
                ans = []
                path = []

            if depth_left == n and depth_right == n:
                ans.append("".join(path))

            # 剪枝
            if depth_left < n :
                path.append('(')
                dfs(depth_left+1,depth_right)
                path.pop()

            if depth_right < n and depth_right < depth_left:
                path.append(')')
                dfs(depth_left,depth_right+1)
                path.pop()

            return ans

        return dfs(0,0)

---

23. 合并 K 个升序链表

困难

给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

示例 1:

输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
  1->4->5,
  1->3->4,
  2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6

示例 2:

输入:lists = []
输出:[]

示例 3:

输入:lists = [[]]
输出:[]

提示:

  • k == lists.length
  • 0 <= k <= 10^4
  • 0 <= lists[i].length <= 500
  • -10^4 <= lists[i][j] <= 10^4
  • lists[i]升序 排列
  • lists[i].length 的总和不超过 10^4

Python做法

class Solution:
    def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]:
        if len(lists) == 0:
            return None  # return []不对

        def mergeTwoListsHelper(list1,list2):
            cur_l1 = list1
            cur_l2 = list2
            l_new_head = ListNode()
            cur_l_new = l_new_head
            while cur_l1 and cur_l2:
                # 当l1 and l2存在时一直迭代

                # 比较l1和l2的大小,如果谁小就将其作为新链表的元素加入,并l = l.next
                if cur_l1.val <= cur_l2.val:
                    cur_l_new.next = cur_l1
                    cur_l1 = cur_l1.next
                else:
                    cur_l_new.next = cur_l2
                    cur_l2 = cur_l2.next
                
                cur_l_new = cur_l_new.next
                
            cur_l_new.next = cur_l1 if cur_l1 else cur_l2
            
            return l_new_head.next

        while len(lists) > 1:
            list1 = lists.pop()
            list2 = lists.pop()
            lists.append(mergeTwoListsHelper(list1,list2))
        
        return lists[0]

---

24. 两两交换链表中的节点

中等

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

示例 1:

img

输入:head = [1,2,3,4]
输出:[2,1,4,3]

示例 2:

输入:head = []
输出:[]

示例 3:

输入:head = [1]
输出:[1]

提示:

  • 链表中节点的数目在范围 [0, 100]
  • 0 <= Node.val <= 100

Python做法

class Solution:
    def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
        cur = head
        counter = 0

        if cur == None:
            return 

        while cur != None and cur.next != None:
            if counter == 0:
                head = cur.next
            else:
                pre.next = cur.next
                
            next_pair = cur.next.next
            cur.next.next = cur
            cur.next = next_pair

            pre = cur
            cur = next_pair
            counter += 1

        return head

---

25. K 个一组翻转链表

困难

给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。

k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

示例 1:

img

输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]

示例 2:

img

输入:head = [1,2,3,4,5], k = 3
输出:[3,2,1,4,5]

提示:

  • 链表中的节点数目为 n
  • 1 <= k <= n <= 5000
  • 0 <= Node.val <= 1000

**进阶:**你可以设计一个只用 O(1) 额外内存空间的算法解决此问题吗?

Python做法

class Solution:
    def reverseKGroup(self, head: ListNode, k: int) -> ListNode:
        # 创建一个哑节点,它的下一个节点指向head
        dummy = ListNode(0)
        dummy.next = head
        start = dummy
        
        while True:
            end = start
            for i in range(k):
                end = end.next
                if not end:
                    return dummy.next
            
            # 保存下一段的开始
            nextGroup = end.next
            # 反转当前段
            prev, curr = end.next, start.next
            while curr != nextGroup:
                temp = curr.next
                curr.next = prev
                prev = curr
                curr = temp
            
            # 将反转后的段连接到前面的段
            temp = start.next
            start.next = end
            start = temp
        
        return dummy.next

---

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

简单

给你一个 非严格递增排列 的数组 nums ,请你** 原地** 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。

考虑 nums 的唯一元素的数量为 k ,你需要做以下事情确保你的题解可以被通过:

  • 更改数组 nums ,使 nums 的前 k 个元素包含唯一元素,并按照它们最初在 nums 中出现的顺序排列。nums 的其余元素与 nums 的大小不重要。
  • 返回 k

判题标准:

系统会用下面的代码来测试你的题解:

int[] nums = [...]; // 输入数组
int[] expectedNums = [...]; // 长度正确的期望答案

int k = removeDuplicates(nums); // 调用

assert k == expectedNums.length;
for (int i = 0; i < k; i++) {
    assert nums[i] == expectedNums[i];
}

如果所有断言都通过,那么您的题解将被 通过

示例 1:

输入:nums = [1,1,2]
输出:2, nums = [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]
解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。

提示:

  • 1 <= nums.length <= 3 * 104
  • -104 <= nums[i] <= 104
  • nums 已按 非严格递增 排列

集合去重

from typing import List

class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        count = set(nums)  # 将列表转换为集合去重
        nums[:] = list(count)  # 将去重后的集合转换为列表赋值给nums
        nums.sort()
        return len(nums)  # 返回去重后的元素个数

---

27. 移除元素

简单

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。

假设 nums 中不等于 val 的元素数量为 k,要通过此题,您需要执行以下操作:

  • 更改 nums 数组,使 nums 的前 k 个元素包含不等于 val 的元素。nums 的其余元素和 nums 的大小并不重要。
  • 返回 k

用户评测:

评测机将使用以下代码测试您的解决方案:

int[] nums = [...]; // 输入数组
int val = ...; // 要移除的值
int[] expectedNums = [...]; // 长度正确的预期答案。
                            // 它以不等于 val 的值排序。

int k = removeElement(nums, val); // 调用你的实现

assert k == expectedNums.length;
sort(nums, 0, k); // 排序 nums 的前 k 个元素
for (int i = 0; i < actualLength; i++) {
    assert nums[i] == expectedNums[i];
}

如果所有的断言都通过,你的解决方案将会 通过

示例 1:

输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2,_,_]
解释:你的函数函数应该返回 k = 2, 并且 nums 中的前两个元素均为 2。
你在返回的 k 个元素之外留下了什么并不重要(因此它们并不计入评测)。

示例 2:

输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3,_,_,_]
解释:你的函数应该返回 k = 5,并且 nums 中的前五个元素为 0,0,1,3,4。
注意这五个元素可以任意顺序返回。
你在返回的 k 个元素之外留下了什么并不重要(因此它们并不计入评测)。

提示:

  • 0 <= nums.length <= 100
  • 0 <= nums[i] <= 50
  • 0 <= val <= 100
class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        nums.sort(key = lambda x:abs(x-val),reverse = True)
        while nums and nums[-1] == val:
            nums.pop()

---

28. 找出字符串中第一个匹配项的下标

简单

给你两个字符串 haystackneedle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1

示例 1:

输入:haystack = "sadbutsad", needle = "sad"
输出:0
解释:"sad" 在下标 0 和 6 处匹配。
第一个匹配项的下标是 0 ,所以返回 0 。

示例 2:

输入:haystack = "leetcode", needle = "leeto"
输出:-1
解释:"leeto" 没有在 "leetcode" 中出现,所以返回 -1 。

提示:

  • 1 <= haystack.length, needle.length <= 104
  • haystackneedle 仅由小写英文字符组成

朴素解法

class Solution {
public:
    int strStr(string haystack, string needle) {      
        // 方法一: 朴素解法
        int n = haystack.size(), m = needle.size();
        for(int i = 0; i <= n-m; i++){
            int j = i, k = 0;
            while(k < m && haystack [j] == needle [k]){
                j++;
                k++;
            }
            if(k == m) return i;
        }
        
        return -1;
    }
};

KMP 解法

class Solution {
public:
    int strStr(string haystack, string needle) {
        // 方法二: KMP 解法
        int n = haystack.size(), m = needle.size();
        if(m == 0) return 0;
        // 设置哨兵
        haystack.insert(haystack.begin(),' ');
        needle.insert(needle.begin(),' ');
        vector <int> next(m+1);
        // 预处理 next 数组
        for(int i = 2, j = 0; i <= m; i++){
            while(j && needle [i]!= needle [j+1]) j = next [j];
            if(needle [i] == needle [j+1]) j++;
            next [i] = j;
        }
        // 匹配过程
        for(int i = 1, j = 0; i <= n; i++){
            while(j && haystack [i] != needle [j+1]) j = next [j];
            if(haystack [i] == needle [j+1]) j++;
            if(j == m) return i - m;
        }

        return -1;        
    }
};

KMP 优化解


29. 两数相除

中等

给你两个整数,被除数 dividend 和除数 divisor。将两数相除,要求 不使用 乘法、除法和取余运算。

整数除法应该向零截断,也就是截去(truncate)其小数部分。例如,8.345 将被截断为 8-2.7335 将被截断至 -2

返回被除数 dividend 除以除数 divisor 得到的

**注意:**假设我们的环境只能存储 32 位 有符号整数,其数值范围是 [−231, 231 − 1] 。本题中,如果商 严格大于 231 − 1 ,则返回 231 − 1 ;如果商 严格小于 -231 ,则返回 -231

示例 1:

输入: dividend = 10, divisor = 3
输出: 3
解释: 10/3 = 3.33333.. ,向零截断后得到 3 。

示例 2:

输入: dividend = 7, divisor = -3
输出: -2
解释: 7/-3 = -2.33333.. ,向零截断后得到 -2 。

提示:

  • -231 <= dividend, divisor <= 231 - 1
  • divisor != 0

官方解法

class Solution:
    def divide(self, dividend: int, divisor: int) -> int:
        INT_MIN, INT_MAX = -2**31, 2**31 - 1

        # 考虑被除数为最小值的情况
        if dividend == INT_MIN:
            if divisor == 1:
                return INT_MIN
            if divisor == -1:
                return INT_MAX
        
        # 考虑除数为最小值的情况
        if divisor == INT_MIN:
            return 1 if dividend == INT_MIN else 0
        # 考虑被除数为 0 的情况
        if dividend == 0:
            return 0
        
        # 一般情况,使用二分查找
        # 将所有的正数取相反数,这样就只需要考虑一种情况
        rev = False
        if dividend > 0:
            dividend = -dividend
            rev = not rev
        if divisor > 0:
            divisor = -divisor
            rev = not rev

        # 快速乘
        def quickAdd(y: int, z: int, x: int) -> bool:
            # x 和 y 是负数,z 是正数
            # 需要判断 z * y >= x 是否成立
            result, add = 0, y
            while z > 0:
                if (z & 1) == 1:
                    # 需要保证 result + add >= x
                    if result < x - add:
                        return False
                    result += add
                if z != 1:
                    # 需要保证 add + add >= x
                    if add < x - add:
                        return False
                    add += add
                # 不能使用除法
                z >>= 1
            return True
        
        left, right, ans = 1, INT_MAX, 0
        while left <= right:
            # 注意溢出,并且不能使用除法
            mid = left + ((right - left) >> 1)
            check = quickAdd(divisor, mid, dividend)
            if check:
                ans = mid
                # 注意溢出
                if mid == INT_MAX:
                    break
                left = mid + 1
            else:
                right = mid - 1

        return -ans if rev else ans

---

31. 下一个排列

中等

整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。

  • 例如,arr = [1,2,3] ,以下这些都可以视作 arr 的排列:[1,2,3][1,3,2][3,1,2][2,3,1]

整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。

  • 例如,arr = [1,2,3] 的下一个排列是 [1,3,2]
  • 类似地,arr = [2,3,1] 的下一个排列是 [3,1,2]
  • arr = [3,2,1] 的下一个排列是 [1,2,3] ,因为 [3,2,1] 不存在一个字典序更大的排列。

给你一个整数数组 nums ,找出 nums 的下一个排列。

必须** 原地 **修改,只允许使用额外常数空间。

示例 1:

输入:nums = [1,2,3]
输出:[1,3,2]

示例 2:

输入:nums = [3,2,1]
输出:[1,2,3]

示例 3:

输入:nums = [1,1,5]
输出:[1,5,1]

提示:

  • 1 <= nums.length <= 100
  • 0 <= nums[i] <= 100

官方解法:两遍扫描

思路及解法

注意到下一个排列总是比当前排列要大,除非该排列已经是最大的排列。我们希望找到一种方法,能够找到一个大于当前序列的新序列,且变大的幅度尽可能小。具体地:

  1. 我们需要将一个左边的「较小数」与一个右边的「较大数」交换,以能够让当前排列变大,从而得到下一个排列。
  2. 同时我们要让这个「较小数」尽量靠右,而「较大数」尽可能小。当交换完成后,「较大数」右边的数需要按照升序重新排列。这样可以在保证新排列大于原来排列的情况下,使变大的幅度尽可能小。

以排列 [4,5,2,6,3,1] 为例:

  • 我们能找到的符合条件的一对「较小数」与「较大数」的组合为 2 与 3,满足「较小数」尽量靠右,而「较大数」尽可能小。
  • 当我们完成交换后排列变为 [4,5,3,6,2,1],此时我们可以重排「较小数」右边的序列,序列变为 [4,5,3,1,2,6]。

具体地,我们这样描述该算法,对于长度为 n 的排列 a

  1. 首先从后向前查找第一个顺序对 (i,i+1),满足 a[i]<a[i+1]。这样「较小数」即为 a[i]。此时 [i+1,n) 必然是下降序列。
  2. 如果找到了顺序对,那么在区间 [i+1,n) 中从后向前查找第一个元素 j 满足 a[i]<a[j]。这样「较大数」即为 a[j]。
  3. 交换 a[i] 与 a[j],此时可以证明区间 [i+1,n) 必为降序。我们可以直接使用双指针反转区间 [i+1,n) 使其变为升序,而无需对该区间进行排序。

注意

如果在步骤 1 找不到顺序对,说明当前序列已经是一个降序序列,即最大的序列,我们直接跳过步骤 2 执行步骤 3,即可得到最小的升序序列。

该方法支持序列中存在重复元素,且在 C++ 的标准库函数 next_permutation 中被采用。

class Solution:
    def nextPermutation(self, nums: List[int]) -> None:
        i = len(nums) - 2
        while i >= 0 and nums[i] >= nums[i + 1]:
            i -= 1
        if i >= 0:
            j = len(nums) - 1
            while j >= 0 and nums[i] >= nums[j]:
                j -= 1
            nums[i], nums[j] = nums[j], nums[i]
        
        left, right = i + 1, len(nums) - 1
        while left < right:
            nums[left], nums[right] = nums[right], nums[left]
            left += 1
            right -= 1

---

32. 最长有效括号

困难

字符串 动态规划

给你一个只包含 '('')' 的字符串,找出最长有效(格式正确且连续)括号子串的长度。

示例 1:

输入:s = "(()"
输出:2
解释:最长有效括号子串是 "()"

示例 2:

输入:s = ")()())"
输出:4
解释:最长有效括号子串是 "()()"

示例 3:

输入:s = ""
输出:0

提示:

  • 0 <= s.length <= 3 * 104
  • s[i]'('')'

动态规划

7ms 击败84.61%

class Solution:
    def longestValidParentheses(self, s: str) -> int:
        n = len(s)
        if n == 0: return 0
        dp = [0] * n
        res = 0
        for i in range(n):
            if i>0 and s[i] == ")":
                if  s[i - 1] == "(":
                    dp[i] = dp[i - 2] + 2
                elif s[i - 1] == ")" and i - dp[i - 1] - 1 >= 0 and s[i - dp[i - 1] - 1] == "(":
                    dp[i] = dp[i - 1] + 2 + dp[i - dp[i - 1] - 2]
                if dp[i] > res:
                    res = dp[i]
        return res

我的做法(匹配信息存储到一个数组中, 然后变为 <最长True子串问题>)

11ms 击败 30.65%

class Solution:
    def longestValidParentheses(self, s: str) -> int:
        # 数组索引括号匹配然后寻找最长连续 True 的部分
        n = len(s)
        if n < 2:
            return 0

        valid = [False for _ in range(n)]
        stack = []

        for i in range(n):
            if s [i] == '(':
                stack.append(i)
            else:
                if not stack:
                    pass
                else:
                    temp_i = stack.pop()
                    valid [temp_i] = True
                    valid [i] = True

        # 对 valid 数组进行处理 : 找到最长连续 True 串
        ans = 0
        # 对开头进行特殊处理(因为开头无法判断上升沿)
        count = int(valid [0])
        count_state = valid [0]
        
        for i in range(1, n):
            cur = valid [i]
            pre = valid [i-1]

            if count_state == True:
                if pre == True and cur == False:
                    # 在遇到下降沿时更新答案
                    ans = max(count, ans)
                    count_state = False
                    count = 0
                else:
                    # 正常计数
                    count+= 1
            else:
                if pre == False and cur == True:
                    # 在遇到上升沿时重新开始计数
                    count_state = True
                    count = 1
            
            # 在最后一次循环的末尾也要更新 ans
            if i == n-1:
                ans = max(count, ans)

        return ans    

33. 搜索旋转排序数组

中等

整数数组 nums 按升序排列,数组中的值 互不相同

在传递给函数之前,nums 在预先未知的某个下标 k0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2]

给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -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

示例 3:

输入:nums = [1], target = 0
输出:-1

提示:

  • 1 <= nums.length <= 5000
  • -104 <= nums[i] <= 104
  • nums 中的每个值都 独一无二
  • 题目数据保证 nums 在预先未知的某个下标上进行了旋转
  • -104 <= target <= 104

二分解法

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        def helper(left, right):

            while left + 1 < right:
                mid = (left + right) // 2
                if nums[mid] < nums[-1]:
                    right = mid
                else:
                    left = mid
            
            return right

        index = helper(-1, len(nums) - 1)
        in1 = bisect_left(nums[:index], target)
        in2 = bisect_left(nums[index:], target)

        if nums[in1] == target or (index + in2 < len(nums) and nums[index + in2] == target):
            if nums[in1] == target:
                return in1
            else:
                return index + in2
        else:
            return -1

---

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

中等

数组二分查找

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

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

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

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

示例 2:

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

示例 3:

输入:nums = [], target = 0
输出:[-1,-1]

提示:

  • 0 <= nums.length <= 105
  • -109 <= nums[i] <= 109
  • nums 是一个非递减数组
  • -109 <= target <= 109

二分解法

class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        if target not in nums:
            return [-1, -1]
        return [bisect_left(nums, target), bisect_right(nums, target) - 1]

---

35. 搜索插入位置

简单

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

请必须使用时间复杂度为 O(log n) 的算法。

示例 1:

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

示例 2:

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

示例 3:

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

提示:

  • 1 <= nums.length <= 104
  • -104 <= nums[i] <= 104
  • nums无重复元素升序 排列数组
  • -104 <= target <= 104

二分解法

class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        left, right = 0, len(nums) - 1
        
        while left <= right:
            middle = (left + right) // 2
            
            if nums[middle] == target:
                return middle
            elif nums[middle] < target:
                left = middle + 1
            else:
                right = middle - 1
                
        return left

---

36. 有效的数独

中等

数组哈希表矩阵

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

  1. 数字 1-9 在每一行只能出现一次。
  2. 数字 1-9 在每一列只能出现一次。
  3. 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)

注意:

  • 一个有效的数独(部分已被填充)不一定是可解的。
  • 只需要根据以上规则,验证已经填入的数字是否有效即可。
  • 空白格用 '.' 表示。

示例 1:

img

输入:board = 
[["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:

输入:board = 
[["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 存在, 因此这个数独是无效的。

提示:

  • board.length == 9
  • board[i].length == 9
  • board[i][j] 是一位数字(1-9)或者 '.'

哈希解法(字典+集合)

class Solution:
    def isValidSudoku(self, board: List[List[str]]) -> bool:
        col_dict = defaultdict(set)
        row_dict = defaultdict(set)
        square_dict = defaultdict(set)

        for row in range(9):
            for col in range(9):
                tmp = board[row][col]
                square_iterator = 3*(row//3) + col//3
                if tmp != ".":
                    if tmp in row_dict[row] or tmp in col_dict[col] or tmp in square_dict[square_iterator]:
                        return False
                    row_dict[row].add(tmp)
                    col_dict[col].add(tmp)
                    square_dict[square_iterator].add(tmp)

        return True


---

38. 外观数列

中等

字符串

「外观数列」是一个数位字符串序列,由递归公式定义:

  • countAndSay(1) = "1"
  • countAndSay(n)countAndSay(n-1) 的行程长度编码。

行程长度编码(RLE)是一种字符串压缩方法,其工作原理是通过将连续相同字符(重复两次或更多次)替换为字符重复次数(运行长度)和字符的串联。例如,要压缩字符串 "3322251" ,我们将 "33""23" 替换,将 "222""32" 替换,将 "5""15" 替换并将 "1""11" 替换。因此压缩后字符串变为 "23321511"

给定一个整数 n ,返回 外观数列 的第 n 个元素。

示例 1:

**输入:**n = 4

输出:"1211"

解释:

countAndSay(1) = "1"

countAndSay(2) = "1" 的行程长度编码 = "11"

countAndSay(3) = "11" 的行程长度编码 = "21"

countAndSay(4) = "21" 的行程长度编码 = "1211"

示例 2:

**输入:**n = 1

输出:"1"

解释:

这是基本情况。

提示:

  • 1 <= n <= 30

**进阶:**你能迭代解决该问题吗?

我的解法

7ms 击败88.12%

class Solution:
    def countAndSay(self, n: int) -> str:
        def countAndSay_helper(s):
            s = s + " "
            pre = None
            iterator = 0
            count = 1
            result = []
            while iterator < len(s):
                if pre:
                    if s[iterator] == pre:
                        count += 1
                    else:
                        # 如果s[iterator] != pre
                        # result追加编码 str(count) + str(pre)
                        result.append(str(count) + str(pre))
                        count = 1
                pre = s[iterator]
                iterator += 1

            return "".join(result)
            
        if n == 1:
            return "1"
        
        return countAndSay_helper(self.countAndSay(n-1))

---

39. 组合总和

中等

数组回溯

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。

对于给定的输入,保证和为 target 的不同组合数少于 150 个。

示例 1:

输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。

示例 2:

输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]

示例 3:

输入: candidates = [2], target = 1
输出: []

提示:

  • 1 <= candidates.length <= 30
  • 2 <= candidates[i] <= 40
  • candidates 的所有元素 互不相同
  • 1 <= target <= 40

深度优先搜索

class Solution:
    def combinationSum(self, candidates, target: int):
        nums = []
        ans = []
        my_dict = {i:j for j,i in enumerate(candidates)}
        #0 深度搜索dfs,初始化[nums数组][ans数组][i索引起点][索引字典],nums数组在每层深搜结束之后都要删除原先添加的数

        def dfs(sum_now,si):
        #1 深度搜索函数定义
            if sum_now >= target:
                if sum_now == target:
                    ans.append(nums.copy())
                return

            for num in candidates[si:]:
                nums.append(num)
                dfs(sum_now+num,my_dict[num])
                nums.pop()


        dfs(0,0)
        #2 从和为零开始的深搜

        return ans
        #3 返回ans

---

40. 组合总和 II

中等

给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用 一次

**注意:**解集不能包含重复的组合。

示例 1:

输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]

示例 2:

输入: candidates = [2,5,2,1,2], target = 5,
输出:
[
[1,2,2],
[5]
]

提示:

  • 1 <= candidates.length <= 100
  • 1 <= candidates[i] <= 50
  • 1 <= target <= 30

深度优先搜索

class Solution:
    def combinationSum2(self, candidates, target: int):
        nums = []
        ans = []
        si = 0
        candidates.sort()
        my_dict = {i:0 for i in candidates}
        count_now = {i:0 for i in candidates}
        for num in candidates:
            my_dict[num] += 1
        #0 深度搜索dfs,初始化[nums数组][ans数组][i索引起点][索引字典],nums数组在每层深搜结束之后都要删除原先添加的数

        def dfs(sum_now):
        #1 深度搜索函数定义
            if sum_now >= target:
                if sum_now == target:
                    ans.append(nums.copy())
                    #11 重置num和sum
                return

            for num in list(set(candidates)):
                if count_now[num] < my_dict[num]:
                    if not nums or (nums and num >= nums[-1]):
                        nums.append(num)
                        count_now[num]+=1
                        dfs(sum_now+num)
                        count_now[nums.pop()]-=1


        dfs(0)
        #2 从和为零开始的深搜

        return ans
        #3 返回ans

---

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值