leetcode題解(數組問題)

面試中的算法問題,有不少並不須要複雜的數據結構支撐。就是用數組,就能考察出不少東西了。其實,經典的排序問題,二分搜索等等問題,就是在數組這種最基礎的結構中處理問題的,今天主要學習常見的數組中處理問題的方法。c++

數組中的問題其實最多見。

  • 排序:選擇排序;插入排序;歸併排序;快速排序
  • 查找:二分查找法
  • 數據結構:棧;隊列;堆

從二分查找法看如何寫出正確的程序

  • 創建一個基礎的框架
  • 什麼是正確的程序

二分查找法

  • 二分查找法的思想在1946年提出。
  • 第一個沒有bug的二分查找法在1962年纔出現。
  • 對於有序數列,才能使用二分查找法 (排序的做用)

須要注意的問題

  • 聲明變量的時候,明確變量的意義,而且在書寫整個邏輯的時候,要不聽的維護住這個變量的意義。
  • 初始值問題
  • 邊界問題
template<typename T>
    int binarySearch( T arr[], int n, T target ){
    
        int l = 0, r = n-1; // 在[l...r]的範圍裏尋找target:前閉後閉
        while( l <= r ){    // 只要還有能夠查找的內容。當 l == r時,區間[l...r]依然是有效的
            int mid = l + (r-l)/2;
            if( arr[mid] == target ) return mid;
            //mid已經判斷過了
            if( target > arr[mid] )
                l = mid + 1;  // target在[mid+1...r]中; [l...mid]必定沒有target
            else    // target < arr[mid]
                r = mid - 1;  // target在[l...mid-1]中; [mid...r]必定沒有target
        }
    
        return -1;
    }
    
複製代碼

** 循環不變量。聲明不變。控制邊界。**面試

int l = 0, r = n-1; // 在[l...r]的範圍裏尋找target:前閉後閉

複製代碼

改變變量定義,依然能夠寫出正確的算法

template<typename T>
    int binarySearch( T arr[], int n, T target ){
    
        int l = 0, r = n; // target在[l...r)的範圍裏,這樣設置才能保證長度爲n
        while( l < r ){    // 當 l == r時,區間[l...r)是一個無效區間 [42,43)
            int mid = l + (r-l)/2;
            if( arr[mid] == target ) return mid;
            if( target > arr[mid] )
                l = mid + 1;    // target在[mid+1...r)中; [l...mid]必定沒有target
            else// target < arr[mid]
                r = mid;        // target在[l...mid)中; [mid...r)必定沒有target
        }
    
        return -1;
    }
    
複製代碼

注意算法

  • 求mid值是採用(l+r)/2容易整形溢出
  • 採用mid = l + (r-l)/2;

如何寫出正確的程序?

  • 明確變量的含義
  • 循環不變量
  • 小數據量調試(4到6個數據)空集,邊界(小數據集上代碼如何運做的)
  • 耐心找到bug,定位錯誤發生的位置。
  • 大數據量測試(性能)

LeetCode題解: Move Zeros

paste image

直觀的解題思路

  • 拿出非0元素
  • 將非0元素拿出來,而後空位補0
class Solution {
    public:
        // 時間複雜度 O(n)
        // 空間複雜度 O(n) 新建立數組
        void moveZeroes(vector<int>& nums) {
    
            vector<int> nonZeroElements;
    
            // 將vec中全部非0元素放入nonZeroElements中
            for( int i = 0 ; i < nums.size() ; i ++ )
                if( nums[i] )
                    nonZeroElements.push_back( nums[i] );
    
            // 將nonZeroElements中的全部元素依次放入到nums開始的位置
            for( int i = 0 ; i < nonZeroElements.size() ; i ++ )
                nums[i] = nonZeroElements[i];
    
            // 將nums剩餘的位置放置爲0
            for( int i = nonZeroElements.size() ; i < nums.size() ; i ++ )
                nums[i] = 0;
        }
    };
    
    int main() {
    
        int arr[] = {0, 1, 0, 3, 12};
        //根據生成的數據建立vector:傳入頭指針和尾指針
        vector<int> vec(arr, arr + sizeof(arr)/sizeof(int));
    
        Solution().moveZeroes(vec);
    
        for( int i = 0 ; i < vec.size() ; i ++ )
            cout<<vec[i]<<" ";
        cout<<endl;
    
        return 0;
    }


