假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。
你可以假设数组中不存在重复的元素。
你的算法时间复杂度必须是 O(log n) 级别。
输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4
输入: nums = [4,5,6,7,0,1,2], target = 3
输出: -1
题目要求算法时间复杂度必须是 O(log n) 的级别,这提示我们可以使用二分搜索的方法。
但是数组本身不是有序的,进行旋转后只保证了数组的局部是有序的,这还能进行二分搜索吗?答案是可以的。
可以发现的是,我们将数组从中间分开成左右两部分的时候,一定有一部分的数组是有序的。拿示例来看,我们从 6 这个位置分开以后数组变成了 [4, 5, 6] 和 [7, 0, 1, 2] 两个部分,其中左边 [4, 5, 6] 这个部分的数组是有序的,其他也是如此。
这启示我们可以在常规二分搜索的时候查看当前 mid 为分割位置分割出来的两个部分 [l, mid] 和 [mid + 1, r] 哪个部分是有序的,并根据有序的那个部分确定我们该如何改变二分搜索的上下界,因为我们能够根据有序的那部分判断出 target 在不在这个部分:
如果 [l, mid - 1] 是有序数组,且 target 的大小满足 [\textit{nums}[l],\textit{nums}[mid])[nums[l],nums[mid]),则我们应该将搜索范围缩小至 [l, mid - 1],否则在 [mid + 1, r] 中寻找。
如果 [mid, r] 是有序数组,且 target 的大小满足 (\textit{nums}[mid+1],\textit{nums}[r]](nums[mid+1],nums[r]],则我们应该将搜索范围缩小至 [mid + 1, r],否则在 [l, mid - 1] 中寻找。
二分搜索、两个判断范围的条件:是否有序,有序范围是否包含目标
class Solution {
public int search(int[] nums, int target) {
if(nums.length == 0)
return -1;
if(nums.length == 1)
return nums[0] == target ? 0 : -1 ;
int l=0,r=nums.length-1;
while(l<=r){
int mid = (l+r)/2;
if(nums[mid] == target)
return mid;
if(nums[0]<= nums[mid]) {
if(nums[0]<= target && target <= nums[mid]) {
r = mid - 1;
} else {
l = mid + 1;
}
} else {
if(nums[mid]<= target && target <= nums[nums.length - 1]) {
l = mid + 1;
} else {
r = mid - 1;
}
}
}
return -1;
}
}
class Solution {
public:
int search(vector<int>& nums, int target) {
int n = (int)nums.size();
if (!n) return -1;
if (n == 1) return nums[0] == target ? 0 : -1;
int l = 0, r = n - 1;
while (l <= r) {
int mid = (l + r) / 2;
if (nums[mid] == target) return mid;
if (nums[0] <= nums[mid]) {
if (nums[0] <= target && target < nums[mid]) {
r = mid - 1;
} else {
l = mid + 1;
}
} else {
if (nums[mid] < target && target <= nums[n - 1]) {
l = mid + 1;
} else {
r = mid - 1;
}
}
}
return -1;
}
};
一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
示例 1:
输入:nums = [4,1,4,6]
输出:[1,6] 或 [6,1]
示例 2:
输入:nums = [1,2,10,4,1,4,3,3]
输出:[2,10] 或 [10,2]
限制:
2 <= nums <= 10000
如果除了一个数字以外,其他数字都出现了两次,那么如何找到出现一次的数字?
答案很简单:全员进行异或操作即可。考虑异或操作的性质:对于两个操作数的每一位,相同结果为 00,不同结果为 11。那么在计算过程中,成对出现的数字的所有位会两两抵消为 00,最终得到的结果就是那个出现了一次的数字。
那么这一方法如何扩展到找出两个出现一次的数字呢?
如果我们可以把所有数字分成两组,使得:
1、两个只出现一次的数字在不同的组中;
2、相同的数字会被分到相同的组中。
那么对两个组分别进行异或操作,即可得到答案的两个数字。这是解决这个问题的关键。
先对所有数字进行一次异或,得到两个出现一次的数字的异或值。
在异或结果中找到任意为 11 的位。
根据这一位对所有的数字进行分组。
在每个组内进行异或操作,得到两个数字。
class Solution {
public:
vector<int> singleNumbers(vector<int>& nums) {
int ret = 0;
for (int n : nums)
ret ^= n;
int div = 1;
while ((div & ret) == 0)
div <<= 1;
int a = 0, b = 0;
for (int n : nums)
if (div & n)
a ^= n;
else
b ^= n;
return vector<int>{a, b};
}
};
class Solution {
public int[] singleNumbers(int[] nums) {
int ret = 0;
for (int n : nums) {
ret ^= n;
}
int div = 1;
while ((div & ret) == 0)
div <<= 1;
int a = 0, b = 0;
for (int n : nums)
if ((div & n)==0)
a ^= n;
else
b ^= n;
int ab[] = new int[2];;
ab[0]=a;
ab[1]=b;
return ab;
}
}
给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
说明:你不能倾斜容器,且 n 的值至少为 2。
输入:[1,8,6,2,5,4,8,3,7]
输出:49
双指针代表了什么?
双指针代表的是可以作为容器边界的所有位置的范围。在一开始,双指针指向数组的左右边界,表示数组中所有的位置都可以作为容器的边界,因为我们还没有进行过任何尝试。在这之后,我们每次将对应的数字较小的那个指针 往另一个指针的方向移动一个位置,就表示我们认为这个指针不可能再作为容器的边界了。
为什么对应的数字较小的那个指针不可能再作为容器的边界了?
考虑第一步,用示例来说,假设当前左指针和右指针指向的数分别为 x 和 y,不失一般性,我们假设x≤y。同时,两个指针之间的距离为t。那么,它们组成的容器的容量为:min(x,y)∗t=x∗t
public class Solution {
public int maxArea(int[] height) {
int l = 0, r = height.length - 1;
int ans = 0;
while (l < r) {
int area = Math.min(height[l], height[r]) * (r - l);
ans = Math.max(ans, area);
if (height[l] <= height[r]) {
++l;
}
else {
--r;
}
}
return ans;
}
}
这是一个 交互式问题 (交互式问题即可以调用题目给出的结果,通过调用接口完成题目)。
给你一个 山脉数组 mountainArr,请你返回能够使得 mountainArr.get(index) 等于 target 最小 的下标 index 值。如果不存在这样的下标 index,就请返回 -1。
注意:对 MountainArray.get 发起超过 100 次调用的提交将被视为错误答案。此外,任何试图规避判题系统的解决方案都将会导致比赛资格被取消。
示例 1:
输入:array = [1,2,3,4,5,3,1], target = 3
输出:2
解释:3 在数组中出现了两次,下标分别为 2 和 5,我们返回最小的下标 2。
示例 2:
输入:array = [0,1,2,4,2,1], target = 3
输出:-1
解释:3 在数组中没有出现,返回 -1。
/**
* // This is MountainArray's API interface.
* // You should not implement it, or speculate about its implementation
* interface MountainArray {
* public int get(int index) {}
* public int length() {}
* }
*/
class Solution {
public int findInMountainArray(int target, MountainArray mountainArr) {
int len = mountainArr.length();
int peakIndex = findMountainTop(mountainArr, 0, len-1);
if(mountainArr.get(peakIndex) == target)
return peakIndex;
int res = findSortedArray(mountainArr, 0, peakIndex - 1, target);
if(res != -1)
return res;
return findReverseArray(mountainArr, peakIndex + 1, len - 1, target);
}
/*
* 在[left...right] 查找山顶元素的下标
*/
private int findMountainTop(MountainArray mountainArr, int left, int right) {
while(left < right) {
int mid = left + (right - left) / 2;
if(mountainArr.get(mid) < mountainArr.get(mid +1)) {
// 下一轮搜索区间 [mid + 1, right]
left = mid + 1;
} else {
// 下一轮搜索区间
right = mid;
}
}
// left==right
return left;
}
/*
* 在左侧查找target的下标
*/
private int findSortedArray(MountainArray mountainArr, int left, int right, int target) {
while(left < right) {
int mid = left + (right - left) / 2;
if(mountainArr.get(mid) < target) {
// 下一轮搜索区间 [mid + 1, right]
left = mid + 1;
} else {
// 下一轮搜索区间
right = mid;
}
}
if (mountainArr.get(left) == target){
return left;
}
return -1;
}
/*
* 在右侧查找target的下标
*/
private int findReverseArray(MountainArray mountainArr, int left, int right, int target) {
while(left < right) {
int mid = left + (right - left + 1) / 2;
if(mountainArr.get(mid) < target) {
// 下一轮搜索区间 [left, mid -1]
right = mid - 1;
} else {
// 下一轮搜索区间[mid, right]
// [left, right(mid)]
left = mid ;
}
}
if (mountainArr.get(left) == target){
return left;
}
return -1;
}
}