题目描述:
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素
我的解法:
public class Solution {
public int SearchInsert(int[] nums, int target) {
int left=0;
int right=nums.Length;
int mid=0;
while(left
解法①
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 0;
}
}
解法②
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 0;
}
}
最快方法:
public class Solution {
public int SearchInsert(int[] nums, int target) {
int l = 0;
int len = nums.Length;
int r = len;
while(l < r) {
int mid = (l+r)>>1;
if(nums[mid] < target) {
l = mid + 1;
} else {
r = mid;
}
}
return l;
}
}
总结:
题目描述:
峰值元素是指其值大于左右相邻值的元素。
给定一个输入数组 nums,其中 nums[i] ≠ nums[i+1],找到峰值元素并返回其索引。
数组可能包含多个峰值,在这种情况下,返回任何一个峰值所在位置即可。
你可以假设 nums[-1] = nums[n] = -∞。
示例 1:
输入: nums = [1,2,3,1]
输出: 2
解释: 3 是峰值元素,你的函数应该返回其索引 2。
解法①
if(num【i+1】>num[i])
i++
else index =i;
简化版本:for(int i=0;i<num.Length;i++) if(num[i]>num[i+1]) return i;
解法②
递归二分查找:
在简单的二分查找中,我们处理的是一个有序数列,并通过在每一步减少搜索空间来找到所需要的数字。在本例中,我们对二分查找进行一点修改。首先从数组 numsnums 中找到中间的元素 midmid。若该元素恰好位于降序序列或者一个局部下降坡度中(通过将 nums[i]nums[i] 与右侧比较判断),则说明峰值会在本元素的左边。于是,我们将搜索空间缩小为 midmid 的左边(包括其本身),并在左侧子数组上重复上述过程。
若该元素恰好位于升序序列或者一个局部上升坡度中(通过将 nums[i]nums[i] 与右侧比较判断),则说明峰值会在本元素的右边。于是,我们将搜索空间缩小为 midmid 的右边,并在右侧子数组上重复上述过程。
就这样,我们不断地缩小搜索空间,直到搜索空间中只有一个元素,该元素即为峰值元素。
public class Solution {
public int findPeakElement(int[] nums) {
return search(nums, 0, nums.length - 1);
}
public int search(int[] nums, int l, int r) {
if (l == r)
return l;
int mid = (l + r) / 2;
if (nums[mid] > nums[mid + 1])
return search(nums, l, mid);
return search(nums, mid + 1, r);
}
}
解法② 迭代二分查找
public class Solution {
public int findPeakElement(int[] nums) {
int l = 0, r = nums.length - 1;
while (l < r) {
int mid = (l + r) / 2;
if (nums[mid] > nums[mid + 1])
r = mid;
else
l = mid + 1;
}
return l;
}
}
我的解法:
public int FindPeakElement(int[] nums) {
int index=0;
int left=0;
int right=nums.Length-1;
while(left!=(right-1)&&left!=right)
{
index=(right+left)/2;
if(nums[index]<nums[index+1])
{
left=index;
}
else right=index;
}
return right;
}
出现的问题:
假如出现实例:【2,1】,则此算法无用。因为我总是默认最后筛选出来的区间,右边的大。
最快方法:
public class Solution {
public int FindPeakElement(int[] nums) {
if (nums.Length == 0 || nums.Length == 1) return 0;
int start = 0;
int end = nums.Length - 1;
while (start < end)
{
int mid = (start + end) / 2;
if (nums[mid] < nums[mid + 1])
{
start = mid + 1;
}
else
{
end = mid;
}
}
return start;
}
}
题目描述:
一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。
示例 1:
输入: [0,1,3]
输出: 2
解法①
左数组nums【i】=i;
右数组nums【i】!=i;所以只要找到右数组的第一个数字就好了
如果num【mid】==mid,则不断右移;
如果num【mid】!=mid则不断左移。
while要遍历每一个数字,所以跳出的条件是left》right
class Solution:
def missingNumber(self, nums: List[int]) -> int:
i, j = 0, len(nums) - 1
while i <= j:
m = (i + j) // 2
if nums[m] == m: i = m + 1
else: j = m - 1
return i;
我的解法:
public int MissingNumber(int[] nums) { //存在的问题:
int left=0;
int right=nums.Length-1;
int mid=right;
if(nums.Length==1) //一开始没有考虑只有一个的情况出现
{
if(nums[0]==0)
return 1;
else if(nums[0]==1)
return 0;
}
while(left<right) //没有考虑只有左边的情况【0,1】
{
mid=(right+left)/2; //也没有考虑只有右边的情况。比如说【1,2】
if(nums[mid]==mid&&nums[mid+1]!=mid+1)
return mid+1;
else if(nums[mid]==mid&&nums[mid+1]==mid+1)
left=mid+1;
else if(nums[mid]!=mid&&nums[mid+1]!=mid+1)
right=mid-1;
}
return left; // 改成left+1则考虑了第一种 ;但是改了之后 第二种情况又错误了
最后更改:
if(nums[left]!=left)
return left;
else return left+1;
再次更改:把{0}跟第一种情况结合起来:
if(nums[right]==nums.length-1)
return nums.length;
最快方法:
public class Solution
{
public int MissingNumber(int[] nums)
{
if (nums.Last() == nums.Length - 1)
return nums.Length;
int left = 0, right = nums.Length - 1;
while (left < right) {
int mid = (left + right) / 2;
if (nums[mid] != mid)
right = mid;
else
left = mid + 1;
}
return left;
}
}
题目描述:
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。
你可以假设数组中不存在重复的元素。
你的算法时间复杂度必须是 O(log n) 级别。
示例 1:
输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4
我的解法:
public class Solution {
public int Search(int[] nums, int target) {
int left=0;
int right=nums.Length-1;
int mid;
if(nums.Length==0) return -1;//加入数组为空
if(nums[0]==target)
return 0;
else if(nums[0]>target)
{
while(left<=right)
{
mid=(right+left)/2;
if(nums[mid]==target)
return mid;
else if(nums[mid]<target)
{
left=mid+1;
}
else if(nums[mid]>target)
{
if(nums[mid]>=nums[0])//[3,1]情况必须要有=号
left=mid+1;
else right=mid-1;
}
}
return -1;
}
else if(nums[0]<target)
{
while(left<=right)
{mid=(right+left)/2;
if(nums[mid]==target)
return mid;
else if(nums[mid]>target)
right=mid-1;
else if(nums[mid]<target)
{
if(nums[mid]<nums[0])
right=mid-1;
else left=mid+1;
}
}
return -1;
}
else return 0;
}
}
问题:
看起来太复杂。
有些情况其实可以合并!
如果数组为空,nums【0】会超出界限;
nums={}的情况时,nums==null不顶用,要用length判断
最快方法:
public class Solution {
public int Search(int[] nums, int target)
{
if (nums == null || nums.Length == 0)
{
return -1;
}
int left = 0;
int right = nums.Length - 1;
int mid;
while(left <= right)
{
mid = left + (right - left) / 2;
if(nums[mid] == target)
{
return mid;
}
if(nums[left] <= nums[mid])
{
if(target >= nums[left] && target < nums[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;
}
}
题目描述:
实现pow(x,n)
解法①:
public double MyPow(double x, int n) {
if (n == 0) { return 1; }
if (n == 1) { return x; }
if (n == -1) { return 1 / x; }
double half = myPow(x, n / 2);
double rest = myPow(x, n % 2);
return rest * half * half;
}
时间:超越8%。要去看别人的代码!
我的解法:
public double MyPow(double x, int n) {
if(n==0||n==-0) return 1;
if(x==0||x== -0) return 0;
if(n==1) return n;
if(n== -1) return 1/n;
double result;
int times;
int count=1;
result=x>0 ? x:-x;
if(n==int.MinValue)
{
times=int.MaxValue;
result=Powtest(result,times,ref count);
result=x*result;
return 1/result;
}
times=n>0 ? n:-n;//times 要取正数
result=Powtest( result,times,ref count);
if(x>0&&n>0) return result;
else if(x>0&&n<0) return 1/result;
else if(x<0&& n%2==0) return result;
else return -result;
}
static double Powtest(double x,int a,ref int count)
{
if(count ==0)
return 1;
while(count<=a)
{
x*=x;
count*=2;
}
return x*Powtest(x,a-count,ref count);
//这里会提示超出界限。stack overflow exception
}
最快方法:
public class Solution {
public double MyPow(double x, int n) {
long N = n;
if(n < 0){
N = -1*N;
x = 1/x;
}
double ans = 1;
double powBase = x;
while(N!= 0){
if(N%2 != 0){
ans = ans * powBase;
}
powBase*=powBase;
N/=2;
}
return ans;
}
}
题目描述:
给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。
函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。
说明:
返回的下标值(index1 和 index2)不是从零开始的。
你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
示例:
输入: numbers = [2, 7, 11, 15], target = 9
输出: [1,2]
解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。
我的解法:
思想:
①将数组分为两部分。左部分小于target/2.右部分大于target/2.
②从左数组遍历,在右数组查找是否存在一个数target-nums【i】。若存在,则返回序列,佛则返回-1。
出现的问题:①最开始查找halfindex代码有问题。
public class Solution {
public int halfidex;
public int left, right;
public int[] TwoSum(int[] numbers, int target) {
int[] index = new int[2];
if (numbers.Length == 2)
{
index[0] = 1;
index[1] = 2;
return index;
}
left = 0;
right = numbers.Length - 1;
halfidex = Findeindex( target / 2,ref numbers);
if(numbers[halfidex-1]==(target/2))
{
if(numbers[halfidex-1]+numbers[halfidex]==target)
{
index[0]=halfidex;index[1]=(halfidex+1);
return index;
}
else if( numbers[halfidex-2]+numbers[halfidex-1]==target)
{
index[0]=(halfidex-1);index[1]=halfidex;
return index;
}
}
for (int i = 0; i < halfidex; i++)
{
index[1] = Select(target - numbers[i],ref numbers);
if (index[1] != -1)
{
index[0] = i + 1;
index[1] += 1;
break;
}
}
return index;
}
int Findeindex(int target,ref int[] numbers)
{
int mid = 0;
while (left <= right)
{
mid = (left + right) / 2;
if (numbers[mid] == target)
return mid+1;
else if (numbers[mid] < target)
left = mid + 1;
else right = mid - 1;
}
return left;
}
int Select(int target,ref int[] numbers)
{
int mid = 0;
left = halfidex;
right = numbers.Length - 1;
while (left <= right)
{
mid = (left + right) / 2;
if (numbers[mid] == target)
return mid;
else if (numbers[mid] > target)
right = mid - 1;
else left = mid + 1;
}
return -1;
}
}
解法①:
int Findeindex(int target, ref int[] numbers)
//这个时候返回的是左部最大值,而不是右部的最小
{
int mid = 0;
Console.Write("half of target is{0}\n", target);
while (left <right)
//改为left<=right 则变为返回右部最小值
{
mid = (left + right) / 2;
if (numbers[mid] == target)//这个时候又要分情况。不如说1,2,3,4,5,6.查找8. 4=8/2,这个时候恰好是右部最小值。但如果
return mid;
//出现两个4,则很可能定位到第一个4,这样左部就绝对找不到正确答案,所以对于出现两个相同的数字
else if (numbers[mid] < target)
//还要额外的判断。
left = mid + 1;
else right = mid - 1;
}
Console.Write(" left is {0}\n", left);
return left;
}
最快方法:
public class Solution {
public int[] TwoSum(int[] numbers, int target) {
int i = 0;
int j = 0;
int k = 1;
int tmp;
int[] num = new int[2] { 0, 0 };
for (i = 0; i < numbers.Length - 1; i++)
{
tmp = numbers[j] + numbers[numbers.Length - k];
if (tmp > target)
{
k++;
continue;
}
else if (tmp < target)
{
j++;
continue;
}
else
{
num[0] = j + 1;
num[1] = numbers.Length - k + 1;
return num;
}
}
num[0] = -1;
return num;
}
}
题目描述:
给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。
示例 1:
输入: [1,3,4,2,2]
输出: 2
算法思想:
①重复的数的特点,遍历整个数组的时候,小于等于它的数等于自身加一
②整个数组就可以分为重复数之前 重复数之后两个数组,则问题就转变为求重复数之后的第一个数。
③left=1,right=n。先去中间值,如果中间值的小于它的个数比它大,说明中间值在重复数之后的数组中,则right=mid,不断逼近
④while跳出的条件是left=right,因为这个时候不用考虑left自身的情况
public class Solution {
public int FindDuplicate(int[] nums) {
int left=1;
int right=nums.Length-1;
int counts;
int mid;
while(left<right)
{
counts=0;
mid=(left+right)/2;
foreach(int i in nums)
if(i<=mid) counts++;
if(counts>mid) right=mid;
else left=mid+1;
}
return right;
}
}
最快方法:
public class Solution {
public int FindDuplicate(int[] nums) {
int left = 1;
int right = nums.Length - 1;
while (left < right) {
int mid = left+(right-left)/2;
int cnt = 0;
foreach (int num in nums) {
if (num <= mid) {
cnt += 1;
}
}
// 根据抽屉原理,小于等于 4 的个数如果严格大于 4 个
// 此时重复元素一定出现在 [1, 4] 区间里
if (cnt > mid) {
// 重复元素位于区间 [left, mid]
right = mid;
} else {
// if 分析正确了以后,else 搜索的区间就是 if 的反面
// [mid + 1, right]
left = mid + 1;
}
}
return left;
}
}
题目描述:
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组。如果不存在符合条件的连续子数组,返回 0。
示例:
输入: s = 7, nums = [2,3,1,2,4,3]
输出: 2
解释: 子数组 [4,3] 是该条件下的长度最小的连续子数组。
进阶:
如果你已经完成了O(n) 时间复杂度的解法, 请尝试 O(n log n) 时间复杂度的解法。
我的解法:两个指针,一个头指针,一个尾指针,从头指针开始往后一直找数组相加,直到和大于等于S,计算长度。
代码:
public class Solution {
public int MinSubArrayLen(int s, int[] nums) {
int minsize=int.MaxValue;
int cums=0;
int length=nums.Length;
for(int i=0;i<length;i++)
{
cums=0;
for(int j=i;j<length;j++)
{
cums+=nums[j];
if(cums>=s)
{
if(minsize>(j-i+1)) minsize=(j-i+1);
break;
}
}
}
return minsize==int.MaxValue? 0:minsize;
}
}
分析:内存使用少,但是时间很长.
解法二:双指针法
算法思想:
用双指针left和right表示一个窗口
1.right向右移增大窗口,直到窗口内的数字和大于等于S,进行到第二步
2.记录此时的长度,left向右移动,开始减少长度,每减少一次,就更新最小长度。直到窗口内的数字和小于S,回到第一步
技巧分析:
与暴力破解法相比,省略了重复的步骤,因为要求的是连续的数组,下一个数组与上一个数组有重复的数字,而暴力破解则每次都要从头来过。
代码:
public class Solution {
public int MinSubArrayLen(int s, int[] nums) {
int left=0,right=0,cnum=0;
int minsize=int.MaxValue;
while(right<nums.Length)//不能是length-1
{
cnum+=nums[right];
while(cnum>=s)
{
if(minsize>(right-left+1)) minsize=(right-left+1);
cnum-=nums[left];
left+=1;
}
right++;
}
return minsize==int.MaxValue? 0:minsize;
}
}
解法③:二分查找算法
算法原理:二分查找算法针对的是有序数组,在本题中,首先要构造一个有序数组。最小长度的值是从【1,2。。。N】之中选择的,而这个数组恰好是升序数组,所以可以利用二分法找出这个数组中mid长度的值的数组和的最大值,如果大于等于S,则左移,否则右移。
代码:
public class Solution {
public int MinSubArrayLen(int s, int[] nums) {
if(nums.Length==0) return 0;
int mid; int left=1,right=nums.Length;
while(left<right)
{
mid=(left+right)/2;
if(GetMax(mid,nums)>=s) right=mid;
else if (GetMax(mid,nums)<s) left=mid+1;
}
if(GetMax(left,nums)<s) return 0;//针对情况 S=3,nums=【1,1】
else return left;
}
public int GetMax(int index,int []nums)
{
int max=0;
int left=0,right=left+index-1,cnums=0;
for(int i=left;i<=right;i++)
cnums+=nums[i];
max=cnums;
while(right<nums.Length-1)
{
right++;
left++;
cnums+=nums[right];cnums-=nums[left-1];
max=max<cnums? cnums:max;
}
return max;
}
}
作者改良版:
public class Solution {
public int MinSubArrayLen(int s, int[] nums) {
if(nums.Length==0) return 0;
int mid; int left=1,right=nums.Length; int min=-1;
while(left<=right)
{
mid=(left+right)/2;
if(GetMax(mid,nums)>=s) {right=mid-1; min=mid;}
else if (GetMax(mid,nums)<s) { left=mid+1;}
}
return min==-1? 0:min;
}
public int GetMax(int index,int []nums)
{
int max=0;
int left=0,right=left+index-1,cnums=0;
for(int i=left;i<=right;i++)
cnums+=nums[i];
max=cnums;
while(right<nums.Length-1)
{
right++;
left++;
cnums+=nums[right];cnums-=nums[left-1];
max=max<cnums? cnums:max;
}
return max;
}
}
最快的方法:双指针法
public class Solution {
public int MinSubArrayLen(int s, int[] nums) {
if (nums == null || nums.Length == 0) {
return 0;
}
int result = int.MaxValue;
int left = 0;
int sum = 0;
for(int i = 0; i < nums.Length; i++) {
sum += nums[i];
while(sum >= s) {
result = Math.Min(result, i - left + 1);
sum -= nums[left++];
}
}
return result == int.MaxValue ? 0 : result;
}
}