1. 程式人生 > >c++ LeetCode(陣列篇簡單級別)演算法例題程式碼詳解(一)

c++ LeetCode(陣列篇簡單級別)演算法例題程式碼詳解(一)

 

原文作者:aircraft

原文連結:https://www.cnblogs.com/DOMLX/p/10940636.html

 

 

    唉!最近忙著面試找實習,然後都是面試的很多是leetcode的演算法題,所以自己就刷了一遍,並且做些筆記,以後再來複習好了,悲催的大學生。。。。。

 

 

一、從(排序!)陣列中刪除重複項

 

給定一個排序陣列,你需要在原地刪除重複出現的元素,使得每個元素只出現一次,返回移除後陣列的新長度。

不要使用額外的陣列空間,你必須在原地修改輸入陣列並在使用 O(1) 額外空間的條件下完成。

示例 1:



給定陣列 nums = [1,1,2], 

函式應該返回新的長度 2, 並且原陣列 nums 的前兩個元素被修改為 1, 2。 

你不需要考慮陣列中超出新長度後面的元素。

 

 

示例 2:



給定 nums = [0,0,1,1,1,2,2,3,3,4],

函式應該返回新的長度 5, 並且原陣列 nums 的前五個元素被修改為 0, 1, 2, 3, 4。

你不需要考慮陣列中超出新長度後面的元素。

 


說明:

為什麼返回數值是整數,但輸出的答案是陣列呢?

請注意,輸入陣列是以“引用”方式傳遞的,這意味著在函式裡修改輸入陣列對於呼叫者是可見的。

你可以想象內部操作如下:

 

// nums 是以“引用”方式傳遞的。也就是說,不對實參做任何拷貝
int len = removeDuplicates(nums);

// 在函式裡修改輸入陣列對於呼叫者是可見的。
// 根據你的函式返回的長度, 它會打印出陣列中該長度範圍內的所有元素。
for (int i = 0; i < len; i++) {
    print(nums[i]);
}

 

 

 

我第一次自寫的程式碼(因為審題不認真,寫成了無序陣列的刪除QAQ---):

用時300ms.

 

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        vector<int> my_nums;
        bool same = 0;
        bool first = 1;
        for (auto num1 : nums) {
            if (first) {
                my_nums.emplace_back(num1);
                first = 0;
                continue;
            }
                for (auto num2 : my_nums) {
                    if (num1 == num2) {
                        same = 1;
                        break;
                    }

                }
                if (!same) my_nums.emplace_back(num1);
                same = 0;

        }
        for (int i = 0; i < my_nums.size(); i++) {
            nums[i] = my_nums[i];
        }
        return my_nums.size();
    }
};

 

大佬們寫的示例程式碼():

用時30ms左右:

 

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int length = nums.size();
        if(length == 0)
            return 0;
        int flag = 0;
        for(int i = 1; i < length; i++){
            while(nums[i] == nums[flag]){
                if(i < length-1)
                    i++;
                else
                    break;
            }
                flag++;
                nums[flag] = nums[i];
        }
        return (flag + 1);
    }
};

 

用時20左右ms

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        
        int n = nums.size();
        if(n == 0 || n == 1)
            return n;
        int index=0;
        int i = 1;
        while(i<n)
        {
            if(nums[index] != nums[i])
            {
                index++;
                nums[index] = nums[i];
            }
            i++;
        }
        return index+1;
    }
};

 

 

自我反思:因為題目看錯導致跟大佬們的程式碼對比反思不多所以就總結兩點

  1.  剛開始刷LeetCode沒有認真對待,沒有審題清楚。
  2.    即使是寫成無序陣列的刪除還是沒有考慮到傳入引數為0和1的情況。 

 

 

二、買賣股票的最佳時機 II

 

給定一個數組,它的第 i 個元素是一支給定股票第 i 天的價格。

設計一個演算法來計算你所能獲取的最大利潤。你可以儘可能地完成更多的交易(多次買賣一支股票)。

注意:你不能同時參與多筆交易(你必須在再次購買前出售掉之前的股票)。

示例 1:

