labuladuo算法小抄模板整理

求模运算的性质:

(a * b) % k = (a % k)(b % k) % k;

框架总结:

回溯算法

result = []
def backtrack(路径, 选择列表):
	if 满足条件列表:
		result.add(路径)
		return 
	
	for选择 in 选择列表:
		做选择
		backtrack(路径, 选择列表)
		撤销选择

二分查找算法

int binarySearch(int[] nums, int target) {
  int left = 0, right = ...;
  
  while (...) {
    int mid = left + (right - left) / 2;
    if (nums[mid] == target) {
      ...
    } else if (nums[mid] < target) {
      left = ...
    } else if (nums[mid] > target) {
      right = ...
    }
  }
  return ...;

滑动窗口算法框架

void slidingWindow(string s, string t) {
  unordered_map<char, int> need, window;
  for (char c: t) {
    need[c]++;
  }
  int left = 0, right = 0;
  int valid = 0;
  while (right < s.size()) {
    char c = s[right++];
    ... // 进行窗口更新的操作
   	// debug的位置
      
    // 判断左窗口是否要收缩。
    while (window needs shrink) {
      char d = s[left++];
      ...// 进行窗口内数据的一系列更新
    }
  }
}

BFS框架

int BFS(Node start, Node target) {
  queue<Node> q;
  set<None> visited;
  
  q.push(start);
  visited.insert(start);
  int step = 0;
  
  while (!q.empty()) {
    int sz = q.size();
    for (int i = 0; i < sz; ++i) {
      Node cur = q.front();
      if (cur == target) {
        return step;
      }
      // 将cur的相邻节点加入队列
      for (Node x : cur.adj()) {
        if (x not in visited) {
          q.push(x);
          visited.insert(x);
        }
      }
    }
    ++step;
  }
}

动态规划的基本框架

for 状态1 in 状态1的所有状态:
	for 状态2 in 状态2的所有状态:
		for ...
			dp[状态1][状态2][...] = 择优(选择1, 选择2, ...)

动态规划子序列问题模板

一维的dp数组:
int n = array.size();
int[] dp = new int[n];
for (int i = 1; i < n; ++i) {
  for (int j = 1; j < i; ++j) {
    dp[i] = 最值(dp[i], dp[j] + ...)
  }
}
// dp[i]的定义:针对单个字符串/数组,如最长递增子序列。
// 在子数组arr[0...i]中,我们要求的子序列(例如:最长递增子序列)的长度是dp[i].
二维的dp数组:
int n = array.size();
int[] dp = new int[n][n];
for (int i = 1; i < n; ++i) {
  for (int j = 1; j < n; ++j) {
    if (arr[i] == arr[j]) {
      dp[i][j] = dp[i][j] + ...;
    } else {
      dp[i][j] = 最值(...);
    }
  }
}
//dp[i][j]的含义:
// case1:涉及两个字符串/数组时:
// 在子数字arr1[0...i]和子数组arr2[0...j]中,我们要求的子序列(如最长公共子序列)的长度为dp[i][j];
// case2:只设计一个字符串/数组时:
// 在arr[i...j]中,我们要求的子序列(例如最长回文子序列)的长度为dp[i][j].

贪心算法调度问题(最多开几个会的类似问题)

int intervalSchedule(vector<vector<int>> intvs) {
  if (intvs.empty()) {
    return 0;
  }
  // 按end升序排序
  sotr(intvs.begin(), intvs.end(), [](vector<int> a, vector<int> b) {
    return b[1] > a[1];
  });
 	// 至少有一个区间不相交
  int count = 1;
  // 排序后,第一个区间就是x
  int x_end = intvs[0][1];
  for (auto interval : intvs) {
    int start = interval[0];
    if (start >= x_end) {
      // 找到下一个选择区间
      ++count;
      x_end = interval[1];
    }
  }
  return count;
}

股票买卖问题

class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {
        // 备注:买进来和卖出去合起来算交易一次。
        if (k < 1 || prices.size() < 2) {
            return 0;
        }
      
      	// dp[i][j][0]:第i天,经过最多j次交易,手里未持有股票的利润。
      	// dp[i][j][1]:第i天,经过最多j次交易,手里持有股票的利润。
        vector<vector<pair<int, int>>> dp(prices.size(), 
            vector<pair<int, int>>(k + 1, make_pair(0, 0)));
        for (int i = 1; i <= k; ++i) {
            dp[0][i].second = -prices[0];
        }
        for (int i = 1; i < prices.size(); ++i) {
            for (int j = 1; j <= k; ++j) {
                dp[i][j].first = max(dp[i - 1][j].first,
                    dp[i - 1][j].second + prices[i]);
                dp[i][j].second = max(dp[i - 1][j].second,
                    dp[i - 1][j - 1].first - prices[i]);
            }
        }
        return dp[prices.size() - 1][k].first;
    }
};

