數組
移動0
283.Move Zeroes
Given an array nums, write a function to move all 0’s to the end of it while maintaining the relative order of the non-zero
elements.Example:
Input: [0,1,0,3,12] Output: [1,3,12,0,0] Note:
You must do this in-place without making a copy of the array. Minimize
the total number of operations.來源:力扣(LeetCode) 鏈接:https://leetcode-cn.com/problems/move-zeroes
著作權歸領釦網絡所有。商業轉載請聯繫官方授權,非商業轉載請註明出處。
這個題我沒做出來,分析下一個巧妙的解法:
void moveZeroes(vector<int>& nums) {
int j = 0;
for (int i = 0; i < nums.size(); i++) {
if (nums[i] != 0) {
swap(nums[i],nums[j++]);
}
}
}
這裏用了雙指針,i作爲遍歷指針,j始終指向第一個0出現的地方(每次i指向元素爲不爲0時j就自加,爲0時j不變)。
- 沒有0的情況:【1,2,3,4,5】:每次都會進入swap函數,由於初始時ij相等,swap不執行。於是j加1,i由於在for循環裏也加一,所以ij在每次循環中是同步加一的,直到他們走到最後,數組不會變。
- 有一個0 的情況:【1,0,2,3,4】:j指向0,i指向2,此時sawp函數運行,0與2換位,數組變爲12034。j自加後還是指向0,i進行下一輪循環指向3,於是又將0與3換位、0與4換位,結束。
- 有多個連續0的情況:【1,0,0,0,2,3】:j指向第一個0,i指向2,於是將02對換得120003,注意中間兩個0是沒有變的。如此再將03對換,結束
- 有多個不連續0的情況:【1,0,4,2,0,3】:經過上面的分析知1042會變爲1420,此時i指向後一個0,而j指向前一個0。142003就和連續0 的情況一樣。
這個方法太強了!
鏈表
說明:第一個算法是迭代,第二個是遞歸
鏈表反轉
Reverse a singly linked list.
Example:
Input: 1->2->3->4->5->NULL Output: 5->4->3->2->1->NULL Follow up:
A linked list can be reversed either iteratively or recursively. Could
you implement both?
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* reverseList1(struct ListNode* head) {//迭代 4ms 時間複雜度o(n)空間複雜度o(1)
if (!head||!head->next) return head;
struct ListNode* cur = head;
struct ListNode* prev = NULL;
struct ListNode* next = cur->next;
while (next) {
//翻轉
cur->next = prev;
//先後移動三指針
prev = cur;
cur = next;
next = next->next;
}
cur->next = prev;
return cur;
}
struct ListNode* reverseList2(struct ListNode* head) {
//遞歸 0ms 時間複雜度o(n)
if (!head||!head->next) return head;//記head->next爲二結點
struct ListNode *p = reverseList2(head->next);//認爲二節點後已經排序完畢,返回新的頭節點p
head->next->next = head;//讓二結點的後繼等於head,完成鏈表反轉
head->next = NULL;//頭節點變爲現在的尾節點,後繼應該爲空
return p;
}
鏈表兩兩反轉
Given a linked list, swap every two adjacent nodes and return its
head.You may not modify the values in the list’s nodes, only nodes itself
may be changed.Example:
Given 1->2->3->4, you should return the list as 2->1->4->3.
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* swapPair1(struct ListNode* head) {
//執行用時 :4 ms
if(head==NULL||head->next==NULL) return head;
struct ListNode* thead =(struct ListNode*)malloc(sizeof(struct ListNode));
thead->val = -1;
thead->next = head;
struct ListNode* s = thead;
struct ListNode* a,*b;
while((s->next) && (s->next->next)){
a = s->next;
b = s->next->next;
s->next = b;
a->next = b->next;
b->next = a;
s = a;//關鍵一步
}
return thead->next;
}
struct ListNode* swapPair2(struct ListNode* head) {
if(!head||!head->next) return head;
struct ListNode *t = head->next;
struct ListNode *p = head->next->next;
t->next = head;
//以上已經實現了翻轉相鄰鏈表
head->next = swapPair2(p);
//head的後繼節點顯然要鏈接到每次遞歸後的開頭節點,也就是返回的t節點【重點理解】
return t;
//這裏只能return t,因爲t是鏈表的開頭節點,也是每次遞歸鏈表的開頭節點
}
迭代算法說一下,參見某大佬的講解
鏈表判環※
Given a linked list, determine if it has a cycle in it.
To represent a cycle in the given linked list, we use an integer pos
which represents the position (0-indexed) in the linked list where
tail connects to. If pos is -1, then there is no cycle in the linked
list.Example 1:
Input: head = [3,2,0,-4], pos = 1 Output: true Explanation: There is a
cycle in the linked list, where tail connects to the second node.
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
bool hasCycle(struct ListNode *head) {
if(!head||!head->next) return 0;
struct ListNode *p = head;
struct ListNode *fast = head;
struct ListNode *slow = head;
while (1){
fast = fast->next->next;
slow = slow->next;//快慢指針
if(!fast||!fast->next) return 0;
if(fast == slow) return 1;
}
//接下來的代碼表示如何找到入環節點
//定理:ptr1 指向鏈表的頭, ptr2 指向相遇點。每次將它們往前移動一步,直到它們相遇,它們相遇的點就是環的入口。
while(p != slow){
p = p->next;
slow = slow->next;
}
return p;
}
//C++使用set(最佳)
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
unordered_set<ListNode *> st;
while(!head){
//count方法統計head出現多少次,次數大於1則返回入環節點
if(st.count(head)) return head;
st.insert(head);
head=head->next;
}
return NULL;
}
};
另:
- 解法一:如果可破壞數據,將元素都修改爲某指定值,從頭遍歷鏈表,如果遍歷到頂說明無環,遍歷到指定值說明有環;
- 解法二:如果已知鏈表長度爲N,從頭遍歷鏈表N次,如果遍歷到頂說明無環,遍歷順利完成,說明未到頂,有環。或設定遍歷時間爲1s,1s內仍未遍歷完說明有環。
刪除鏈表重複結點
棧&隊列
有效括號
Given a string containing just the characters ‘(’, ‘)’, ‘{’, ‘}’, ‘[’
and ‘]’, determine if the input string is valid.An input string is valid if:
Open brackets must be closed by the same type of brackets. Open
brackets must be closed in the correct order. Note that an empty
string is also considered valid.Example 1:
Input: “()” Output: true
// 執行用時 :0 ms, 在所有 C++ 提交中擊敗了100.00%的用戶
// 內存消耗 :8.4 MB, 在所有 C++ 提交中擊敗了86.49%的用戶
class Solution {
public:
bool isValid(string s) {
if(!s.length()) return 1;
stack<char> st;
for(char &i : s){//重點
switch (i){
case '(':
case '{':
case '[':
st.push(i);
break;
case ')':
if(!st.size()||st.top() != '(') return 0;
st.pop();
break;
case ']':
if(!st.size()||st.top() != '[') return 0;
st.pop();
break;
case '}':
if(!st.size()||st.top() != '{') return 0;
st.pop();
}//代碼還能優化嗎
}
if(!st.size()) return 1;
return 0;
}
};
#python解法 時間複雜度O(n2)
class Solution:
def isValid(self, s):
while '{}' in s or '()' in s or '[]' in s:
s = s.replace('{}', '')
s = s.replace('[]', '')
s = s.replace('()', '')
return s == ''
棧實現隊列
class MyQueue {
public:
/** Initialize your data structure here. */
MyQueue() {
}
/** Push element x to the back of queue. */
void push(int x) {
s1.push(x);
}
/** Removes the element from in front of queue and returns that element. */
int pop() {
if(!s2.size()){
while(s1.size()){
s2.push(s1.top());
s1.pop();////彈出棧頂元素, 但不返回其值
}
}
int val = s2.top();
s2.pop();
return val;
}
/** Get the front element. */
int peek() {
if(!s2.size()){
while(s1.size()){
s2.push(s1.top());
s1.pop();
}
}
return s2.top();
}
/** Returns whether the queue is empty. */
bool empty() {
return !(s1.size()+s2.size());
}
private:
stack<int> s1,s2;
};
/**
* Your MyQueue object will be instantiated and called as such:
* MyQueue* obj = new MyQueue();
* obj->push(x);
* int param_2 = obj->pop();
* int param_3 = obj->peek();
* bool param_4 = obj->empty();
*/
隊列實現棧
class MyStack {
public:
/** Initialize your data structure here. */
MyStack() {
}
/** Push element x onto stack. */
void push(int x) {
q1.push(x);
}
/** Removes the element on top of the stack and returns that element. */
int pop() {
while(q1.size() != 1){
q2.push(q1.front());
q1.pop();
}
int num = q1.front();
q1.pop();
while(q2.size()){
q1.push(q2.front());
q2.pop();
}
return num;
}
/** Get the top element. */
int top() {
return q1.back();
}
/** Returns whether the stack is empty. */
bool empty() {
return !(q1.size()+q2.size());
}
private:
queue<int> q1,q2;
};
/**
* Your MyStack object will be instantiated and called as such:
* MyStack* obj = new MyStack();
* obj->push(x);
* int param_2 = obj->pop();
* int param_3 = obj->top();
* bool param_4 = obj->empty();
*/
返回數據流中第k大的元素(小頂堆)
在優先隊列中,元素被賦予優先級。當訪問元素時,具有最高優先級的元素最先刪除。優先隊列具有最高級先出 (first in, largest out)的行爲特徵。通常採用堆或二叉搜索樹來實現。
Design a class to find the kth largest element in a stream. Note that
it is the kth largest element in the sorted order, not the kth
distinct element.Your KthLargest class will have a constructor which accepts an integer
k and an integer array nums, which contains initial elements from the
stream. For each call to the method KthLargest.add, return the element
representing the kth largest element in the stream.Example:
int k = 3; int[] arr = [4,5,8,2]; KthLargest kthLargest = new
KthLargest(3, arr); kthLargest.add(3); // returns 4
kthLargest.add(5); // returns 5 kthLargest.add(10); // returns 5
kthLargest.add(9); // returns 8 kthLargest.add(4); // returns 8
- 算法一:維護一個自動排序的數組,保存整個數組中k個較大值:插入新數並自動排序後,若有k個以上的元素,則去除最小的那個即開頭元素。時間複雜度N*klog(k) (插入N個數據,每次都排序)
class KthLargest {
public:
//維護一個新數組st,是數組nums中的k個較大值
KthLargest(int k, vector<int>& nums) {
for(int i : nums){
st.insert(i);
if(st.size() > k){
//如果st有k+1個元素,那麼去除最小的元素,也就是開頭那個
st.erase(st.begin());
}
}
K = k;//需要把k保存起來
}
int add(int val) {
st.insert(val);
//如果元素超過k個,去除最小的那個
if(st.size() > K){
st.erase(st.begin());
}
return *st.begin();
}
private:
int K;
multiset<int> st;//自動排序 允許重複
};
/**
* Your KthLargest object will be instantiated and called as such:
* KthLargest* obj = new KthLargest(k, nums);
* int param_1 = obj->add(val);
*/
- 算法二:維護一個大小爲k的小頂堆。每次插入數據時,如果比堆頂元素小,則不用插入;如果大,則插入並調整堆。輸出堆底最後一個元素即爲第k個最大元素。時間複雜度:最好N*1,最壞N*log(2k)=N*logk(取後者)。
一般stl都是降序,需要實現升序時,加上greater<>:
set<int,greater<int>> st;
class KthLargest {
int K;
priority_queue<int, vector<int>, greater<int>> pq;
/*
priority_queue<Type, Container, Functional>
Type爲數據類型, Container爲保存數據的容器,Functional爲元素比較方式。
如果不寫後兩個參數,那麼容器默認用的是vector,比較方式默認用operator<,
也就是優先隊列是大頂堆,隊頭元素最大,本題爲小頂堆。
*/
public:
KthLargest(int k, vector<int>& nums) {
for (int n : nums) {
pq.push(n);
if (pq.size() > k) pq.pop();
}
K = k;
}
int add(int val) {
pq.push(val);
if (pq.size() > K) pq.pop();
return pq.top();
}
};
/*作者:guohaoding
鏈接:https://leetcode-cn.com/problems/kth-largest-element-in-a-stream/solution/703-shu-ju-liu-zhong-de-di-kda-yuan-su-liang-chong/
來源:力扣(LeetCode)
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。
*/
滑動窗口最大值(優先隊列)
Given an array nums, there is a sliding window of size k which is
moving from the very left of the array to the very right. You can only
see the k numbers in the window. Each time the sliding window moves
right by one position. Return the max sliding window.Example:
Input: nums = [1,3,-1,-3,5,3,6,7], and k = 3 Output: [3,3,5,5,6,7]
Explanation:
Window position | Max |
---|---|
[1 3 -1] -3 5 3 6 7 | 3 |
1 [3 -1 -3] 5 3 6 7 | 3 |
1 3 [-1 -3 5] 3 6 7 | 5 |
1 3 -1 [-3 5 3] 6 7 | 5 |
1 3 -1 -3 [5 3 6] 7 | 6 |
1 3 -1 -3 5 [3 6 7] | 7 |
- 算法一:multiset保存當前的窗口,每次將當前窗口最大值傳入answer數組中。pos指針從nums數組第一個元素指向最後一個元素,每次移動過程中向集合中插入所指元素。以k=3爲例,pos = 3,4…時,窗口內元素個數大於3個,需要刪除最左邊的數據;pos=2,3,4…時,窗口元素個數大於等於3個,可以向ans數組寫入當前窗口最大值。時間複雜度n*klog(k)
注意C++中,end()不是指向最後一個元素,而是最後一個元素的後一個元素,所以返回最後一個元素時不能用*st.end()而要用*st.rbegin()
// 執行用時 :92 ms, 在所有 C++ 提交中擊敗了37.34%的用戶
class Solution {
multiset<int> st;
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
vector<int> ans;
for(int pos = 0;pos < nums.size();pos++){
if(pos >= k) st.erase(st.find(nums[pos-k]));//刪除左邊數據
st.insert(nums[pos]);
if(pos >= k-1) ans.push_back(*st.rbegin());
}
return ans;
}
};
- 算法二:優先隊列(大頂堆)
思想同算法一,維護一個多重集合變爲維護一個大頂堆,但是我不會C++優先隊列中刪除某特定值的元素,即刪除窗口最左邊的數據,挖坑。時間複雜度n*logk - 算法三:雙端隊列
進新元素時,幹掉比自己小的元素,時間複雜度n
class Solution {
vector<int> ans;
deque<int> dequ;
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
for (int pos = 0; pos < nums.size(); pos++) {
if (!dequ.empty() && dequ.front() == pos-k) {
dequ.pop_front();
}
while (!dequ.empty() && nums[pos] > nums[dequ.back()]){
dequ.pop_back();
}
dequ.push_back(pos);
if (pos >= k-1) ans.push_back(nums[dequ.front()]);
}
return ans;
}
};
Map&Set
有效的字母異位詞
Given two strings s and t , write a function to determine if t is an
anagram of s.Example 1:
Input: s = “anagram”, t = “nagaram” Output: true Example 2:
Input: s = “rat”, t = “car” Output: false
STL 容器比較:
模板 | key | 排序 |
---|---|---|
set | 不允許重複 | 自動排序 |
map | 不允許重複 | 自動排序 |
multimap | 允許重複 | 自動排序 |
unordered_map | 不允許重複 | 無序 |
unordered_multimap | 允許重複 | 無序 |
總結:set和map都是不允許重複自動排序的,加multi表示允許重複,加unordered表示無序。
unordered_map:
在cplusplus的解釋:
無序映射是關聯容器,用於存儲由鍵值和映射值組合而成的元素,並允許基於鍵快速檢索各個元素。
在unordered_map中,鍵值通常用於唯一標識元素,而映射值是與該鍵關聯的內容的對象。鍵和映射值的類型可能不同。
在內部,unordered_map中的元素沒有按照它們的鍵值或映射值的任何順序排序,而是根據它們的散列值組織成桶以允許通過它們的鍵值直接快速訪問單個元素(具有常數平均時間複雜度)。
unordered_map容器比映射容器更快地通過它們的鍵來訪問各個元素,儘管它們通過其元素的子集進行範圍迭代通常效率較低。
無序映射實現直接訪問操作符(operator []),該操作符允許使用其鍵值作爲參數直接訪問映射值。
容器中的迭代器至少是前向迭代器。
關鍵詞:無序的 快速的檢索 達到的是更快的訪問 但是子集的範圍迭代效率低
//時間複雜度 N
class Solution {
unordered_map<char,int> counts;
//對每個字母出現次數計數。如counts['a']=3表示a出現了3次
public:
bool isAnagram(string s, string t) {
if(s.length() != t.length()) return false;
for(int i = 0;i < s.length();i++){
counts[s[i]]++;//串1中的字母出現時加一
counts[t[i]]--;//串2中的字母出現時減一
}
//如果串1和串2字母全相同,counts中的計數應爲0
for(auto count:counts)
if(count.second) return false;
return true;
}
};
//另有將兩個字符串數組排序後再比較的算法,Nlog(N)(快排)
兩數之和
Given an array of integers, return indices of the two numbers such
that they add up to a specific target.You may assume that each input would have exactly one solution, and
you may not use the same element twice.Example:
Given nums = [2, 7, 11, 15], target = 9,
Because nums[0] + nums[1] = 2 + 7 = 9, return [0, 1].
來源:力扣(LeetCode) 鏈接:https://leetcode-cn.com/problems/two-sum
著作權歸領釦網絡所有。商業轉載請聯繫官方授權,非商業轉載請註明出處。
- 算法一:暴力法 O(n2)
- 算法二:set記錄
這道題有個巨坑,就是C++的set是自動排序的。我一開始用set,放入3,2,4後,再找2的時候,返回的下標是1,而答案應該是2,也就是set給我排序了,下標變了。花了好久時間想爲什麼返回下標是1。。所以改用vector解決問題。還有個坑,distance函數我不知道,爲什麼C++find不能直接返回下標,要返回迭代器,還得distance函數找距離。。
class Solution {
vector<int> record;
//set自動排序 不能用set做本題,只能用map/vector
vector<int> res;
public:
vector<int> twoSum(vector<int>& nums, int target) {
for(int i = 0;i < nums.size();i++){
//vector的find方法要用stl的,它沒有自己的
auto p = find(record.begin(),record.end(),target - nums[i]);
if( p == record.end()){
record.push_back(nums[i]);
}else{
res.push_back(i);
res.push_back(distance(record.begin(),p));
break;//找到即可返回
}
}
return res;
}
};
算法三:map
在算法二我們知道set是一維的,由下標確定值。而map是二維的,可以存元素以及下標,就這樣省去了找下標的時間。
class Solution {
unordered_map<int,int> record;
vector<int> res;
public:
vector<int> twoSum(vector<int>& nums, int target) {
for(int i=0;i<nums.size();i++){
int tmp=target-nums[i];
if(record.find(tmp)==record.end()){
record[nums[i]]=i;
}else
res.push_back(i);
res.push_back(record[tmp]);
//無須distance函數找下標
break;
}
return res;
}
};
算法三隻是換成map,時間變爲12ms,而算法二是56ms,(消耗內存都差不多),時間複雜度相同,爲什麼時間上差這麼多?可能是distance函數慢了。
三數之和
- 算法一:暴力O(n3)
- 算法二:兩層循環,在循環內部找target-a-b,找到了就結束,沒找到就把a和b插入set中,O(n2),空間O(n)
- 算法三:先排序(快排nlogn),b和c作爲雙指針,a+b+c值大於0,說明大了,左移c;小於0,說明小了右移b。時間O(n2),空間無。
二叉樹&二叉搜索樹
二叉樹遍歷
typedef struct TreeNode
{
int data;
TreeNode * left;
TreeNode * right;
TreeNode * parent;
}TreeNode;
void pre_order(TreeNode * Node)
{
if(Node != NULL)
{
printf("%d ", Node->data);
pre_order(Node->left);
pre_order(Node->right);
}
}
void middle_order(TreeNode *Node) {
if(Node != NULL) {
middle_order(Node->left);
printf("%d ", Node->data);
middle_order(Node->right);
}
}
void after_order(TreeNode *Node) {
if(Node != NULL) {
after_order(Node->left);
after_order(Node->right);
printf("%d ", Node->data);
}
}
驗證二叉搜索樹
Given a binary tree, determine if it is a valid binary search tree
(BST).Assume a BST is defined as follows:
The left subtree of a node contains only nodes with keys less than the
node’s key. The right subtree of a node contains only nodes with keys
greater than the node’s key. Both the left and right subtrees must
also be binary search trees.Example 1:
2
/ \
1 3
Input: [2,1,3] Output: true
- 算法一:中序遍歷這棵樹,如果是二叉搜索樹,結果應該爲升序。O(n)
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
vector<int> vec;
int flag = 1;
public:
bool isValidBST(TreeNode* root) {
middle_order(root);
return flag;
}
void middle_order(TreeNode *Node) {
if(Node != NULL) {
middle_order(Node->left);
if(vec.size() == 0||Node->val > vec.back()){
vec.push_back(Node->val);
}else {flag = 0;}
middle_order(Node->right);
}
}
};
我這個做法裏面有flag,不知道好不好,執行用時 :16 ms, 在所有 C++ 提交中擊敗了77.06%的用戶內存消耗 :21.5 MB, 在所有 C++ 提交中擊敗了5.16%的用戶。內存消耗的有點多。應該有改進的地方。
copy一個更好的寫法,24 ms, 20.6 MB ,代碼簡潔一點。
class Solution {
public:
long x = LONG_MIN;
bool isValidBST(TreeNode* root) {
if(root == NULL){
return true;
}
else{
if(isValidBST(root->left)){
if(x < root->val){
x = root->val;
return isValidBST(root->right);
}
}
}
return false;
}
};
- 算法二:遞歸(copy的代碼)
class Solution {
public:
bool isValidBST(TreeNode* root) {
return helper(root,LONG_MIN,LONG_MAX);
}
bool helper(TreeNode* cur,long lt,long rt){
if(cur==NULL) return true;
if(cur->val<=lt||cur->val>=rt) return false;
if(helper(cur->left,lt,cur->val)&&helper(cur->right,cur->val,rt)) return true;
return false;
}
};
作者:24shi-01fen-_00_01
鏈接:https://leetcode-cn.com/problems/validate-binary-search-tree/solution/liang-chong-fang-fa-di-gui-zhong-xu-bian-li-by-24s/
來源:力扣(LeetCode)
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。
最近公共祖先
Given a binary search tree (BST), find the lowest common ancestor
(LCA) of two given nodes in the BST.According to the definition of LCA on Wikipedia: “The lowest common
ancestor is defined between two nodes p and q as the lowest node in T
that has both p and q as descendants (where we allow a node to be a
descendant of itself).”Given binary search tree: root = [6,2,8,0,4,7,9,null,null,3,5]
Example 1:
Input: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8 Output: 6
Explanation: The LCA of nodes 2 and 8 is 6.
以節點3和0爲例
算法一:如果有parent指針,3和0第一個相同的parent節點即爲所求- 算法二:從根節點遍歷到0和3,記錄他們的路徑。如620、6243,第一個分叉的節點2即爲所求。O(N)
- 算法三:遞歸O(N):注意這個是二叉搜索樹
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
struct TreeNode* lowestCommonAncestor(struct TreeNode* root, struct TreeNode* p, struct TreeNode* q) {
if(root==NULL) return NULL;
if(root==p||root==q) return root;//找到了
if(root->val>p->val&&root->val>q->val) return lowestCommonAncestor(root->left,p,q);
if(root->val<p->val&&root->val<q->val) return lowestCommonAncestor(root->right,p,q);
return root;
}