輸入: [7,1,5,3,6,4]
輸出: 7
解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 3 天(股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。
     隨後,在第 4 天(股票價格 = 3)的時候買入,在第 5 天(股票價格 = 6)的時候賣出, 這筆交易所能獲得利潤 = 6-3 = 3 。

示例 2:

輸入: [1,2,3,4,5]
輸出: 4
解釋: 在第 1 天(股票價格 = 1)的時候買入,在第 5 天 (股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5-1 = 4 。
     注意你不能在第 1 天和第 2 天接連購買股票,之後再將它們賣出。
     因為這樣屬於同時參與了多筆交易,你必須在再次購買前出售掉之前的股票。

示例 3:

輸入: [7,6,4,3,1]
輸出: 0
解釋: 在這種情況下, 沒有交易完成, 所以最大利潤為 0。

 

我的程式碼:

12ms左右

class Solution {
public:
    int maxProfit(vector<int>& prices) {
    int sell;
    int sell_value = 0;
    int buy_value = 0;
    int profit = 0;
    int len = prices.size();
    if (len < 2) return 0;
    int i = 0;
    int index;

    while (i < len) {
        index = i + 1;
        if (index < len) {
            if (prices[i] < prices[index]) {
                buy_value = prices[i];
                sell_value = prices[index];
                sell = index;
                while (++index < len) {
                    if (prices[sell] > prices[index]) {
                        profit += sell_value - buy_value;
                        i = index - 1;
                        buy_value = sell_value = 0;
                        break;
                    }
                    else {
                        sell = index;
                        sell_value = prices[index];
                    }
                }
            }
        }
        if (index == len)break;
        i++;
    }
    profit += sell_value - buy_value;
    return profit;
}
};

先講講我一開始看到這道題目是怎麼分析的,從題意上看:

  1. 陣列從大到小例如[5,4,3,2,1]的就直接沒有利潤,利潤置為0;
  2. 陣列從小到大的直接返回最後的減去最前的就是利潤;
  3. 陣列無序的,找到第一個滿足[i]<[index=i+1]的,i就是我們的買入點,index就先設定為賣的點;
  4. 然後從index往後遍歷,只要是找到小於index這個陣列值的數的座標就可以作為下一個買入點,到這裡先計算第一個買入點和賣出點的利潤,然後將下一個買入點座標賦值給i,這樣就變成了3步驟的重複,後面如果是從小到大或者大到小又變成計算1,2步驟
  5. 利潤profit從每個步驟中都加等起來,得到最終利潤

 

然後看看大佬們的程式碼:

1ms左右

class Solution {
public:
    int maxProfit(vector<int>& prices) 
    {
        if (prices.size()<2)
            return 0;
        int profit=0;
        for (int i=0;i<prices.size()-1;i++)
        {
            if (prices[i]<prices[i+1])
                profit+=(prices[i+1]-prices[i]);
        }
        return profit;
    }
};

  

  行吧,看到這個程式碼的時候我就知道我是個傻子了,演算法題說白了是什麼?不就是數學題嗎,數學題考什麼?考邏輯思維唄!!!(我根本沒有抓住這題的本質QAQ)

大佬們都是從數學上直接看這題的本質,不管我這個股票怎麼買賣,只要有買入點和賣出點,那麼最終的利潤都可以靠後一個個大的值減去鄰近的前一個小的值不斷的累加起來得到。

比如[1,7,8,9],買入1在9賣出利潤8,也可以等於 9-8加8-7加7-1得到利潤值,不管是[1,2,3,4,5]還是[5,4,3,2,1]或者無序的[7,1,5,3,6,4]都可以這樣累加差值得到利潤。

 

自我反思:emmmmm.....沒什麼好反思的,我就是個菜雞和傻子。。。。。。

 

 

三、旋轉陣列

 

給定一個數組,將陣列中的元素向右移動 個位置,其中 是非負數。

示例 1:

輸入: [1,2,3,4,5,6,7]k = 3
輸出: [5,6,7,1,2,3,4]
解釋:
向右旋轉 1 步: [7,1,2,3,4,5,6]
向右旋轉 2 步: [6,7,1,2,3,4,5]
向右旋轉 3 步: 
[5,6,7,1,2,3,4]

示例 2:

輸入: [-1,-100,3,99]k = 2
輸出: [3,99,-1,-100]
解釋: 

說明:

  • 儘可能想出更多的解決方案,至少有三種不同的方法可以解決這個問題。
  • 要求使用空間複雜度為 O(1) 的原地演算法。

我的程式碼:

30ms左右

class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        vector<int> temp;
        int len = nums.size();
        int pos;
        for(int i =0; i < len; i++){
            pos = (2*len + i - k) % len;
            temp.emplace_back(nums[pos]);
        }
        pos = -1;
        while(++pos<len)nums[pos] = temp[pos];
    }
};

 

 大佬的程式碼:

