- 记录于2019年5月23日
31. 下一个排列
① 题目描述
- 实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
- 如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
- 必须原地修改,只允许使用额外常数空间。
- 以下是一些例子,输入位于左侧列,其相应输出位于右侧列。
1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1
② 一次遍历
- 例如
158476531
,我们从十位开始,十位右边没有大于 3 的。再看百位,百位右边没有大于 5 的。直到 4 ,右边出现了很多大于 4 的,选那个刚好大于 4 的,也就是 5 。然后交换,变成 1585
764
31,数字变大了,但并不是刚好大于158476531
,我们还需要将 5 右边的数字从小到大排列。变成158513467
,就可以结束了。 - 具体分析过程,参考动图。
- 代码如下:
public void nextPermutation(int[] nums) {
int i = nums.length - 2; // 从十位开始找
//找到第一个不再递增的位置
while (i >= 0 && nums[i + 1] <= nums[i]) {
i--;
}
//如果到了最左边,就直接倒置输出
if (i < 0) {
reverse(nums, 0);
return;
}
int j = nums.length - 1;
//找到刚好大于 nums[i]的位置
while (nums[j] <= nums[i]) {
j--;
}
// 交换数字
swap(nums, i, j);
//对目标位置的右侧进行倒置
reverse(nums, i + 1);
}
public void reverse(int[] nums, int start) {
int end = nums.length - 1;
while (start < end) {
swap(nums, start, end);
start++;
end--;
}
}
public void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
32. 最长有效括号
① 题目描述
- 给定一个只包含
'('
和')'
的字符串,找出最长的包含有效括号的子串的长度。 - 示例 1:
输入: “(()”
输出: 2
解释: 最长有效括号子串为 “()”
- 示例 2:
输入: “)()())”
输出: 4
解释: 最长有效括号子串为 “()()”
② 暴力法(Time Limit Exceeded
)
- 包含优先括号的子串长度肯定为偶数,所以只需要判断长度为2,4,6,…的子串。
- 判断长度为2的子串,从下标0开始判断,对于每一个下标都只有一种子串,
(i,i+1)
;同时,最后一组子串的下标应该i <= len - curLen
。 - 利用辅助方法
isValid
判断当前子串是否包含有效的括号,如果为true
且max < curLen
就可以更新max。注意: 这时可以判断长度为curLen += 2
的子串了,因为只要求我们找到长度,并没有要求找到所有的子串。 - 运行报错:
Time Limit Exceeded
,代码如下:
public int longestValidParentheses(String s) {
int len = s.length();
if (len == 0) {
return 0;
}
int max = 0;
for (int curLen = 2; curLen <= len; curLen += 2) {
for (int i = 0; i <= len - curLen; i++) {
int start = i;
int end = start + curLen - 1;
if (isValid(s, start, end) && max < curLen) {
max = curLen;
break;
}
}
}
return max;
}
public boolean isValid(String s, int start, int end) {
int count = 0;
for (int i = start; i <= end; i++) {
if (s.charAt(i) == '(') {
count++;
} else {
count--;
}
if (count < 0) {
return false;
}
}
return count == 0;
}
③ 暴力法优化
- 解法一中,我们会做很多重复的判断,例如类似于这样的,()()(),从下标 0 开始,我们先判断长度为 2 的是否是合法序列。然后再判断长度是 4 的字符串是否符合,但会从下标 0 开始判断。判断长度为 6 的字符串的时候,依旧从 0 开始,但其实之前已经确认前 4 个已经组成了合法序列,所以我们其实从下标 4 开始判断就可以了。
- 基于此,我们可以换一个思路,我们判断从每个位置开始的最长合法子串是多长即可。而判断是否是合法子串,我们不用栈,而是用一个变量记录当前的括号情况,遇到左括号加 1,遇到右括号减 1,如果变成 0 ,我们就更新下最长合法子串。
- 注意: 外层循环
i
从0开始,执行条件为i < len
;内层循环j
从j = i
开始,执行条件为j < len
。i < len
和j = i
是为了保证count无需在内层循环之前做更新。 - 时间复杂度:
O(n²)
。 - 空间复杂度:
O(1)
。 - 代码如下:
public int longestValidParentheses(String s) {
int len = s.length();
if (len == 0) {
return 0;
}
int max = 0;
for (int i = 0; i < len; i++) {
int count = 0;
for (int j = i; j < len; j++) {
if (s.charAt(j) == '(') {
count++;
} else {
count--;
}
if (count == 0 && max < (j - i + 1)) {
max = j - i + 1;
continue;
}
if (count < 0) {
break;
}
}
}
return max;
}
④ 动态规划
- 用
dp[i]
表示下标以i结尾的最长有效括号。 - 当
s[i]='('
时,dp[i]=0
, 不用更新dp[i]
的值,让其为初始值0.因为不可能组成有效的括号; - 当
s[i]=')'
时,有两种情况:
①s[i-1]='('
时,dp[i] = dp[i - 2] + 2
;
②s[i-1]=')'
并且s[i - dp[i - 1] - 1]== '('
时,dp[i] = dp[i - dp[i - 1] - 2] + dp[i - 1] + 2
。 - 注意:
① 直接从下标i = 1
开始,因为i=0,无论为何值,都不可能存在有效的括号。
② 当s[i-1]='('
,更新dp[i] = dp[i - 2] + 2
时,一定要判断i - 2 >= 0
。因为可能是()
或(()
或)()
的情况。
③ 当s[i-1]=')'
,要判断s[i - dp[i - 1] - 1]== '('
时,一定要先判断i - dp[i - 1] - 1 >= 0
。因为可能是())
的情况。
④ 当s[i-1]=')'
并且s[i - dp[i - 1] - 1]== '('
时,更新dp[i] = dp[i - dp[i - 1] - 2] + dp[i - 1] + 2
时,一定要判断i - dp[i - 1] - 2 >= 0)
。因为可能是(())
的情况。 - 代码如下:
public int longestValidParentheses(String s) {
int len = s.length();
if (len == 0) {
return 0;
}
int max = 0;
int[] dp = new int[len];
for (int i = 1; i < len; i++) {
if (s.charAt(i) == ')') {
if (s.charAt(i - 1) == '(') {
dp[i] = (i - 2 >= 0) ? dp[i - 2] + 2 : 2;
} else {
if (i - dp[i - 1] - 1 >= 0 && s.charAt(i - dp[i - 1] - 1) == '(') {
dp[i] = (i - dp[i - 1] - 2 >= 0) ? dp[i - dp[i - 1] - 2] + dp[i - 1] + 2 : dp[i - 1] + 2;
}
}
max = Math.max(dp[i], max);
}
}
return max;
}