力扣高頻|算法面試題彙總(八):排序與檢索
力扣鏈接
目錄:
- 1.最大數
- 2.擺動排序 II
- 3.尋找峯值
- 4.尋找重複數
- 5.計算右側小於當前元素的個數
1.最大數
給定一組非負整數,重新排列它們的順序使之組成一個最大的整數。
示例 1:
輸入: [10,2]
輸出: 210
示例 2:
輸入: [3,30,34,5,9]
輸出: 9534330
說明: 輸出結果可能非常大,所以你需要返回一個字符串而不是整數。
思路:
剛開始直接想的就是用字典排序,然後進行字符串拼接,但是遇到輸入[3,30,34,5,9]
,會得到輸出9534303
,但真正的最大值應該是9534330
,所有需要自定義排序,比較。
C++
class Solution {
public:
static bool cmp(string& num1, string& num2) {
string n1 = num1 + num2;
string n2 = num2 + num1;
return n1 > n2;
}
string largestNumber(vector<int>& nums) {
vector<string> strNum;
for(int i = 0; i < nums.size(); ++i){
strNum.push_back(to_string(nums[i]));
}
sort(strNum.begin(), strNum.end(), cmp);
string num;
if (strNum[0] == "0")//考慮全零的情況
return "0";
for(int i = 0; i < strNum.size(); ++i)
num += strNum[i];
return num;
}
};
Python
import functools
class Solution:
def largestNumber(self, nums):
# 自定義排序
nums = sorted(nums, key=functools.cmp_to_key(self.cmp))
if nums[0] == 0:
return "0"
num = ""
for n in nums:
num += str(n)
return num
def cmp(self, num1, num2):
if str(num1) + str(num2) > str(num2) + str(num1):
return -1
else:
return 1
2.擺動排序 II
給定一個無序的數組 nums,將它重新排列成 nums[0] < nums[1] > nums[2] < nums[3]… 的順序。
示例 1:
輸入: nums = [1, 5, 1, 1, 6, 4]
輸出: 一個可能的答案是 [1, 4, 1, 5, 1, 6]
示例 2:
輸入: nums = [1, 3, 2, 2, 3, 1]
輸出: 一個可能的答案是 [2, 3, 1, 3, 1, 2]
說明:
你可以假設所有輸入都會得到有效的結果。
進階:
你能用 O(n) 時間複雜度和 / 或原地 O(1) 額外空間來實現嗎?
思路:
參考大佬的思路:
1.先把數組排序,如把[1, 3, 2, 2, 3, 1]
,排序成[1,1,2,2,3,3]
2.拆分成兩個數組:奇數組[1,1,2]
和偶數組[2,3,3]
,如果無法等分,保證奇數組長度大於偶數組長度即可,因爲是把偶數組穿插到技術組中,且數組是升序。
3.把奇數組和偶數組分別逆序,如[2,1,1]
和[3,3,2]
。
4.偶數組穿插到奇數中:[2,3,1,3,1,2]
5.算法的實現複雜度,空間複雜度
C++
class Solution {
public:
void wiggleSort(vector<int>& nums) {
// 先排序
sort(nums.begin(), nums.end());
// 輔助數組
vector<int> tempOdd;
vector<int> tempEven;
tempOdd.assign(nums.begin(), nums.begin() + (nums.size() +1)/ 2 );// 加1保證奇數數組比偶數數組大
tempEven.assign(nums.begin() + (nums.size()+1) / 2, nums.end());// 這是因爲偶數數組來穿插到奇數數組中
// 逆序穿插
int k = tempOdd.size() - 1;
for (int i = 0; i < nums.size(); i += 2) {
cout << "k:" << k << endl;
nums[i] = tempOdd[k--];
}
k = tempEven.size() - 1;
for (int i = 1; i < nums.size(); i += 2) {
cout << "k:" << k << endl;
nums[i] = tempEven[k--];
}
}
};
Python:
class Solution:
def wiggleSort(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
getNum = sorted(nums)
tempOdd = getNum[: (len(nums)+1)//2]
tempEven = getNum[(len(nums)+1)//2:]
k = len(tempOdd) - 1
for i in range(0, len(nums), 2):
nums[i] = tempOdd[k]
k -= 1
k = len(tempEven) - 1
for i in range(1, len(nums), 2):
nums[i] = tempEven[k]
k -= 1
思路2:
實現複雜度,空間複雜度的算法,還是參考剛剛的參考大佬的思路:
1.使用快速選擇找到數組的中位數,實現時間複雜度。因爲不關心奇偶數組的內部排序,只關心奇數組的最大值小於等於偶數組的最小值(臨界點),這個鄰接點就是中位數。
2.使用虛地址進行映射,實現空間複雜度,(這個nb),不夠和上述思路不一樣的是:上述思路是將奇數組(較小的數組)排前面,偶數組(較大的數組)排後面,現在將較大的數組排前面,較小的數組放後面,方便地址映射:#define A(i) nums[(1+2*(i)) % (n|1)]
C++
class Solution {
public:
void wiggleSort(vector<int>& nums) {
int n = nums.size();
// Find a median.
auto midptr = nums.begin() + n / 2;
nth_element(nums.begin(), midptr, nums.end());
int mid = *midptr;
// Index-rewiring.
#define A(i) nums[(1+2*(i)) % (n|1)]
// 3-way-partition-to-wiggly in O(n) time with O(1) space.
int i = 0, j = 0, k = n - 1;
while (j <= k) {
if (A(j) > mid)
swap(A(i++), A(j++));
else if (A(j) < mid)
swap(A(j), A(k--));
else
j++;
}
}
};
3.尋找峯值
峯值元素是指其值大於左右相鄰值的元素。
給定一個輸入數組 nums,其中 nums[i] ≠ nums[i+1],找到峯值元素並返回其索引。
數組可能包含多個峯值,在這種情況下,返回任何一個峯值所在位置即可。
你可以假設 nums[-1] = nums[n] = -∞。
示例 1:
輸入: nums = [1,2,3,1]
輸出: 2
解釋: 3 是峯值元素,你的函數應該返回其索引 2。
示例 2:
輸入: nums = [1,2,1,3,5,6,4]
輸出: 1 或 5
解釋: 你的函數可以返回索引 1,其峯值元素爲 2;
或者返回索引 5, 其峯值元素爲 6。
說明:
你的解法應該是 O(logN) 時間複雜度的。
思路:
比較num[i]
和num[i+1]
的值即可。
峯值出現一般有三種情況:
- 1.數據呈現遞增的趨勢,那麼峯值在末尾。
- 2.數據呈現遞減的趨勢,那麼峯值在開頭。
- 3.數據成波動的形式,那麼峯值在第一個出現
nums[i] > nums[i+1]
的地方。 - 4.算法時間複雜度:
C++
class Solution {
public:
int findPeakElement(vector<int>& nums) {
for(int i = 0; i <nums.size() - 1; ++i)
if(nums[i] > nums[i+1])
return i;
return nums.size() -1;
}
};
Python
class Solution:
def findPeakElement(self, nums: List[int]) -> int:
for i in range(len(nums) -1):
if nums[i] > nums[i+1]:
return i
return len(nums)-1
思路2:
使用遞歸。 每次計算start和end的中間數mid。如果mid大於mid+1的數,則峯值在左邊,否則在右邊。邊界條件爲:end == start。
時間複雜度 : ,空間複雜度。
class Solution {
public:
int findPeakElement(vector<int>& nums) {
// 輸入 數組、 起始點、 截止點、 中間數
return findPeakElementCore(nums, 0, nums.size() - 1);
}
int findPeakElementCore(vector<int>& nums, int start, int end){
if(start == end)
return start;
int mid = (end + start)/ 2 ;
if(nums[mid] <= nums[mid + 1])
return findPeakElementCore(nums, mid + 1, end);// 峯值在右邊
else
return findPeakElementCore(nums, start, mid);// 峯值在左邊
}
};
Python
class Solution:
def findPeakElement(self, nums: List[int]) -> int:
return self.findPeakElementCore(nums, 0 , len(nums) - 1)
def findPeakElementCore(self, nums, start, end):
if start == end:
return start
mid = (start + end)//2
if nums[mid] <= nums[mid + 1]:
return self.findPeakElementCore(nums, mid + 1, end)
else:
return self.findPeakElementCore(nums, start, mid)
思路3:
在思路2的基礎上進一步優化,使用迭代遞歸,這樣時間複雜度不變,但空間複雜度降爲
C++
class Solution {
public:
int findPeakElement(vector<int>& nums) {
int start = 0;
int end = nums.size() - 1;
while(start < end){
int mid = (start + end)/2;
if(nums[mid] < nums[mid+ 1] )
start = mid + 1;
else
end = mid;
}
return start;
}
};
Python
class Solution:
def findPeakElement(self, nums: List[int]) -> int:
start = 0
end = len(nums) - 1
while start < end:
mid = (start + end) // 2
if nums[mid] <= nums[mid + 1]:
start = mid + 1
else:
end = mid
return start
4.尋找重複數
給定一個包含 n + 1 個整數的數組 nums,其數字都在 1 到 n 之間(包括 1 和 n),可知至少存在一個重複的整數。假設只有一個重複的整數,找出這個重複的數。
示例 1:
輸入: [1,3,4,2,2]
輸出: 2
示例 2:
輸入: [3,1,3,4,2]
輸出: 3
說明:
不能更改原數組(假設數組是隻讀的)。
只能使用額外的 O(1) 的空間。
時間複雜度小於 O(n2) 。
數組中只有一個重複的數字,但它可能不止重複出現一次。
思路:
先進行排序,重複的數字必定相鄰,時間複雜度,空間複雜度。產生副本爲O(n)。
C++
class Solution {
public:
int findDuplicate(vector<int>& nums) {
sort(nums.begin(), nums.end());
for(int i = 0; i < nums.size() - 1; ++i){
if(nums[i] == nums[i + 1])
return nums[i];
}
return 0;
}
};
Python
class Solution:
def findDuplicate(self, nums: List[int]) -> int:
nums = sorted(nums)
for i in range(len(nums) - 1):
if nums[i] == nums[i+1]:
return nums[i]
return 0
思路2:
把數據想象成鏈表,使用快慢指針查找。
數組:num[val] = next
,起索引模擬成鏈表的值,其索引對應的值模擬成下一個鏈表的地址(索引)。由於存在重複的數字,在存在相同的映射,使得這個鏈表成環。如val1 = val2,則num[val1] = num[val2];
。
步驟如下:
- 1.先使用快慢指針找到第一個相遇的點。
- 2.把慢指針復位爲0,快指針位置不變,但沒錯移動和慢指針一樣,只移動一次。
- 3.快慢指針相遇的前一個點,則爲重複的數字。
這個思路和環形鏈表找到入口節點基本一致。這個時間複雜度,空間複雜度
C++
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int pFast = 0;
int pSlow = 0;
do{
pFast = nums[pFast];
pFast = nums[pFast];
pSlow = nums[pSlow];
}while(pFast != pSlow);
//cout<<"pFast:"<<pFast<<" pSlow:"<<pSlow<<endl;
pSlow = 0;
while(nums[pFast] != nums[pSlow]){
pSlow = nums[pSlow];
pFast = nums[pFast];
}
return nums[pFast];
}
};
Python:
class Solution:
def findDuplicate(self, nums: List[int]) -> int:
pFast = 0
pSlow = 0
while True:
pFast = nums[pFast]
pFast = nums[pFast]
pSlow = nums[pSlow]
if pSlow == pFast:
break
pSlow = 0
while nums[pFast] != nums[pSlow]:
pFast = nums[pFast]
pSlow = nums[pSlow]
return nums[pSlow]
5.計算右側小於當前元素的個數
給定一個整數數組 nums,按要求返回一個新數組 counts。數組 counts 有該性質: counts[i] 的值是 nums[i] 右側小於 nums[i] 的元素的數量。
示例:
輸入: [5,2,6,1]
輸出: [2,1,1,0]
解釋:
5 的右側有 2 個更小的元素 (2 和 1).
2 的右側僅有 1 個更小的元素 (1).
6 的右側有 1 個更小的元素 (1).
1 的右側有 0 個更小的元素.
思路:
參考大佬巧妙的思路,總結一下:
1.從右往左遍歷,因爲需要記錄比遍歷數字小的元素,所以用一個排序數組(從小到大)存儲每一次遍歷的結果。
2.獲取遍歷的元素,計算這個元素插入需要插入到這個排序數組中的索引,由於數組是從小到大的,那麼這個索引其實就是比當前數字小的個數。
3.把索引值放入結果中。
4.逆序輸出結果。
算法的複雜度的上界爲:
C++
class Solution {
public:
vector<int> countSmaller(vector<int>& nums) {
vector<int> sortNums;
vector<int> res;
for(auto itear = nums.rbegin(); itear < nums.rend(); ++itear){
// 尋找索引
// lower_bound()返回第一個大於等於x的數的地址
int index = lower_bound(sortNums.begin(), sortNums.end(), *itear) - sortNums.begin();
res.push_back(index);
// 插入排序數組
sortNums.insert(sortNums.begin() + index, *itear);
}
// 逆序
reverse(res.begin(),res.end());
return res;
}
};
Python:
class Solution:
def countSmaller(self, nums: List[int]) -> List[int]:
sortList = []
res = []
for num in reversed(nums):
# 判斷數字的插入位置 索引對應即有多少個比當前小數字小的數字
index = bisect.bisect_left(sortList, num)
# 索引即結果,存儲
res.append(index)
# 將數字插入排序列表
sortList.insert(index, num)
# 逆序輸出
return res[::-1]