好吧這次沒有大佬的程式碼,因為那個座標圖上12ms大佬的程式碼實在點不出來啊,太小了,我的滑鼠移動了半天都點不出來,MMP這leetcode前端程式設計師該不會是個傻Z吧  天哪嚕!!!!

 

 

自我反思:題目要求用O(1)的原地演算法,而我的是O(n),恩,我是個菜雞,天哪好想看看12ms那個O(1)怎麼寫。。。。哪位大哥好心幫我點出來評論在我部落格下面唄。。。。難受QAQ

 

四、存在重複

 

給定一個整數陣列,判斷是否存在重複元素。

如果任何值在陣列中出現至少兩次,函式返回 true。如果陣列中每個元素都不相同,則返回 false。

示例 1:

輸入: [1,2,3,1]
輸出: true

示例 2:

輸入: [1,2,3,4]
輸出: false

示例 3:

輸入: [1,1,1,3,3,4,3,2,4,2]
輸出: true

我的程式碼:
第一次看到這個題目的時候,第一時間想到的就是常規的兩個for迴圈遍歷。。。。。然後就超時了,果然沒有這麼簡單。只好用map鍵值對的方式來完成這個題目了

44ms左右:

 

class Solution {
public:
    bool containsDuplicate(vector<int>& nums) {
        unordered_map<int, int> map;
        if (nums.size() < 2) return false;
        for (auto nm : nums) {
            map[nm]++;
        }
        for (auto mp : map) {
            if (mp.second > 1)return true;
        }
        return false;
    }

};

 

 

 

36ms左右:

class Solution {
public:
    bool containsDuplicate(vector<int>& nums) {
        unordered_map<int,string> map;
        for(auto nm:nums){
            if(map.find(nm) != map.end()) return true;
            map.insert(pair<int,string>(nm,"1"));
        }
        return false;
    }

};

 

  上面兩種其實只是運用場景不同,

  第一種:全部存入後,再去看哪一個存入次數大於1次。適用於重複數在中間或者很後面。

  第二種:每次存入時都判斷是否已經有這個數字存入了,也就是每次都查詢一次。適用於重複數在比較前的場景。

  綜合的話還是覺得第一種更好。

 

 

大佬們的程式碼:

20ms左右

const static auto io_speed_up = []() {
    std::ios::sync_with_stdio(false);
    cin.tie(0);
    return 0;
}();
class Solution {
public:
    bool containsDuplicate(vector<int>& nums) {
        if(nums.size()==1)return false;
        else {
            sort(nums.begin(),nums.end());
            int size=nums.size();
            for(int i=0;i<size-1;i++){
                if(nums[i]==nums[i+1])return true;
            }
            return false;
        }
    }
};

看到這個程式碼的時候我都懵了,天哪你們還可以用標準庫自帶的sort排序。。。。。我服了!!!

然後還加了一個std::ios::sync_with_stdio(false);關閉緩衝

以及cin.tie(0);在預設的情況下cin繫結的是cout,每次執行 << 操作符的時候都要呼叫flush,這樣會增加IO負擔。可以通過tie(0)(0表示NULL)來解除cin與cout的繫結,進一步加快執行效率。這個有刷ACM的話就比較常見。

 

自我反思:果然我還是太單純了,不夠狡猾!!!

 

五、只出現一次的數字

 

給定一個非空整數陣列,除了某個元素只出現一次以外,其餘每個元素均出現兩次。找出那個只出現了一次的元素。

說明:

你的演算法應該具有線性時間複雜度。 你可以不使用額外空間來實現嗎?

示例 1:

輸入: [2,2,1]
輸出: 1

