刷题算法框架

本文深入探讨了动态规划在解决子序列、背包、路径、打家劫舍、股票买卖等问题中的应用,详细介绍了各种经典问题的状态转移方程和边界条件。同时,还涵盖了回溯、BFS、DFS、滑动窗口、二分搜索等算法框架,以及图遍历、区间问题的处理策略。通过实例解析,帮助读者掌握这些算法的实现思路和优化方法。
摘要由CSDN通过智能技术生成

1 动态规划框架
(1)定义变量(一直变化的值,无限一定不是变量)
(2)写状态转移方程
(3)求最优值
(4)确定边界

1.1 子序列
(1)最大子序和
(2)最长上升子序列
(3)最大回文子序列
(4)最长公共子序列
(5)二维上升子序列
(6)编辑距离

int n = array.length;
int[] dp = new int[n];

for (int i = 1; i < n; i++) {
    for (int j = 0; j < i; j++) {
        dp[i] = 最值(dp[i], dp[j] + ...)
    }
}
int n = arr.length;
int[][] dp = new dp[n][n];

for (int i = 0; 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] = 最值(...)
    }
}

1.2 背包
(1)0-1背包
(2)完全背包
(3)子集划分
(4)零钱兑换
0-1

for i in [1..N]:
    for w in [1..W]:
        dp[i][w] = max(
            dp[i-1][w],
            dp[i-1][w - wt[i-1]] + val[i-1]
        )
return dp[N][W]

完全

for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= amount; j++) {
        if (j - coins[i-1] >= 0)
            dp[i][j] = dp[i - 1][j] 
                     + dp[i][j-coins[i-1]];
return dp[N][W]

1.3 路径
// 状态转移

for (int i = 1; i < m; i++) {
    for (int j = 1; j < n; j++) {
        dp[i][j] = Math.min(
            dp[i - 1][j],
            dp[i][j - 1]
        ) + grid[i][j];
    }
}
// 定义:从 src 出发,k 步之内到达 s 的最短路径权重
int dp(int s, int k) {
    // base case
    if (s == src) {
        return 0;
    }
    if (k == 0) {
        return -1;
    }
    // 初始化为最大值,方便等会儿取最小值
    int res = Integer.MAX_VALUE;
    if (indegree.containsKey(s)) {
        // 当 s 有入度节点时,分解为子问题
        for (int[] v : indegree.get(s)) {
            int from = v[0];
            int price = v[1];
            // 从 src 到达相邻的入度节点所需的最短路径权重
            int subProblem = dp(from, k - 1);
            // 跳过无解的情况
            if (subProblem != -1) {
                res = Math.min(res, subProblem + price);
            }
        }
    }
    // 如果还是初始值,说明此节点不可达
    return res == Integer.MAX_VALUE ? -1 : res;
}

1.4 打家劫舍

 int rob(int[] nums) {
    int n = nums.length;
    // dp[i] = x 表示:
    // 从第 i 间房子开始抢劫,最多能抢到的钱为 x
    // base case: dp[n] = 0
    int[] dp = new int[n + 2];
    for (int i = n - 1; i >= 0; i--) {
        dp[i] = Math.max(dp[i + 1], nums[i] + dp[i + 2]);
    }
    return dp[0];
}

1.5 股票买卖
1.6 最小次数
1.7 游戏问题(戳气球、扔鸡蛋)
1.8.1 戳气球
1.9 贪心算法
1.9.1 区间
1、排序。常见的排序方法就是按照区间起点排序,或者先按照起点升序排序,若起点相同,则按照终点降序排序。当然,如果你非要按照终点排序,无非对称操作,本质都是一样的。
2、画图。就是说不要偷懒,勤动手,两个区间的相对位置到底有几种可能,不同的相对位置我们的代码应该怎么去处理。
场景1:求最少的不相交区间
根据end升序排序,存在交集,每次选择最小的end。

int count = 1;
int curEnd = intvs[0][1];
for (int[] intv : intvs) {
    int start = intv[0];
    if(curEnd <= start){
        count++;
        x_end = interval[1];
    }
}

场景2:使用最少的短区间拼接长区间
按照start升序排列,存在交集,每次选择最大的end

for (int[] clip : clips) {
    while (i < n && clip[0] <= curEnd) {
        nextEnd = Math.max(clip[1], nextEnd);
        i++;
    }
    // 找到下一个视频,更新 curEnd
    res++;
    curEnd = nextEnd;
    if (curEnd > time) {
        return res;
    }
}

场景3:给你输入若干时间区间,让你计算同一时刻「最多」有几个区间重叠。

分成两个数组,一个按照start排序,一个按照end排序,遇到start 就count+1,遇到end就count-1
  // 扫描过程中的计数器
    int count = 0;
    // 双指针技巧
    int res = 0, i = 0, j = 0;
    while (i < n && j < n) {
        if (begin[i] < end[j]) {
            // 扫描到一个红点
            count++;
            i++;
        } else {
            // 扫描到一个绿点
            count--;
            j++;
        }
        // 记录扫描过程中的最大值
        res = Math.max(res, count);
    }

    return res;
}

1.9.2跳跃游戏

2 回溯框架

public void backTrack(nums,track) {
    for (int i = 0; i < nums.length; i++) {
        if (满足了条件) {
            res.add(track);
        }
        //剪枝
        track.add(nums[i]);
        backTrack(nums);
        track.remove(nums[i]);
    }
}

3 BFS框架

public void bfs(TreeNode root) {
    Queue<TreeNode> queue = new LinkedList();
    queue.add(root);
    while (!queue.isEmpty()) {
        //该层元素个数
        int size = queue.size();
        for (int i = 0; i < size; i++) {
            TreeNode node = queue.poll();
            if (node.left != null) {
                queue.offer(node.left);
            }
            if (node.right != null) {
                queue.offer(node.right);
            }
        }
    }
}

