寻找旋转排序数组中的最小值
根据题意可知,一定存在一个点,让整个数组断崖式下跌,
[7, 6, 5, 4, 3, 2, 0, 1] 比如在这个数组中的0
我们需要找到这个断崖式下跌的点,就是这个数组的最小值
我们考虑数组中的最后一个元素 x:在最小值右侧的元素(不包括最后一个元素本身),它们的值一定都严格小于 x;而在最小值左侧的元素,它们的值一定都严格大于 x。因此,我们可以根据这一条性质,通过二分查找的方法找出最小值。
查找第一个大于等于目标值的元素(加了其它代码的解释,原来的题解放在链接里了)
class Solution {
public:
int findMin(vector<int>& nums) {
int low = 0;
int high = nums.size() - 1;
while (low < high) {
int pivot = low + (high - low) / 2;
if (nums[pivot] < nums[high]) {
high = pivot;
}
else {
low = pivot + 1;
}
}
return nums[low];
}
};
第一种:最容易想到的暴力解法
时间复杂度:o(mn)
空间复杂度:o(1)
第二种:暴力解法的进阶版,用二分查找每一行,这样可以减少时间复杂度
时间复杂度:o(nlogn)
空间复杂度:o(1)
第三种:比较巧妙的方法,从左上或者右下角开始查找,以右下角matrix[n - 1][0] 为例,如果当前matrix[i][j] 值为 a
第二种: 二分优化暴力查找
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
int n = matrix.size(), m = matrix[0].size();
for (int i = 0; i < n; i++) {
auto ans = lower_bound(matrix[i].begin(), matrix[i].end(), target);
//auto tmp = ans - matrix[i].begin();
//if (ans != matrix[i].end() && matrix[i][tmp] == target) {
if (ans != matrix[i].end() && *ans == target) {
return true;
}
}
return false;
}
};
或者不用库函数,手写二分:
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
int n = matrix.size(), m = matrix[0].size();
for (int i = 0; i < n; i++) {
int l = 0, r = m;
while (l < r) {
int mid = (r - l) / 2 + l;
if (matrix[i][mid] < target) {
l = mid + 1;
} else {
r = mid;
}
}
if (l < m && matrix[i][l] == target) return true;
}
return false;
}
};
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
int n = matrix.size(), m = matrix[0].size();
for (int i = 0; i < n; i++) {
int l = 0, r = m - 1;
while (l <= r) {
int mid = (r - l) / 2 + l;
if (matrix[i][mid] < target) {
l = mid + 1;
} else if (matrix[i][mid] > target) {
r = mid - 1;
} else {
return true;
}
}
}
return false;
}
};
upper_bound, lower_bound两者都是定义在头文件里。用 二分查找的方法在一个排好序的数组中进行查找。既然是二分,时间复杂度就是O(logN)。
基础用法
upper_bound(begin, end, value)
在从小到大的排好序的数组中,在数组的 [begin, end) 区间中二分查找第一个大于value的数,找到返回该数字的地址,没找到则返回end。
lower_bound(begin, end, value)
在从小到大的排好序的数组中,在数组的 [begin, end) 区间中二分查找第一个大于等于value的数,找到返回该数字的地址,没找到则返回end。
用greater()重载
upper_bound(begin, end, value, greater<int>())
在从大到小的排好序的数组中,在数组的 [begin, end) 区间中二分查找第一个小于value的数,找到返回该数字的地址,没找到则返回end。
lower_bound(begin, end, value, greater<int>())
在从大到小的排好序的数组中,在数组的 [begin, end) 区间中二分查找第一个小于等于value的数,找到返回该数字的地址,没找到则返回end。
第三种:将二维矩阵抽象成「以右上角为根的 BST」
以右上为根
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
int n = matrix.size(), m = matrix[0].size();
int i = 0, j = m - 1;
while (i < n && j >= 0) {
if (matrix[i][j] > target) j--;
else if (matrix[i][j] < target) i++;
else return true;
}
return false;
}
};
以左下为根……
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
int n = matrix.size(), m = matrix[0].size();
int i = n - 1, j = 0;
while (i >= 0 && j < m) {
if (matrix[i][j] > target) i--;
else if (matrix[i][j] < target) j++;
else return true;
}
return false;
}
};
二分查找的难点在于边界判断,如果实在没法判断可以写几个数字自己模拟二分过程推导一次就能得出结论。以下为个人总结,仅供参考
记住两个模板,在这两个模板上修改:
当我们需要查找第一个大于等于target的数时:
int l = 0, r = n;
while (l < r) {
......
if (nums[mid] >= targer) {
r = mid;
} else {
l = mid + 1;
}
}
int l = 0, r = n - 1;
while (l <= r) {
......
if (nums[mid] == target) {
return mid + 1;
} else if (nums[mid] < target) {
l = mid + 1;
} else {
r = mid - 1;
}
}
return l;
当我们需要查找第一个大于target的数时:
int l = 0, r = n;
while (l < r) {
......
if (nums[mid] >= targer) {
r = mid;
} else {
l = mid + 1;
}
}
int l = 0, r = n - 1;
while (l <= r) {
......
if (nums[mid] == target) {
return mid + 1;
} else if (nums[mid] < target) {
l = mid + 1;
} else {
r = mid - 1;
}
}
return l;
同理可以推出其他情况。
现在让我们用抽象成BST的方法来重新做以下这道题
搜索二维矩阵
我们从以右上为根节点
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
vector<int> ans;
int n = matrix.size(), m = matrix[0].size();
int i = 0, j = m - 1;
while (i < n && j >= 0) {
if (matrix[i][j] > target) {
j--;
} else if (matrix[i][j] < target) {
i++;
} else return true;
}
return false;
}
};
合并两个有序链表
方法一:迭代
当 list1 和 list2都不为空时,依次连接,之后判断哪个链表不为空,把剩余链表合并。
时间复杂度:O(n+m)
空间复杂度:O(1)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
ListNode* dummy = new ListNode(-1);
ListNode* ans = dummy;
while (list1 != nullptr && list2 != nullptr) {
if (list1->val < list2->val) {
ans->next = list1;
list1 = list1->next;
} else {
ans->next = list2;
list2 = list2->next;
}
ans = ans->next;
}
ans->next = list1 == nullptr ? list2 : list1;
return dummy->next;
}
};
方法二:递归
一开始并没有想到这个递归方法,所以需要再次复习递归的知识点:
在此题中,终止条件就是list1 == nullptr || list2 == nullptr
我们可以如下递归地定义两个链表里的 merge 操作(忽略边界情况,比如空链表等):
也就是说,两个链表头部值较小的一个节点与剩下元素的 merge 操作结果合并。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
if (list1 == nullptr) return list2;
else if (list2 == nullptr) return list1;
else if (list1->val < list2->val) {
list1->next = mergeTwoLists(list1->next, list2);
return list1;
} else {
list2->next = mergeTwoLists(list1, list2->next);
return list2;
}
}
};
两数相加
暴力思路:先把链表转为int, 加起来之后再放到int里,那肯定是会超时/超空间的
看了题解可以发现:
1.如果遍历到某个链表之后为空,值为0
2.链表每一位上的和为 (list1->val + list2->val + carry)% 10,carry为上一位的进位,初始值为0
3.链表每一位上的进位为 carry = (list1->val + list2->val + carry) / 10
4.因为是逆序存储,那么直接计算,如果最后一位有进位,那么end->next后再new 一个 node(1)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* dummy = new ListNode(-1);
ListNode* result = dummy;
int carry = 0;
while (l1 != nullptr || l2 != nullptr) {
int x = l1 == nullptr ? 0 : l1->val;
int y = l2 == nullptr ? 0 : l2->val;
int tmp = (x + y + carry) % 10;
result->next = new ListNode(tmp);
result = result->next;
carry = (x + y + carry) / 10;
if (l1 != nullptr) l1 = l1->next;
if (l2 != nullptr) l2 = l2->next;
}
if (carry) result->next = new ListNode(1);
return dummy->next;
}
};
删除链表的倒数第 N 个结点
第一次遍历得出链表个数,第二次遍历删除节点
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* p = head, *q = head;
int num = 0;
while(p != nullptr) {
p = p->next;
num++;
}
// 如果要删除的节点是第一个节点,直接返回第二个节点即可
if (num == n) return head->next;
num = num - n - 1;
while (num--) {
q = q->next;
}
//如果要删除的是最后一个节点之后的节点,空节点,直接return head
if (q->next != nullptr)
q->next = q->next->next;
return head;
}
};