示例 2:

輸入: [4,1,2,1,2]
輸出: 4

 

我的程式碼:

48ms左右

 

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int len = nums.size();
        if(len == 1) return nums[0];
        if(len == 0) return -8989;
        sort(nums.begin(),nums.end());
        for(int i = 0; i < len ; i++){
            if(i == 0){
                if(nums[i] != nums[i + 1])return nums[i];
            } 
            else if(i == len - 1){
                if(nums[i] != nums[i - 1])return nums[i];
            }
            else if(nums[i] != nums[i - 1] && nums[i] != nums[i+1]) return nums[i];
        }
        return -8989;
    }
};

 

這次我看到這次就想到上一題,先排序好在進行常規遍歷對比,沒什麼好說的,大部分人沒有經常刷演算法題的都是我這個思維。

 

 

看看大佬們的程式碼:

20ms左右

 

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        //求vector的最大值和最小值
        int l = *min_element(nums.begin(), nums.end()), h = *max_element(nums.begin(), nums.end()); 
        
        int mid, count; 
        while(l <= h){
            count = 0;
            mid = l + (h - l) / 2;
            for(int i = 0; i < nums.size(); i ++){
                if(nums[i] <= mid)
                    count++;
            }
            if(count % 2 == 0)
                l = mid + 1;
            else
                h = mid - 1;
        }
        return l;
    }
};

 

這位大佬數學應用的不錯啊,直接就用二分計數的方法加上類似於遞迴的形式,不斷的確定出那個單數在哪一個值的區間,然後將其返回出來

 

8ms左右

 

 

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        //一個數異或一遍即可
        int res = 0;
        for (int i = 0; i < nums.size(); i++){
            res ^= nums[i];
        }
        return res;
    }
};

 

這位大佬就讓我感覺到了,他這程式設計基礎肯定無比的紮實,直接就用異或這個辦法一次遍歷得出答案。

異或就是隻有在兩個比較的位不同時其結果是1,否則結果為0。兩個數相同的時候就是0了,而這組數裡只有一個數字是單獨出現的,全部異或完一遍剩下的那個數就是結果

 

 

 

自我反思:對於程式設計這種事,數學跟程式設計知識基礎都是不可或缺的,像小小的位運算子有時候都會幫助我們剩下很多多餘的操作。

 

六、兩個陣列的交集 II

給定兩個陣列,編寫一個函式來計算它們的交集。

示例 1:

輸入: nums1 = [1,2,2,1], nums2 = [2,2]
輸出: [2,2]

示例 2:

輸入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
輸出: [4,9]

說明:

  • 輸出結果中每個元素出現的次數,應與元素在兩個陣列中出現的次數一致。
  • 我們可以不考慮輸出結果的順序。

進階:

  • 如果給定的陣列已經排好序呢?你將如何優化你的演算法?
  • 如果 nums1 的大小比 nums2 小很多,哪種方法更優?
  • 如果 nums2 的元素儲存在磁碟上,磁碟記憶體是有限的,並且你不能一次載入所有的元素到記憶體中,你該怎麼辦?

我的程式碼:

16ms左右:

class Solution {
public:
    vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
        vector<int> copy_nums1;
        vector<int> copy_nums2;
        vector<int> res;
        int len1 = nums1.size();
        int len2 = nums2.size();
        if(len1 == 0 || len2 == 0) return res;
        copy_nums1.assign(nums1.begin(),nums1.end());
        copy_nums2.assign(nums2.begin(),nums2.end());
        for(int i = 0; i < len1; i++){
            for(int j = 0; j < len2; j++){
                if(copy_nums1[i] == copy_nums2[j]){
                    res.emplace_back(copy_nums1[i]);
                    copy_nums2.erase(copy_nums2.begin()+j);
                    len2--;
                    break;
                }
            }
        }
        return res;
    }
};

  剛開始看到這個題目的時候我只想出了兩個辦法,第一個就是上面這個---複製兩個陣列之後可以判斷兩個陣列,小的那個放在外迴圈,當然這裡沒有加這個判斷,因為那是進階的內容了。

  一個外迴圈陣列第二個數組裡面查詢,找到之後存入res,為了避免二次利用那個數,直接將第二個陣列數(反正也是拷貝的陣列,可以直接在上面進行操作)刪除,最後返回的就是兩個陣列的交集了

 

 

