常见面试算法总结

快排

时间复杂度 O(n*log(n))

空间复杂度 O(1)

#pragma once
#include <vector>
using namespace std;

static void exchnage_number(vector<int> &nums, int x, int y) {
	int tmp = nums[x];
	nums[x] = nums[y];
	nums[y] = tmp;
}

static int partition(vector<int> &nums, int begin, int end) {
	//随机一个元素,避免最差情况 1,2,3,4
	int x = begin + rand() % (end - begin + 1);
	exchnage_number(nums, x, end);
	//p1 始终指向第一个小于x的位置初始化为 -1
	//p2 用于遍历
	int p1 = begin - 1, p2 = begin;
	while (p2 < end)
	{
		if (nums[p2] < nums[end]) {//一旦发小了就让P1+1的位置为这个数字
			p1++;
			exchnage_number(nums, p1, p2);//原先p1 + 1 的位置一定大于x
		}
		p2++;
	}

	p1++; //p1此时为第一个大于等于x的位置
	exchnage_number(nums, p1, end);
	return p1;
}

void quick_sort(vector<int> &nums, int begin, int end) {
	if (begin < end) {
		int pos = partition(nums, begin, end);
		quick_sort(nums, begin, pos - 1);
		quick_sort(nums, pos + 1, end);
	}
}

归并

时间复杂度 O(n*log(n))

空间复杂度 O(n)

static void merge_vec(vector<int> &nums, int p1, int p2, int len) {
	int cur = 0;
	int i = p1, j = p2;
	vector<int> tmpVec(len * 2);
	//j不能越界
	while (i < p1 + len && j < p2 + len && j < nums.size()) {
		tmpVec[cur++] = nums[i] < nums[j] ? nums[i++] : nums[j++];
	}

	while (i < p1 + len) {
		tmpVec[cur++] = nums[i++];
	}

	while (j < p2 + len && j < nums.size()) {
		tmpVec[cur++] = nums[j++];
	}

	for (int k = 0; k < cur; ++k) {
		nums[p1++] = tmpVec[k];
	}
}

void merge_sort(vector<int> &nums) {
	int len = 1;
	while (len < nums.size()) {
		int i = 0;
		while (i < nums.size()) {
			int fir = i;
			int sec = i + len;
			if (sec < nums.size()) {
				merge_vec(nums, fir, sec, len);
			}
			i += 2 * len;
		}
		len *= 2;
	}
}

KMP

时间复杂度 O(n + m)

空间复杂度 O(n)

aa b aa
前缀aa 和 后最aa 相等那么我们直接判断 aa 后面的下一个字符

int KMP(string haystack, string needle) {
	if (needle.empty()) {
		return 0;
	}
	//0 意味着你需要从第一个位置开始判断了
	vector<int> pre(needle.size(), 0);
	/*
	* 根据模式串来构建前缀表
	*/
	for (int i = 1, j = 0; i < needle.size(); ++i) {
		while (j > 0 && needle[j] != needle[i]) {
			//如果当前失败了 前缀数组指引我们应该从哪一个位置开始判断
			j = pre[j - 1];
		}

		if (needle[j] == needle[i]) {
			j++;
		}
		//needle[i] 和 needle[i] 相等,所以前缀表的第i个位置指向 j + 1
		//就是该位置的后一个位置期望是当前前缀的j + 1位置
		pre[i] = j;
	}

	for (int i = 0, p = 0; i < haystack.size(); i++) {
		while (p > 0 && haystack[i] != needle[p]) {
			//因为相对的P位置已经匹配不上
			//查询前缀表 我们当前位置最快可以和谁比较
			p = pre[p - 1];
		}

		if (haystack[i] == needle[p]) {
			p++;
		}
		//已经等于模式串的长度了,可以结束了
		if (p == needle.size()) {
			return i - needle.size() + 1;
		}
	}

	return -1;
}

并查集

	int findFather(vector<int> &father, int i) {
		if (father[i] != i) {
			father[i] = findFather(father, father[i]);
		}

		return father[i];
	}

	bool uion(vector<int> &father, int i, int j) {
		int fatherI = findFather(father, i);
		int fatherJ = findFather(father, j);

		if (fatherI != fatherJ) {
			father[fatherI] = fatherJ;
			return true;
		}

		return false;
	}
//use....................
	for (int i = 0; i < n; ++i) {
		for (int j = i + 1; j < n; ++j) {
			if (graph[i][j] == 1 && uion(father, i, j))
				ret--;
		}
	}


最大堆 和 最小堆

struct AA {
	int id;	

	bool operator< (const AA &a) const {
		return id < a.id;
	}
	bool operator> (const AA &a) const {
		return id > a.id;
	}

};

//less -> 最大堆;  greater -> 最小堆
	priority_queue<AA, vector<AA>, greater<AA>> mQ;