LRU

class Node {
public:
  int key, val;
  Node* next, prev;
  Node(int k, int v) {
    key = k;
    val = v;
  }
};

// 构建双向链表
class DoubleList {
public: 
  // 在链表头部添加节点x
  void addFirst(Node *x);
  
  // 删除链表中的x节点
  void remove(Node *x);
  
  // 删除链表中最后一个节点,并返回
  Node* removeLast();
  
  // 返回链表长度
  int size();
};

class LRUCache {
private: 
  Map<int, Node*> my_map;  // 映射表
  DoubleList cache;  // 双向链表
  int cap;  // 最大容量
  
public:
  LRUCache(int capacity) {
    this.cap = capacity;
  }
  
  int get(int key) {
    if (my_map.count(key) == 0) {
      return -1; 
    }
    int val = my_may.get(key)->val;
    // 利用put方法把该数据提前
    put(key, val);
    return val;
  }
  
  void put(int key, int val) {
    // 先把新节点做出了
    Node* x = new Node(key, val);
    
    if (my_map.count(key)) {
      // 删除旧的节点,新的插到头部
      cache.remove(my_map.get(key));
      cache.addFirst(x);
      // 更新map中的数据
      map.put(key, x);
    } else {
      if (cap == cache.size()) {
        // 删链表最后一个数据
        Node* last = cache.removeLast();
        my_map.remove(last->key);
      }
      // 添加到头部
      cache.addFirst(x);
      map.put(key, x);
    }
  }
};

二叉数算法的框架

// 通用框架
void traverse(TreeNode* root) {
  // root需要做什么,在这里做
  // 其他不用root操心,抛给框架
  traverse(root->left);
  traverse(root->right);
}

// BST(二叉搜索树)的框架
void BST(TreeNode* root, int target) {
  if (root->val == target) {
    // 找到目标,做点什么
  }
  if (root->val < target) {
    BST(root->right, target);
  } else {
    BST(root->right, target);
  }
}

单调栈问题(下一个更大的数)

vector<int> nextGreaterElement(vector<int> &nums) {
  vector<int> ans(nums.size()); // 存放答案的数组
  stack<int> s;
  for (int i = nums.size() - 1; i >= 0; --i) {
    while (!s.empty() && s.top() <= nums[i]) {
      s.pop(); //小的数字弹出,
    }
      ans[i] = s.empty() ? -1 : s.top(); //这个元素后面第一个比他大的值
      s.push(nums[i]); // 进栈
  } 
   return ans;
}

单调队列

class MonotonicQueue {
private:
  deque<int> data;
  
public:
  void push(int n) {
    while (!data.empty() && data.back() < n) {
      data.pop_back();
    }
    data.push_back(n);
  }
  
  int max() {
    return data.front();
  }
  
  void pop(int n) {
    if (!data.empty() && data.front() == n) {
      data.pop_front();
    }
  }
};

vector<int> maxSlidingWindow(vector<int>& nums, int k) {
  MonotonicQueue Window;
  vector<int> res;
  fof (int i = 0; i < nums.size(); ++i) {
    if (i < k - 1) {
      window.push(nums[i]);
    } else {
      window.push(nums[i]);
      res.push_back(window.max());
      window.pop(nums[i - k + 1]);
    }
  }
  return res;
}

twoSum问题

// 利用哈希表优化时间负责度,用空间换取时间。
class TwoSum {
private:
  map<int, int> freq;
public:
  void add(int number) {
    freq.insert(make_pair(number, freq[number]++));
  }
  
  bool find(int value) {
    for (auto key : freq) {
      int other = value - key.first;
      
      // 情况1
      if (other == key.first && key.second > 1) {
        return true;
      }
      
      // 情况2
      if (other != key && freq.count(other) > 0) {
      	return true;
      }
    }
    return false;
  }
};

常用的位操作

// 利用或操作和空格将英文字母转化为小写
('a' | ' ') = 'a';

// 利用与操作和下划线将英文字母转换为大写
('b' & '_') = 'B';

// 利用异或操作和空格进行英文字符大小写互换
('d' ^ ' ') = 'D';

// 判断两个数是否异号
int x = -1, y = 2;
bool f = ((x ^ y) < 0); // true

int x = 3, y = 2;
bool f = ((x ^ y) < 0); // false