大佬們的程式碼:

8ms左右:

class Solution {
public:
    vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
        unordered_map<int,int>m;
        vector<int>res;
        for(auto a:nums1)++m[a];
        for(auto a:nums2)
        {
            if(m[a]-->0)res.push_back(a);
        }
        return res;
    }
};

這就是我第二個想法,用map來儲存,不過呢,他是用unordered_map底層是hash,查詢方面速度會有一些優勢。

 

大佬們程式碼:

1ms左右:

class Solution {
public:
    vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
       vector<int>res;
        sort(nums1.begin(),nums1.end());
        sort(nums2.begin(),nums2.end());
        int i=0,j=0;
        while(i<nums1.size()&&j<nums2.size())
        {
            if(nums1[i]==nums2[j])
            {
                res.push_back(nums1[i]);
                ++i;++j;
            }
            else if(nums1[i]<nums2[j])
            {
                i++;
            }
            else
                j++;
        }
        return res;
    }
};

  這個就是進階裡面將陣列排序之後在去尋找兩個陣列的交集,這裡兩個陣列排序完之後沒有什麼好探討的,就是使用類似雙指標的形式去不斷遍歷兩個陣列找到交集儲存

 

自我反思:進階裡面的三沒有怎麼去思考,不過我想無非就是分段讀取吧。

 

七、加一

 

給定一個由整陣列成的非空陣列所表示的非負整數,在該數的基礎上加一。

最高位數字存放在陣列的首位, 陣列中每個元素只儲存一個數字。

你可以假設除了整數 0 之外,這個整數不會以零開頭。

示例 1:

輸入: [1,2,3]
輸出: [1,2,4]
解釋:

示例 2:

輸入: [4,3,2,1]
輸出: [4,3,2,2]
解釋:

 

 

我的程式碼:

8ms左右:

class Solution {
public:
    vector<int> plusOne(vector<int>& digits) {
    const int size = digits.size();
    for (int i = size - 1; i >= 0; i--){
        digits[i]++;
        digits[i] %= 10;
        if (digits[i] != 0) return digits;
    }
    reverse(digits.begin(), digits.end());
    digits.emplace_back(1);
    reverse(digits.begin(), digits.end());
    return digits;
}
};

  這題無非就是兩種情況一種是末尾加一不會大於10的,就直接末尾加一返回陣列就行了。

還有一種就是999,或者9999這種全是9的情況,加一的話要不斷的進位,那麼問題就轉化成了如何在這個陣列前面加一個1上去。

所以我這裡用逆序補1後在逆序回來。

 

大佬們的程式碼:

1ms左右:

class Solution {
public:
    vector<int> plusOne(vector<int>& digits) {
    const int size = digits.size();
    for (int i = size - 1; i >= 0; i--)
    {
        digits[i]++;
        digits[i] %= 10;
        if (digits[i] != 0) return digits;
    }
    digits.resize(size + 1);
    digits[0] = 1;
    return digits;
    }
};

  大佬們比我分析的好,我沒有想到這題陣列值全是0的情況恰好可以直接使用resize來改變記憶體,resize改變後的陣列預設全部初始化為0,恰好這題就是,只要把第一個0改成1就行了。

 

自我反思:對STL容器的各種方法和使用場景還沒有很好的掌握。

 

八、移動零

 

給定一個數組 nums,編寫一個函式將所有 0 移動到陣列的末尾,同時保持非零元素的相對順序。

示例:

輸入: [0,1,0,3,12]
輸出: [1,3,12,0,0]

說明:

  1. 必須在原陣列上操作,不能拷貝額外的陣列。
  2. 儘量減少操作次數。

 

我的程式碼:

56ms左右

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int len = nums.size();
        for(int i = 0; i < len; i++){
            if(nums[i] == 0){
                nums.erase(nums.begin() + i);
                nums.emplace_back(0);
                len--;
                i--;
            }
        } 
    }
};

  我的思路就是遇到0就將其刪除然後補充在最後,這樣的邏輯雖然簡單,但是依靠vector函式來操作的話內部其實要做許許多多的事,導致時間很慢。

 

