labuladong算法

一,双指针

1.1链表

1、合并两个有序链表
2、链表的分解
3、合并 k 个有序链表(用到归并思想,每次合并两条链表)
4、寻找单链表的倒数第 k 个节点(前后两个指针,先走k步)
5、寻找单链表的中点(快慢指针,fast走两步,slow走一步,当 fast 走到链表末尾时,slow 就指向了链表中点)
6、判断单链表是否包含并找出环起点
(快慢指针,fast2步,slow1步,当fast.next为null,则无环,若fast与slow相遇,有环)
(快慢指针相遇时,fast回到起点,slow继续在相遇点,继续走,两指针每次走1步,再次相遇则是相遇点)
7、判断两个单链表是否相交并找出交点
(链表A后面接链表B,链表B后面接链表A,两指针走,当走到两指针值一样,则是交点,且有交点)

1.2数组

左右指针快慢指针

【原地修改数组】(快慢指针
1、删除有序数组中的重复项(快慢指针,fast遇到重复项跳过,否则赋值给slow)
2、移动零,将数组中的所有值为 0 的元素移到数组末尾(思路与上题一样,fast遇到0跳过,最后把slow后面都赋值为0)

左右指针
1、二分查找(left与right指针)
2、升序数组,两数之和为目标值(左右指针,开始在两端,相向而行)
3、反转数组(左右指针,开始在两端,相向而行,交换值)
4、回文串判断(左右指针,开始在两端,相向而行)
5、最长回文子串(左右指针,从中间向两端,得到回文子串。原字符串每个i遍历一遍,每次都pal(i,i)与pal(i,i+1)。以 s[i] 为中心,以 s[i] 和 s[i+1] 为中心)

二,二叉树

思想

1、是否可以通过遍历一遍二叉树得到答案?

回溯算法「遍历」

如果可以,用一个 traverse 函数配合外部变量来实现,这叫「遍历」的思维模式。
没有返回值的backtrack函数

2、是否可以定义一个递归函数,通过子问题(子树)的答案推导出原问题的答案?

动态规划算法「分解子树」

如果可以,写出这个递归函数的定义,并充分利用这个函数的返回值,这叫「分解问题」的思维模式。
带有返回值的 dp 函数

层序遍历(BFS框架)

迭代遍历
队列实现,常用于求无权图的最短路径问题

三,动态规划

动态规划问题
一般形式就是求最值(比如说:求最长递增子序列,最小编辑距离呀等等)
核心问题是穷举

列出正确的「状态转移方程
判断算法问题是否具备「最优子结构」,是否能够通过子问题的最值得到原问题的最值
存在「重叠子问题」,使用「备忘录」或者「DP table」来优化穷举过程,避免不必要的计算

思想

明确 base case -> 明确「状态」-> 明确「选择」 -> 定义 dp 数组/函数的含义。

# 方法1:自顶向下递归的动态规划
def dp(状态1, 状态2, ...):
    for 选择 in 所有可能的选择:
        # 此时的状态已经因为做了选择而改变
        result = 求最值(result, dp(状态1, 状态2, ...))
    return result

# 方法2:自底向上迭代的动态规划
# 初始化 base case
dp[0][0][...] = base case
# 进行状态转移
for 状态1 in 状态1的所有取值:
    for 状态2 in 状态2的所有取值:
        for ...
            dp[状态1][状态2][...] = 求最值(选择1,选择2...)

1、斐波那契数列
方法1(暴力穷举)
方法2(带备忘录的递归解法)
造一个「备忘录」(数组或哈希表),每次算出某个子问题的答案后别急着返回,先到「备忘录」里再返回;每次遇到一个子问题先去「备忘录」里一查,如果发现之前已经解决过这个问题了,直接把答案拿出来用
方法3dp 数组的迭代解法)
把这个「备忘录」独立出来成为一张表,通常叫做 DP table,在这张表上完成「自底向上」的推算

2、凑零钱问题,零钱兑换

四,回溯算法

回溯算法是在遍历「树枝」,DFS 算法是在遍历「节点」

思想

1、路径:也就是已经做出的选择。
2、选择列表:也就是你当前可以做的选择。
3、结束条件:也就是到达决策树底层,无法再做选择的条件。
回溯框架
核心就是 for 循环里面的递归,在递归调用之做选择」,在递归调用之撤销选择

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

1、全排列
2、N皇后。
本质上跟全排列问题差不多,决策树的每一层表示棋盘上的每一行;每个节点可以做出的选择是,在该行的任意一列放置一个皇后。
因为皇后是一行一行从上往下放,只检查了左上角,右上角和上方的格子

