1. 下一个排列(Medium)
实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
必须原地修改,只允许使用额外常数空间。
1 | 以下是一些例子,输入位于左侧列,其相应输出位于右侧列。 |
解答:
思路:
首先,我们观察到对于任何给定序列的降序,没有可能的下一个更大的排列。
例如,以下数组不可能有下一个排列:[9, 5, 4, 3, 1]
我们需要从右边找到第一对两个连续的数字 $a[i]$ 和 $a[i-1]$,它们满足 $a[i]>a[i-1]$。现在,没有对 $a[i-1]$右侧的重新排列可以创建更大的排列,因为该子数组由数字按降序组成。因此,我们需要重新排列 $a[i-1]$ 右边的数字,包括它自己。
现在,什么样子的重新排列将产生下一个更大的数字呢?我们想要创建比当前更大的排列。因此,我们需要将数字 $a[i-1]$ 替换为位于其右侧区域的数字中比它更大的数字,例如 $a[j]$。
我们交换数字 $a[i-1]$ 和 $a[j]$。我们现在在索引 $i-1$ 处有正确的数字。 但目前的排列仍然不是我们正在寻找的排列。我们需要通过仅使用 $a[i-1]$右边的数字来形成最小的排列。 因此,我们需要放置那些按升序排列的数字,以获得最小的排列。
但是,请记住,在从右侧扫描数字时,我们只是继续递减索引直到我们找到 $a[i]$ 和 $a[i-1]$ 这对数。其中,$a[i] > a[i-1]$。因此,$a[i-1]$ 右边的所有数字都已按降序排序。此外,交换 $a[i-1]$和 $a[j]$ 并未改变该顺序。因此,我们只需要反转$ a[i-1]$ 之后的数字,以获得下一个最小的字典排列。
1 | class Solution(object): |
2. 最长有效括号(Hard)
给定一个只包含 ‘(‘ 和 ‘)’ 的字符串,找出最长的包含有效括号的子串的长度。
示例 1:
1 | 输入: "(()" |
示例 2:
1 | 输入: ")()())" |
解答:
思路:动态规划
- 用一个
dp
数组来存放以每个index
为结尾的最长有效括号子串长度,例如:dp[3] = 2
代表以index为3
结尾的最长有效括号子串长度为2
- 很明显
dp[i]
和dp[i-1]
之间是有关系的
- 当
s[i] == ‘(’
时,dp[i]
显然为0
, 由于我们初始化dp的时候就全部设为0了,所以这种情况压根不用写 - 当
s[i] == ')'
时, 如果在dp[i-1]
的所表示的最长有效括号子串之前还有一个'('
与s[i]
对应,那么dp[i] = dp[i-1] + 2
, 并且还可以继续往前追溯(如果前面还能连起来的话)
1 | class Solution(object): |
思路二:栈
与找到每个可能的子字符串后再判断它的有效性不同,我们可以用栈在遍历给定字符串的过程中去判断到目前为止扫描的子字符串的有效性,同时能的都最长有效字符串的长度。我们首先将0放入栈顶。
对于遇到的每个’(‘ ,我们将0放入栈中。 对于遇到的每个‘)’ ,如果当前栈长度大于1,我们弹出栈顶的元素并加2(意思是当前读到的’)’和上一个’(‘做匹配,长度为2,同时删除掉一个’(‘),将得到的值加到栈顶元素,表示读到当前字符时的最长有效括号长度。通过这种方法,我们继续计算有效子字符串的长度,并最终返回最长有效子字符串的长度。
1 | class Solution(object): |
3. 搜索旋转排序数组(Medium)
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。
你可以假设数组中不存在重复的元素。
你的算法时间复杂度必须是 O(log n) 级别。
示例 1:
1 | 输入: nums = [4,5,6,7,0,1,2], target = 0 |
示例 2:
1 | 输入: nums = [4,5,6,7,0,1,2], target = 3 |
解答:
思路:二分法
很简单。
直接使用二分法,判断那个二分点,有几种可能性
直接等于target
在左半边的递增区域
a. target 在 left 和 mid 之间
b. 不在之间
在右半边的递增区域
a. target 在 mid 和 right 之间
b. 不在之间
1 | class Solution(object): |
4. 在排序数组中查找元素的第一个和最后一个位置(Medium)
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
你的算法时间复杂度必须是 O(log n) 级别。
如果数组中不存在目标值,返回 [-1, -1]。
示例 1:
1 | 输入: nums = [5,7,7,8,8,10], target = 8 |
示例 2:
1 | 输入: nums = [5,7,7,8,8,10], target = 6 |
解答:
思路:
二分法,先找target
出现的左边界,判断是否有target
后再判断右边界
- 找左边界:二分,找到一个index
- 该
index
对应的值为target
- 并且它左边
index-1
对应的值不是target
(如果index
为0
则不需要判断此条件) - 如果存在
index
就将其append
到res
中
- 该
- 判断此时
res
是否为空,如果为空,说明压根不存在target
,返回[-1, -1]
- 找右边界:二分,找到一个index(但是此时用于二分循环的l可以保持不变,r重置为len(nums)-1,这样程序可以更快一些)
- 该
index
对应的值为target
- 并且它右边
index+1
对应的值不是target
(如果index
为len(nums)-1
则不需要判断此条件) - 如果存在
index
就将其append
到res
中
- 该
1 | class Solution(object): |
5. 搜索插入位置(Easy)
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素。
示例 1:
1 | 输入: [1,3,5,6], 5 |
示例 2:
1 | 输入: [1,3,5,6], 2 |
示例 3:
1 | 输入: [1,3,5,6], 7 |
示例 4:
1 | 输入: [1,3,5,6], 0 |
解答:
思路:
很简单,二分法。
寻找插入点使用二分法,但与寻找某数字不同的是,需要考虑一些边界条件:
- 当插入数字和nums中某数字相等时,插入到左边还是右边?本题要求插到左边;
- 插入数字在nums第一个数字左边,或在最后一个数字右边;
推荐记住其中的几个关键点写法。
1 | class Solution(object): |