大佬們的程式碼:

16ms左右:

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int j = 0;//快慢指標
        for(int i = 0;i < nums.size();i++){
           while(nums[j] != 0 && j < nums.size()-1){
                    j++;
                }
            if(nums[i] != 0 && i > j){
                nums[j] = nums[i];
                nums[i] = 0;
            }
            
        }
    }
};

  大佬的思路就是快慢指標,一個指標也就是for迴圈正常遍歷,而另外一個指標就不斷的先找到0的位置停止下來,等到for迴圈到了0後面的位置並且是有效值,就將他們交換。

  說白了就是將0後最近的非0數與其交換,不過這樣的換法,從全域性上看就是不斷的把每個0往末尾移動,移動的次數非常多。

 

8ms左右:

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int len = nums.size();
        int i = -1,j = 0;
        while(j < len)
        {
            if(nums[j] != 0)
            {
                i += 1;
                nums[i] = nums[j];
            }
            j += 1;
        }
        i += 1;
        while(i < len)
        {
            nums[i] = 0;
            i++;
        }
    }
};

  這個程式碼就跟16ms那個程式碼不一樣的思路,不是將0不斷的往後搬了。而是將有效的數字都提到前面來,然後用i記住有效數字的最後一位在哪,之後空間的全部一次性置為0。

 

 

 九、兩數之和

給定一個整數陣列 nums 和一個目標值 target,請你在該陣列中找出和為目標值的那 兩個 整數,並返回他們的陣列下標。

你可以假設每種輸入只會對應一個答案。但是,你不能重複利用這個陣列中同樣的元素。

示例:

給定 nums = [2, 7, 11, 15], target = 9

因為 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

我的程式碼:

兩種方法,一種常規遍歷,一種hash儲存

44ms左右

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        int len = nums.size();
        vector<int> res;
        for(int i = 0; i < len; i++){
            for(int j = i + 1; j < len; j++){
                if(nums[i] + nums[j] == target){
                    res.emplace_back(i);
                    res.emplace_back(j);
                    return res;
                }
            }
        }
        return res;
    }
};

  上面這種就是常規的遍歷,沒什麼好說的。

 

4ms左右

vector<int> twoSum(vector<int>& nums, int target) {
    unordered_map<int, int> m;
    vector<int> temp;
    int len = nums.size();
    for (int i = 0; i < len; ++i) {
        m[nums[i]] = i;
    }
    for (int i = 0; i < len; ++i) {
        if (m.count(target - nums[i]) && m[target - nums[i]] != i) {
            temp.emplace_back(i);
            temp.emplace_back(m[target - nums[i]]);
            return tem;
        }

    }
    return tem;
}

  這裡還是用unordered_map容器來儲存查詢,因為要返回的是下標。所以先將所有的值作為鍵,下標作為值存入容器。然後就是遍歷,因為兩數之和為target,那麼已經知道一個值為Nums[i],那另外一個值就是target - nums[i]。

    m[target - nums[i]] != i 加上這句就是為了防止自己找到自己 比如 【3,2,7】target = 6 ,這裡的話不加判斷就會返回自己3的下標兩次【0,0】

  然後這裡使用unodered_map而不是map,因為本題不需要存入有序序列,並且本題有頻繁的查詢工作,unodered_map底層為hash查詢比紅黑樹更快。

 

 

 

 十、有效的數獨

判斷一個 9x9 的數獨是否有效。只需要根據以下規則,驗證已經填入的數字是否有效即可。

  1. 數字 1-9 在每一行只能出現一次。
  2. 數字 1-9 在每一列只能出現一次。
  3. 數字 1-9 在每一個以粗實線分隔的 3x3 宮內只能出現一次。

上圖是一個部分填充的有效的數獨。

數獨部分空格內已填入了數字,空白格用 '.' 表示。

示例 1:

輸入:
[
  ["5","3",".",".","7",".",".",".","."],
  ["6",".",".","1","9","5",".",".","."],
  [".","9","8",".",".",".",".","6","."],
  ["8",".",".",".","6",".",".",".","3"],
  ["4",".",".","8",".","3",".",".","1"],
  ["7",".",".",".","2",".",".",".","6"],
  [".","6",".",".",".",".","2","8","."],
  [".",".",".","4","1","9",".",".","5"],
  [".",".",".",".","8",".",".","7","9"]
]
輸出: true

