这题本质上还是 二分查找,只是数组被"旋转"了。
正常二分里,数组整体有序。
但这里:
[4,5,6,7,0,1,2]
整体不是有序的。
不过有个非常关键的性质:
每次二分后,左右两边一定有一边是有序的。
这就是突破口。
一、核心思路
每次取中点:
mid = (left + right) / 2
然后判断:
nums[left] <= nums[mid]
如果成立:
说明 左半边有序
否则:
说明 右半边有序
然后看 target 是否落在有序区间内。
二、举例彻底理解
数组:
[4,5,6,7,0,1,2]
target = 0
第一次
left = 0 -> 4
right = 6 -> 2
mid = 3 -> 7
现在:
4 5 6 7 | 0 1 2
看:
nums[left] <= nums[mid]
4 <= 7
成立。
说明:
左边 [4,5,6,7] 有序
然后判断:
target=0 是否在:
[4,7]
之间。
显然不在。
所以:
去右边找
即:
left = mid + 1
第二次
现在:
0 1 2
即:
left = 4
right = 6
mid = 5
nums[mid] = 1
看:
nums[left] <= nums[mid]
0 <= 1
成立。
说明:
左边 [0,1] 有序
target=0 是否在:
[0,1]
之间?
在。
所以:
去左边继续二分
即:
right = mid - 1
第三次
left = 4
right = 4
mid = 4
发现:
nums[mid] == target
返回 4。
三、最重要的理解
旋转数组虽然整体无序:
4 5 6 7 0 1 2
但它一定是:
一段升序 + 一段升序
所以:
每次二分后,一定至少有一边是完全有序的。
我们就利用这一边判断 target 在不在里面。
四、代码(最经典写法)
java
class Solution {
public int longestValidParentheses(String s) {
int max = 0;
int[] dp = new int[s.length()];
for (int i = 1; i < s.length(); i++) {
if (s.charAt(i) == ')') {
// 1. ()
if (s.charAt(i - 1) == '(') {
dp[i] = (i >= 2 ? dp[i - 2] : 0) + 2;
}
// 2. ))
else if (i - dp[i - 1] - 1 >= 0
&& s.charAt(i - dp[i - 1] - 1) == '(') {
dp[i] = dp[i - 1] + 2;
if (i - dp[i - 1] - 2 >= 0) {
dp[i] += dp[i - dp[i - 1] - 2];
}
}
max = Math.max(max, dp[i]);
}
}
return max;
}
}
五、你真正需要记住的只有两句话
1. 判断哪边有序
nums[left] <= nums[mid]
成立:
左边有序
否则:
右边有序
2. 判断 target 在不在有序区间
左边有序:
nums[left] <= target && target < nums[mid]
右边有序:
nums[mid] < target && target <= nums[right]
六、为什么时间复杂度还是 O(log n)
因为每次都:
砍掉一半区间
所以仍然是二分查找。
这题是二分查找里非常经典的一道"变形题"。
你如果已经会普通二分,这题真正难的点只有:
"每次一定有一半有序"
一旦理解这一点,后面就是普通二分逻辑了。