21. 合并两个有序链表
简单
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例 1:
输入: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
l1
和l2
均按 非递减顺序 排列
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:
输入: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:
输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]
示例 2:
输入: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. 找出字符串中第一个匹配项的下标
简单
给你两个字符串 haystack
和 needle
,请你在 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
haystack
和needle
仅由小写英文字符组成
朴素解法
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
官方解法:两遍扫描
思路及解法
注意到下一个排列总是比当前排列要大,除非该排列已经是最大的排列。我们希望找到一种方法,能够找到一个大于当前序列的新序列,且变大的幅度尽可能小。具体地:
- 我们需要将一个左边的「较小数」与一个右边的「较大数」交换,以能够让当前排列变大,从而得到下一个排列。
- 同时我们要让这个「较小数」尽量靠右,而「较大数」尽可能小。当交换完成后,「较大数」右边的数需要按照升序重新排列。这样可以在保证新排列大于原来排列的情况下,使变大的幅度尽可能小。
以排列 [4,5,2,6,3,1] 为例:
- 我们能找到的符合条件的一对「较小数」与「较大数」的组合为 2 与 3,满足「较小数」尽量靠右,而「较大数」尽可能小。
- 当我们完成交换后排列变为 [4,5,3,6,2,1],此时我们可以重排「较小数」右边的序列,序列变为 [4,5,3,1,2,6]。
具体地,我们这样描述该算法,对于长度为 n 的排列 a:
- 首先从后向前查找第一个顺序对 (i,i+1),满足 a[i]<a[i+1]。这样「较小数」即为 a[i]。此时 [i+1,n) 必然是下降序列。
- 如果找到了顺序对,那么在区间 [i+1,n) 中从后向前查找第一个元素 j 满足 a[i]<a[j]。这样「较大数」即为 a[j]。
- 交换 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
在预先未知的某个下标 k
(0 <= 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-9
在每一行只能出现一次。 - 数字
1-9
在每一列只能出现一次。 - 数字
1-9
在每一个以粗实线分隔的3x3
宫内只能出现一次。(请参考示例图)
注意:
- 一个有效的数独(部分已被填充)不一定是可解的。
- 只需要根据以上规则,验证已经填入的数字是否有效即可。
- 空白格用
'.'
表示。
示例 1:
输入: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
---