示例 2:

輸入:
[
  ["8","3",".",".","7",".",".",".","."],
  ["6",".",".","1","9","5",".",".","."],
  [".","9","8",".",".",".",".","6","."],
  ["8",".",".",".","6",".",".",".","3"],
  ["4",".",".","8",".","3",".",".","1"],
  ["7",".",".",".","2",".",".",".","6"],
  [".","6",".",".",".",".","2","8","."],
  [".",".",".","4","1","9",".",".","5"],
  [".",".",".",".","8",".",".","7","9"]
]
輸出: false
解釋: 除了第一行的第一個數字從 5 改為 8 以外,空格內其他數字均與 示例1 相同。
     但由於位於左上角的 3x3 宮內有兩個 8 存在, 因此這個數獨是無效的。

說明:

  • 一個有效的數獨(部分已被填充)不一定是可解的。
  • 只需要根據以上規則,驗證已經填入的數字是否有效即可。
  • 給定數獨序列只包含數字 1-9 和字元 '.' 。
  • 給定數獨永遠是 9x9 形式的。

 

我的程式碼:

32ms左右

class Solution {
public:
    bool containsDuplicate(vector<char>& nums) {
    if (nums.size() < 2) return false;
    unordered_map<char, int> map;
    for (auto nm : nums) {
        map[nm]++;
    }
    for (auto mp : map) {
        if (mp.second > 1)return true;
    }
    return false;
}

bool isValidSudoku(vector<vector<char>>& board) {
    const int bufsize = 2;
    vector<vector<char> > raw_col = { { '0' },{ '0' } };
    vector<vector<char> > three_temp = { { '0' },{ '0' },{ '0' } };

    for (int i = 0; i < board.size(); i++) {
        for (int j = 0; j < board[0].size(); j++) {
            if (board[i][j] != '.') {
                raw_col[0].emplace_back(board[i][j]);

                if (j <3) three_temp[0].emplace_back(board[i][j]);
                else if (j < 6) three_temp[1].emplace_back(board[i][j]);
                else three_temp[2].emplace_back(board[i][j]);
            }

        }
        if (containsDuplicate(raw_col[0])) return false;
        raw_col[0].clear();
        if ((i + 1) % 3 == 0) {
            for (int k = 0; k < three_temp.size(); k++){
                    if (containsDuplicate(three_temp[k])) return false;
                    three_temp[k].clear();
            }
        }
    }
    for (int j = 0; j < board[0].size(); j++) {
        for (int i = 0; i < board.size(); i++) {
            if (board[i][j] != '.') raw_col[1].emplace_back(board[i][j]);
        }
        if (containsDuplicate(raw_col[1])) return false;
        raw_col[1].clear();
    }
    return true;

}
};

  我的思路很簡單,就是分別計算每一行每一列是否有重複,然後對於,判斷每個九宮格是否有重複我則是採用一個二維陣列來儲存,看圖可以劃分為9個九宮格,我每次儲存橫向的三個九宮格然後進行判斷裡面是否出現重複。

  然後跳到下三個個橫向九宮格的時候,將陣列清空在次存入判斷

 

 

大佬們的程式碼:

12ms左右

class Solution {
public:
    bool isValidSudoku(vector<vector<char>>& board) {
        int col[10][10], line[10][10], sq[10][10][10];
        
        memset(col,0,sizeof(col));
        memset(line,0,sizeof(line));
        memset(sq,0,sizeof(sq));
        
        for(int i=0;i<9;i++){//i -> line
            for(int j=0;j<9;j++){ // j->col
                if(board[i][j]=='.') continue;
                int val=board[i][j]-'0';
                // if(val<=0 || val>9) return false;
                if(col[j][val] || line[i][val] || sq[i/3][j/3][val]) return false;
                col[j][val]=line[i][val]=sq[i/3][j/3][val]=1;
            }
        }
        return true;
    }
};

  大佬則是採用建立三個標記空間的形式,把所有的資料用點亮標記的形式來判斷,比如if(col[j][val])就是判斷j列裡val是否已經點亮為1,如果已經為1那麼說明這列裡面已經存在一個val值,那麼就重複了。行也是這樣類推。

  然後就是判斷九個九宮格內是否重複,也是將九宮格劃分好,比如sq[i/3][j/3][val]=1;這就是第i行的第j個九宮格中的val元素點亮置為1.。i除以3是因為i值為0-3之間就是第一行的某個九宮格,3-6則是第二行的某個九宮格。。。。j的意思也這樣理解

 

 