/ 最新
/ 用法
/ 想要小的在前就用大于号,比如按字母顺序输出,就要用大于号
/

	auto cmp = [](const pair<string, int> & x, const pair<string, int> & y) {
		return x.second == y.second ? x.first > y.first : x.second < y.second;
	};
	priority_queue<pair<string, int>, vector<pair<string, int>>, decltype(cmp) > q(cmp);



STL堆——通过map 找第一个大于x的元素

	map<int, int>::iterator it, p1, p2;
	//大于等于 x 的第一个元素
	p1 = mp.lower_bound(3);
	//大于 x 的第一个元素
	p2 = mp.upper_bound(3);


String 使用

	string s1 = "01234";
	auto s2 = s1.substr(1, 1); // "1"
	s1 = s1.erase(3);	//"012"
	s1.append(2, '#'); //"012##"

链表常用操作

	struct ListNode {
		int val;
		ListNode *next;
		ListNode() : val(0), next(nullptr) {}
		ListNode(int x) : val(x), next(nullptr) {}
		ListNode(int x, ListNode *next) : val(x), next(next) {}
		
	};
	/** ---------------- 反转链表 ---------------------**/
	ListNode* reverseList(ListNode* head) {
		ListNode *pre = NULL, *cur = head;
		while (cur != NULL) {
			ListNode *next = cur->next;
			cur->next = pre;
			pre = cur;
			cur = next;
		}

		return pre;
	}
	/** ---------------- 获取链表中位数 ---------------------**/
		//偶数时正好 奇书时前半段多一个
	ListNode* getMidList(ListNode* head) {
		ListNode dummy;
		dummy.next = head;

		ListNode *fast = &dummy, *slow = &dummy;
		while (fast != NULL && fast->next != NULL) {
			slow = slow->next;
			fast = fast->next;
			if (fast->next != NULL)
				fast = fast->next;
		}
		return slow->next;
	}

组合问题

整体思路都是围绕每一个位置的元素是选还是不选

	void serchSubsets(vector<vector<int>> &rets, vector<int> &cache, vector<int>& nums, int p) {
		if (p >= nums.size()) {
			rets.emplace_back(cache);
			return;
		}
		//不要
        serchSubsets(rets, cache, nums, p + 1);
		//要
		cache.push_back(nums[p]);
		serchSubsets(rets, cache, nums, p + 1);
		cache.pop_back();
	}
	/ 过滤重复元素 / 
	void dfs(vector<vector<int>> &rets, vector<int> &cache, vector<int>& candidates, int target, int p) {
		if (target == 0) {
			rets.emplace_back(cache);
		} else if (target > 0 && p < candidates.size()) {
		

			cache.push_back(candidates[p]);
			dfs(rets, cache, candidates, target - candidates[p], p + 1);
			cache.pop_back();

            dfs(rets, cache, candidates, target, getNext(candidates, p));
		}
	}

	int getNext(vector<int>& candidates, int index) {
		int next = index + 1;
		while (next < candidates.size() && candidates[index] == candidates[next]) {
			next++;
		}

		return next;
	}

排列问题

不含重复元素,我最早的构想,拿标记 递归 回溯
	#define USED 100

	void dfs(vector<vector<int>> &rets, vector<int> &cache, vector<int>& nums) {
		if (cache.size() == nums.size()) {
			rets.emplace_back(cache);
		}
		else {
			for (int i = 0; i < nums.size(); ++i) {
				if (USED != nums[i]) {
					cache.push_back(nums[i]);
					nums[i] = USED;
					dfs(rets, cache, nums);
					nums[i] = cache.back();
					cache.pop_back();
				}
			}
		}
	}

	vector<vector<int>> permute(vector<int>& nums) {
		vector<vector<int>> rets;
		vector<int> cache;

		dfs(rets, cache, nums);

		return rets;
	}
版本2
	void search(vector<vector<int>> &rets, vector<int>& nums, int i) {
		if (i == nums.size() - 1) {
			rets.emplace_back(nums);
		}
		else {
			for (int j = i; j < nums.size(); ++j) {
				int tmp = nums[j]; nums[j] = nums[i]; nums[i] = tmp;
				search(rets, nums, i + 1);
                tmp = nums[j]; nums[j] = nums[i]; nums[i] = tmp;
			}
		}
	}

	vector<vector<int>> permute(vector<int>& nums) {
		vector<vector<int>> rets;
		search(rets, nums, 0);

		return rets;
	}
包含重复元素

