劍指offer——數組

  答案來自https://blog.csdn.net/c406495762/article/details/79247243

劍指Offer(一):二維數組中的查找 

題目

在一個二維數組中,每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請完成一個函數,輸入這樣的一個二維數組和一個整數,判斷數組中是否含有該整數。

1、思路

首先選取數組中右上角的數字。如果該數字等於要查找的數字,查找過程結束;如果該數字大於要查找的數組,剔除這個數字所在的列;如果該數字小於要查找的數字,剔除這個數字所在的行。也就是說如果要查找的數字不在數組的右上角,則每一次都在數組的查找範圍中剔除一行或者一列,這樣每一步都可以縮小查找的範圍,直到找到要查找的數字,或者查找範圍爲空。

2、舉例

如果在一個二維數組中找到數字7,則返回true,如果沒有找到,則返回false。

劍指Offer(一):二維數組中的查找

查找過程如下:

劍指Offer(一):二維數組中的查找

3、編程實現

C++:

class Solution {
public:
    bool Find(int target, vector<vector<int> > array) {
        int rows = array.size();
        int cols = array[0].size();
        if(!array.empty() && rows >0 && cols > 0){
            int row = 0;
            int col = cols-1;
            while(row <rows && col >= 0){
                if(array[row][col] == target){
                    return true;
                }
                else if(array[row][col] > target){
                    --col;
                }
                else{
                    ++row;
                }
            }
        }
        return false;
    }
};

 

 

劍指Offer(六):旋轉數組的最小數字 

 

題目

把一個數組最開始的若干個元素搬到數組的末尾,我們稱之爲數組的旋轉。 輸入一個非遞減排序的數組的一個旋轉,輸出旋轉數組的最小元素。 例如數組{3,4,5,1,2}爲{1,2,3,4,5}的一個旋轉,該數組的最小值爲1。 NOTE:給出的所有元素都大於0,若數組大小爲0,請返回0。

1、思路

我們注意到旋轉之後的數組實際上可以劃分爲兩個排序的字數組,而且前面的字數組的元素大於或者等於後面字數組的元素。我們還注意到最小的元素剛好是這兩個字數組的分界線。在排序的數組中可以用二分查找實現O(logn)的查找。本題給出的數組在一定程度上是排序的,因此我們可以試着用二分查找法的思路來尋找這個最小的元素。

  • 把一個數組最開始的若干個元素搬到數組的末尾,我們稱之爲數組的旋轉。 輸入一個非遞減排序的數組的一個旋轉,輸出旋轉數組的最小元素。 例如數組{3,4,5,1,2}爲{1,2,3,4,5}的一個旋轉,該數組的最小值爲1。 NOTE:給出的所有元素都大於0,若數組大小爲0,請返回0。
  • 接着我們可以找到數組中間的元素。如果中間元素位於前面的遞增子數組,那麼它應該大於或者等於第一個指針指向的元素。此時最小元素應該位於該中間元素之後,然後我們把第一個指針指向該中間元素,移動之後第一個指針仍然位於前面的遞增子數組中。
  • 同樣,如果中間元素位於後面的遞增子數組,那麼它應該小於或者等於第二個指針指向的元素。此時最小元素應該位於該中間元素之前,然後我們把第二個指針指向該中間元素,移動之後第二個指針仍然位於後面的遞增子數組中。
  • 第一個指針總是指向前面遞增數組的元素,第二個指針總是指向後面遞增數組的元素。最終它們會指向兩個相鄰的元素,而第二個指針指向的剛好是最小的元素,這就是循環結束的條件。

示意圖如下:

劍指Offer(六):旋轉數組的最小數字

 

特殊情況:

  • 如果把排序數組的0個元素搬到最後面,這仍然是旋轉數組,我們的代碼需要支持這種情況。如果發現數組中的一個數字小於最後一個數字,就可以直接返回第一個數字了。
  • 下面這種情況,即第一個指針指向的數字、第二個指針指向的數字和中間的數字三者相等,我們無法判斷中間的數字1是數以前面的遞增子數組還是後面的遞增子數組。正樣的話,我們只能進行順序查找。
  • 劍指Offer(六):旋轉數組的最小數字

 

2、代碼

C++:

class Solution {
public:
    int minNumberInRotateArray(vector<int> rotateArray) {
        int size = rotateArray.size();							//數組長度
        if(size == 0){
            return 0;
        }
        int left = 0;											//左指針
        int right = size - 1;									//右指針
        int mid = 0;											//中間指針
        while(rotateArray[left] >= rotateArray[right]){			//確保旋轉
            if(right - left == 1){								//左右指針相鄰
                mid = right;
                break;
            }
            mid = left + (right - left) / 2;					//計算中間指針位置
            //特殊情況:如果無法確定中間元素是屬於前面還是後面的遞增子數組,只能順序查找
            if(rotateArray[left] == rotateArray[right] && rotateArray[mid] == rotateArray[left]){
                return MinInOrder(rotateArray, left, right);
            }
            //中間元素位於前面的遞增子數組,此時最小元素位於中間元素的後面
            if(rotateArray[mid] >= rotateArray[left]){
                left = mid;
            }
            //中間元素位於後面的遞增子數組,此時最小元素位於中間元素的前面
            else{
                right = mid;
            }
        }
        return rotateArray[mid];
    }
private:
    //順序尋找最小值
    int MinInOrder(vector<int> &num, int left, int right){
        int result = num[left];
        for(int i = left + 1; i < right; i++){
            if(num[i] < result){
                result = num[i];
            }
        }
        return result;
    }
};

 

 

 劍指Offer(十三):調整數組順序使奇數位於偶數前面

題目

輸入一個整數數組,實現一個函數來調整該數組中數字的順序,使得所有的奇數位於數組的前半部分,所有的偶數位於位於數組的後半部分,並保證奇數和奇數,偶數和偶數之間的相對位置不變。

1、思路

創建雙向隊列,遍歷數組,奇數前插入,偶數後插入。最後使用assign方法實現不同容器但相容的類型賦值。

2、代碼

C++:

class Solution {
public:
    void reOrderArray(vector<int> &array) {
        deque<int> result;
        int num = array.size();
        for(int i = 0; i < num; i++){
            if(array[num - i - 1] % 2 == 1){
                result.push_front(array[num - i - 1]);
            }
            if(array[i] % 2 == 0){
                result.push_back(array[i]);
            }
        }
        array.assign(result.begin(),result.end());
    }
};

劍指Offer(二十八):數組中出現次數超過一半的數字

題目

數組中有一個數字出現的次數超過數組長度的一半,請找出這個數字。例如輸入一個長度爲9的數組{1,2,3,2,2,2,5,4,2}。由於數字2在數組中出現了5次,超過數組長度的一半,因此輸出2。如果不存在則輸出0。

1、思路

數組中有一個數字出現的次數超過數組長度的一半,也就是說它出現的次數比其他所有數字出現次數的和還要多。因此我們可以考慮在遍歷數組的時候保存兩個值:一個是數組的一個數字,一個是次數。當我們遍歷到下一個數字的時候,如果下一個數字和我們之前保存的數字相同,則次數加1;如果下一個數字和我們之前保存的數字不同,則次數減1。如果次數爲零,我們需要保存下一個數字,並把次數設爲1。由於我們要找的數字出現的次數比其他所有數字出現的次數之和還要多,那麼要找的數字肯定是最後一次把次數設爲1時對應的數字。

2、代碼

C++:

class Solution {
public:
    int MoreThanHalfNum_Solution(vector<int> numbers) {
        if(numbers.empty()){
            return 0;
        }
        // 遍歷每個元素,並記錄次數;若與前一個元素相同,則次數加1,否則次數減1
        int result = numbers[0];
        int times = 1;
        for(int i = 1; i < numbers.size(); ++i){
            if(times == 0){
                // 更新result的值爲當前元素,並置次數爲1
                result = numbers[i];
                times = 1;
            }
            else if(numbers[i] == result){
                times++;
            }
            else{
                times--;
            }
        }
        // 判斷result是否符合條件,即出現次數大於數組長度的一半
        times = 0;
        for(int i = 0; i < numbers.size(); ++i)
        {
            if(numbers[i] == result){
                times++;
            }
        }
        return (times > (numbers.size() >> 1)) ? result : 0;
    }
};

 

劍指Offer(五十):數組中重複的數字

 

題目

