代碼隨想錄——陣列
理論基礎
二分查找
704. 二分查找 - 力扣(LeetCode)
代碼/思路
在一個有序陣列中通過二分查找解決找到目標值的問題,
C++版
//版本一:左閉右閉的寫法
class Solution {
public:
int search(vector<int>& nums, int target) {
//定義target在[left,right]閉區間
int left=0;
int right = nums.size()-1;
while(left<=right){
//防止溢位,等同于(left + right)/2
int middle = left + ((right-left)/2);
if(nums[middle]>target){
// target 在左區間,所以[left, middle - 1]
right=middle-1;
}else if(nums[middle]<target){
// target在右區間,所以[middle + 1, right]
left=middle+1;
}else{// nums[middle] == target
// 陣列中找到目標值,直接回傳下標
return middle;
}
}
//未找到目標就回傳-1下標
return -1;
}
};
//版本二:左閉右開的寫法
class Solution {
public:
int search(vector<int>& nums, int target) {
//定義target在[left,right) 左閉右開區間
int left=0;
int right = nums.size();
while(left<right){
//防止溢位,等同于(left + right)/2
int middle = left + ((right-left)/2);
if(nums[middle]>target){
// target 在左區間,所以[left, middle)
right=middle;
}else if(nums[middle]<target){
// target在右區間,所以[middle + 1, right)
left=middle+1;
}else{// nums[middle] == target
// 陣列中找到目標值,直接回傳下標
return middle;
}
}
//未找到目標就回傳-1下標
return -1;
}
};
Java版
//版本一的寫法
class Solution {
public int search(int[] nums, int target) {
// 避免當 target 小于nums[0] 或 大于nums[nums.length - 1]時多次回圈運算
if (target < nums[0] || target > nums[nums.length - 1]) {
return -1;
}
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + ((right - left) >> 1);
if (nums[mid] == target)
return mid;
else if (nums[mid] < target)
left = mid + 1;
else if (nums[mid] > target)
right = mid - 1;
}
return -1;
}
}
JavaScript版
//版本一的寫法
var search = function(nums, target) {
let left = 0, right = nums.length - 1;
while (left <= right) {
//注意向下取整,防止變成浮點數
let mid = left + Math.floor((right - left)/2);
if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] < target) {
left = mid + 1;
} else {
return mid;
}
}
return -1;
};
時間復雜度
- 時間復雜度:\(O(\log n)\),其中 \(n\) 是陣列的長度,由于每次查找都會將查找范圍縮小一半,因此二分查找的時間復雜度是 \(O(\log n)\),其中 \(n\) 是陣列的長度,
- 空間復雜度:\(O(1)\),
35. 搜索插入位置
代碼/思路
暴力搜索
遍歷查找
C++版
//版本一:暴力寫法
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
// 無非就三種情況:
// 1. 插入陣列所有元素前或所有陣列元素的末尾
// 2. 找到陣列中的目標索引
// 3. 插入到陣列的某個位置
// 因此,先遍歷查找
for(int i=0;i<nums.size();i++){
// 一旦發現大于或者等于target的num[i],符合2與3的情況
if(nums[i]>=target){
return i;
}
}
//當發現陣列未空,或者已經遍歷完了,那么回傳該陣列本身就符合第1種情況
return nums.size();
}
};
復雜度分析
- 時間復雜度:\(O(n)\)
- 空間復雜度:\(O(1)\)
二分查找
這題的本質其實跟704題一模一樣,最侄訓是可以通過二分查找決定目標值是要在陣列中的索引 或 是應插入的位置,
C++版
//版本二:二分查找的左閉右閉的寫法
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int left=0,right=nums.size()-1;
while(left<=right){
int middle=left+((right-left)/2);
if (nums[middle] > target) {
right = middle - 1;
} else if (nums[middle] < target) {
left = middle + 1;
} else {
return middle;
}
}
// 分為以下四種情況:
// 1、目標值在陣列所有元素之前 [0, -1]
// 2、目標值等于陣列中某一個元素 return middle;
// 3、目標值插入陣列中的位置,return right + 1
// 4、目標值在陣列所有元素之后的情況,因為是右閉區間,所以 return right + 1
return right+1;
}
};
//版本三:二分查找的左閉右開的寫法
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int left = 0,right = nums.size();
while (left < right) {
int middle = left + ((right - left) >> 1);
if (nums[middle] > target) {
right = middle;
} else if (nums[middle] < target) {
left = middle + 1;
} else {
return middle;
}
}
// 分別為以下四種情況
// 目標值在陣列所有元素之前 [0,0)
// 目標值等于陣列中某一個元素 return middle
// 目標值插入陣列中的位置 [left, right) ,return right 即可
// 目標值在陣列所有元素之后的情況 [left, right),因為是右開區間,所以 return right
return right;
}
};
Java版
//版本二的寫法
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) {
right = mid - 1;
} else if (nums[mid] < target) {
left = mid + 1;
} else {
return mid;
}
}
return right + 1;
}
}
JavaScript版
//版本二的寫法
var searchInsert = function (nums, target) {
let left = 0, right = nums.length - 1;
while (left <= right) {
const mid = left + Math.floor((right - left) >> 1);
if (target > nums[mid]) {
left = mid + 1;
} else if(target < nums[mid]){
right = mid - 1;
}else{
return mid;
}
}
return right+1;
};
復雜度分析
- 時間復雜度:\(O(log n)\)
- 空間復雜度:\(O(1)\)
34. 在排序陣列中查找元素的第一個和最后一個位置
代碼/思路
這題采用 \(C++\) 的 \(upper_bound()\) 和 \(lower_bound()\) 就可以找到目標值 \(target\) 在 排序陣列的開始位置和結束位置,而這兩個函式可以用二分查找實作,代碼參考《谷歌高暢Leetcode刷題筆記》,具體實作思路其實也可以看leetcode的官方題解,
C++版
//版本一:二分查找
class Solution {
private:
//這里采用左閉右閉的寫法,upper_bound()與lower_bound()的唯一不同之處在于
//要令upper指向最后一位就令nums[mid]>target而不是像lower一樣令nums[mid]>=target
int lower_bound(vector<int> &nums,int target){
int left=0,right=nums.size()-1;
while(left<=right){
int mid=left+(right-left)/2;
if(nums[mid]>=target){
right=mid-1;
}else{
left=mid+1;
}
}
return left;
}
int upper_bound(vector<int> &nums,int target){
int left=0,right=nums.size()-1;
while(left<=right){
int mid=left+(right-left)/2;
if(nums[mid]>target){
right=mid-1;
}else{
left=mid+1;
}
}
return left;
}
public:
vector<int> searchRange(vector<int>& nums, int target) {
//如果一開始的陣列為空是無效的
if(nums.empty()) return vector<int>{-1,-1};
int lower=lower_bound(nums,target);
//這里要減一
int upper=upper_bound(nums,target)-1;
//如果開始位置已經到了末尾或者根本不等于我們的目標值就是無效的
if(lower==nums.size() || nums[lower]!=target){
return vector<int>{-1,-1};
}
return vector<int>{lower,upper};
}
};
Java版
class Solution {
public int[] searchRange(int[] nums, int target) {
//根據之前的C++的代碼內容,就可以知道upper_bound()和lower_bound()怎么寫在一起,但是要注意之前的判斷條件其實也要跟著變得如下嚴謹
int lowerIndex = binarySearch(nums, target, true);
int upperIndex = binarySearch(nums, target, false) - 1;
//在保證lower的下標要小于upper的下標 與 upper的下標不超過nums陣列的長度的情況下,再確認upper與lower值是否跟target相同,
if (lowerIndex <= upperIndex && upperIndex < nums.length && nums[lowerIndex] == target && nums[upperIndex] == target) {
return new int[]{lowerIndex, upperIndex};
}
return new int[]{-1, -1};
}
public int binarySearch(int[] nums, int target, boolean be_lower) {
int left = 0, right = nums.length - 1;
while (left <= right) {
//不用擔心溢位
int mid = (left + right) / 2;
if (nums[mid] > target || (be_lower && nums[mid] >= target)) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return left;
}
}
JavaScript版
const binarySearch = (nums, target, be_lower) => {
let left = 0, right = nums.length - 1;
while (left <= right) {
//向下取整
const mid = Math.floor((left + right) / 2);
if (nums[mid] > target || (be_lower && nums[mid] >= target)) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return left;
}
var searchRange = function(nums, target) {
let ans = [-1, -1];
const lowerIndex = binarySearch(nums, target, true);
const upperIndex = binarySearch(nums, target, false) - 1;
if (lowerIndex <= upperIndex && upperIndex < nums.length && nums[lowerIndex] === target && nums[upperIndex] === target) {
ans = [lowerIndex, upperIndex];
}
return ans;
};
Python版
class Solution(object):
def searchRange(self, nums, target):
# 這題的含義類似于C++的lower_bound 和 upper_bound 函式,具體的函式的實作,可以通過二分查找
def lower_bound(nums,target):
left=0;right=len(nums)
while left<right:
mid=left+(right-left)/2
if nums[mid]>=target:
right=mid
else:
left=mid+1
return left
def upper_bound(nums,target):
left=0;right=len(nums)
while left<right:
mid=left+(right-left)/2
if nums[mid]>target:
right=mid
else:
left=mid+1
return left
if len(nums)==0:return [-1,-1]
lower=lower_bound(nums,target)
# 這里需要減一位,因為得到的值的下標會溢位
upper=upper_bound(nums,target)-1
if lower==len(nums) or nums[lower]!=target:
return [-1,-1]
return [lower,upper]
復雜度分析
- 時間復雜度:\(O(\log n)\) ,其中 \(n\) 為陣列的長度,二分查找的時間復雜度為 \(O(\log n)\),一共會執行兩次,因此總時間復雜度為 \(O(\log n)\),
- 空間復雜度:\(O(1)\)
69. x 的平方根
代碼/思路
這題就是給你一個值,然后求它的算術平方根,一般人的思路也許就是硬算,就跟自己的朋友學數學一樣(宣告:這個朋友不是我),或者投機取巧用已有的輪子,
其實這題也可以使用二分查找,因為我們可以設定\([1,x]\)的區間(當然要注意\(x\)為\(0\)的情況),在二分查找該區間要的數值\(sqrt\)的程序中,其實令值\(x\)除以中間值\(mid\),再跟\(sqrt\)一一比較判斷即可,
C++版
//版本一:二分查找
class Solution {
public:
int mySqrt(int x) {
if(x==0)return x;
int left=1,right=x;
while(left<=right){
int mid=left+(right-left)/2;
int sqrt=x/mid;
if(mid == sqrt) return mid;
else if (mid>sqrt) right=mid-1;
else left=mid+1;
}
return right;
}
};
Java版
class Solution {
public int mySqrt(int x) {
int left=0,right=x,result=-1;
while(left<=right){
int mid=left+(right-left)/2;
if((long) mid*mid<=x){
result=mid;
left=mid+1;
}else{
right=mid-1;
}
}
return result;
}
}
JavaScript版
var mySqrt = function(x) {
let left=0,right=x,result=-1;
while(left<=right){
let mid=Math.floor((left+right)/2);
if(mid*mid<=x){
result=mid;
left=mid+1;
}else{
right=mid-1;
}
}
return result;
};
Python版
class Solution(object):
def mySqrt(self, x):
# 這里單獨拿出0的情況分析
if x==0:return x
left=1;right=x
while left<=right:
mid=left+(right-left)/2
sqrt=x/mid # mid*mid=x
if mid==sqrt:
return mid
elif mid>sqrt:
right=mid-1
else:
left=mid+1
return right
復雜度分析
- 時間復雜度:\(O(\log x)\) ,
- 空間復雜度:\(O(1)\),
另外跟著leetcode寫袖珍計算器演算法,牛頓迭代法,
袖珍計算器演算法
「袖珍計算器演算法」是一種用指數函式 \(\exp\) 和對數函式 \(\ln\) 代替平方根函式的方法,
我們將 \(\sqrt{x}\)寫成冪的形式 \(x^{1/2}\) ,再使用自然對數 \(e\) 進行換底,即可得到
\[\sqrt{x} = x^{1/2} = (e ^ {\ln x})^{1/2} = e^{\frac{1}{2} \ln x} \]這樣我們就可以得到 \(\sqrt{x}\) 的值了,
C++版
class Solution {
public:
int mySqrt(int x) {
//單獨拿0出來處理
if(x==0){
return 0;
}
//經過轉換的公式
int ans=exp(0.5*log(x));
//由于計算機無法存盤浮點數的精確值,會有整數1的相差,所以要對 ans 與 ans+1 進行判斷
return ((long long)(ans+1)*(ans+1)<=x ? ans+1:ans);
}
};
復雜度分析
- 時間復雜度:\(O(1)\),由于內置的 \(exp\) 函式與 \(log\) 函式一般都很快,我們在這里將其復雜度視為 \(O(1)\),
- 空間復雜度:\(O(1)\),
牛頓迭代法
這理論把我看到吐了,讓我想起自己不自量力地參加數學建模競賽的那些日子,
牛頓迭代法的本質是借助泰勒級數,根據高等數學的內容,解答給的是二階泰勒公式\(y=f(x)=x^2?C\)
以我們高中熟悉的斜線方程\(y-y_i=k(x-x_i)+b\)為例,其中斜率\(k\)為\(f(x)\)的導數,
由于選擇 \(x_0=C\) 作為初始值,找 點 \((x_i, x_i^2 - C)\) 作直線,之后經過轉換,求的\(x\)值就成為了新的迭代結果\(x_{i+1}\),
也即leetcode官方解答的
\[x_{i+1} = \frac{1}{2}\left(x_i + \frac{C}{x_i}\right) \]C++版
class Solution {
public:
int mySqrt(int x) {
//單獨拿0出來處理
if(x==0){
return 0;
}
//C表示待求出平方根的那個整數,而又選擇 x_0 = C 作為初始值
double C=x,x0=x;
while(true){
double xi=0.5*(x0+C/x0);
//設立極限值
if(fabs(x0-xi)<1e-7){
break;
}
//迭代遞進
x0=xi;
}
//向下取整
return int(x0);
}
};
復雜度分析
- 時間復雜度:\(O(\log x)\) ,此方法是二次收斂的,相較于二分查找更快,
- 空間復雜度:\(O(1)\),
367. 有效的完全平方數
代碼/思路
這題如果采用暴力的寫法,其實就是遍歷\([1,完全平方數]\)的區間,一個一個算,
那么清楚暴力的方法后,就可以通過二分查找去縮短查找耗時,
當然其實也可以用牛頓迭代法,具體的實作步驟其實跟上一題一致,只不過由于這題的結果為布林值,所以代碼上特別處理,
C++版
//版本一:暴力寫法
class Solution {
public:
bool isPerfectSquare(int num) {
//要防溢位,用long
long x=1,square=1;
while(square<=num){
if(square==num){
return true;
}
//遍歷
++x;
square=x*x;
}
//遍歷完都沒找到
return false;
}
};
//版本二:二分查找
class Solution {
public:
bool isPerfectSquare(int num) {
int left=0,right=num;
while(left<=right){
int mid=left+(right-left)/2;
long square=(long) mid*mid;
if(square<num){
left=mid+1;
}else if(square > num){
right=mid-1;
}else{
return true;
}
}
return false;
}
};
//版本三:牛頓迭代法
class Solution {
public:
bool isPerfectSquare(int num) {
int C=num;
double x0=num;
while(true){
double xi=0.5*(x0+C/x0);
//不給用函式fabs,這里求極限值
if(x0-xi<1e-6){
break;
}
x0=xi;
}
int x=(int) x0;
//判斷
return x * x == num;
}
};
Java版
//版本二的寫法
class Solution {
public boolean isPerfectSquare(int num) {
int left=0,right=num;
while(left<=right){
int mid=left+(right-left)/2;
long square=(long) mid*mid;
if(square<num){
left=mid+1;
}else if(square > num){
right=mid-1;
}else{
return true;
}
}
return false;
}
}
JavaScript版
//版本二的寫法
var isPerfectSquare = function(num) {
let left=0,right=num;
while(left<=right){
let mid=Math.floor((left+right)/2);
let square=mid*mid;
if(square<num){
left=mid+1;
}else if(square>num){
right=mid-1;
}else{
return true;
}
}
return false;
};
如果是暴力
- 時間復雜度:\(O(\sqrt{n})\),其中 \(n\) 為正整數 \(\textit{num}\)的最大值,我們最多需要遍歷 \(\sqrt{n} + 1\)
- 空間復雜度:\(O(1)\),
如果是二分查找或者牛頓迭代的話
復雜度分析
- 時間復雜度:\(O(\log n)\) ,
- 空間復雜度:\(O(1)\),
雙指標——左右、快慢
27. 移除元素
C++版
//版本一:暴力寫法
//版本二:二分查找
Java版
//版本二的寫法
JavaScript版
//版本二的寫法
283. 移動零
C++版
//版本一:暴力寫法
//版本二:二分查找
Java版
//版本二的寫法
JavaScript版
//版本二的寫法
844. 比較含退格的字串
C++版
//版本一:暴力寫法
//版本二:二分查找
Java版
//版本二的寫法
JavaScript版
//版本二的寫法
977. 有序陣列的平方
C++版
//版本一:暴力寫法
//版本二:二分查找
Java版
//版本二的寫法
JavaScript版
//版本二的寫法
滑動視窗
209. 長度最小的子陣列
C++版
//版本一:暴力寫法
//版本二:二分查找
Java版
//版本二的寫法
JavaScript版
//版本二的寫法
904. 水果成籃
C++版
//版本一:暴力寫法
//版本二:二分查找
Java版
//版本二的寫法
JavaScript版
//版本二的寫法
76. 最小覆寫子串
C++版
//版本一:暴力寫法
//版本二:二分查找
Java版
//版本二的寫法
JavaScript版
//版本二的寫法
模擬
59. 螺旋矩陣 II
C++版
//版本一:暴力寫法
//版本二:二分查找
Java版
//版本二的寫法
JavaScript版
//版本二的寫法
54. 螺旋矩陣
C++版
//版本一:暴力寫法
//版本二:二分查找
Java版
//版本二的寫法
JavaScript版
//版本二的寫法
劍指 Offer 29. 順時針列印矩陣
C++版
//版本一:暴力寫法
//版本二:二分查找
Java版
//版本二的寫法
JavaScript版
//版本二的寫法
總結
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/502002.html
標籤:其他