// 交换两个数
int a = 1, b = 2;
a ^= b;
b ^= a;
a ^= b;
// 现在a = 2, b = 1;

// 加一
int n = 1;
n = -~n; //n = 2;

// 减一
int n = 2;
n = ~-n; // n = 1;

// 消除数字n的二进制表示中的最后一个1
n & (n - 1);

前缀和

int n = nums.length;
vector<int> pre_sum(n + 1, 0);
pre_sum[0] = 0;
for (int i = 0; i < n; ++i) {
  pre_sum[i + 1] = pre_sum[i] + nums[i];
}

FloodFill算法(二维矩阵中的搜索问题)

// (x, y)为坐标位置
void fill(int x, int y) {
  fill(x - 1, y);  // 上
  fill(x + 1, y);  // 下
  fill(x, y - 1);  // 左
  fill(x, y + 1);  // 右
}

并查集

class UF {
private:
  int count;
  vector<int> parent;
  vector<int> size;
public:
  UF(int n) {
    this->count = n;
    for (int i = 0; i < n; ++i) {
      parent.push_back(i);
      size[i] = 1;
    }
  }
  
  void union(int p, int q) {
    int root_p = find(p);
    int root_q = find(q);
    if (root_p == root_q) {
      return;
    }
    
    if (size[root_p] > size[root_q]) {
      parent[root_p] = root_p;
      size[root_p] += size[root_q];
    } else {
      parent[root_p] = root_q;
      size[root_q] += size[root_p];
    }
    --count;
  }
  
  bool connected(int p, int q) {
    int root_p = find(p);
    int root_q = find(q);
    return  (root_p == root_q);
  }
  
  int find(int x) {
    while (parent[x] != x) {
      parent[x] = parent[parent[x]];
      x = parent[x];
    }
    return x;
  }
  
  int count() {
    return count;
  }
};
  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Leetcode算法小抄是一份权威的算法手册,包含了Leetcode上常见的算法题目的解法和详细讲解。这个小抄对于想要提升自己算法能力的程序员来说非常有用。该小抄包括以下内容: 1.基础数据结构:包括数组、链表、栈、队列、树、哈希表等。 2.算法基础:包括排序算法、搜索算法、贪心算法、动态规划等。 3.高级算法:包括图论、字符串匹配、线性代数、计算几何等。 每个算法题目都附有详细的解析和代码实现,方便程序员进行学习和练习。此外,该小抄还提供了优秀的算法实现其他程序员的思路和解答,这对于新手来说尤为重要。 总之,Leetcode算法小抄是一份非常实用的算法手册,如果你想成为一名出色的程序员,学习和掌握其中的内容必不可少。 ### 回答2: LeetCode算法小抄是一份非常实用的算法指南,它包含了大量的算法问题和解答,而且所有的算法问题都是以LeetCode网站上的题目为蓝本的。这个小抄主要面向准备参加Google、Facebook、 Apple等知名科技公司的笔试或者面试的程序员,也适用于想要提高自己算法能力的人。这份小抄的编制者是Steven Halim和Felix Halim,也就是ACM竞赛的著名选手和教练。他们将自己多年的ACM竞赛经验倾囊相授,帮助大家提高算法能力。小抄中包含了高频出现的数据结构算法,如树、图、排序、数组、动态规划等,每个算法都有详细的解释和代码实现。此外,小抄还包含了一些实用技巧,如测试用例设计、代码调试、复杂度分析等。总之,LeetCode算法小抄是一份非常实用、全面的算法指南,如果你想要提高自己的算法能力,相信它一定能为你带来帮助。 ### 回答3: LeetCode算法小抄是一个常用的算法学习工具,它主要是为了帮助程序员更加高效地学习和掌握LeetCode算法。LeetCode算法小抄中收录了大量经典的算法题目,并提供了详细的题解和代码示例,涵盖了各种数据结构算法和编程技术。 LeetCode算法小抄的优点在于它的简便性和针对性。其内容结构清晰,难度逐渐增加,让读者能够逐步学习并掌握更加复杂的数据结构算法。同时,小抄中提供了大量的代码示例和优化方法,可以帮助读者更加深入地理解和掌握算法。 另外,LeetCode算法小抄还提供了各种算法题目的分类、标签和解法推荐,让读者能够更加容易地找到自己需要的题目和解法。同时,小抄中还提供了一些常见的面试题目和解题思路,可以帮助读者更好地应对工作中和面试中的挑战。 总之,LeetCode算法小抄是一本非常实用的算法学习工具,它可以帮助程序员更加高效地学习和掌握算法知识。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值