寫在前面
華爲面試題庫刷題第二次整理,這次整理的都是簡單題的多解法,大部分都是用了巧妙方法使空間複雜度位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);
}
};
位運算
又是位運算,這東西是真的騷,反正沒做過基本想不出來,不過這次做過了以後就不能忘了。
根據位運算異或的性質,可以得到以下的規律:
- a ^ 0 = a;
- a ^ a = 0;
- 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;
}
};