4ms左右

class Solution
{
public:
    bool isValidSudoku(vector<vector<char>> &board)
    {
        char k;
        int num;
        bitset<9> hang;
        vector<bitset<9>> lie(9, hang), box(9, hang);
        for (int i = 0; i < 9; ++i) //行
        {
            for (int j = 0; j < 9; ++j) //列
            {
                k = board[i][j];
                if (k != '.')
                {
                    num = k - '0' - 1;
                    if (hang[num] && lie[j][num] && box[i / 3 * 3 + j / 3][num])
                        return 0;else
              hang[num] = lie[j][num] = box[i / 3 * 3 + j / 3][num] = 1;
                }
            }
            hang.reset();
        }
        return 1;
    }
};

  這個大佬應該是有刷過ACM之類的,跟上一個12ms的思路其實差不多,就是對記憶體各方面做了優化,用bitset來儲存資料,bitset就像一個bool型別的陣列一樣,但是有空間優化——bitset中的一個元素一般只佔1 bit,相當於一個char元素所佔空間的八分之一。

 

 

十一、旋轉影象

給定一個 × n 的二維矩陣表示一個影象。

將影象順時針旋轉 90 度。

說明:

你必須在原地旋轉影象,這意味著你需要直接修改輸入的二維矩陣。請不要使用另一個矩陣來旋轉影象。

示例 1:

給定 matrix = 
[
  [1,2,3],
  [4,5,6],
  [7,8,9]
],

原地旋轉輸入矩陣,使其變為:
[
  [7,4,1],
  [8,5,2],
  [9,6,3]
]

示例 2:

給定 matrix =
[
  [ 5, 1, 9,11],
  [ 2, 4, 8,10],
  [13, 3, 6, 7],
  [15,14,12,16]
], 

原地旋轉輸入矩陣,使其變為:
[
  [15,13, 2, 5],
  [14, 3, 4, 1],
  [12, 6, 8, 9],
  [16, 7,10,11]
]

8ms左右:
class Solution {
public:
    void rotate(vector<vector<int>>& matrix) {
        int length = matrix.size(); // 調換對角元素 
        for (int i = 0; i < length; i++) {
            for (int j = 0; j < length - i; j++) {
                int tmp = matrix[i][j];
                matrix[i][j] = matrix[length - j - 1][length - i - 1];
                matrix[length - j - 1][length - i - 1] = tmp;
            }
        } // 調換列元素 
        for (int i = 0; i < length; i++) {
            for (int j = 0; j < length / 2; j++) {
                int tmp = matrix[j][i];
                matrix[j][i] = matrix[length - j - 1][i];//這裡用異或來調換兩個元素也行,自己喜歡
                matrix[length - j - 1][i] = tmp;
            }
        }
    }
};

 

本來是想直接用影象旋轉的原理公式,然是因為題目要求了只能在原陣列中做旋轉操作,那麼就只能看看在元素調換的方面有沒有什麼規律。

調換所有(/)左下到右上對角線的對角元素

之前:

[
  [1,2,3],
  [4,5,6],
  [7,8,9]
],


之後:

[
  [9,6,3],
  [8,5,2],
  [7,4,1]
],


逆序所有列元素
之前:

[
  [9,6,3],
  [8,5,2],
  [7,4,1]
],


之後:

[
  [7,4,1],
  [8,5,2],
  [9,6,3]
],

 

同理如果要調換所有(\)左上到右下對角線的對角元素,那麼就要逆序所有行元素



也可以看看我的其他面試題總結:

c++面試題中經常被面試官面試的小問題總結(一)(本篇偏向基礎知識)