文章目录
- 面试经典150题【131-140】
- 123.买卖股票的最佳时机III
- 188.买卖股票的最佳时机IV
- 二分查找的板子:
- 35.搜索插入位置
- 74.搜索二维矩阵
- 162.寻找峰值
- 33.搜索旋转排序数组
- 34.在排序数组中查找元素的第一个和最后一个位置
- 153.寻找旋转排序数组中的最小值
- 4.寻找两个正序数组的中位数
面试经典150题【131-140】
123.买卖股票的最佳时机III
buy1代表第一次买,sell1代表第一次卖。
buy2代表第二次买,sell2代表第二次卖。
每个值的最大值/迭代值,都与上一个商业操作有关。
同一天买入卖出不影响,因为利润为0
class Solution {
public int maxProfit(int[] prices) {
int buy1=-prices[0],sell1=0;
int buy2=-prices[0],sell2=0;
for(int i=0;i<prices.length;i++){
buy1=Math.max(buy1,-prices[i]);
sell1=Math.max(sell1,buy1+prices[i]);
buy2=Math.max(buy2,sell1-prices[i]);
sell2=Math.max(sell2,buy2+prices[i]);
}
return sell2;
}
}
188.买卖股票的最佳时机IV
class Solution {
public int maxProfit(int k, int[] prices) {
if (prices.length == 0) {
return 0;
}
int n = prices.length;
//k = Math.min(k, n / 2);
int[] buy = new int[k];
int[] sell = new int[k];
Arrays.fill(buy,-prices[0]);
Arrays.fill(sell,0);
for (int i = 0; i < n; ++i) {
buy[0]=Math.max(buy[0],-prices[i]);
sell[0]=Math.max(sell[0],buy[0]+prices[i]);
for (int j = 1; j < k; ++j) {
buy[j] = Math.max(buy[j], sell[j-1] - prices[i]);
sell[j] = Math.max(sell[j], buy[j] + prices[i]);
}
}
return Arrays.stream(sell).max().getAsInt();
}
}
无需考虑同一天的买卖和k值与n/2的问题,直接模版梭哈。
在一轮i的循环中,对无数个buy和sell赋值即可
注意要对buy和sell初始化。
二分查找的板子:
小于等于的
class Solution {
public int searchInsert(int[] nums, int target) {
int left = 0, right = nums.length - 1; // 注意
while(left <= right) { // 注意
int mid = (left + right) / 2; // 注意
if(nums[mid] == target) { // 注意
// 相关逻辑
} else if(nums[mid] < target) {
left = mid + 1; // 注意
} else {
right = mid - 1; // 注意
}
}
// 相关返回值
return ?;
}
}
小于的
class Solution {
public int searchInsert(int[] nums, int target) {
int left = 0, right = nums.length; // 注意
while(left < right) { // 注意
int mid = (left + right) / 2; // 注意
if(nums[mid] == target) {
// 相关逻辑
} else if(nums[mid] < target) {
left = mid + 1; // 注意
} else {
right = mid; // 注意
}
}
// 相关返回值
return ?;
}
}
然后我们看一下Java内置的Arrays.binarySearch的方法:
private static int binarySearch0(int[] a, int fromIndex, int toIndex,
int key) {
int low = fromIndex;
int high = toIndex - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
int midVal = a[mid];
if (midVal < key)
low = mid + 1;
else if (midVal > key)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found.
}
我们可以发现他就是用的小于等于的模版,只不过最后的return的时候有些特别。
35.搜索插入位置
class Solution {
public int searchInsert(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target)
return mid;
if (nums[mid] > target)
right = mid - 1;
if (nums[mid] < target)
left = mid + 1;
}
return left;
}
}
74.搜索二维矩阵
先往下搜索找到具体的行,再往右搜索找具体的值。
但是这个题咔咔错
首先用找到target左边的元素的方法,然后再用标准二分查找有没有这个元素即可。
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int len1 = matrix.length, len2 = matrix[0].length;
int index = binarySearchFirstColumn(matrix,target);
if(index<0) return false;
if (matrix[index][0] == target)
return true;
int[] matrixRow = new int[len2];
for (int i = 0; i < len2; i++) {
matrixRow[i] = matrix[index][i];
}
int ans = Arrays.binarySearch(matrixRow, target);
if (ans < 0)
return false;
else
return true;
}
public int binarySearchFirstColumn(int[][] matrix, int target) {
int low = 0, high = matrix.length - 1;
while (low <= high) {
int mid = (high - low ) / 2 + low;
if(matrix[mid][0]==target) return mid;
if (matrix[mid][0] < target) {
low = mid+1 ;
} else {
high = mid - 1;
}
}
return high<0? 0:high;
}
}
尤其是这个找第一列,最后的return很特别。是判断high是否小于0
这种是寻找小于等于目标值的最右边的索引。return high<0? 0:high
162.寻找峰值
class Solution {
public int findPeakElement(int[] nums) {
int left = 0, right = nums.length - 1;
for (; left < right; ) {
int mid = left + (right - left) / 2;
if (nums[mid] > nums[mid + 1]) {
right = mid;
} else {
left = mid + 1;
}
}
return left;
}
}
对于1,4,5,6,2,7,8,9,10来说,
只要数组中存在一个元素比相邻元素大,那么沿着它一定可以找到一个峰值。2<7.则最后答案是10.
33.搜索旋转排序数组
nums是升序的。
class Solution {
public int search(int[] nums, int target) {
int left = 0, right = nums.length - 1, mid = 0;
while (left <= right) {
mid = (left + right) / 2;
if (target == nums[mid])
return mid;
// mid在左区间里
if (nums[mid] >= nums[left]) {
if (target >= nums[left] && target < nums[mid]) {
// target在mid的左边
right = mid - 1;
} else {
left = mid + 1;
}
} else {
if (target > nums[mid] && target <= nums[right]) {
left = mid + 1;
} else {
right = mid - 1;
}
}
}
return -1;
}
}
旋转以后,肯定是4567123,nums[0]一定比后面的右段大
先判断nums[mid]和nums[0]的关系,判断是在左段(数值比较大的一段)还是右段(数值比较小的一段)
然后比较target和nums[mid]的关系,判断他在mid的左边还是右边。
34.在排序数组中查找元素的第一个和最后一个位置
class Solution {
public int[] searchRange(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
int first = -1;
int last = -1;
// 找第一个等于target的位置
while (left <= right) {
int middle = (left + right) / 2;
if (nums[middle] == target) {
first = middle;
right = middle - 1; // 重点
} else if (nums[middle] > target) {
right = middle - 1;
} else {
left = middle + 1;
}
}
// 最后一个等于target的位置
left = 0;
right = nums.length - 1;
while (left <= right) {
int middle = (left + right) / 2;
if (nums[middle] == target) {
last = middle;
left = middle + 1; // 重点
} else if (nums[middle] > target) {
right = middle - 1;
} else {
left = middle + 1;
}
}
return new int[] { first, last };
}
}
以找最左边的first为例,即使找到了,也要再做一次right = mid-1;继续遍历。直到不等于(即越界)
153.寻找旋转排序数组中的最小值
一定要理解旋转数组是一个数字大的前半段和一个数字小的后半段。
如果nums[mid]>nums[right] ,说明mid在左半段,则left=mid+1
右边不能-1,万一右半段只有一个数字。
当然如果nums[left]<nums[mid],这说明nums[left]就是最小值了。
class Solution {
public int findMin(int[] nums) {
int left = 0;
int right = nums.length - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] > nums[right]) {
left = mid + 1;
} else {
right = mid;
}
}
return nums[left];
}
}
4.寻找两个正序数组的中位数
当 [ [a1],[b1,b2,b3] | [a2,…an],[b4,…bn] ]
我们只需要比较 b3 和 a2 的关系的大小,就可以知道这种分法是不是准确的!
例如:我们令:
nums1 = [-1,1,3,5,7,9]
nums2 = [2,4,6,8,10,12,14,16]
当 m1 = 4,m2 = 3 ,它的中位数就是median = (num1[m1] + num2[m2])/2
时间复杂度:O(log(min(m,n)))
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int n1 = nums1.length;
int n2 = nums2.length;
if (n1>n2)
return findMedianSortedArrays(nums2, nums1);
//对于6+8而言,k=7 0-7和8-15. 对于6+7而言,K还是7,0-6,7,8-14
int k = (n1 + n2 + 1)/2;
int left = 0;
//这个right也设置的很巧妙。
int right = n1;
//这里的left和right都是对于短的nums1而言的。
while(left < right){
int m1 = left +(right - left)/2;
int m2 = k - m1;
if (nums1[m1] < nums2[m2-1])
left = m1 + 1;
else
right = m1;
}
//这样对于7+7的来说,m1=7,m2=0
//对于 6+7而言,K=7,m1=6,m2=1
int m1=left,m2=k-left;
int c1 = Math.max(m1 <= 0 ? Integer.MIN_VALUE : nums1[m1-1],
m2 <= 0 ? Integer.MIN_VALUE : nums2[m2-1]);
if ((n1 + n2) % 2 == 1)
return c1;
int c2 = Math.min( m1 >= n1 ? Integer.MAX_VALUE :nums1[m1],
m2 >= n2 ? Integer.MAX_VALUE : nums2[m2]);
return (c1 + c2) * 0.5;
}
}
感觉二分这边就很恶心。上一道题就需要设置right=n1才行。然而大部分题是设置right=n1-1;
需要控制好很多边界变量的设置,即使会了板子也不一定能搞出来。