(排列组合子集)
主要变体:
形式一元素无重不可复选,即 nums 中的元素都是唯一的,每个元素最多只能被使用一次,这也是最基本的形式。
形式二元素可重不可复选,即 nums 中的元素可以存在重复,每个元素最多只能被使用一次。
(如果一个节点有多条值相同的树枝相邻,则只遍历第一条,剩下的都剪掉,不要去遍历)
(需要先进行排序,让相同的元素靠在一起,如果发现 nums[i] == nums[i-1],则跳过:)
形式三元素无重可复选,即 nums 中的元素都是唯一的,每个元素可以被使用若干次。

五,BFS算法

本质:在一幅「图」中找到从起点 start 到终点 target 的最短路径

BFS框架:

(队列与 哈希表)

var BFS = function(start, target) {
    var q = []; // 核心数据结构
    var visited = new Set(); // 避免走回头路
    
    q.push(start); // 将起点加入队列
    visited.add(start);
    var step = 0; // 记录扩散的步数

    while (q.length != 0) {
        var sz = q.length;
        /* 将当前队列中的所有节点向四周扩散 */
        for (var i = 0; i < sz; i++) {
            var cur = q.shift();
            /* 划重点:这里判断是否到达终点 */
            if (cur === target)
                return step;
            /* 将 cur 的相邻节点加入队列 */
            for (var x in cur.adj()) {
                if (!visited.has(x)) {
                    q.push(x);
                    visited.add(x);
                }
            }
        }
        /* 划重点:更新步数在这里 */
        step++;
    }
};

1、二叉树的最小高度
2、打开转盘锁(解开密码锁的最少次数,有死亡锁)
问题:
1、会走回头路。
2、终止条件。
3、对 deadends 的处理
解决方法:
1,用set记录需要跳过的死亡密码(剪枝),已经穷举过的密码

双向BFS

本质:
传统的 BFS 框架就是从起点开始向四周扩散,遇到终点时停止;
双向 BFS 则是从起点终点同时开始扩散,当两边有交集的时候停止。(局限性:必须知道终点在哪里)

六,二分搜索算法

寻找一个数

[left,right]
while(left<=right)
相等便返回

var binarySearch = function(nums, target) {
    var left = 0; // 注意
    var right = nums.length - 1; // 注意

    while(left <= right) {
        var mid = left + Math.floor((right - left) / 2);
        if(nums[mid] == target)
            return mid; 
        else if (nums[mid] < target)
            left = mid + 1; // 注意
        else if (nums[mid] > target)
            right = mid - 1; // 注意
    }
    return -1;
};

寻找左侧边界

[left,right)
while(left<right)
相等后,在[left,mid-1]内搜索,即[left, mid)

var left_bound = function(nums, target) {
    var left = 0;
    var right = nums.length; // 注意
    while (left < right) { // 注意
        var mid = left + Math.floor((right - left) / 2);
        if (nums[mid] == target) {
            right = mid;
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid; // 注意
        }
    }
    return left;
}

寻找右侧边界

[left,right)
while(left<right)
相等后,在[mid+1,right)内搜索,即[mid + 1, right)

var right_bound = function(nums, target) {
    var left = 0, right = nums.length;
    
    while (left < right) {
        var mid = left + Math.floor((right - left) / 2);
        if (nums[mid] === target) {
            left = mid + 1; // 注意
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid;
        }
    }
    return left - 1; // 注意
}

七,滑动窗口

框架

var slidingWindow = function(s) {
    const window = new Map();
    
    let left = 0, right = 0;
    while (right < s.length) {
        // c 是将移入窗口的字符
        const c = s[right];
        // 增大窗口
        right++;
        // 进行窗口内数据的一系列更新
        ...
        
        // (循环)判断左侧窗口是否要收缩
        while (window needs shrink) {
            // d 是将移出窗口的字符
            const d = s[left];
            // 缩小窗口
            left++;
            // 进行窗口内数据的一系列更新
            ...
        }
    }
};

八,股票买卖

状态机

dp[i][k][0 or 1]
0 <= i <= n - 1, 1 <= k <= K
n 为天数,大 K 为交易数的上限,01 代表是否持有股票。
此问题共 n × K × 2 种状态,全部穷举就能搞定。

状态转移方程

base case:
dp[-1][...][0] = dp[...][0][0] = 0
dp[-1][...][1] = dp[...][0][1] = -infinity

状态转移方程:
dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])

九,并查集

let parent=new Array(n).fill(0).map((element,index)=>index)
const find=function(parent,index){
    if(parent[index]!=index){
        parent[index]=find(parent,parent[index])
    }
    return parent[index]
}
const union=function(parent,index1,index2){
    parent[find(parent,index1)]=find(parent,index2)
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值