4 树遍历框架

// 二叉树遍历框架
void traverse(TreeNode root) {
    traverse(root.left);
    traverse(root.right);
}

5 DFS框架(二维)

void dfs(int[][] grid, int i, int j, boolean[][] visited) {
    int m = grid.length, n = grid[0].length;
    if (i < 0 || j < 0 || i >= m || j >= n) {
        //越界
        return;
    }
    if (visited[i][j]) {
        //已遍历 
        return;
    }
    visited[i][j] = true;
    //上
    dfs(grid, i - 1, j, visited);
    //下
    dfs(grid, i + 1, j, visited);
    //左
    dfs(grid, i, j - 1, visited);
    //右
    dfs(grid, i, j + 1, visited);
}

6 滑动窗口框架

public void slidingWindow(String s) {
    //定义窗口
    StringBuilder window;
    int left = 0;
    int right = 0;
    for (int i = 0; i < s.length(); i++) {
        char c = s.charAt(right);
        window.append(c);
        if (window 需要缩减){
            char d = s.charAt(left);
            left++;
        }
    }
}

7 二分搜索框架

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

8 反转链表框架

int reverse(ListNode node) {
    ListNode pre = null, cur = node, nxt = node;
    while (cur.next != null) {
        nxt = cur.next;
        cur.next = pre;
        pre = cur;
        cur = nxt;
    }
}

9 移除相同元素框架

int removeDuplicates(int[] nums) {
    if (nums.length == 0) {
        return 0;
    }
    int slow = 0, fast = 0;
    while (fast < nums.length) {
        if (nums[fast] != nums[slow]) {
            slow++;
            // 维护 nums[0..slow] 无重复
            nums[slow] = nums[fast];
        }
        fast++;
    }
}

10 图遍历

void traverse(Graph graph, int s) {
    if (visited[s]) return;
    // 经过节点 s
    visited[s] = true;
    for (TreeNode neighbor : graph.neighbors(s))
        traverse(neighbor);
}

11 区间相关
(1)排序
(2)取反

12 快速排序框架

void sort(int[] nums, int lo, int hi) {
    if (lo > hi) return;
    // 通过交换元素构建分界点索引 p
    int p = getIndex(nums, lo, hi);
    sort(nums, lo, p - 1);
    sort(nums, p + 1, hi);
}

```java
private int getIndex(int[] nums, int lo, int hi) {
    int pivot = nums[lo];
    while (lo < hi) {
        while (lo < hi && nums[hi] > pivot) hi--;
        nums[lo] = nums[hi];
        while (lo < hi && nums[lo] < pivot) lo++;
        nums[hi] = nums[lo];
    }
    nums[lo] = pivot;
    return lo;
}

13 单调栈

List<Integer> nextGreaterElement(int[] nums) {
    for (int i = nums.length - 1; i >= 0; i--) {
        if (!stack.isEmpty() && nums[i] > stack.peek()) {
            stack.pop();
        }
        // nums[i] 身后的 next great number
        res[i] = s.empty() ? -1 : s.top();
        stack.push(nums[i]);
    }
    return res;
}

14 前缀和

List<Integer> prefixSum(int[] nums) {
    sum[0] = 0;
    for (int i = 0; i < nums.length; i++) {
        sum[i + 1] = sum[i] + nums[i];
    }
    // sum of nums[j..i-1]
    sum[i] - sum[j]
}

15 差分数组

public class Difference {

    private int[] diff;

    public Difference(int[] nums) {
        assert nums.length > 0;
        diff = new int[nums.length];
        // 构造差分数组
        diff[0] = nums[0];
        for (int i = 1; i < nums.length; i++) {
            diff[i] = nums[i] - nums[i - 1];
        }
    }

    public void increment(int i, int j, int val) {
        diff[i] += val;
        if (j + 1 < diff.length) {
            diff[j + 1] -= val;
        }
    }

    public int[] result() {
        int[] res = new int[diff.length];
        // 根据差分数组构造结果数组
        res[0] = diff[0];
        for (int i = 1; i < diff.length; i++) {
            res[i] = res[i - 1] + diff[i];
        }
        return res;
    }
}

16 nSum
两数之和

public List<List<Integer>>  twoSum(int[] nums, int target) {
    Arrays.stream(nums).sorted();
    int lo = 0;
    int hi = nums.length - 1;
    while (lo < hi) {
        int sum = nums[lo] + nums[hi];
        int left = nums[lo], right = nums[hi];
        if (sum < target) {
            lo++;
        } else if (sum > target) {
            hi--;
        } else {
           res.add({left,right});
           while (lo<hi && nums[lo] == left) lo++;
           while (lo < hi && nums[hi] == right) hi--;
        }
    }
}

三数之和(其他的依次类推)

/* 计算数组 nums 中所有和为 target 的三元组 */
vector<vector<int>> threeSumTarget(vector<int>& nums, int target) {
    // 数组得排个序
    sort(nums.begin(), nums.end());
    int n = nums.size();
    vector<vector<int>> res;
    // 穷举 threeSum 的第一个数
    for (int i = 0; i < n; i++) {
        // 对 target - nums[i] 计算 twoSum
        vector<vector<int>> 
            tuples = twoSumTarget(nums, i + 1, target - nums[i]);
        // 如果存在满足条件的二元组,再加上 nums[i] 就是结果三元组
        for (vector<int>& tuple : tuples) {
            tuple.push_back(nums[i]);
            res.push_back(tuple);
        }
        // 跳过第一个数字重复的情况,否则会出现重复结果
        while (i < n - 1 && nums[i] == nums[i + 1]) i++;
    }
    return res;
}

17 区间算法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值