-
數組
數組基本上屬於使用最多的一種數據結構,因爲其連續的內存分佈,使得訪問和查找等方面都可以做到O(1)時間複雜度,缺點就在添加和刪除操作上,因爲需要保持內存的連續性,需要對後續的元素進行移動,所以該部分算法爲O(N)。
數組在訪問方面,使用下標或者迭代器基本上沒有差別,唯一問題可能在使用迭代器會顯得比較高大上一些而已。
關於本週練習的數組方面操作:刪除排序數組中的重複項:
//使用stl內置函數實現
int removeDuplicates(vector<int>& nums) {
if(nums.size() < 2)
return nums.size();
vector<int>::iterator it = unique(nums.begin(), nums.end());
return (it - nums.begin());
}
//或者自行移動元素
int removeDuplicates(vector<int>& nums) {
if(nums.size() < 2)
return nums.size();
int i = 0, j = 0;
while(++i < nums.size())
{
if(nums[i] != nums[j])
nums[++j] = nums[i];
}
return j + 1;
}
//上邊兩種做法都沒有真正的從數組中刪除元素,只是交換完元素後,把結束位置返回了出來,節省數組刪除元素時O(N)的操作。
合併兩個有序數組:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
if(n < 1)
return;
int i = m - 1;
int j = n - 1;
int k = m + n - 1;
while(i >= 0 && j >= 0)
{
nums1[k--] = nums1[i] > nums2[j] ? nums1[i--] : nums2[j--];
}
if(j >= 0)
std::copy(nums2.begin(), nums2.begin() + j + 1, nums1.begin());
}
給定一個由整數組成的非空數組所表示的非負整數,在該數的基礎上加一:
vector<int> plusOne(vector<int>& digits) {
for(int i = digits.size() - 1; i >= 0; i--)
{
digits[i]++;
if(digits[i] != 10)
return digits;
else
digits[i] = 0;
}
if(digits[0] == 0)
{
digits[0] = 1;
digits.push_back(0);
}
return digits;
}
給定一個數組,將數組中的元素向右移動 k 個位置,其中 k 是非負數。
//數組向右移動k位,就等於從(n-k)分開,反轉左右兩邊,然後再反轉整個數組
void rotate(vector<int>& nums, int k) {
std::reverse(nums.begin(), nums.end() - k % nums.size());
std::reverse(nums.end() - k % nums.size(), nums.end());
std::reverse(nums.begin(), nums.end());
}
//當然stl內置庫也有該函數
//std::rotate(nums.begin(), nums.end() - k % nums.size(), nums.end());
給定一個數組 nums,編寫一個函數將所有 0 移動到數組的末尾,同時保持非零元素的相對順序。
void moveZeroes(vector<int>& nums) {
if(nums.size() > 1)
{
for(int i = 0, j= 0; i < nums.size(); i++)
{
if(nums[i] != 0)
swap(nums[i], nums[j++]);
}
}
}
輸入一個矩陣,按照從外向裏以順時針的順序依次打印出每一個數字。
vector<int> spiralOrder(vector<vector<int>>& matrix) {
vector<int>res;
int m = matrix.size();
if(m <= 1)
return matrix[0];
int n = matrix[0].size();
res.resize(m * n);
int index = 0;
int lmin = 0, lmax = n - 1, hmin = 0, hmax = m - 1;
while(lmin <= lmax || hmin <= hmax)
{
for(int l = lmin; l <= lmax; l++)
{
res[index++] = matrix[hmin][l];
}
for(int h = hmin + 1; h <= hmax; h++)
{
res[index++] = matrix[h][lmax];
}
if(lmin < lmax && hmin < hmax)
{
for(int l = lmax - 1; l > lmin; l--)
{
res[index++] = matrix[hmax][l];
}
for(int h = hmax; h > hmin; h--)
{
res[index++] = matrix[h][lmin];
}
}
lmin++;
hmin++;
lmax--;
hmax--;
}
return res;
}
給你一個未排序的整數數組,請你找出其中沒有出現的最小的正整數。
//將每個元素放在對應下標的位置上,然後遍歷找到第一個不等於下標+1的數即是所求。
int firstMissingPositive(vector<int>& nums) {
int n = nums.size();
for (int i = 0; i < n; ++i) {
while (nums[i] > 0 && nums[i] <= n && nums[nums[i] - 1] != nums[i]) {
swap(nums[nums[i] - 1], nums[i]);
}
}
for (int i = 0; i < n; ++i) {
if (nums[i] != i + 1) {
return i + 1;
}
}
return n + 1;
}
LRU緩存機制的實現
class LRUCache {
int size = 0;
vector<int> operVec;
map<int, int> cacheMap;
public:
LRUCache(int capacity) {
size = capacity;
}
int get(int key) {
if(cacheMap.find(key) != cacheMap.end())
{
if(operVec.size() == size)
operVec.erase(operVec.begin() + getIndex(key));
operVec.insert(operVec.begin(), key);
return cacheMap[key];
}
return -1;
}
void put(int key, int value) {
if(operVec.size() == size)
{
if(cacheMap.find(key) == cacheMap.end())
{
cacheMap.erase(operVec[size - 1]);
operVec.erase(operVec.begin() + size - 1);
}
else
{
operVec.erase(operVec.begin() + getIndex(key));
}
}
else
{
if(cacheMap.find(key) != cacheMap.end())
{
operVec.erase(operVec.begin() + getIndex(key));
}
}
cacheMap[key] = value;
operVec.insert(operVec.begin(), key);
}
int getIndex(int key)
{
for(int i = 0; i < operVec.size(); i++)
{
if(key == operVec[i])
return i;
}
return -1;
}
};
-
鏈表
鏈表也是平時經常使用的一種數據結構,它在使用上剛好與數組的時間複雜度互補,刪除和插入操作都是O(1),但是在查找方面需要從頭到尾依次遍歷,所以爲O(N)。鏈表由分爲單向鏈表,雙向鏈表,循環鏈表等,在使用中主要掌握鏈表的添加和刪除操作。將兩個升序鏈表合併爲一個新的 升序 鏈表並返回。新鏈表是通過拼接給定的兩個鏈表的所有節點組成的。
//遍歷的方法來實現
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode *preHead = new ListNode(-1);
ListNode *node = preHead;
while(l1 && l2)
{
if(l1->val <= l2->val)
{
node->next = l1;
l1 = l1->next;
}
else
{
node->next = l2;
l2 = l2->next;
}
node = node->next;
}
node->next = l1 == nullptr ? l2 : l1;
return preHead->next;
}
//使用遞歸
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if(!l1)
return l2;
if(!l2)
return l1;
if(l1->val < l2->val)
{
l1->next = mergeTwoLists(l1->next, l2);
return l1;
}
else
{
l2->next = mergeTwoLists(l1, l2->next);
return l2;
}
}
給定一個鏈表,兩兩交換其中相鄰的節點,並返回交換後的鏈表。
//遍歷
ListNode* swapPairs(ListNode* head) {
ListNode *node = head, *prev = NULL, *prev2 = NULL;
head = (head && head->next) ? head->next : head;
while(node)
{
if(!prev)
{
prev = node;
node = node->next;
}
else
{
prev->next = node->next;
node->next = prev;
if(prev2)
prev2->next = node;
node = prev->next;
prev2 = prev;
prev = NULL;
}
}
return head;
}
//遞歸
ListNode* swapPairs(ListNode* head) {
if(!head || !(head->next))
return head;
ListNode *first = head, *second = head->next;
first->next = swapPairs(second->next);
second->next = first;
return second;
}
編寫代碼,移除未排序鏈表中的重複節點。保留最開始出現的節點。
ListNode* removeDuplicateNodes(ListNode* head) {
unordered_set<int> st;
ListNode *node = head, *prev=nullptr;
while(node)
{
if(st.count(node->val))
{
prev->next = node->next;
}
else
{
prev = node;
st.insert(node->val);
}
if(node->next == nullptr)
prev->next = nullptr;
node = node->next;
}
return head;
}
給定一個鏈表,判斷鏈表中是否有環。
//快慢指針方法,快指針一次2步,慢指針一次1步,如果兩者相等,則說明有環狀存在。
bool hasCycle(ListNode *head) {
if(head == nullptr || head->next == nullptr){
return false;
}
ListNode* slow = head;
ListNode* fast = head->next;
while(slow != fast){
if(fast == nullptr || fast->next == nullptr){
return false;
}
slow = slow->next;
fast = fast->next->next;
}
return true;
}
接雨水 給定 n 個非負整數表示每個寬度爲 1 的柱子的高度圖,計算按此排列的柱子,下雨之後能接多少雨水。
int trap(vector<int>& height) {
if(height.size() < 3)
return 0;
int res = 0, left = 0, right = height.size() - 1;
int leftMax = 0, rightMax = 0;
while(left < right)
{
if(height[left] < height[right])
{
height[left] >= leftMax ? leftMax = height[left] : res += leftMax - height[left];
left++;
}
else
{
height[right] >= rightMax ? rightMax = height[right] : res += rightMax - height[right];
right--;
}
}
return res;
}
- 棧
棧也是線性存儲結構的一種,遵守先進後出(FILO)原則,只能在棧頂進行添加和刪除操作。作爲數據存儲以及計算機系統的基本結構,各種編程語言在編譯時候也是使用棧的格則來進行操作的,在考慮問題時,如果該問題有連續相關性,基本就可以用棧來解決。比如二叉樹的DFS遍歷,就是棧使用的典型範例。
二叉樹的遍歷
//遍歷
vector<int> postorderTraversal(TreeNode* root) {
vector<int> res;
if(root)
{
stack<TreeNode*> st;
st.push(root);
while(!st.empty())
{
TreeNode* temp = st.top();
st.pop();
if(temp)
{
st.push(temp);
st.push(nullptr);
if(temp->right)
st.push(temp->right);
if(temp->left)
st.push(temp->left);
}
else
{
res.push_back(st.top()->val);
st.pop();
}
}
}
return res;
}
/*
* 只需要更換中間幾行順序,就可以完美實現前序,中序,後序遍歷,用壓入null節點來作爲輸出標記
*/
給定一個 N 叉樹,返回其節點值的前序遍歷。
vector<int> preorder(Node* root) {
vector<int> res;
if(root)
{
stack<Node *> st;
st.push(root);
while(!st.empty())
{
Node *temp = st.top();
st.pop();
res.push_back(temp->val);
for(int i = temp->children.size() - 1; i >= 0; i--)
{
st.push(temp->children[i]);
}
}
}
return res;
}
給定一個二叉樹,判斷其是否是一個有效的二叉搜索樹
bool isValidBST(TreeNode* root) {
if(!root || (!(root->left) && !(root->right)))
{
return true;
}
long long left = LONG_MIN;
stack<TreeNode*> st;
st.push(root);
while(!st.empty())
{
TreeNode* tm = st.top();
st.pop();
if(tm)
{
if(tm->right)
st.push(tm->right);
st.push(tm);
st.push(nullptr);
if(tm->left)
st.push(tm->left);
}
else
{
tm = st.top();
st.pop();
if(tm->val > left)
{
left = tm->val;
}
else
{
return false;
}
}
}
return true;
最小棧,給定一個棧可以快速的返回棧內最小值
//解題思路爲維護兩個棧,min棧內存儲每個節點的對應的最小值
class MinStack {
stack<int> st;
stack<int> stMin;
public:
/** initialize your data structure here. */
MinStack() {
stMin.push(INT_MAX);
}
void push(int x) {
st.push(x);
if(stMin.top() >= x)
stMin.push(x);
}
void pop() {
if(stMin.top() == st.top())
stMin.pop();
st.pop();
}
int top() {
return st.top();
}
int getMin() {
return stMin.top();
}
};
-
隊列
隊列作爲與棧對應的一種結構,主要區別在先進先出(FIFO),只能在隊頭添加刪除元素,在隊尾添加元素。與棧類似,也是用於處理連續相關性比較多,常見於樹的BFS遍歷,界面的滑動取值等方面。在基礎數據結構中還存在雙向隊列(Deque)的數據結構,結合了棧和隊列的特點,在雙端皆可以進行添加和刪除操作。給定一個含有 n 個正整數的數組和一個正整數 s ,找出該數組中滿足其和 ≥ s 的長度最小的連續子數組,並返回其長度。如果不存在符合條件的連續子數組,返回 0。
int minSubArrayLen(int s, vector<int>& nums) {
int minSize = INT_MAX;
deque<int> deq;
int sum = 0;
for(int i = 0; i < nums.size(); i++)
{
deq.push_back(nums[i]);
sum += nums[i];
while(sum - deq.front() >= s)
{
sum -= deq.front();
deq.pop_front();
}
if(sum >= s && deq.size() < minSize)
{
minSize = deq.size();
}
}
if(minSize > nums.size())
return 0;
return minSize;
}
//遍歷解法
int minSubArrayLen(int s, vector<int>& nums) {
int minSize = INT_MAX;
int sum = 0, j = 0;
for(int i = 0; i < nums.size(); i++)
{
sum += nums[i];
while(sum >= s)
{
minSize = min(minSize, i - j + 1);
sum -= nums[j++];
}
}
return minSize > nums.size() ? 0 : minSize;
}
給你一個二叉樹,請你返回其按 層序遍歷 得到的節點值。 (即逐層地,從左到右訪問所有節點)。
vector<vector<int>> levelOrder(TreeNode* root) {
vector <vector <int>> ret;
if (!root)
return ret;
queue <TreeNode*> q;
q.push(root);
while (!q.empty()) {
int currentLevelSize = q.size();
ret.push_back(vector <int> ());
for (int i = 1; i <= currentLevelSize; ++i) {
auto node = q.front(); q.pop();
ret.back().push_back(node->val);
if (node->left) q.push(node->left);
if (node->right) q.push(node->right);
}
}
return ret;
}
設計循環雙端隊列
class MyCircularDeque {
int size;
vector<int> values;
public:
/** Initialize your data structure here. Set the size of the deque to be k. */
MyCircularDeque(int k) {
size = k;
}
/** Adds an item at the front of Deque. Return true if the operation is successful. */
bool insertFront(int value) {
if(values.size() == size)
return false;
values.insert(values.begin(), value);
return true;
}
/** Adds an item at the rear of Deque. Return true if the operation is successful. */
bool insertLast(int value) {
if(values.size() == size)
return false;
values.push_back(value);
return true;
}
/** Deletes an item from the front of Deque. Return true if the operation is successful. */
bool deleteFront() {
if(values.size() > 0)
{
values.erase(values.begin());
return true;
}
return false;
}
/** Deletes an item from the rear of Deque. Return true if the operation is successful. */
bool deleteLast() {
if(values.size() > 0)
{
values.pop_back();
return true;
}
return false;
}
/** Get the front item from the deque. */
int getFront() {
if(values.size() > 0)
return values.front();
return -1;
}
/** Get the last item from the deque. */
int getRear() {
if(values.size() > 0)
return values.back();
return -1;
}
/** Checks whether the circular deque is empty or not. */
bool isEmpty() {
return values.empty();
}
/** Checks whether the circular deque is full or not. */
bool isFull() {
return values.size() == size;
}
};