不要小看簡單題-華爲面試題庫簡單題一題多解整理

寫在前面

華爲面試題庫刷題第二次整理,這次整理的都是簡單題的多解法,大部分都是用了巧妙方法使空間複雜度位O(1),簡單題也有它的魅力和價值。
個人博客的鏈接:flamsteed

136. 只出現一次的數字

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

說明:

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

解法:要求時間複雜度O(N),空間複雜度O(1),講道理,想不出來,只相處一種排序之後遍歷一遍得出答案的方法,時間複雜度O(N*log(N)),空間複雜度O(1)。

排序

排序後遍歷,沒有出現兩次就是答案

代碼:

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

集合

集合可以自動去重,然後求和乘2再減去原來的和就是答案,時間複雜度O(N),空間複雜度O(N)。

代碼:

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        set<int> s;
        int sum1(0),sum2(0);
        for(auto x:nums){
            s.insert(x);
            sum1 += x;
        }
        for(auto x:s){
            sum2 += x;
        }
        return (sum2*2-sum1);
    }
};

位運算

又是位運算,這東西是真的騷,反正沒做過基本想不出來,不過這次做過了以後就不能忘了。

根據位運算異或的性質,可以得到以下的規律:

  1. a ^ 0 = a;
  2. a ^ a = 0;
  3. a ^ b ^ c = a ^ c ^ b;

所以,根據以上的規律,所有數字異或一邊,就會剩下答案。

代碼:

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int res = 0;
        for(auto x:nums)
            res ^= x;
        return res;
    }
};

189. 旋轉數組

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

示例 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]

說明:

儘可能想出更多的解決方案,至少有三種不同的方法可以解決這個問題。

要求使用空間複雜度爲O(1)的原地算法。

解法:最樸素的k次移動一位的方法爆了,上了出題者的鬼當,只能O(N)做的話,只能想出來用數組佔存一部分,移動好了再填進去。

額外數組存儲方法

代碼:

class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        if(nums.empty()) return;
        int l = nums.size();
        k = k % l;
        if(k==0) return;
        vector<int> a;
        for(int i=l-k; i<l; i++)
            a.push_back(nums[i]);
        for(int i=l-1; i>=k; i--)
            nums[i] = nums[i-k];
        for(int i=0; i<a.size(); i++)
            nums[i] = a[i];
        return;
    }
};

本地直接交換

題目的正解很巧妙,根據下標i%k的值進行移動,遍歷一遍,O(N)解法,而且無額外空間。

代碼:

class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        if(nums.empty()) return;
        int l = nums.size();
        k = k % l;
        if(k==0) return;
        
        int count(0);
        int p = 0;
        int current = 0;
        int pre = nums[0];
        while(count<l){
            do{
                int next = (current+k) % l;
                int t = nums[next];
                nums[next] = pre;
                pre = t;
                current = next;
                count++;
            }while(current!=p);
            current = ++p;
            pre = nums[p];
        }
        return;
    }
};

234. 迴文鏈表

請判斷一個鏈表是否爲迴文鏈表。

示例 1:

輸入: 1->2

輸出: false

進階:

你能否用 O(n) 時間複雜度和 O(1) 空間複雜度解決此題?

解法:這題O(N)很簡單,但是O(1)空間就有點難了。

存進數組

大部分解法就是存進數組,然後用正常的方法判斷迴文。

代碼:

/**
* Definition for singly-linked list.
* struct ListNode {
*     int val;
*     ListNode *next;
*     ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
    bool isPalindrome(ListNode* head) {
        vector<int> a;
        ListNode* p{head};
        while(p){
            a.push_back(p->val);
            p = p->next;
        }
        bool f = true;
        for(int i=0; i<a.size()/2; i++){
            if(a[i]!=a[a.size()-i-1]){
                f = false;
                break;
            }
        }
        return f;
    }
};

反轉後一半

利用快慢指針找到中間點,然後把後面一半的鏈表反轉一下,然後順序比較。

代碼:

/**
* Definition for singly-linked list.
* struct ListNode {
*     int val;
*     ListNode *next;
*     ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
    bool isPalindrome(ListNode* head) {
        if(!head) return true;
        
        ListNode* firstend = secondHalf(head);
        ListNode* Second = reverseList(firstend->next);

        ListNode* p1 = head;
        ListNode* p2 = Second; 

        bool f = true;
        while(f && p2){
            if(p1->val != p2->val) f = false;
            p1 = p1->next;
            p2 = p2->next;
        }
        return f;
    }
    ListNode* reverseList(ListNode* head){//反轉
        ListNode* prev = NULL;
        ListNode* curr = head;
        while(curr){
            ListNode* t = curr->next;
            curr->next = prev;
            prev = curr;
            curr = t;
        }
        return prev;
    }
    ListNode* secondHalf(ListNode* head){//尋找中間點
        ListNode* slow{head};
        ListNode* fast{head};
        while(fast->next && fast->next->next){
            slow = slow->next;
            fast = fast->next->next;
        }
        return slow;
    }
};

160. 相交鏈表

編寫一個程序,找到兩個單鏈表相交的起始節點。

輸入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3

輸出:Reference of the node with value = 8

輸入解釋:相交節點的值爲 8 (注意,如果兩個列表相交則不能爲 0)。從各自的表頭開始算起,鏈表 A 爲 [4,1,8,4,5],鏈表 B 爲 [5,0,1,8,4,5]。在 A 中,相交節點前有 2 個節點;在 B 中,相交節點前有 3 個節點。

注意:

如果兩個鏈表沒有交點,返回 null.

在返回結果後,兩個鏈表仍須保持原有的結構。

可假定整個鏈表結構中沒有循環。

程序儘量滿足 O(n) 時間複雜度,且僅用 O(1) 內存。

解法:

雙向指針

A的指針遍歷完A 接着從headB開始遍歷

B的指針遍歷完B 接着從headA開始遍歷

兩個指針都最多走m + n + 1步。

當兩個指針同時爲空時,表示不相交;當兩個都非空且相等時,表示相交

時間複雜度O(m + n) 空間複雜度O(1)

代碼:

/**
* Definition for singly-linked list.
* struct ListNode {
*     int val;
*     ListNode *next;
*     ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if(!headA||!headB) return nullptr;
        
        ListNode* curr_a = headA;
        ListNode* curr_b = headB;

        while(curr_a!=curr_b){
            curr_a = (!curr_a) ? headB : curr_a->next;
            curr_b = (!curr_b) ? headA : curr_b->next;
        }
        return curr_a;
    }
};

哈希表法

使用一個hash set 遍歷一個鏈表,set中存放其所有指針, 遍歷另一個鏈表,去set中找相同指針
時間複雜度O(m + n) 空間複雜度O(m) 或 O(n)

代碼:

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        unordered_set<ListNode*> s;
        ListNode* curr_a{headA};
        while(curr_a){
            s.insert(curr_a);
            curr_a = curr_a->next;
        }
        ListNode* curr_b{headB};
        while(curr_b){
            if(s.find(curr_b)!=s.end())
                return curr_b;
            curr_b = curr_b->next;
        }
        return nullptr;
    }
};

暴力法有丶傻,不寫了。

141. 環形鏈表

給定一個鏈表,判斷鏈表中是否有環。

爲了表示給定鏈表中的環,我們使用整數 pos 來表示鏈表尾連接到鏈表中的位置(索引從 0 開始)。 如果 pos 是 -1,則在該鏈表中沒有環。

示例 1:

輸入:head = [3,2,0,-4]

輸出:true

解釋:鏈表中有一個環,其尾部連接到第二個節點。

解法:

哈希表法

和上一題的哈希表法大同小異,只不過這回是邊存邊判斷。

代碼:

/**
* Definition for singly-linked list.
* struct ListNode {
*     int val;
*     ListNode *next;
*     ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
    bool hasCycle(ListNode *head) {
        unordered_set<ListNode*> s;
        ListNode* t = head;
        while(t){
            if(!s.empty() && s.find(t)!=s.end())
                return true;
            s.insert(t);
            t = t->next;
        }
        return false;
    }
};

雙指針法

如果存在環路,那麼快指針一定會追上慢指針,所以設置兩個快慢指針就行了,很巧妙,空間複雜度O(1)。

代碼:

class Solution {
public:
    bool hasCycle(ListNode *head) {
        if(!head || !head->next) return false;
        ListNode* slow = head;
        ListNode* fast = head->next;
        while(fast!=slow){
            if(!fast || !fast->next) return false;
            slow = slow->next;
            fast = fast->next->next;
        }
        return true;
    }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章