力扣高頻|算法面試題彙總(八):排序與檢索

力扣高頻|算法面試題彙總(八):排序與檢索

力扣鏈接
目錄:

  • 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,所有需要自定義排序,比較30+3>3+30?30+3 > 3+30?
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.算法的實現複雜度O(nlogn)O(nlogn),空間複雜度O(n)O(n)
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
實現複雜度O(n)O(n),空間複雜度O(1)O(1)的算法,還是參考剛剛的參考大佬的思路:
1.使用快速選擇找到數組的中位數,實現時間複雜度O(n)O(n)。因爲不關心奇偶數組的內部排序,只關心奇數組的最大值小於等於偶數組的最小值(臨界點),這個鄰接點就是中位數。
2.使用虛地址進行映射,實現空間複雜度O(1)O(1),(這個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.算法時間複雜度:O(n)O(n)
    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。
時間複雜度 : O(logn)O(logn),空間複雜度O(logn)O(logn)

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的基礎上進一步優化,使用迭代遞歸,這樣時間複雜度不變,但空間複雜度降爲O(1)O(1)
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(nlgn+n)O(nlgn + n),空間複雜度O(1)(orO(n))O(1)(orO(n))。產生副本爲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.快慢指針相遇的前一個點,則爲重複的數字。
    這個思路和環形鏈表找到入口節點基本一致。這個時間複雜度O(n)O(n),空間複雜度O(1)O(1)
    在這裏插入圖片描述
    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.逆序輸出結果。
算法的複雜度的上界爲:O(nlogn)O(nlogn)

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]
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章