精题分享!!

好题分享,总结+经验

57. 插入区间力扣

给你一个 无重叠的 ,按照区间起始端点排序的区间列表。
在列表中插入一个新的区间,你需要确保列表中的区间仍然有序且不重叠(如果有必要的话,可以合并区间)。
示例 1:
输入:intervals = [[1,3],[6,9]], newInterval = [2,5]
输出:[[1,5],[6,9]]
示例 2:
输入:intervals = [[1,2],[3,5],[6,7],[8,10],[12,16]], newInterval = [4,8]
输出:[[1,2],[3,10],[12,16]]
解释:这是因为新的区间 [4,8] 与 [3,5],[6,7],[8,10] 重叠。
示例 3:
输入:intervals = [], newInterval = [5,7]
输出:[[5,7]]
示例 4:
输入:intervals = [[1,5]], newInterval = [2,3]
输出:[[1,5]]
示例 5:
输入:intervals = [[1,5]], newInterval = [2,7]
输出:[[1,7]]
提示:
0 <= intervals.length <= 104
intervals[i].length == 2
0 <= intervals[i][0] <= intervals[i][1] <= 105
intervals 根据 intervals[i][0] 按 升序 排列
newInterval.length == 2
0 <= newInterval[0] <= newInterval[1] <= 105

这个题考察的内容很简单,但是需要分析各种情况。但是核心策略肯定是遍历的时候相互比较,看需要插入的地方在哪里,遍历的时候我们设原数组的值为[x, y],插入数组的值为[a, b]。
先从简单的分析开始

  1. a > y 那么插入的位置肯定再当前位置之后
  2. b < x 那么就找到插入位置了,而且这个位置一定是在中间,没有和原数组有交叉,因为我们在找前面的时候都是a > y 那么这里的a一定大于前一个位置的x
  3. a >= x && b <= y 这就是在中间,不用进行扩展了
  4. a >= x && b > y 要往右边扩展
  5. a < x && b <= y 要往左边扩展
  6. a < x && b > y 两边都要扩展

理清楚各种情况之后就需要去处理,这里我的策略是找到对应的情况的时候,如果是1那就跳过,如果是2就记录插入的位置,并且标记是直接插入不是扩展,其他情况都标记扩展,并且要在当时就扩展被比较的原数组,然后在入库的时候统一进行合并处理。

class Solution 
{
public:
    vector<vector<int>> insert(vector<vector<int>>& intervals, vector<int>& newInterval) 
    {
        vector<vector<int>> res;
        if(intervals.size() == 0) 
        {
            res.push_back(newInterval);
            return res;
        }
        // 是否是插入
        bool isInert = true;
        int tmp = 0, index = intervals.size();
        for(int i = 0; i < intervals.size(); ++i)
        {
            int &x = intervals[i][0], &y = intervals[i][1];
            if(newInterval[0] > y) continue;
            else if(newInterval[1] < x)
            {
                index = i;
                break;
            }
            else if(newInterval[1] <= y && newInterval[0] >= x)
            {
            	// 标记为扩展
                isInert = false;
                break;
            }
            else if(newInterval[0] >= x && newInterval[1] > y)
            {
            	// 标记为扩展并且更新当前被比较的数组
                isInert = false;
                y = newInterval[1];
                break;
            }
            else if(newInterval[0] < x && newInterval[1] <= y)
            {
                isInert = false;
                x = newInterval[0];
                break;
            }
            else if(newInterval[0] < x && newInterval[1] > y)
            {
                isInert = false;
                x = newInterval[0];
                y = newInterval[1];
                break;
            }
        }
        // 统一进行处理
        for(int i = 0; i < intervals.size(); ++i)
        {
            if(isInert && i == index)
            {
                res.push_back(newInterval);
            }
            int t = i;
            for(int j = i + 1; j < intervals.size(); ++j)
            {
                if(intervals[i][1] >= intervals[j][0])
                {
                    intervals[i][1] = max(intervals[i][1], intervals[j][1]);
                    t = j;
                }
                else break;
            }
            res.push_back(intervals[i]);
            i = t;
        }
        if(isInert && index == intervals.size())
            res.push_back(newInterval);
        return res;
    }
};

823. 带因子的二叉树力扣