核心思想,如果排序后和前一个相等,且前一个已经推出搜索,我们直接跳过,因为会做重复的工作


	void dfs(vector<vector<int>> &rets, vector<int> &cache, vector<int>& nums, vector<int> &mark) {
		if (cache.size() == nums.size()) {
			rets.emplace_back(cache);
		}
		else {
			for (int i = 0; i < nums.size(); ++i) {
				if (mark[i] == 0) {
					if (i - 1 >= 0 && nums[i] == nums[i - 1] && mark[i - 1] == 0)
						continue;

					cache.push_back(nums[i]);
					mark[i] = 1;
					dfs(rets, cache, nums, mark);
					mark[i] = 0;
					cache.pop_back();
				}
			}
		}
	}

	vector<vector<int>> permuteUnique(vector<int>& nums) {
		vector<vector<int>> rets;
		vector<int> cache;
		vector<int> mark(nums.size(), 0);
		sort(nums.begin(), nums.end());
		dfs(rets, cache, nums, mark);

		return rets;
	}
版本2
	void ExChange(vector<int> &nums, int i, int j) {
		int tmp = nums[i];
		nums[i] = nums[j];
		nums[j] = tmp;
	}

	void searchPer(vector<vector<int>> &ret, vector<int>& nums, int p) {
		if (p == nums.size()) {
			ret.emplace_back(nums);
			return;
		}
		else {
			unordered_set<int> st;

			for (int i = p; i < nums.size(); ++i) {
				if (st.find(nums[i]) == st.end()) {
					st.insert(nums[i]);
					
					ExChange(nums, i, p);
					searchPer(ret, nums, p + 1);
					ExChange(nums, i, p);
				}
			}
		}
	}

	vector<vector<int>> permuteUnique(vector<int>& nums) {
		vector<vector<int>> ret;

		searchPer(ret, nums, 0);

		return ret;
	}

前缀树

每一步的时间复杂度都是 n(单词长度)

	struct Node {
		vector<Node*> chirds;
		bool isEnd;
		Node() :chirds(vector<Node*>(26, NULL)), isEnd(false) {
		};
	};

	void insertToTree(Node* root, const string& str) {
		Node* p = root;
		for (char c : str) {
			if (p->chirds[c - 'a'] == NULL) {
				p->chirds[c - 'a'] = new Node();
			}
			p = p->chirds[c - 'a'];
		}

		p->isEnd = true;
	}

	string findPre(Node* root, string &word) {
		string ret = "";
		Node* p = root;
		for (char c : word) {
			if (p->chirds[c - 'a'] == NULL)
				break;

			p = p->chirds[c - 'a'];
			ret.append(1, c);

			if (p->isEnd)
				break;
		}

		return p->isEnd ? ret : "";
	}

二分

右收敛 那么左可以相等
左收敛 那么右可以相等
根据题意

	int peakIndexInMountainArray(vector<int>& arr) {
		int i = 0, j = arr.size() - 1;

		while (i < j) {
			int mid = j - (j - i) / 2; 
			if (arr[mid] > arr[mid - 1]) {
				i = mid;
			}
			else {
				j = mid - 1;
			}
		}
		return i;
	}

滑动窗口

1.初始化 i = 0 ,j =0
2.j 向后扩展,记录满足条件的元素
3 i 弹出不满足的窗口前部
4. 窗口大小为 j - i + 1

	 int lengthOfLongestSubstringKDistinct(string s, int k) {
		 unordered_map<char, int> table;
		 int count = 0;

		 int ret = 0;

		 for (int i = 0, j = 0; j < s.size(); ++j) {
			 table[s[j]]++;
			 if (table[s[j]] == 1) {
				 count++;
			 }
		 
			 while (count > k) {
				 
				 table[s[i]]--;
				 if (table[s[i]] == 0) {
					 count--;
				 }
				 i++;
			 }

			 ret = max(ret, j - i + 1);
		 }

		 return ret;
	 }

水塘抽样

在这里插入图片描述

	int pick(int target) {
		int ans;
		for (int i = 0, cnt = 0; i < nums.size(); ++i) {
			if (nums[i] == target) {
				++cnt; // 第 cnt 次遇到 target
				if (rand() % cnt == 0) {
					ans = i;
				}
			}
		}
		return ans;
	}

除法不要比大小

6076. 表示一个折线图的最少线段数

本来我是用的是除法,但是一直ac不了,改成乘法后AC

	int minimumLines(vector<vector<int>>& stockPrices) {
		if (stockPrices.size() < 2)
			return 0;

		sort(stockPrices.begin(), stockPrices.end());

		long y = stockPrices[1][1] - stockPrices[0][1];
		long x = stockPrices[1][0] - stockPrices[0][0];

		int ret = 1;

		for (int i = 2; i < stockPrices.size(); ++i) {
			long _y = stockPrices[i][1] - stockPrices[i - 1][1];
			long _x = stockPrices[i][0] - stockPrices[i - 1][0];

			if (_x * y != _y * x) {
				y = _y;
				x = _x;
				ret++;
			}
		}

		return ret;
	}

最小栈

最小栈确定边界

TODO

前缀和

TODO

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值