1. 程式人生 > >程式設計師進階之演算法練習:LeetCode專場

程式設計師進階之演算法練習:LeetCode專場

歡迎大家前往騰訊雲+社群,獲取更多騰訊海量技術實踐乾貨哦~

本文由落影發表

前言

LeetCode上的題目是大公司面試常見的演算法題,今天的目標是拿下5道演算法題: 題目1是基於連結串列的大數加法,既考察基本資料結構的瞭解,又考察在處理加法過程中的邊界處理; 題目2是求陣列出現頻率前k大的數字,考察思維能力,程式碼很短; 題目3是給出從兩個陣列中選擇數字,組成一個最大的數字,考察的是貪心的思想; 前三個都偏向於考察想法,實現的程式碼都比較簡單; 題目4、5是資料結構實現題,也是大部分人比較頭疼的題目,因為需要較多的資料結構和STL實現,並且還有時間和空間的限制。

正文

1、Add Two Numbers II

題目連結 題目大意

給倆個連結串列,節點由0~9的數字組成,分別表示兩個數字; 求出兩個數字的和,以連結串列的形式返回。

例如
Input: (7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)

7243 + 564 =7807

Output: 7 -> 8 -> 0 -> 7

題目解析: 題目的意思很明顯,就是把兩個數字加起來,需要考慮進位的情況。 因為是單向的連結串列,遍歷後很難回溯,所以先把數字存到vec中。 並且為了處理方便,vec的最低位存在vec的起始部分。 於是從0開始遍歷兩個vec即可,注意考慮最後進位的情況。

複雜度解析: 時間複雜度是O(N) 空間複雜度是O(N)

struct ListNode {
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode *ret = NULL;
        vector<int> vec1, vec2;
        sum(l1, vec1);
        sum(l2, vec2);
        int n = vec1.size(), m = vec2.size(), flag = 0;
        for (int i = 0; i < n || i < m; ++i) {
            int x = 0, y = 0;
            if (i < n) {
                x = vec1[i];
            }
            if (i < m) {
                y = vec2[i];
            }
            int s = x + y + flag;
            if (s > 9) {
                s -= 10;
                flag = 1;
            }
            else {
                flag = 0;
            }
            ListNode *tmp = new ListNode(s);
            tmp->next = ret;
            ret = tmp;
        }
        if (flag) {
            ListNode *tmp = new ListNode(1);
            tmp->next = ret;
            ret = tmp;
        }
        return ret;
    }
    
    void sum(ListNode* list, vector<int> &vec) {
        if (list->next) {
            sum(list->next, vec);
        }
        vec.push_back(list->val);
    }
};

2.Top K Frequent Elements

題目連結 題目大意

給出一個數組和一個數字k,返回按數字出現頻率的前k個的數字; 1 <= k <= n, n是陣列大小;

 example,
 Given [1,1,1,2,2,3] and k = 2, return [1,2].

題目解析:

題目分為兩個步驟: 1、統計每個數字的出現次數; 2、從中選擇k個次數最多的數字;

一個簡單的做法: 用雜湊表統計每個數字的出現次數; 把每個數字的出現次數和數字組成一個pair,放入優先佇列;

這樣從優先佇列中取出k個即可。

複雜度解析: 時間複雜度是O(NlogN),主要在最後的優先佇列。

其他解法: 有一個O(NlogK)的優化; 首先把佇列變成最小有限佇列, 每次pair放入優先對時,如果當前的size大於k,那麼彈出top; 這樣每次的操作從O(logN)變成O(logK)。

class Solution {
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
        unordered_map<int, int> numsHash;
        for (int i = 0; i < nums.size(); ++i) {
            ++numsHash[nums[i]];
        }
        priority_queue<pair<int, int>> q;
        for (int i = 0; i < nums.size(); ++i) {
            if(numsHash[nums[i]]) {
                q.push(make_pair(numsHash[nums[i]], nums[i]));
                numsHash[nums[i]] = 0;
            }
        }
        vector<int> ret;
        for (int i = 0; i < k; ++i) {
            ret.push_back(q.top().second);
            q.pop();
        }
        return ret;
    }
}leetcode;

3、create-maximum-number