複製代碼

即便簡單的算法也能進一步優化。

  • 不開闢額外空間
  • k - [0…k)中保存全部當前遍歷過的非0元素
class Solution {
    public:
        // 時間複雜度 O(n)
        // 空間複雜度 O(1)
        void moveZeroes(vector<int>& nums) {
    
            int k = 0; // nums中, [0...k)的元素均爲非0元素
    
            // 遍歷到第i個元素後,保證[0...i]中全部非0元素
            // 都按照順序排列在[0...k)中
            for(int i = 0 ; i < nums.size() ; i ++ )
                if( nums[i] )
                    nums[k++] = nums[i];
    
            // 將nums剩餘的位置放置爲0
            for( int i = k ; i < nums.size() ; i ++ )
                nums[i] = 0;
        }
    };
    
    int main() {
    
        int arr[] = {0, 1, 0, 3, 12};
        vector<int> vec(arr, arr + sizeof(arr)/sizeof(int));
    
        Solution().moveZeroes(vec);
    
        for( int i = 0 ; i < vec.size() ; i ++ )
            cout<<vec[i]<<" ";
        cout<<endl;
    
        return 0;
    }
    
複製代碼

進一步優化

  • 非0的賦值不用操做了。數組

  • 非0的與0直接互換。微信

class Solution {
    public:
        // 時間複雜度 O(n)
        // 空間複雜度 O(1)
        void moveZeroes(vector<int>& nums) {
    
            int k = 0; // nums中, [0...k)的元素均爲非0元素
    
            // 遍歷到第i個元素後,保證[0...i]中全部非0元素
            // 都按照順序排列在[0...k)中
            // 同時, [k...i] 爲0
            for(int i = 0 ; i < nums.size() ; i ++ )
                if( nums[i] )
                    swap( nums[k++] , nums[i] );
    
        }
    };
    
    
複製代碼

** 極端狀況:若是都爲非0,則每一個都本身和本身交換**數據結構

class Solution {
    public:
        // 時間複雜度 O(n)
        // 空間複雜度 O(1)
        void moveZeroes(vector<int>& nums) {
    
            int k = 0; // nums中, [0...k)的元素均爲非0元素
    
            // 遍歷到第i個元素後,保證[0...i]中全部非0元素
            // 都按照順序排列在[0...k)中
            // 同時, [k...i] 爲0
            for(int i = 0 ; i < nums.size() ; i ++ )
                if( nums[i] )
                    //
                    if( k != i )
                        swap( nums[k++] , nums[i] );
                    else// i == k
                        k ++;
        }
    };
    
    
複製代碼

類似題目

  • leetcode 27
  • leetcode 26
  • leetcode 80

注意的問題框架

  • 如何定義刪除?從數組中去除?仍是放在數組末尾?
  • 剩餘元素的排列是否要保證原有的相對順序?
  • 是否有空間複雜度的要求? O(1)

基礎算法思路的應用

75 Sort Colors

paste image

基數排序法

// 時間複雜度: O(n)
    // 空間複雜度: O(k), k爲元素的取值範圍
    // 對整個數組遍歷了兩遍
    class Solution {
    public:
        void sortColors(vector<int> &nums) {
    
            int count[3] = {0};    // 存放0,1,2三個元素的頻率
            for( int i = 0 ; i < nums.size() ; i ++ ){
                assert( nums[i] >= 0 && nums[i] <= 2 );
                count[nums[i]] ++;
            }
    
            int index = 0;
            for( int i = 0 ; i < count[0] ; i ++ )
                nums[index++] = 0;
            for( int i = 0 ; i < count[1] ; i ++ )
                nums[index++] = 1;
            for( int i = 0 ; i < count[2] ; i ++ )
                nums[index++] = 2;
    
            // 小練習: 更加自使用的計數排序
        }
    };
    
    int main() {
    
        int nums[] = {2, 2, 2, 1, 1, 0};
        vector<int> vec = vector<int>( nums , nums + sizeof(nums)/sizeof(int));
    
        Solution().sortColors( vec );
        for( int i = 0 ; i < vec.size() ; i ++ )
            cout<<vec[i]<<" ";
        cout<<endl;
    
        return 0;
    }
   