给出一个含有不重复整数元素的数组 arr ,每个整数 arr[i] 均大于 1。
用这些整数来构建二叉树,每个整数可以使用任意次数。其中:每个非叶结点的值应等于它的两个子结点的值的乘积。
满足条件的二叉树一共有多少个?答案可能很大,返回 对 109 + 7 取余 的结果。
示例 1:
输入: arr = [2, 4]
输出: 3
解释: 可以得到这些二叉树: [2], [4], [4, 2, 2]
示例 2:
输入: arr = [2, 4, 5, 10]
输出: 7
解释: 可以得到这些二叉树: [2], [4], [5], [10], [4, 2, 2], [10, 2, 5], [10, 5, 2].
提示:
1 <= arr.length <= 1000
2 <= arr[i] <= 109
arr 中的所有值 互不相同

看到这个题,不自觉的会感觉种类太多了,太难分了,但是转念一想当我们组合比较复杂的数的时候是可以分为各个小问题来解决,下面的子节点是不是可以提前求出来然后直接求值,那么知道了这个思路就会很轻松解决这个问题了。
关于需要用到前面结果的问题,那么就是动态规划。
但是我们需要先知道小结点的值,所以我们还得先排序,之后就是动态规划的板子了

class Solution 
{
public:
    const int mod = 1e9 + 7;
    int numFactoredBinaryTrees(vector<int>& arr) 
    {
        // 属实是动归
        sort(arr.begin(), arr.end());
        int n = arr.size(), sum = 0;
        vector<long long> dp(n);
        for(int i = 0; i < n; ++i)
        {
            dp[i] = 1;
            for(int left = 0, right = i - 1; left <= right; ++left)
            {
                while(left <= right && (long long)arr[right] * arr[left] > arr[i]) --right;
                if(left <= right && (long long)arr[right] * arr[left] == arr[i])
                {
                    if(left == right) dp[i] = (dp[i] + dp[left] * dp[right]) % mod;
                    else dp[i] = (dp[i] + dp[left] * dp[right] * 2) % mod;
                }
            }
            sum = (sum + dp[i]) % mod;
        }
        return sum;
    }
};

关于如何求里面判断条件可以自行调整

1654. 到家的最少跳跃次数力扣

有一只跳蚤的家在数轴上的位置 x 处。请你帮助它从位置 0 出发,到达它的家。
跳蚤跳跃的规则如下:
它可以 往前 跳恰好 a 个位置(即往右跳)。
它可以 往后 跳恰好 b 个位置(即往左跳)。
它不能 连续 往后跳 2 次。
它不能跳到任何 forbidden 数组中的位置。
跳蚤可以往前跳 超过 它的家的位置,但是它 不能跳到负整数 的位置。
给你一个整数数组 forbidden ,其中 forbidden[i] 是跳蚤不能跳到的位置,同时给你整数 a, b 和 x ,请你返回跳蚤到家的最少跳跃次数。如果没有恰好到达 x 的可行方案,请你返回 -1 。
示例 1:
输入:forbidden = [14,4,18,1,15], a = 3, b = 15, x = 9
输出:3
解释:往前跳 3 次(0 -> 3 -> 6 -> 9),跳蚤就到家了。
示例 2:
输入:forbidden = [8,3,16,6,12,20], a = 15, b = 13, x = 11
输出:-1
示例 3:
输入:forbidden = [1,6,2,14,5,17,4], a = 16, b = 9, x = 7
输出:2
解释:往前跳一次(0 -> 16),然后往回跳一次(16 -> 7),跳蚤就到家了。
提示:
1 <= forbidden.length <= 1000
1 <= a, b, forbidden[i] <= 2000
0 <= x <= 2000
forbidden 中所有位置互不相同。
位置 x 不在 forbidden 中。

其实这个题思路是很明确的,因为这个是典型的dfs选择问题,当我们在一个状态的时候我们是选择往前走还是往后走,这里往后走是由限制条件的。
基本的思路我们了解了,那么就开始考虑终止条件和细节了,我们先看下面的数据范围,我们可以推算得到我最远能走的距离也就是6000,范围知道了,那么根据选择问题我们还得记录状态,要不然就是无限循环了。这里很重要。

  1. 如果我往前走,我们需不需要标记这个点之后不可到达了,答案是需要,因为如果我往前走,那么我们走到的这个点是自由的,也就是下一次我们依旧可以往前走或者往后走,那么我之后无论是以什么方式再回到这个点,我在该点的情况已经走过一遍了,所以之后再来是无意义的。
  2. 但是往后走不一样,我往后走到一个点,我之后只能往前走了,不能继续往后走,所以少了一种往后走的情况,所以不能标记。