題目連結 題目大意: 給出兩個陣列,陣列只包括0~9十個數字,長度分別為n、m; 從兩個陣列中選出k個數,組成一個長度為k的數字,要求: 1、從陣列n、m選擇出來的數字相對位置不變; 2、最後的數字最大; 輸出最後的數字。

 Example 1:
 nums1 = [3, 4, 6, 5]
 nums2 = [9, 1, 2, 5, 8, 3]
 k = 5
 return [9, 8, 6, 5, 3]
 
 Example 2:
 nums1 = [6, 7]
 nums2 = [6, 0, 4]
 k = 5
 return [6, 7, 6, 0, 4]

題目解析:

要求最後數字最大,那麼儘可能把數字大的排在前面; 在都合法的前提下,99* 肯定比 98*要大; 那麼可以按照這樣的貪心策略: 先列舉t,t表示從陣列nums1中選出t個數字,那麼陣列nums2中應該選出k-t個數字; 兩個陣列的所有數字組成最大的數字,因為兩個陣列間的數字是可以任意順序,那麼只需每次選擇較大的放在前面即可。

問題簡化成,O(N)每次從陣列中選出t個最大的數字; 這個可以用貪心解決: 假設陣列當前列舉到第i個,且nums[i]=x; 從左到右遍歷已經選擇的數,當遇到一個數字t,t<x時,判斷插入x後,後續是否存在合法解;如果存在則替換,否則直到最後,插入尾部;

class Solution {
public:
    vector<int> maxNumber(vector<int>& nums1, vector<int>& nums2, int k) {
        int n = (int)nums1.size(), m = (int)nums2.size();
        vector<int> ret(k, 0);
        for (int i = max(0, k - m); i <= k && i <= n; ++i) {
            vector<int> tmp1 = maxArray(nums1, i);
            vector<int> tmp2 = maxArray(nums2, k - i);
            vector<int> tmp = merge(tmp1, tmp2, k);
            if (greater(tmp, 0, ret, 0)) {
                ret = tmp;
            }
        }
        return ret;
    }
    
    vector<int> maxArray(vector<int> &nums, int k) {
        int n = (int)nums.size();
        vector<int> ret(k, 0);
        for (int i = 0, j = 0; i < n; ++i) {
            while (n - i + j > k && j > 0 && ret[j - 1] < nums[i]) {
                --j;
            }
            if (j < k) {
                ret[j++] = nums[i];
            }
        }
        return ret;
    }
    
    vector<int> merge(vector<int>& nums1, vector<int>& nums2, int k) {
        vector<int> ret(k, 0);
        for (int i = 0, j = 0, r = 0; r < k; ++r) {
            ret[r] = greater(nums1, i, nums2, j) ? nums1[i++] : nums2[j++];
        }
        return ret;
    }
    
    bool greater(vector<int> &nums1, int i, vector<int> &nums2, int j) {
        while (i < nums1.size() && j < nums2.size() && nums1[i] == nums2[j]) {
            ++i;
            ++j;
        }
        return j == nums2.size() || (i < nums1.size() && nums1[i] > nums2[j]);
    }
};

4、 Insert Delete GetRandom O(1) - Duplicates allowed

題目連結 題目大意: 實現一個數據結構,包括以下三個方法: 1、insert(val): 插入一個數字; 2、remove(val): 移除一個數字; 3、getRandom: O(1)隨機返回一個數字;

 Example
 插入數字1;
 collection.insert(1);
 插入數字1:
 collection.insert(1);
 插入數字2
 collection.insert(2);
 隨機返回數字,要求 2/3可能返回1, 1/3可能返回2;
 collection.getRandom();

題目解析:

插入和移除數字不麻煩,考慮如何在O(1)時間返回一個數字。 容易知道,放在數組裡面可以,然後隨機返回一個位置可以實現。 增加可以在陣列最末端增加; 刪除陣列中間某個數字時,可以把最末端的數字放到刪除的位置上;

現在的問題是,如何快速找到陣列中該刪除的某個位置; 考慮用hash來實現。 陣列就是vector<pair<int, int> >; first存val,second存出現次數; 再用一個雜湊map,unordered_map<int, vector

class RandomizedCollection {
public:
    /** Initialize your data structure here. */
    RandomizedCollection() {
        
    }
    
    /** Inserts a value to the collection. Returns true if the collection did not already contain the specified element. */
    bool insert(int val) {
        bool ret = hashMap.find(val) == hashMap.end();
        hashMap[val].push_back(randVec.size());
        randVec.push_back(make_pair(val, hashMap[val].size() - 1));
        return ret;
    }
    