複製代碼

能夠只掃描一遍麼?性能

一次三路快排學習

設置三個索引:zero two i測試

三路快排

// 時間複雜度: O(n)
    // 空間複雜度: O(1)
    // 對整個數組只遍歷了一遍
    class Solution {
    public:
        void sortColors(vector<int> &nums) {
    
            int zero = -1;          // [0...zero] == 0
            int two = nums.size();  // [two...n-1] == 2
            for( int i = 0 ; i < two ; ){
                if( nums[i] == 1 )
                    i ++;
                else if ( nums[i] == 2 )
                    swap( nums[i] , nums[--two]);
                else{ // nums[i] == 0
                    assert( nums[i] == 0 );
                    swap( nums[++zero] , nums[i++] );
                }
            }
        }
    };
    
    int main() {
    
        int nums[] = {2, 2, 2, 1, 1, 0};
        vector<int> vec = vector<int>( nums , nums + sizeof(nums)/sizeof(int));
    
        Solution().sortColors( vec );
        for( int i = 0 ; i < vec.size() ; i ++ )
            cout<<vec[i]<<" ";
        cout<<endl;
    
        return 0;
    }
    

複製代碼

類似題目

  • 88 Merge Sorted Array
  • 215 Kth Largest Element in an Array

雙索引技術-對撞指針

167 兩數之和 II - 輸入有序數組

paste image

須要考慮的問題

  • 若是沒有解怎樣?保證有解
  • 若是有多個解怎樣?返回任意解

解法

  • 最直接的思考:暴力解法。雙層遍歷,O(n^2)

    • 暴力解法沒有充分利用原數組的性質 —— 有序:有序?二分搜索?
  • 二分搜索法

    • 對於每一個i, 在剩餘數組中查找target-nums[i]的值
    • 時間複雜度爲O(NlogN)
      有序的二分搜索
  • 對撞指針

初始化的ij

  • 通常會是大於或者小於。
  • 若是大i++ 小 j--
  • 兩個索引在往中間走。對撞指針。

代碼實現

// 時間複雜度: O(n)
    // 空間複雜度: O(1)
    class Solution {
    public:
        vector<int> twoSum(vector<int>& numbers, int target) {
    
            assert( numbers.size() >= 2 );
            // assert( isSorted(numbers) );
    
            int l = 0, r = numbers.size()-1;
            while( l < r ){
    
                if( numbers[l] + numbers[r] == target ){
                    int res[2] = {l+1, r+1};
                    return vector<int>(res, res+2);
                }
                else if( numbers[l] + numbers[r] < target )
                    l ++;
                else // numbers[l] + numbers[r] > target
                    r --;
            }
    
            throw invalid_argument("the input has no solution");
        }
    
    };
複製代碼

類似題目

  • 125 Valid Palindrome
    • 空字符串如何看?
    • 字符的定義?
    • 大小寫問題
  • 344 Reverse String
  • 345 Reverse Vowels of a String
  • 11 Container With Most Water

雙索引技術-滑動窗口

209長度最小的子數組

paste image

什麼是子數組

什麼叫子數組?

  • 通常不要求連續
  • 而這個題目中規定了子數組要連續這樣的特性。
    • 若是沒有解怎麼辦?返回0

暴力解O(N^3)

  • 計算其和sum,驗證sum >= s
  • 時間複雜度O(n^3)

代碼實現

int minSubArrayLen(int s, vector<int>& nums) {

        assert(s > 0);

        int res = nums.size() + 1;
        for(int l = 0 ; l < nums.size() ; l ++)
            for(int r = l ; r < nums.size() ; r ++){
                int sum = 0;
                for(int i = l ; i <= r ; i ++)
                    sum += nums[i];
                if(sum >= s)
                    res = min(res, r - l + 1);
            }

        if(res == nums.size() + 1)
            return 0;

        return res;
    }
