刷题没啥感觉,就拿起了《labuladuong的算法小抄》,感觉还不错,记录一下笔记,以下代码均为伪代码,便于理解
动态规划
形式:求最值
核心:穷举
动态规划的琼剧有点特别,存在重叠子问题,需要备忘录或者DP table来优化琼剧过程
三要素:重叠子问题、最优子结构、状态转移方程
斐波那契数列
1.暴力递归
时间复杂度O(2^n),指数级别,爆炸。
动态规划问题的第一个性质:重叠子问题
2.带备忘录的递归解法
int fib(int N){
if(N<0)return 0;
//备忘录初始化为0
memo;
return helper(memo,N);
}
int helper(memo,N){
//base case
if n==1||n==2------->return 1;
//已计算过
if(memo[n]!=0)return memo[n];
memo[n]=helper(memo,n-1)+helper(memo,n-2);
return memo[n];
}
3.迭代解法
自底向上的解法,使用table保存表格,或者仅存储之前的两个状态
状态方程:
凑零钱问题
1.暴力队规
2.备忘录的递归
3.迭代
int coinChange(coins,amount){
//数组大小为amout+1,初始值为amout+1
dp(amout+1,amout+1);
//base case
dp[0]=0;
for(i=0;i<dp.size();i++){
for(int coin:coins){
//子问题无解,跳过
if(i-coin<0)continue;
dp[i]=min(dp[i],dp[i-coin]+1);
}
}
//如果还是初始值,则为无解,返回-1
return (dp[amount]==amount+1)?-1:dp[amount];
}
回溯算法
实质:决策树的遍历
三步骤:1.做选择
2.进入下一步决策
3.撤销选择
res
def backtrack(路径,选择列表):
if 满足结束:
res.add(路径)
return
for 选择 in 选择列表:
做选择
backtrack()
撤销选择
全排列问题
List<List<Integer>> res = new List<>();
//主函数
List<List<Integer>> premute(int[] nums){
LinkedList<Integer> track = new ListedList<>();
backtrack(nums,track);
return res;
}
void backtrack(int[] nums,LinkedList<Integer> track){
//触发结束条件
if(track.size==nums.length){
res.add(new ListedList(track));
return;
}
for(int i = 0;i<nums.length;i++){
//排除不合法的选择
if(track.contains(num[i]))continue;
//做选择
track.add(num[i]);
//进入下一层决策树
backtrack(nums,track);
//取消选择
track.removeLast();
}
}
N皇后问题
res
vector<vector<string>> solveNQuees(int n){
vector<string> board(n,string(n,'.'));
backtrack(board,0);
return res;
}
void backtrack(board,row){
//结束条件
if(row==board.size){
res.add
return
}
int n = board[row].size();
for(int col=0;col<n;col++){
//排除不合法
if(!isValid()){
continue;
}
//做选择
board[row][col]='Q';
//下一步选择
backtrack(board,row+1);
//撤销选择
board[row][col]='.';
}
}
二分查找
框架
int binarySearch(int[] nums,int target){
int left = 0,right=...;
while(...){
int mid = left +(right-left)/2;
if(nums[mid]==target){
...
}else if(<){
...
}else if(>){
}
}
return ...;
}
技巧:
1.不要出现else
2.注意right初始值,while条件,left= ,right=(搜索空间的闭开关系)
滑动窗口算法框架
void slidingWindow(String s,string t){
map<char,int> need,window;
for(char c:t)need[c]++;
int left=right=0;
int valid=0;
while(right<s.size()){
charc = s[right];
right++;
//进行窗口内数据的一系列更新
...
/**debug 输出的位置**/
print(left,right);
//判断左侧是否要收缩
while(window needs shrink){
char d = s[left];
left++;
//窗口内数据的一些列更新
...
}
}
}
最小覆盖子串
string minWindow(string s,string t){
map<> need,window;
t--->need;
int left=right=0;
int valid=0;
int start = 0,len=INT_MAX;
while(right<s.size()){
char c=s[right];
right++;
//窗口数据更新
if(need.contain(c)){
window[c]++;
if(window[c]==need[c]){
valid++;
}
}
//判断是否收缩
while(valid==need.size()){
//更新最小覆盖子串
if(right-left){
start=left
len = right-left;
}
char d =s[left];
left++;
//窗口数据更新
if(need.contain(d)){
window[d]--;
if(window[d]==need[d]){
valid--;
}
}
}
}
return len == INT_MAX? "" : s.substr(start,len);
}
BFS算法
本质:图种找到start到target的最近问题
比如:迷宫,连连看…
框架
int BFS(Node start,Node target){
Queue<Node> q;//核心数据结构
Set<Node> visited;//避免走回头路
q.add(start);
visit.add(start);
int step=0;
while(q.empty()){
for(int i = 0;i<q.size;i++){
Node cur = q.pop();
//划重点:这里判断是否到达终点
if(cur is target)
return step;
for(Node x:cur.adj()){//adj()表示周围的值
if(x not in visited){
q.add(x);
visited.add(x);
}
}
}
step++;
}
}
二叉树的最小深度
int minDepth(TreeNode root){
if(root==null) return 0;
Queue q;
q.add(root);
int depth=1;
while(!q.isEmpty()){
for(int i =0;i<q.size;i++){
TreeNode cur = q.pop();
//判断是否终点
if(cur.left&&right==null){
return depth;
}
if(cur.left!=null)q.add(cur.left);
if(cur.right!=null)q.add(cur.right);
}
depth++;
}
return depth;
}