    /** Removes a value from the collection. Returns true if the collection contained the specified element. */
    bool remove(int val) {
        bool ret = hashMap.find(val) != hashMap.end();
        if (ret) {
            auto last = randVec.back();
            hashMap[last.first][last.second] = hashMap[val].back();
            randVec[hashMap[val].back()] = last;
            hashMap[val].pop_back();
            if (hashMap[val].empty()) {
                hashMap.erase(val);
            }
            randVec.pop_back();
        }
        return ret;
    }
    
    /** Get a random element from the collection. */
    int getRandom() {
        return randVec[rand() % randVec.size()].first;
    }
    
private:
    unordered_map<int, vector<int>> hashMap;
    vector<pair<int, int>> randVec;
}leetcode;

5、 All O`one Data Structure

題目連結 題目大意

實現一個數據結構,要求: 1、Inc(Key) - Inserts a new key with value 1. Or increments an existing key by 1. Key is guaranteed to be a non-empty string. 2、Dec(Key) - If Key's value is 1, remove it from the data structure. Otherwise decrements an existing key by 1. If the key does not exist, this function does nothing. Key is guaranteed to be a non-empty string. 3、GetMaxKey() - Returns one of the keys with maximal value. If no element exists, return an empty string "". 4、GetMinKey() - Returns one of the keys with minimal value. If no element exists, return an empty string "".

要求所有的資料結構的時間複雜度是O(1);

題目解析:

在不考慮複雜度的前提下,樸素做法是遍歷,O(N); 簡單的優化,用map來維護優先佇列,操作1、2先獲取key值,更新完重新插入;操作3、4直接拿佇列top;每個操作的複雜度是O(LogN);

題目要求是O(1),那麼必然不能使用樹型別的結構,應該利用題目特性,配合hash以及貪心來實現。

假設有一個key-hash表,來存key的對應值。 操作1、先看keyHash裡面是否有key,有則+1,無則插入; 操作2、先看keyHash裡面是否有key,有則-1,無則Nothing;

為了維護最值,引入連結串列list,裡面所有的元素是從小到大;每個元素是一個桶,桶裡放著值相同的key; 操作3、直接獲取list頭元素的值; 操作4、直接獲取list尾元素的值;

同時,操作1、2在操作的過程中,需要把當前key值從list對應的桶裡移除,放到上一個或者下一個桶裡,或者丟棄。 為了實現O(1)獲取key所在位置,可以用iter-hash來維護key所對應元素的迭代器。

struct Bucket {
    int value;
    unordered_set<string> keys;
};

class AllOne {
public:
    list<Bucket> buckets;
    unordered_map<string, list<Bucket>::iterator> bucketOfKey;
    /** Initialize your data structure here. */
    AllOne() {
        
    }
    /** Inserts a new key <Key> with value 1. Or increments an existing key by 1. */
    void inc(string key) {
        if (bucketOfKey.find(key) == bucketOfKey.end()) {
            bucketOfKey[key] = buckets.insert(buckets.begin(), {0, {key}});
        }
        auto next = bucketOfKey[key], bucket = next++;
        if (next == buckets.end() || next->value > bucket->value + 1) {
            next = buckets.insert(next, {bucket->value+1, {}});
        }
        next->keys.insert(key);
        bucketOfKey[key] = next;
        
        bucket->keys.erase(key);
        if (bucket->keys.empty()) {
            buckets.erase(bucket);
        }
    }
    
    
    /** Decrements an existing key by 1. If Key's value is 1, remove it from the data structure. */
    void dec(string key) {
        if (bucketOfKey.find(key) == bucketOfKey.end()) {
            return ;
        }
        auto pre = bucketOfKey[key], bucket = pre;
        if (pre != buckets.begin()) {
            --pre;
        }
        
        bucketOfKey.erase(key);
        if (bucket->value > 1) {
            if (bucket == buckets.begin() || pre->value < bucket->value - 1) {
                pre = buckets.insert(bucket, {bucket->value - 1, {}});
            }
            pre->keys.insert(key);
            bucketOfKey[key] = pre;
        }
        
        bucket->keys.erase(key);
        if (bucket->keys.empty()) {
            buckets.erase(bucket);
        }
    }
    
    /** Returns one of the keys with maximal value. */
    string getMaxKey() {
        return buckets.empty() ? "" : *(buckets.rbegin()->keys.begin());
    }
    
    /** Returns one of the keys with Minimal value. */
    string getMinKey() {
        return buckets.empty() ? "" : *(buckets.begin()->keys.begin());
    }
}leetcode;

總結

這5個題目如果都能獨立完成,那麼水平已經可以足以應付國內各大企業的演算法面。 演算法重在勤思多練,埋怨公司出演算法題是沒用的,多花時間準備才是正道。

此文已由作者授權騰訊雲+社群釋出,更多原文請點選

搜尋關注公眾號「雲加社群」,第一時間獲取技術乾貨,關注後回覆1024 送你一份技術課程大禮包!

相關推薦

程式設計師演算法練習LeetCode專場

歡迎大家前往騰訊雲+社群,獲取更多騰訊海量技術實踐乾貨哦~ 本文由落影發表 前言 LeetCode上的題目是大公司面試常見的演算法題,今天的目標是拿下5道演算法題: 題目1是基於連結串列的大數加法,既考察基本資料結構的瞭解,又考察在處理加法過程中的邊界處理; 題目2是求陣列出現頻率前k大的數字,考察思維能

java程式設計師路需要的學習過程

其實本來真的沒打算寫這篇文章,主要是LZ得記憶力不是很好,不像一些記憶力強的人,面試完以後,幾乎能把自己和麵試官的對話都給記下來。LZ自己當初面試完以後,除了記住一些聊過的知識點以外,具體的內容基本上忘得一乾二淨,所以寫這篇文章其實是很有難度的。 但是,最近問LZ的人實

JAVA-程式設計師

       自己大學期間學習的是軟體工程,從需求分析到專案上線整套流程都接觸過,大二就開始接觸java,但是沒有好好把握。大把時間虛度在lol裡面了。已經在工作的我,作為一個菜鳥程式設計師,自己也有一

公司中 C和C++程式設計師

從一次考試說起。 2010年10月份,綜合部邀請我給新入職3個月的員工草擬考試試題,這些同事大部分在公司做的實習,算起來至少也有5、6個月的工作經驗了吧。 試題的內容,是針對日常需要面對的問題出的案例,比如,查詢檔案、修改下許可權等,其中最後20分題,就是按要求在螢幕

ASP.NET 高階程式設計師路——快捷鍵篇

引言:我們都知道快捷鍵使用得熟,將極大的提高我們的開發效率。可是我發現許多開發人員老喜歡用滑鼠去點,不擅長使用快捷鍵。 1、VS常用快捷鍵 這個我們記住開發中常用的就可以了。 F4:開啟屬性面板。

程式設計師路(C、C++、Java、Python經典書籍及學習順序)

程式設計師進階之路 初級: 《計算機程式的構造和解釋》 C語言: 1.《C語言程式設計:現代方法:第2版》 2.《C Primer Plus 第五版》 3.《C程式設計語言(第2版·新版)》 4.《C和指標》 5.《C專家程式設計》 6.《C 陷阱與缺陷》 7.《資料結構C

程式設計師-八大演算法攻略

常見的八大排序演算法,以及它們之間的關係如下所示:一、插入排序-直接插入排序      1.演算法思想:直接插入排序是一種簡單插入排序,基本思想是:把n個待排序的元素看成為一個有序表和一個無序表。開始時有序表中只包含1個元素,無序表中包含有n-1個元素,排序過程中每次從無序表

圖解|搞定分散式?程式設計師

> 程式設計是一門藝術,它的魅力在於創造。 65 哥已經工作兩年了,一直做著簡單重複的程式設計工作,活活熬成了一個只會 CRUD 的打工 boy。 > 65 哥:總是聽大佬講分散式分散式,什麼才是分散式系統呢? 分散式系統是一個硬體或軟體系統分佈在不同的網路計算機上,彼此之間僅僅通過訊息傳遞

【今日薦文】三十五年經驗分享程式設計師八法

如果你的目標僅僅是提高自己,那麼很容易實現,但是如果你的目標是成為一個偉大的程式設計師,那麼這就不簡單了。 很多人都願意說,我想變得更好,但是更好是什麼卻很模糊,而且人們也不知道該怎麼樣去做。 時間到了,提高你的程式設計技能,認真+嚴肅,走起! 我在這裡分享八法

程式設計師怎麼成為一個軟體架構師?

作者:程式設計小丫 來源:CSDN部落格 序:的確沒想到隨手寫的東西有那麼多的回覆,不管怎樣還是挺高興的。在這裡謝謝大家的關注了。其實做了這麼多年的技術腦子裡總會跳出很多的想法,但很少有時間靜下來仔細地思考思考,寫寫部落格也算是一種自我歸納和總結吧。  “軟體架構師”這個名詞也不知是什麼時候

程式設計師——程式碼簡潔

         儘管糟糕的程式碼也能執行,但如果程式碼不整潔,會使整個開發團隊泥足深陷,寫得不好的程式碼每年都要耗費難以計數的時間和資源。然而這種情況並非無法避免《程式碼整潔之道》為一切有志於改善程式碼質量的程式設計

Java程式設計師架構師其實並不難,關鍵在於選擇。

很多人做java開發2,3年後,都會感覺自己遇到瓶頸。什麼都會又什麼都不會,如何改變困境,為什麼很多人寫了7,8年還是一個碼農,工作中太多被動是因為不懂底層原理。公司的工作節奏又比較快,難有機會學習架構原理,也沒人教,所以這個時候,學習架構原理,擴充套件思維,對自己以後職業生涯尤為重要。 同樣公

iOS 程式設計師架構師必備的 6項 硬技能!這些你都知道嗎?

前言: 之前很多人問過我這麼個問題,說怎樣才能成為一名高階iOS工程師?我覺得這是一個很好的話題,技術人的職業規劃不管如何發展,總歸是一個從初級到高階的過程,不要妄想從初級一步跨越到架構師、CTO,產品經理 之類的職位,所以高階工程師這個過渡階段顯得就很重要了,那麼今天就來

Java程式設計師架構師難嗎?不,那是你沒找對方法

很多人做java開發2,3年後,都會感覺自己遇到瓶頸。什麼都會又什麼都不會,如何改變困境,為什麼很多人寫了7,8年還是一個碼農,工作中太多被動是因為不懂底層原理。公司的工作節奏又比較快,難有機會學習架構原理,也沒人教,所以這個時候,學習架構原理,擴充套件思維,對自己以後職業生涯尤為重要。 同樣公

程式設計師內功修煉演算法與資料結構 為機器學習、大資料補足演算法知識

現在外面的演算法課程層出不窮,少則大幾百,多則上千,但是無論課程質量與否,關鍵還是要靠自己學習了基本的知識以後,就可以通過自身進一步昇華。課程的清晰程度和講授質量都是一流水準,備課專業,良心之作。跟完這個課程自己學到的不光是資料結構的知識,還有很多附加的老師潛移默化帶給我的其他程式設計方面的提升,思考問題

伊始--淺談C++程式設計師歷程(一)

最近看了一篇《回答阿里社招面試如何準備,順便談談對於Java程式猿學習當中各個階段的建議》,心中有一些感觸。 在看這篇文章前幾周,還挺迷茫的。不知不覺也工作了很久,雖然在上班期間,每天或多或少地都在寫程式碼,但是很多都是僅僅增加熟練度,或者說是將別人現有的

Java程式設計師全過程

學習Java,書籍是必不可少的學習工具之一,尤其是對於自學者而言。廢話不多說,下邊就給大家推薦一些Java進階的好書。 第一部分:Java語言篇 1.《Java程式設計規範》 適合物件:初級、中級 介紹:這本書的作者是被譽為Java之父的James Gosling,入門

【JVM】程式設計師JVM(一)——Java記憶體區域

一、前言       這篇部落格起,小編會向一個更加深層次、逼格滿滿的區域進發——JVM。       可以說JVM不是一個新鮮的東西,但是做java的都會了解JVM,都聽過JVM。有的時候我們寫的程式碼執行跟JVM也有關係。 二、JVM介紹    

Java程式設計師架構師的五個階段,你到了哪各階段?

之前有個討論:實現同樣功能,簡潔程式碼一定比複雜程式碼效率高嗎?有的說,還得看演算法,如果演算法相同,簡潔程式碼效率應該會高一些。有的說,即使演算法相同,簡潔程式碼也不見得比複雜程式碼效率高,而應儘可能減少迴圈的使用,特別是少用多重迴圈,或者儘可能在一個迴圈中做更多的事。如此

程式設計師必備的五個網站

1、TopCoderTopCoder是一個非常出名的程式設計競技網站。不少的程式設計師會在上面參加一些演算法挑戰,如果你確實很牛掰,甚至會得到線上外包類的工作機會。2、CodechefCodechef上面的程式設計題目難度分好幾個等級,包括入門、簡單、中等、高難度等。在這個技