複製代碼

暴力解的優化O(N^2)

int minSubArrayLen(int s, vector<int>& nums) {

        assert(s > 0);

        // sums[i]存放nums[0...i-1]的和
        vector<int> sums(nums.size() + 1, 0);
        for(int i = 1 ; i <= nums.size() ; i ++)
            sums[i] = sums[i-1] + nums[i-1];

        int res = nums.size() + 1;
        for(int l = 0 ; l < nums.size() ; l ++)
            for(int r = l ; r < nums.size() ; r ++){
                // 使用sums[r+1] - sums[l] 快速得到nums[l...r]的和
                if(sums[r+1] - sums[l] >= s)
                    res = min(res, r - l + 1);
            }

        if(res == nums.size() + 1)
            return 0;

        return res;
    }
    
複製代碼

滑動窗口解

  • 若是當前子數組不到就日後再看一個

  • 窗口不停向前滑動。

// 滑動窗口的思路
// 時間複雜度: O(n)
// 空間複雜度: O(1)
class Solution {
public:
    int minSubArrayLen(int s, vector<int>& nums) {
        //nums[l...r]爲咱們的滑動窗口
        int l = 0, r = -1;
        int sum = 0;
        int res = nums.size() + 1;
        while(l < nums.size()){
            if(r+1 < nums.size() && sum < s){
                r++;
                sum += nums[r];
            }else{
                sum -= nums[l];
                l++;
            }
            
            if(sum >= s){
                res = min(res, r - l + 1);
            }
        }
        
        if(res == nums.size() + 1)
            return 0;
        return res;
    }
};

複製代碼

在滑動窗口中作記錄

無重複字符的最長子串

paste image

注意

字符集?只有字母?數字+字母?ASCII?
大小寫是否敏感?

思路

日後++

  • j++若是沒有重複元素,窗口j繼續日後
  • 若是有重複元素,i++去除重複
  • freq[256]記錄窗口中的元素

實現代碼

class Solution {
    public:
        int lengthOfLongestSubstring(string s) {
    
            int freq[256] = {0};
    
            int l = 0, r = -1; //滑動窗口爲s[l...r]
            int res = 0;
    
            // 整個循環從 l == 0; r == -1 這個空窗口開始
            // 到l == s.size(); r == s.size()-1 這個空窗口截止
            // 在每次循環裏逐漸改變窗口, 維護freq, 並記錄當前窗口中是否找到了一個新的最優值
            while( l < s.size() ){
    
                if( r + 1 < s.size() && freq[s[r+1]] == 0 )
                    freq[s[++r]] ++;
                else    //r已經到頭 || freq[s[r+1]] == 1
                    freq[s[l++]] --;
    
                res = max( res , r-l+1);
            }
    
            return res;
        }
    };
    
    int main() {
    
        cout << Solution().lengthOfLongestSubstring( "abcabcbb" )<<endl;
        cout << Solution().lengthOfLongestSubstring( "bbbbb" )<<endl;
        cout << Solution().lengthOfLongestSubstring( "pwwkew" )<<endl;
        cout << Solution().lengthOfLongestSubstring( "" )<<endl;
    
        return 0;
    }
    
複製代碼

類似題目

  • 438 Find All Anagrams in a String

    • 字符集範圍?英文小寫字母
    • 返回的解的順序?任意。
  • 76 Minimum Window Substring

    • 字符集範圍
    • 若沒有解? 返回「」
      *如有多個解?保證只有一個解
    • 什麼叫包含全部字符?S = 「a」,T = 「aa」

-------------------------華麗的分割線--------------------

看完的朋友能夠點個喜歡/關注,您的支持是對我最大的鼓勵。

我的博客番茄技術小棧掘金主頁

想了解更多,歡迎關注個人微信公衆號:番茄技術小棧

番茄技術小棧
相關文章
相關標籤/搜索