经典题目
189. 旋转数组(三次反转)
leetcode 189
给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。
思路: 题述操作相当于先将整个数组做一次反转,然后分别对前k个元素和后n-k个元素做反转。
注意: 当k % n == 0时,数组各位置不会发生变化,因此反转前令k = k % n,防止后面序号溢出。
class Solution {
public:
void reverse(vector<int>& nums, int l, int r) {
while(l < r) {
int tmp = nums[l];
nums[l] = nums[r];
nums[r] = tmp;
l++;
r--;
}
}
void rotate(vector<int>& nums, int k) {
int n = nums.size();
k = k % n;
// 反转整个数组
reverse(nums, 0, n - 1);
// 反转前k个元素
reverse(nums, 0, k - 1);
// 反转后n - k个元素
reverse(nums, k, n - 1);
return;
}
};
61. 旋转链表(闭环断环)
leetcode 61
给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。
思路: 和数组不同,链表可以轻易形成闭环,因此我们可以先遍历一次链表,将尾节点next指针指向头节点以形成闭环,同时获得链表长度n。然后再次从头节点遍历到n-k处,找到新链表的头节点,同时断环。
注意: 和旋转数组一样,要令k = k % n,防止序号溢出。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* rotateRight(ListNode* head, int k) {
if(head == 0) return head;
// 遍历链表
int n = 1;
ListNode* p = head;
while(p->next != 0) {
p = p->next;
n++; // 统计链表长度
}
p->next = head; // 将链表闭环
k = k % n; // 别忘了处理k>n的情况!!!
// 找到“旋转”后新链表的尾元素
int count = n - k;
while(count-- > 0) {
p = p->next;
}
ListNode* new_head = p->next; // 确定新链表头部
p->next = 0; // 断环
return new_head;
}
};
33. 搜索旋转排序数组(二分法)
leetcode 33
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。
- 你可以假设数组中不存在重复的元素。
- 你的算法时间复杂度必须是 O(log n) 级别。
思路: 每次二分中点mid的左右两个区间至少有一个是有序的,可通过比较
n
u
m
s
[
m
i
d
]
nums[mid]
nums[mid]和
n
u
m
s
[
0
]
nums[0]
nums[0]来判断。然后判断
t
a
r
g
e
t
target
target是否在有序的区间内,并依此缩小范围。
注意: 因为数组中不存在重复元素,因此不存在[1, 1, 0, 1, 1, 1, 1]这种
n
u
m
s
[
m
i
d
]
=
n
u
m
s
[
l
]
nums[mid]=nums[l]
nums[mid]=nums[l]但
m
i
d
mid
mid左侧无序的情况,所以不用像下面的lc81题那样单独考虑这种情况。
class Solution {
public:
int search(vector<int>& nums, int target) {
int l = 0, r = nums.size() - 1;
while(l <= r) {
int mid = l + (r - l) / 2;
if(nums[mid] == target) return mid; // 找到目标,返回true
// if(nums[mid] == nums[l]) { // nums[l]不是target,跳过
// l++;
// continue;
// }
if(nums[mid] >= nums[l]) { // mid的左侧有序
if(target >= nums[l] && target < nums[mid]) r = mid - 1; // target在mid的左侧
else l = mid + 1;
}
else { // mid的右侧有序
if(target > nums[mid] && target <= nums[r]) l = mid + 1; // target在mid的右侧
else r = mid - 1;
}
}
return -1;
}
};
81. 搜索旋转排序数组 II(二分法)
leetcode 81
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,0,1,2,2,5,6] 可能变为 [2,5,6,0,0,1,2] )。
编写一个函数来判断给定的目标值是否存在于数组中。若存在返回 true,否则返回 false。本题中的 nums 可能包含重复元素.
思路: 和lc33的思路基本一样,但是由于nums种可能包含重复元素,因此要去除重复元素的影响。
注意: 如果
n
u
m
s
=
[
1
,
1
,
0
,
1
,
1
,
1
,
1
]
nums=[1, 1, 0, 1, 1, 1, 1]
nums=[1,1,0,1,1,1,1],第一次循环
n
u
m
s
[
m
i
d
]
=
=
n
u
m
s
[
l
]
nums[mid] == nums[l]
nums[mid]==nums[l]但是
m
i
d
mid
mid的左侧并不是有序的,因此可以通过
l
l
l++来跳过重复元素。
class Solution {
public:
bool search(vector<int>& nums, int target) {
int l = 0, r = nums.size() - 1;
while(l <= r) {
int mid = l + (r - l) / 2;
if(nums[mid] == target) return true; // 找到目标,返回true
if(nums[mid] == nums[l]) { // nums[l]不是target,跳过
l++;
continue;
}
if(nums[mid] > nums[l]) { // mid的左侧有序
if(target >= nums[l] && target < nums[mid]) r = mid - 1; // target在mid的左侧
else l = mid + 1;
}
else { // mid的右侧有序
if(target > nums[mid] && target <= nums[r]) l = mid + 1; // target在mid的右侧
else r = mid - 1;
}
}
return false;
}
};
153. 寻找旋转排序数组中的最小值(二分法)
leetcode 153
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
请找出其中最小的元素。
你可以假设数组中不存在重复元素。
思路: 首先易知最小的元素是数组右半部分的第一个元素,因此二分搜索时当
m
i
d
mid
mid出现在左半部分时
l
=
m
i
d
+
1
l=mid+1
l=mid+1,当
m
i
d
mid
mid出现在右半部分时
r
=
m
i
d
r=mid
r=mid,以确保要找的元素在
[
l
,
r
]
[l,r]
[l,r]中。
注意: 退出条件是
l
=
=
r
l==r
l==r。
class Solution {
public:
int findMin(vector<int>& nums) {
int l = 0, r = nums.size() - 1;
// 保证最小值在[l,r]之内
while(l < r) {
int mid = l + (r - l) / 2;
if(nums[mid] < nums[r]) r = mid; // 如果[mid,r]递增,则在[l,mid]查找
else l = mid + 1; // 否则在[mid+1,r]中查找
}
// l==r时即为最小值
return nums[l];
}
};
154. 寻找旋转排序数组中的最小值 II(二分法)
leetcode 154
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
请找出其中最小的元素。
注意数组中可能存在重复的元素。
思路: 和lc153的思路类似,但是要注意重复元素的处理。
注意: 这里我们和右届
n
u
m
s
[
r
]
nums[r]
nums[r]比较,因为右届的更新是
r
=
m
i
d
r = mid
r=mid,即使数组所有元素均相同,最终return
n
u
m
s
[
l
]
nums[l]
nums[l]时也不会越界。
class Solution {
public:
int findMin(vector<int>& nums) {
int l = 0, r = nums.size() - 1;
while(l <= r) {
int mid = l + (r - l) / 2;
if(nums[mid] == nums[r]) { // nums[r]不是唯一最小值,跳过
r--;
continue;
}
if(nums[mid] < nums[r]) r = mid; // 如果[mid,r]递增,则在[l,mid]查找
else l = mid + 1; // 否则在[mid+1,r]中查找
}
return nums[l];
}
};