2020刷题
2020刷题
动态规划
基本思想
问题的最优解如果可以由子问题的最优解得到,则可以先求子问题的最优解,再构造原问题的最优解;若原问题有较多的重复出现,则可以自底向上从最终子问题向原问题逐步求解。
斐波那契数列
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#include <deque>
using namespace std;
class Solution {
public:
int fib(int n) {
// 初始条件处理
int first = 0, second = 1;
if (n == 0)
return first;
if (n == 1)
return second;
int res = 0;
for (int i = 2; i <= n; i++){ // for和while循环均可
res = (first + second) % (int)(1e9+7);
first = second;
second = res;
}
return res;
}
};
int main()
{
Solution s;
cout << s.fib(6) << endl;
return 0;
}
背包问题
- 01背包:每件物品只有一个
- 完全背包:物品数量无限,对于第i件物品,选择拿0,1,2...件,直到超过背包的大小(遍历的时候增加一层循环)
- 多重背包问题:物品的数量有限,两个限制条件:物品数量和背包大小
- 多维背包问题:物品的重量和体积两个限制条件,此时动态数组为三维的
- 分组背包:每个组内的物品最多只能选一件,动态数组为前k组物品花费v能取得的最大权值
- 总件数最小,多少件物品可以装满背包等等
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#include <deque>
using namespace std;
int bag_1(vector<int> weight, vector<int>value, int size){
// 01背包问题
// 每件物品只有一个,value和weight都大于0
// 物品不可拆分
// weight={2, 3, 4, 5}, value={3, 4, 5, 6}
int m = weight.size(), n = size;
vector<vector<int> > dp(m + 1, vector<int>(n + 1, 0));
for (int i = 1; i <= m; i++){
for (int j = 1; j <= n; j++){
if (weight[i - 1] > j){
dp[i][j] = dp[i - 1][j];
}
else{
// 选择是否拿第i件物品
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i - 1]] + value[i - 1]);
}
}
}
return dp[m][n];
}
int bag_2(vector<int> weight, vector<int>value, int size){
// 完全背包问题
// 每件物品数量无限
// weight={2, 3, 4, 5}, value={3, 4, 5, 6}
int m = weight.size(), n = size;
vector<vector<int> > dp(m + 1, vector<int>(n + 1, 0));
for (int i = 1; i <= m; i++){
for (int j = 1; j <= n; j++){
for (int k = 0; k * weight[i - 1] <= j; k++){
// 选择拿几个i物品
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - k * weight[i - 1]] + k * value[i - 1]);
}
}
}
return dp[m][n];
}
int main()
{
vector<int> weight = {2,2,6,6,6,2,2,2};
vector<int> value = {5,5,8,8,8,10,10,10};
cout << bag_1(weight, value, 10) << endl;
return 0;
}
打家劫舍
// 打家劫舍1
class Solution {
public:
int rob(vector<int>& nums) {
if (nums.empty()) return 0;
// 子问题:
// f(k) = 偷 [0..k) 房间中的最大金额
// f(0) = 0
// f(1) = nums[0]
// f(k) = max{ rob(k-1), nums[k-1] + rob(k-2) }
int size = nums.size();
if (size == 1) return nums[0];
vector<int> dp(size, 0);
dp[0] = nums[0];
dp[1] = max(nums[0], nums[1]);
for (int i = 2; i < size; i++){
dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
}
return dp[size - 1];
}
};
// 打家劫舍2
class Solution {
public:
int rob(vector<int>& nums) {
if (nums.empty()) return 0;
// 环状排列以为这第一个房子和最后一个房子只能选择一个
// 选择第一个房子:nums[0:n-2]
// 不选择第一个房子:nums[1:n-1]
// 取两者情况的最大值
int size = nums.size();
if (size == 1) return nums[0];
int max_1 = rob1(nums, 0, size - 2);
int max_2 = rob1(nums, 1, size - 1);
return max(max_1, max_2);
}
int rob1(vector<int>& nums, int start, int end){
int pre_max = 0;
int cur_max = 0;
for (int i = start; i <= end; i++){
int temp = cur_max;
cur_max = max(pre_max + nums[i], cur_max);
pre_max = temp;
}
return cur_max;
}
};
最长公共子序列LCS
-
多种问题都可以转化成LCS
-
让字符串成为回文串的最少插入次数
class Solution {
public:
int longestCommonSubsequence(string text1, string text2) {
int m = text1.size(), n = text2.size();
vector<vector<int> > dp(m + 1, vector<int>(n + 1, 0));
// dp[i][j] 表示text1的前i个字符和text2的前j个字符的最长公共子序列
// 注意第一行,第一列是初始条件,需要赋值为0
for (int i = 1; i <= m; i++){
for (int j = 1; j <= n; j++){
if (text1[i - 1] == text2[j - 1]){
dp[i][j] = dp[i - 1][j - 1] + 1;
}
else{
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[m][n];
}
};
零钱问题
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
// 动态规划解法
// f(i)表示凑成金额i所需最少的硬币个数
// 每次判断时,遍历所有的硬币面值
vector<int> dp(amount + 1, amount + 1); // 注意初始化,硬币数的最大值
dp[0] = 0; // 0可以用0个硬币凑成
for (int i = 1; i <= amount; i++){
for (int j = 0; j < coins.size(); j++){
// 对最后一枚硬币进行遍历
if (coins[j] <= i){
dp[i] = min(dp[i], dp[i - coins[j]] + 1);
}
}
}
return dp[amount] == amount + 1 ? -1 : dp[amount];
}
};
class Solution {
public:
int change(int amount, vector<int>& coins) {
// 动态规划:二维动态规划表
int m = coins.size(), n = amount;
vector<vector<int> > dp(m + 1, vector<int>(n + 1, 0));
for (int i = 1; i <= n; i++){
dp[0][i] = 0;
}
for (int i = 0; i <= m; i++){
// 注意初始化为1
dp[i][0] = 1;
}
// 注意m,n的顺序
for (int i = 1; i <= m; i++){
for (int j = 1; j <= n; j++){
for (int k = 0; k * coins[i - 1] <= j; k++){
// 取0,1,2...枚第i种硬币
dp[i][j] += dp[i - 1][j - k * coins[i - 1]];
}
}
}
return dp[m][n];
}
};
股票问题
回溯法
基本思想
又称试探法,逐步进行探索,发现选择不是正确解,就退回一步重新选择。一般使用递归实现
- 递归出口
- 递归函数的参数
- 递归函数的处理过程
单词搜索
class Solution {
public:
bool dfs(vector<vector<char>>& board, int i, int j, string word,
int len, vector<vector<bool>> &visit){
// i, j 访问到矩阵的哪个元素
// len访问到字符串哪个元素
if (len >= word.size())
return true;
bool res = false;
if (i < board.size() && j < board[0].size() && visit[i][j] == false && board[i][j] == word[len]){
visit[i][j] = true;
if (dfs(board, i, j - 1, word, len + 1, visit) || dfs(board, i, j + 1, word, len + 1, visit)
|| dfs(board, i - 1, j, word, len + 1, visit) || dfs(board, i + 1, j, word, len + 1, visit))
res = true;
visit[i][j] = false;
}
return res;
}
bool exist(vector<vector<char>>& board, string word) {
int m = board.size(), n = board[0].size();
vector<vector<bool> > visit(m, vector<bool>(n, false));
int len = 0, i = 0, j = 0;
bool res;
for (int i = 0; i < m; i++){
for (int j = 0; j < n; j++){
res = dfs(board, i, j, word, len, visit);
if (res == true)
return true;
}
}
return false;
}
};
// 不需要标记矩阵
class Solution {
public:
bool dfs(vector<vector<char>>& board, int i, int j, string word, int len){
// i, j 访问到矩阵的哪个元素
// len 访问到字符串哪个元素
if (len >= word.size())
return true;
bool res = false;
if (i < board.size() && j < board[0].size() && board[i][j] == word[len]){
char temp = board[i][j];
board[i][j] = '#'; // 避免重复访问
if (dfs(board, i, j - 1, word, len + 1) // 访问下一个元素
|| dfs(board, i, j + 1, word, len + 1)
|| dfs(board, i - 1, j, word, len + 1)
|| dfs(board, i + 1, j, word, len + 1))
res = true;
board[i][j] = temp; // 重置为初始状态
}
return res;
}
bool exist(vector<vector<char>>& board, string word) {
if (board.empty()) return false;
int m = board.size(), n = board[0].size();
for (int i = 0; i < m; i++){
for (int j = 0; j < n; j++){
// 从每个格子出发,若成功,直接返回
if(dfs(board, i, j, word, 0))
return true;
}
}
return false;
}
};
解数独
全排列
class Solution {
public:
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int> > res;
vector<int> visit;
backtrack(res, nums, visit);
return res;
}
bool contain(vector<int> &visit, int num){
int i;
for (i = 0; i < visit.size(); i++){
if (visit[i] == num)
break;
}
return i == visit.size() ? false : true;
}
void backtrack(vector<vector<int> > &res, vector<int>& nums, vector<int> &visit){
// 结束条件
if (visit.size() == nums.size()){
res.push_back(visit);
return;
}
// 做出选择
// 进入决策树
// 撤销选择
for (int i = 0; i < nums.size(); i++){
if (contain(visit, nums[i]) == true) continue;
visit.push_back(nums[i]);
backtrack(res, nums, visit);
visit.pop_back();
}
return;
}
};
class Solution {
public:
vector<vector<int> > res;
vector<int> sol;
vector<int> nums;
vector<vector<int>> permuteUnique(vector<int>& nums) {
vector<bool> used(nums.size(), false);
sort(nums.begin(), nums.end());
this -> nums = nums;
dfs(used);
return res;
}
void dfs(vector<bool> &used){
if (sol.size() == nums.size()){
res.push_back(sol);
return;
}
for (int i = 0; i < nums.size(); i++){
// 两种情况:当前值用过了和当前值未用过
// 1. nums[i-1] 没用过,说明回溯到了同一层,此时接着使用nums[i]会与nums[i-1]重复
// 2. nums[i-1] 用过了, 说明此时在nums[i-1]的下一层,相等不会重复
if (used[i] || (i > 0 && !used[i-1] && nums[i] == nums[i-1]))
continue;
used[i] = true;
sol.push_back(nums[i]);
dfs(used);
sol.pop_back();
used[i] = false;
}
}
};
N皇后
class Solution {
public:
vector<vector<string> > res;
vector<string> cur;
vector<vector<string>> solveNQueens(int n) {
vector<string> cur(n, string(n, '.'));
this -> cur = cur;
backtrack(0);
return res;
}
void backtrack(int row){
if (row >= cur.size()){
res.push_back(cur);
return;
}
for (int i = 0; i < cur.size(); i++){
if (valid(row, i) == false) continue;
cur[row][i] = 'Q';
backtrack(row + 1);
cur[row][i] = '.';
}
return;
}
bool valid(int row, int col){
bool flag = true;
// 行无冲突
// 其实行无需检测,因为是对当前行的遍历
for (int i = 0; i < cur.size(); i++){
if (cur[row][i] == 'Q'){
flag = false;
break;
}
}
// 列检测
for (int i = 0; i < cur.size() && flag == true; i++){
if (cur[i][col] == 'Q'){
flag = false;
break;
}
}
// 左上方无冲突
for (int i = row - 1, j = col - 1; flag == true && i >= 0 && j >= 0; i--, j--){
if (cur[i][j] == 'Q'){
flag = false;
break;
}
}
// 右上方无冲突
for (int i = row - 1, j = col + 1; flag == true && i >= 0 && j < cur.size(); i--, j++){
if (cur[i][j] == 'Q'){
flag = false;
break;
}
}
return flag;
}
};
二叉树
链表
BFS
一般用来求解:从一个点,到另一个终点,求最短路径
双向BFS:起点和终点一起遍历
二叉树的最小深度
// 最小深度
class Solution {
public:
int minDepth(TreeNode* root) {
if (!root) return 0;
int step = 1;
queue<TreeNode*> q;
q.push(root); // 初始化队列
while (!q.empty()){
int size = q.size();
for (int i = 0; i < size; i++){
TreeNode* cur = q.front();
q.pop();
// 判断是否到达终止条件
if (cur -> left == NULL && cur -> right == NULL)
return step;
if (cur -> left != NULL)
q.push(cur -> left);
if (cur -> right != NULL)
q.push(cur -> right);
}
step++;
}
return step;
}
};
二分搜索
- 基本二分
- 左边界二分
- 右边界二分
int basic_binary(vector<int> nums, int target){
// 左右闭区间
int left = 0, right = nums.size() - 1;
// 终止条件:left > right
while (left <= right){
// 防止溢出
// 注意/不能直接换成>>,因为>>是最后计算的
int mid = left + (right - left) / 1;
if (nums[mid] == target)
return mid;
else if (nums[mid] > target){
right = mid - 1;
}
else if (nums[mid] < target){
left = mid + 1;
}
}
return -1;
}
int left_binary(vector<int> nums, int target){
// 寻找左侧边界的二分搜索
// 左右闭区间, 搜索区间为[mid, right]
int left = 0, right = nums.size() - 1;
// 终止条件:left > right
while (left <= right){
// 防止溢出
int mid = left + (right - left) / 2;
if (nums[mid] == target)
// 搜索区间为[left, mid - 1]
right = mid - 1;
else if (nums[mid] > target){
// 搜索区间为[left, mid - 1]
right = mid - 1;
}
else if (nums[mid] < target){
// 搜索区间为[mid + 1, right]
left = mid + 1;
}
}
if (left >= nums.size() || nums[left] != target){
return -1;
}
return left;
}
int right_binary(vector<int> nums, int target){
// 寻找右侧边界的二分搜索
// 左右闭区间, 搜索区间为[mid, right]
int left = 0, right = nums.size() - 1;
// 终止条件:left > right
while (left <= right){
// 防止溢出
int mid = left + (right - left) / 2;
if (nums[mid] == target)
// 搜索区间为[mid + 1, right]
left = mid + 1;
else if (nums[mid] > target){
// 搜索区间为[left, mid - 1]
right = mid - 1;
}
else if (nums[mid] < target){
// 搜索区间为[mid + 1, right]
left = mid + 1;
}
}
if (right < 0 || nums[right] != target){
return -1;
}
return right;
}
滑动窗口
- 双指针
- 快慢指针
最小覆盖子串
class Solution {
public:
string minWindow(string s, string t) {
// 滑动窗口: [left, right)
map<char, int> need, window;
for (char c:t)
need[c]++;
int left = 0, right = 0;
int valid = 0; // 表示窗口中满足need条件的字符数
int start = 0, len = INT32_MAX;
while (right < s.size()){
char c = s[right]; // 即将移入窗口的字符
right++; // 右移窗口
if (need.count(c)){
window[c]++;
if (window[c] == need[c]){
valid++;
}
}
while (valid == need.size()){
// 更新最小覆盖子串
if (right - left < len){
start = left;
len = right - left;
}
// d为即将移除窗口的字符
char d = s[left];
left++;
if (need.count(d)){
if (window[d] == need[d]){
valid--;
}
window[d]--;
}
}
}
return len == INT32_MAX ? "" : s.substr(start, len);
}
};
双指针
class Solution {
public:
bool hasCycle(ListNode *head) {
if (!head) return false;
ListNode* slow = head, *fast = head;
while (fast != NULL && fast -> next != NULL){
fast = fast -> next -> next;
slow = slow -> next;
if (fast == slow){
return true;
}
}
return false;
}
};
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
if (!head) return NULL;
ListNode *slow = head, *fast = head;
while (fast != NULL && fast -> next != NULL){
fast = fast -> next -> next;
slow = slow -> next;
if (fast == slow){
slow = head;
while (fast != slow){
fast = fast -> next;
slow = slow -> next;
}
return slow;
}
}
return NULL;
}
};
常见
- 曼哈顿距离:参考 思路为x,y分开计算,然后使用前缀和和后缀和(注意思想)
邮局选址
bool cmp(int p1, int p2){
return p1 < p2;
}
void print(vector<int> &nums){
cout << "-----------" << endl;
for (auto num:nums){
cout << num << endl;
}
}
int dist(vector<int> &nums, int target){
vector<int> pre(nums.size(), 0), aft(nums.size(), 0);
sort(nums.begin(), nums.end(), cmp);
int index = 0;
for (; index < nums.size(); index++){
if (nums[index] > target){
break;
}
}
index--;
int sum = 0;
for (int i = 0; i < nums.size(); i++){ // 前缀和
sum += nums[i];
pre[i] = sum;
}
sum = 0;
for (int i = nums.size() - 1; i >= 0; i--){ // 后缀和
sum += nums[i];
aft[i] = sum;
}
cout << index << endl;
return 0;
}
int main()
{
vector<int> nums = {1, 5, 6, 7, 2, 3, 4, 8, 9};
dist(nums, 4);
return 0;
}
自定义sort
struct Point{
int x;
int y;
Point(int new_x, int new_y): x(new_x), y(new_y){}
};
void print(const Point p){
cout << "(" << p.x << ", " << p.y << ")" << endl;
}
bool cmp(const Point p1, const Point p2){
return p1.x < p2.x; // 递增排序
// return p1.x < p2.x; // 递减排序
}
void my_sort(){
vector<Point> p;
p.push_back(Point(1, 3));
p.push_back(Point(5, 2));
p.push_back(Point(4, 9));
p.push_back(Point(2, 1));
sort(p.begin(), p.end(), cmp);
for (int i = 0; i < p.size(); i++){
print(p[i]);
}
}
x的n次方
int pow(int x, int n){
if (n == 0) return 1;
if (x == 1) return 1;
int res = 1;
if (n % 2 == 0){
int temp = pow(x, n / 2);
res = temp * temp;
}
else{
int temp = pow(x, n / 2);
res = temp * temp * x;
}
return res;
}
kmp算法
快速进行字符串模式匹配,暴力法需要进行指针回溯,复杂度高
// 右值匹配, 蛮力算法
int pipei(string str, string pattern){
// 返回子串在主串中的位置
int i = 0, j = 0;
while (i < str.size() && j < pattern.size()){
if (str[i] == pattern[j]){
i++;
j++;
}
else{
i = i - j + 1;
j = 0;
}
}
return j == pattern.size() ? i - j : -1;
}
int main()
{
string str = "acdfraxyxyrrwwe";
string pattern = "xyxy";
cout << pipei(str, pattern) << endl;
return 0;
}
void getNext(string str, vector<int> &next){
// a b a b c d a b a b b d的next数组为:
//[-1 0 0 1 2 0 0 1 2 3 4 0]
// 用字符串本身与字符串进行匹配
// 当前字符匹配: next[i + 1] = next[i] + 1 = j + 1
// 当前字符不匹配: j = next[j]
next[0] = -1;
int i = 0, j = -1;
while (i < next.size() - 1){
if (j == -1 || str[i] == str[j]){
i++;
j++;
next[i] = j;
}
else{
j = next[j];
}
}
}
// kmp算法
int pipei(string str, string pattern){
// i指向主串,j指向模式串
vector<int> next(pattern.size(), 0);
getNext(pattern, next);
int i = 0, j = 0;
// 特别注意:j可能为-1,但是size()的返回值为unsigned int
// 需要先转成int再进行比较
while (i < str.size() && j < (int)pattern.size()){
if (j == -1 || str[i] == pattern[j]){
i++;
j++;
}
else{
// 当前位置不匹配,j指向下一个位置
j = next[j];
}
}
return j == pattern.size() ? i - j : -1;
}
int main()
{
string str = "ababcdababbd";
string pattern = "daba";
cout << pipei(str, pattern) << endl;
return 0;
}
生产者消费者
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
using namespace std;
sem_t sem; // 定义全局的信号量结构
pthread_mutex_t mutex; // 定义全局互斥锁
// pthread_mutex_lock(&mutex) // 阻塞加锁,加锁成功返回0
// pthread_mutex_trylock(&mutex) // 非阻塞加锁,线程不是挂起等待
// pthread_mutex_unlock(&mutex) // 解锁
void A(){
cout << "A" << endl;
sem_post(&sem);
}
void B(){
sem_wait(&sem);
cout << "B" << endl;
}
int main()
{
pthread_t privider, handler;
sem_init(&sem, 0, 0); // 初始化信号量
pthread_mutex_init(&mutex, NULL); // 初始化锁
pthread_create(&privider, NULL, (void*)&A, NULL);
pthread_create(&handler, NULL, (void*)&B, NULL);
pthread_join(privider, NULL); // 获取另一个线程的终止状态,释放该线程的资源
pthread_join(handler, NULL);
sem_destroy(&sem);
return 0;
}
nSum问题
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
sort(nums.begin(), nums.end());
vector<int> res;
int left = 0, right = nums.size() - 1;
while (left < right){
if (nums[left] + nums[right] == target){
res.push_back(left);
res.push_back(right);
return res;
}
else if (nums[left] + nums[right] > target){
right--;
}
else{
left++;
}
}
return res;
}
};
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int> > res;
if (nums.size() < 3) return res;
sort(nums.begin(), nums.end());
int a;
for (int i = 0; i < nums.size(); i++){
if (i > 0 && nums[i] == nums[i -1])
continue;
a = nums[i];
int low = i + 1, high = nums.size() - 1;
while (low < high){
int left = nums[low], right = nums[high];
if (left + right < - a){
low++;
}
else if (left + right > - a){
high--;
}
else{
vector<int> cur;
cur.push_back(a);
cur.push_back(left);
cur.push_back(right);
res.push_back(cur);
while (low < high && nums[low] == left) low++;
while (low < high && nums[high] == right) high--;
}
}
}
return res;
}
};
洗牌算法
通过蒙特卡洛算法进行随机性验证,即多次洗牌,看各个结果出现次数是否平均
void shuffle(vector<int>nums){
int n = nums.size();
for (int i = 0; i < n; i++){
int rand = randInt(i, n - 1); // [i, n-1]随机选取一个元素
swap(nums[i], nums[rand]);
}
}
快排
int partition(vector<int> &nums, int left, int right){
int pivot = nums[left];
int index = left + 1; // 指向大于pivot的第一个元素
for (int i = left + 1; i <= right; i++){
if (nums[i] < pivot){
swap(nums[index], nums[i]);
index++;
}
}
index--; // 需要减1后交换
swap(nums[left], nums[index]);
return index;
}
void quick_sort(vector<int> &nums, int left, int right){
if (left >= right) return;
int index = partition(nums, left, right);
quick_sort(nums, left, index - 1);
quick_sort(nums, index + 1, right);
}
int main()
{
vector<int> nums = {6, 3, 7, 5, 2, 9, 1};
quick_sort(nums, 0, 6);
for (auto i:nums)
cout << i << endl;
return 0;
}
归并排序
void merge(vector<int> &nums, int left, int right, int mid){
vector<int> temp;
int i = left, j = mid + 1;
while (i <= mid && j <= right){
if (nums[i] <= nums[j]){
temp.push_back(nums[i]);
i++;
}
else{
temp.push_back(nums[j]);
j++;
}
}
while (i <= mid){
temp.push_back(nums[i]);
i++;
}
while (j <= right){
temp.push_back(nums[j]);
j++;
}
for (int i = left; i <= right; i++){
nums[i] = temp[i - left]; // 注意temp的下标
}
}
void merge_sort(vector<int> &nums, int left, int right){
if (left >= right) return;
int mid = (left + right) / 2;
merge_sort(nums, left, mid);
merge_sort(nums, mid + 1, right);
merge(nums, left, right, mid);
}
int main()
{
vector<int> nums = {6, 3, 7, 5, 2, 9, 1, 0};
merge_sort(nums, 0, 7);
for (auto i:nums)
cout << i << endl;
return 0;
}
鸡蛋掉落
鸡蛋掉落 看题解
智力题
男孩女孩
两个孩子,其中一个为男孩,另一个也是男孩的概率是多少?
生日悖论
23个人中,至少两个人生日是同一天的概率达到50%