-
数组
数组基本上属于使用最多的一种数据结构,因为其连续的内存分布,使得访问和查找等方面都可以做到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;
}
};