在一個長度爲n的數組裏的所有數字都在0到n-1的範圍內。 數組中某些數字是重複的,但不知道有幾個數字是重複的。也不知道每個數字重複幾次。請找出數組中任意一個重複的數字。 例如,如果輸入長度爲7的數組{2,3,1,0,2,5,3},那麼對應的輸出是第一個重複的數字2。

1、思路

還可以把當前序列當成是一個下標和下標對應值是相同的數組(時間複雜度爲O(n),空間複雜度爲O(1)); 遍歷數組,判斷當前位的值和下標是否相等:

  • 若相等,則遍歷下一位;
  • 若不等,則將當前位置i上的元素和a[i]位置上的元素比較:若它們相等,則找到了第一個相同的元素;若不等,則將它們兩交換。換完之後a[i]位置上的值和它的下標是對應的,但i位置上的元素和下標並不一定對應;重複2的操作,直到當前位置i的值也爲i,將i向後移一位,再重複2。

本文采用思路3,如果還是不懂,看下面的實例分析就懂了!

舉例說明:{2,3,1,0,2,5,3}

  • 0(索引值)和2(索引值位置的元素)不相等,並且2(索引值位置的元素)和1(以該索引值位置的元素2爲索引值的位置的元素)不相等,則交換位置,數組變爲:{1,3,2,0,2,5,3};
  • 0(索引值)和1(索引值位置的元素)仍然不相等,並且1(索引值位置的元素)和3(以該索引值位置的元素1爲索引值的位置的元素)不相等,則交換位置,數組變爲:{3,1,2,0,2,5,3};
  • 0(索引值)和3(索引值位置的元素)仍然不相等,並且3(索引值位置的元素)和0(以該索引值位置的元素3爲索引值的位置的元素)不相等,則交換位置,數組變爲:{0,1,2,3,2,5,3};
  • 0(索引值)和0(索引值位置的元素)相等,遍歷下一個元素;
  • 1(索引值)和1(索引值位置的元素)相等,遍歷下一個元素;
  • 2(索引值)和2(索引值位置的元素)相等,遍歷下一個元素;
  • 3(索引值)和3(索引值位置的元素)相等,遍歷下一個元素;
  • 4(索引值)和2(索引值位置的元素)不相等,但是2(索引值位置的元素)和2(以該索引值位置的元素2爲索引值的位置的元素)相等,則找到了第一個重複的元素。

2、代碼

C++:

class Solution {
public:
    // Parameters:
    //        numbers:     an array of integers
    //        length:      the length of array numbers
    //        duplication: (Output) the duplicated number in the array number
    // Return value:       true if the input is valid, and there are some duplications in the array number
    //                     otherwise false
    bool duplicate(int numbers[], int length, int* duplication) {
        // 非法輸入
        if(numbers == NULL || length <= 0){
            return false;
        }
        // 非法輸入
        for(int i = 0; i < length; i++){
            if(numbers[i] < 0 || numbers[i] > length - 1){
                return false;
            }
        }
        // 遍歷查找第一個重複的數
        for(int i = 0; i < length; i++){
            while(numbers[i] != i){
                if(numbers[i] == numbers[numbers[i]]){
                    *duplication = numbers[i];
                    return true;
                }
                swap(numbers[i], numbers[numbers[i]]);
            }
        }
        return false;
    }
};

 

劍指Offer(三十五):數組中的逆序對 

題目

在數組中的兩個數字,如果前面一個數字大於後面的數字,則這兩個數字組成一個逆序對。輸入一個數組,求出這個數組中的逆序對的總數P。並將P對1000000007取模的結果輸出。 即輸出P%1000000007。

輸入描述:

題目保證輸入的數組中沒有的相同的數字

數據範圍:

對於%50的數據,size<=10^4

對於%75的數據,size<=10^5

對於%100的數據,size<=2*10^5

輸入:1,2,3,4,5,6,7,0

輸出:7

如數組{7,5,6,4},逆序對總共有5對,{7,5},{7,6},{7,4},{5,4},{6,4};

思路1:暴力解法,順序掃描整個數組,每掃描到一個數字的時候,逐個比較該數字和它後面的數字的大小。如果後面的數字比它小,則這兩個數字就組成一個逆序對。假設數組中含有n個數字,由於每個數字都要和O(n)個數字作比較,因此這個算法的時間複雜度是O(n^2)。