然后我们需要维护一个数组来储存被标记的点

class Solution {
private:
    // 递归函数
    // curr 表示当前的位置
    // steps表示步数,默认从0开始
    // lastBack表示上一步是否是back,避免连续两次的back
    void dfs(bool* visited, int a, int b, int x, int& res, int curr, int steps, bool lastBack)
    {
        if (res == -1 && curr >= 0 && curr <= 6000)
        {
            if (curr != x)
            {
                // 要过滤掉已经遍历过的情况
                // 先前进
                if (!visited[curr+a])
                {
                    visited[curr+a] = true;
                    dfs(visited, a, b, x, res, curr+a, steps+1, false);
                }
                // 后后退
                if (!lastBack && (curr-b >= 0) && !visited[curr-b])
                {
                    // 因为前进到一个位置,它两个选择都可以做
                    // visited[curr - b] = true;
                    dfs(visited, a, b, x, res, curr-b, steps+1, true);
                }
            }
            else
            {
                // 达到终点了,直接返回
                res = steps;
            }
        }
    }
public:
    int minimumJumps(vector<int>& forbidden, int a, int b, int x) {
        // unordered_set<int> visited;
        bool visited[8000];
        memset(visited, 0, sizeof(bool)*8000);
        // 默认把forbidden插入已经遍历的里面表示不可达
        for (int f : forbidden)
        {
            // visited.insert(f);
            visited[f] = true;
        }

        // 默认-1表示不可达
        int res = -1;
        dfs(visited, a, b, x, res, 0, 0, false);
        return res;
    }
};

这里为什么找到了就返回,是因为我们每一次都是走一步,找到了就是最优解了,不会存在比这个还小的值了

1504. 统计全 1 子矩形力扣

给你一个 m x n 的二进制矩阵 mat ,请你返回有多少个 子矩形 的元素全部都是 1 。
示例 1:
输入:mat = [[1,0,1],[1,1,0],[1,1,0]]
输出:13
解释:
有 6 个 1x1 的矩形。
有 2 个 1x2 的矩形。
有 3 个 2x1 的矩形。
有 1 个 2x2 的矩形。
有 1 个 3x1 的矩形。
矩形数目总共 = 6 + 2 + 3 + 1 + 1 = 13 。
示例 2:
输入:mat = [[0,1,1,0],[0,1,1,1],[1,1,1,0]]
输出:24
解释:
有 8 个 1x1 的子矩形。
有 5 个 1x2 的子矩形。
有 2 个 1x3 的子矩形。
有 4 个 2x1 的子矩形。
有 2 个 2x2 的子矩形。
有 2 个 3x1 的子矩形。
有 1 个 3x2 的子矩形。
矩形数目总共 = 8 + 5 + 2 + 4 + 2 + 2 + 1 = 24 。
提示:
1 <= m, n <= 150
mat[i][j] 仅包含 0 或 1

这种类型的题,也就是看到矩阵然后要求什么图形之类的,一般都是以 右下角为判断焦点,但是这个题我以前也从来没遇到过,我只知道大概率是以右下角来判断是不是矩形,然后看答案被上了一课。
很妙,看答案都能看懂的,设计的很秒

class Solution 
{
public:
    // 一开始的思路是对的,这种一般都是以右下角来判断一个结果是否合理
    // 但是无奈不会后续的判断
    int numSubmat(vector<vector<int>>& mat) 
    {
        int n = mat.size(), m = mat[0].size();
        vector<vector<int>> dp(n, vector<int>(m));
        int cnt = 0;
        for(int i = 0; i < n; ++i)
        {
            cnt = 0;
            for(int j = 0; j < m; ++j)
            {
                if(mat[i][j] == 1) dp[i][j] = ++cnt;
                else cnt = 0;
            }
        }

        int max_num = INT_MAX, res = 0;
        for(int i = 0; i < n; ++i)
        {
            for(int j = 0; j < m; ++j)
            {
                max_num = dp[i][j];
                for(int k = i; k >= 0 && max_num; --k)
                {
                    // 木桶效益,短板最为重要
                    max_num = min(max_num, dp[k][j]);
                    res += max_num;
                }
            }
        }

        return res;
    }
};

往左上角延申这个是可以想到的,但是后续短板的处理是真的设计的可以。

分享到此结束。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值