189. 轮转数组
思路1: 引用辅助数组 + 拷贝
func rotate(nums []int, k int) {
res := make([]int, len(nums))
for i, v := range nums {
res[(i+k) % len(nums)] = v
}
copy(nums, res)
}
思路2: 字符串逆转
func rotate(nums []int, k int) {
reverse := func(nums[]int, beg int, end int) {
for beg < end {
nums[beg], nums[end-1] = nums[end-1], nums[beg]
beg++;
end--;
}
}
k = k % len(nums)
reverse(nums, 0, len(nums))
reverse(nums, 0, k)
reverse(nums, k, len(nums))
}
153. 寻找旋转排序数组中的最小值
- 有序数组旋转
- 寻找最小值
- 不允许重复
func findMin(nums []int) int {
l, r := 0, len(nums) - 1
for l < r {
m := l + (r - l)/2
if nums[m] < nums[r] {
r = m
}else {
l = m + 1
}
}
return nums[r]
}
此处,为什么只看右边界nums[r]呢? 因为数组可能没有旋转过,如果我们通过左边界来判断,会误以为最小值在右子数组中。
154. 寻找旋转排序数组中的最小值 II
在153的前提下,允许数字重复。因此相等nums[r] == nums[m] 时我们需要将r挪动一个位置。直接将r = m可能会错过最小值, 因为可能出现这种状况[2, 2, 2, 1, 2]
- 允许重复
- 寻找最小值
- 有序数组旋转
class Solution {
public:
int findMin(vector<int>& nums) {
int l = 0, r = nums.size() - 1;
int m ;
while(l < r) {
m = l + (r - l)/2;
if (nums[r] > nums[m]) {
r = m;
}else if(nums[r] == nums[m]) {
r--;
}else{
l = m + 1;
}
}
return nums[r];
}
};
这个二分的写法与我们之前提及的二分查找的模板不一致,用之前的模板反而无法实现这样的情形。
33. 搜索旋转排序数组
其实,与153的旋转的概念依旧是一样的,只不过问题从找最小值,变成了找target
由于是有序数组的旋转,中点会将数组划分成两个子数组。可能两个子数组都有序,也可能一个有序而另一个无序,不可能出现两个都无序的情况。
对于有序的子数组,我们可以直接找到target在不在里面
对于无序的子数组,我们可以继续用中点划分
- 不允许重复
- 判断是否包含target值
- 有序数组旋转
class Solution {
public:
int search(vector<int>& nums, int target) {
int l = 0, r = nums.size() - 1;
while (l <= r) {
int m = l + (r - l)/2;
if(nums[m] == target) return m;
if(nums[m] < nums[r]) {
// 右边有序
if(nums[m] < target && nums[r] >= target){
// target在右子数组内部
l = m + 1;
}else{
r = m;
}
}else{
// 左边有序
if(nums[m] > target && nums[l] <= target){
// target在左子数组内部
r = m;
}else{
l = m + 1;
}
}
}
return -1;
}
};
81. 搜索旋转排序数组 II
此处其实是33的进阶版本,多加了一个条件,即允许重复值。
同样,如果相等我们就挪动一步呗。
- 允许重复
- 判断是否包含target值
- 有序数组旋转
class Solution {
public:
bool search(vector<int>& nums, int target) {
int l = 0, r = nums.size() - 1;
while (l <= r) {
int m = l + (r - l)/2;
if(nums[m] == target) return true;
if(nums[m] < nums[r]) {
// 右边有序
if(nums[m] < target && nums[r] >= target){
// target在右子数组内部
l = m + 1;
}else{
r = m;
}
}else if(nums[m] > nums[r]){
// 左边有序
if(nums[m] > target && nums[l] <= target){
// target在左子数组内部
r = m;
}else{
l = m + 1;
}
}else{
r--;
}
}
return false;
}
};
这里的挪动一步,可以是l也可以是r
面试题 10.03. 搜索旋转数组
- 有序数组的旋转
- 允许重复
- 寻找target值的最小下标
因为允许重复, 所以我们找到相同的只能一步一步挪
对于81略作修改,对于输入[5,5, 1, 2,3,4,5]
时target为5的情况,我们需要先排除掉。因此,只需要用常规作法即可。
class Solution {
public:
int search(vector<int>& nums, int target) {
int l = 0, r = nums.size() - 1;
while (l <= r) {
int m = l + (r - l)/2;
// 排除target恰好同时在最前和最后
if(nums[l] == target) return l;
if(nums[m] == target) {
for(;m >= 0 && nums[m] == target; m--);
return m + 1;
}
if(nums[m] < nums[r]) {
// 右边有序
if(nums[m] < target && nums[r] >= target){
// target在右子数组内部
l = m + 1;
}else{
r = m;
}
}else if(nums[m] > nums[r]){
// 左边有序
if(nums[m] > target && nums[l] <= target){
// target在左子数组内部
r = m;
}else{
l = m + 1;
}
}else{
r--;
}
}
return -1;
}
};
对于中间那部分,如果担心复杂度超过O(logn), 可以也可以写成这样
class Solution {
public:
int search(vector<int>& nums, int target) {
int l = 0, r = nums.size() - 1;
while (l <= r) {
int m = l + (r - l)/2;
// 排除target恰好同时在最前和最后
if(nums[l] == target) return l;
if(nums[m] == target) {
// for(;m >= 0 && nums[m] == target; m--);
// return m + 1;
r = m;
continue;
}
if(nums[m] < nums[r]) {
// 右边有序
if(nums[m] < target && nums[r] >= target){
// target在右子数组内部
l = m + 1;
}else{
r = m;
}
}else if(nums[m] > nums[r]){
// 左边有序
if(nums[m] > target && nums[l] <= target){
// target在左子数组内部
r = m;
}else{
l = m + 1;
}
}else{
r--;
}
}
return -1;
}
};