思路2:分治思想,採用歸併排序的思路來處理,如下圖,先分後治:

劍指Offer(三十五):數組中的逆序對

先把數組分解成兩個長度爲2的子數組,再把這兩個子數組分解成兩個長度爲1的子數組。接下來一邊合併相鄰的子數組,一邊統計逆序對的數目。在第一對長度爲1的子數組{7}、{5}中7>5,因此(7,5)組成一個逆序對。同樣在第二對長度爲1的子數組{6},{4}中也有逆序對(6,4),由於已經統計了這兩對子數組內部的逆序對,因此需要把這兩對子數組進行排序,避免在之後的統計過程中重複統計。


逆序對的總數 = 左邊數組中的逆序對的數量 + 右邊數組中逆序對的數量 + 左右結合成新的順序數組時中出現的逆序對的數量

總結一下:

這是一個歸併排序的合併過程,主要是考慮合併兩個有序序列時,計算逆序對數。

對於兩個升序序列,設置兩個下標:兩個有序序列的末尾。每次比較兩個末尾值,如果前末尾大於後末尾值,則有”後序列當前長度“個逆序對;否則不構成逆序對。然後把較大值拷貝到輔助數組的末尾,即最終要將兩個有序序列合併到輔助數組並有序。

這樣,每次在合併前,先遞歸地處理左半段、右半段,則左、右半段有序,且左右半段的逆序對數可得到,再計算左右半段合併時逆序對的個數。

2、代碼

C++:

注意:InversePairsCore形參的順序是(data,copy),而遞歸調用時實參是(copy,data)。

要明白遞歸函數InversePairsCore的作用就行了,它是對data的左右半段進行合併,複製到輔助數組copy中有序。最後,data和copy兩個數組都是有序的。

(評論補充 解釋)

如上圖在(a)圖中:
是兩個排好序的數組,一個是5,7,另一個是4,6.
我們用兩個指針P1和P2,一個指向左邊那麼數組的未尾,一個指向右邊那麼數組的未尾。
於是我們比較P1和P2所指向的這兩個數,由於P1指向的數大於P2指向的數,又由於我們右
邊的那個數組是從小到大排好序的,於是逆序數就是P2加它左邊的數=2了。然後我們將
P1指向的數放入輔助數組中,用P3指示。P1左移一位。P2保持不動
(b)圖中:
由於P1比P2小,因此此時P2指向的數無用了。因爲P1應該是在左邊的數組中屬於最大的了
於是將P2指向的數複製到P3。然後將P2左移一位。
(C)圖中:
和(a) 圖相似了。

 

class Solution {
public:
    int InversePairs(vector<int> data) {
        if(data.size() == 0){
            return 0;
        }
        // 排序的輔助數組
        vector<int> copy;
        for(int i = 0; i < data.size(); ++i){
            copy.push_back(data[i]);
        }
        return InversePairsCore(data, copy, 0, data.size() - 1) % 1000000007;
    }
    long InversePairsCore(vector<int> &data, vector<int> &copy, int begin, int end){
        // 如果指向相同位置,則沒有逆序對。
        if(begin == end){
            copy[begin] = data[end];
            return 0;
        }
        // 求中點
        int mid = (end + begin) >> 1;
        // 使data左半段有序,並返回左半段逆序對的數目
        long leftCount = InversePairsCore(copy, data, begin, mid);
        // 使data右半段有序,並返回右半段逆序對的數目
        long rightCount = InversePairsCore(copy, data, mid + 1, end);
        
        int i = mid; // i初始化爲前半段最後一個數字的下標
        int j = end; // j初始化爲後半段最後一個數字的下標
        int indexcopy = end; // 輔助數組複製的數組的最後一個數字的下標
        long count = 0; // 計數,逆序對的個數,注意類型
        
        while(i >= begin && j >= mid + 1){
            if(data[i] > data[j]){
                copy[indexcopy--] = data[i--];
                count += j - mid;
            }
            else{
                copy[indexcopy--] = data[j--];
            }
        }
        for(;i >= begin; --i){
            copy[indexcopy--] = data[i];
        }
        for(;j >= mid + 1; --j){
            copy[indexcopy--] = data[j];
        }
        return leftCount + rightCount + count;
    }
};

 

 

 

 


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