leetcode 31-40

31. 下一个排列

分析

应该尽可能的保证排列的高位不变, 变低位, 这样的话才是比当前字典序大的最小的
因此可以从后往前去考虑, 如果到某一位的时候, 当前数与后面的数构成降序关系, 那么当前这位不可能变大,
因为前面这些数保证不变, 当前数已经是后面数中最大的数了, 因此当前位的数字不可能变得更大, 因此当前位不能动任何手脚.
也就是说, 只要后面是降序(不一定严格降序), 那么当前位的数字不可能发生任何变化, 如果想让当前字典序变得大一些, 应该找到一个非降序的位置, 前一个数比后面一个数稍微小一些, 那么前一个数可以稍微变大一些, 因为后面的数中存在比当前数更大的数, 那么当前这位数应该变大成什么数呢, 变大成后面的数中, 比当前数大的最小的数(即大于当前数的且最接近当前数的数), 在这里插入图片描述
应该将后面这个匹配的数与当前数交换, 交换完后, 后面的排列应该越小越好(将后面的数变成升序)
步骤:
找到第1个非降序的位置, 找到比当前数大的最小的数, 交换完(后面还是降序, 因为选出来的数是比当前数大的最小的数), 再将后面的数逆序

类似题 leetcode 60

code

class Solution {
public:
    void nextPermutation(vector<int>& nums) {
        int k = nums.size() - 1;
        while (k > 0 && nums[k - 1] >= nums[k]) k --;
        if (k <= 0) { // 说明整个数列都是降序的
            reverse(nums.begin(), nums.end()); 
        }else {
            int t = k;
            while (t < nums.size() && nums[t] > nums[k - 1]) t ++;
            swap(nums[k - 1], nums[t - 1]);
            reverse(nums.begin() + k, nums.end());
        }
    }
};

32. 最长有效括号

分析

合法的括号序列

  1. ( ) 数量相等
  2. 任意前缀中( 数量 >= )数量

首先, 可以先考虑将整个序列分成若干段.(???)

可以证明任何一个合法序列存在于某一段里, 而不会跨越两段之间
怎么去分段, 从前往后考虑, 记录下左括号数量和右括号数量, 找到第一个不合法的位置(即:第一个前缀)数量 > (数量), 在不合法的位置右边划1条线
在这里插入图片描述

命题: 任何一个合法的括号序列, 不会横跨这条线
证明: 假设存在合法区间1横跨了不合法区间(图中三角线后面划的线的的地方), 那么1所在区间 ( >= )数量, 由于三角形前面所在区间括号不合法, 因此2所在区域 ( < ), 2区间 - 1区间 那么3区间也不合法, 因此存在比三角形更靠前的位置, 有不合法区间, 与图中三角形选出来为第1个不合法区间位置,矛盾
在这里插入图片描述
重新开始计数, 再找到)大于(数量的位置, 再划1条线, 找到所有满足)比较多的位置, 划1条线. 刚才已经证明了任何合法的括号序列, 不会横跨这条线 , 因此找合法的括号序列, 都只需要在每一段内部去找.
每一段内部有一个性质, 除了最后一个)位置, 左括号的数量 >= 右括号的数量

因此在每一段内部, 除了最后一个位置, 都满足( >= ), 意味着任何一个)都能找到匹配的(, 在去考虑这一个段内最大长度时候, 可以对所有合法的序列, 按照右括号的位置来分类. 即: 对每一个右括号, 求一下以这个右括号为右端点的最长的合法序列的左端点在什么位置.
把每个右括号都枚举一遍后, 再取max就是整个的最大长度
怎么找靠左的位置, 使得这一段是合法的?
栈, 将所有妹子放到栈里, 每来一个男生, 那么就将妹子拉走, 那么最靠左的位置是 栈顶元素的后面一个位置, 因为当前栈顶元素无法匹配右括号, 因此最大匹配的左端点 = 当前剩余的栈顶元素的下一个位置, 特殊情况:栈为空的话, 整个区间都是合法区间(从起点到当前位置全取)
在这里插入图片描述

每个元素进栈出栈1次, O(n)

code

class Solution {
public:
    int longestValidParentheses(string s) {
        stack<int> stk;
        int res = 0;
        for (int i = 0, start = -1; i < s.size(); i ++ ){ // start表示起点的前一个位置
            if (s[i] == '('){
                stk.push(i);
            }else {
                if (stk.size()){ // 先要判断栈不空, 才能继续做
                    stk.pop(); // pop完后, 需要考虑栈顶是否为空, 计算最大合法区间
                    if (stk.size()){
                        res = max(res, i - stk.top()); // 合法的左端点位置 = stk.top() + 1, 因为计算区间 后面还要 + 1, 抵消了
                    }else { // 如果当前位置是‘)’ , 且栈为空, 表示右括号无法匹配, 那么划线, 并更新下一段的起点
                        res = max(res, i - start);
                    }
                }else {
                    start = i;
                }
            }
        }
        return res;
    }
};

33. 搜索旋转排序数组

81.搜索旋转排序数组 II 简单版本
两次二分, 第1次找分界点, 第二次找答案

code

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int n = nums.size() - 1;
        int l = 0, r = n;
        while (l < r){
            int mid = l + r + 1 >> 1;
            if (nums[mid] >= nums[0]) l = mid;
            else r = mid - 1;
        }
        if (target >= nums[0]) r = l, l = 0;
        else l ++, r = n;
        while (l < r){
            int mid = l + r >> 1;
            if (nums[mid] >= target) r = mid;
            else l = mid + 1;
        }
        if (nums[r] != target) return -1;
        return r;
    }
};

34. 在排序数组中查找元素的第一个和最后一个位置

分析

二分模板题

code

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        if (nums.empty()) return {-1, -1};
        vector<int> res;
        int l = 0, r = nums.size() - 1;
        while (l < r){
            int mid = l + r >> 1;
            if (nums[mid] >= target) r = mid;
            else l = mid + 1;
        }

        if (nums[l] != target) return {-1, -1};
        else res.push_back(l);

        l = 0, r = nums.size() - 1;
        while (l < r){
            int mid = l + r + 1 >> 1;
            if (nums[mid] <= target) l = mid;
            else r = mid - 1;
        }
        if (nums[r] != target) return {-1, -1};
        else res.push_back(r);
        return res;
    }
};

35. 搜索插入位置

分析

二分模板题
注意: 右边界可以取到

code

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int l = 0, r = nums.size();
        while (l < r){
            int mid = l + r >> 1;
            if (nums[mid] >= target) r = mid;
            else l = mid + 1;
        }
        return l;
    }
};

36. 有效的数独

分析

code

class Solution {
public:
    bool isValidSudoku(vector<vector<char>>& board) {
        bool st[9];
        // 行判断
        for (int i = 0; i < 9; i ++){
            memset(st, 0, sizeof st);
            for (int j = 0; j < 9; j ++){
                if (board[i][j] != '.'){
                    int t = board[i][j] - '1';
                    if (st[t]) return false;
                    st[t] = true;
                }
            }
        }
        // 列判断
        for (int i = 0; i < 9; i ++){
            memset(st, 0, sizeof st);
            for (int j = 0; j < 9; j ++){
                if (board[j][i] != '.'){
                    int t = board[j][i] - '1';
                    if (st[t]) return false;
                    st[t] = true;
                }
            }
        }
        // 3x3小方格判断
        // i, j 表示3x3小方格, 最左上角位置, 每次偏移3格子
        for (int i = 0; i < 9; i += 3)
            for (int j = 0; j < 9; j += 3){
                memset(st, 0, sizeof st);
                // x, y代表3x3小方格内部偏移
                for (int x = 0; x < 3; x ++)
                    for (int y = 0; y < 3; y ++){
                        if (board[i + x][j + y] != '.'){
                            int t = board[i + x][j + y] - '1';
                            if (st[t]) return false;
                            st[t] = true;
                        }
                    }
            }
        return true;
    }

};

37. 解数独

分析

怎么能快速判断当前空位可以填哪些数
需要记录下 每行, 每列, 每个小方阵, 哪些数已经填过了, 开个bool数组就可以了
在这里插入图片描述

code

class Solution {
public:
    bool row[9][9], col[9][9], cell[3][3][9];
    void solveSudoku(vector<vector<char>>& board) {
        memset(row, 0, sizeof row);
        memset(col, 0, sizeof col);
        memset(cell, 0, sizeof cell);
        for (int i = 0; i < 9; i ++ )   
            for (int j = 0; j < 9; j ++ )
                if (board[i][j] != '.'){
                    int t = board[i][j] - '1';
                    row[i][t] = col[j][t] = cell[i / 3][j / 3][t] = 1;
                }
        
        dfs(board, 0, 0);
    }

    bool dfs(vector<vector<char>> &board, int x, int y){
        if (y == 9) y = 0, x ++;
        if (x == 9) return true;

        if (board[x][y] != '.') return dfs(board, x, y + 1);
        for (int i = 0; i < 9; i ++ ){
            if (!row[x][i] && !col[y][i] && !cell[x / 3][y / 3][i]) {
                row[x][i] = col[y][i] = cell[x / 3][y / 3][i] = true;
                board[x][y] = i + '1';
                if (dfs(board, x, y + 1)) return true;
                board[x][y] = '.';
                row[x][i] = col[y][i] = cell[x / 3][y / 3][i] = false;
            }
        }
        return false;
    }
};

38. 外观数列

分析

给了一段数字, 将相同的一段读出来即可
外循环i操作n - 1次, 内循环j每次读当前字符串s, 然后再新建一个k指针, 去判断与s[j]相等的字符的个数
遍历到一段相同的, 就更新t, 然后将j指针更新成k

code

class Solution {
public:
    string countAndSay(int n) {
        string s = "1";
        for (int i = 0; i < n - 1; i ++ ){
            string t; // 每次新的一项用t来表示
            for (int j = 0; j < s.size();){
                int k = j + 1;
                while (k < s.size() && s[k] == s[j]) k ++;
                t += to_string(k - j) + s[j];
                j = k;
            }
            s = t;
        }
        return s;
    }
};

39. 组合总和

分析

顺序: 按照当前每个数选几个来搜
在这里插入图片描述

联动 leetcode 47全排列, 90子集II

code

class Solution {
public:
    vector<vector<int>> res;
    vector<int> path;
    vector<vector<int>> combinationSum(vector<int>& cs, int target) {
        dfs(cs, 0, target);
        return res;
    }
    void dfs(vector<int>& cs, int u, int target){
        if (target == 0){
            res.push_back(path);
            return ;
        }

        if (u == cs.size()) return ; // 当前枚举完最后一个数, 且还没有合法方案, 直接返回
        for (int i = 0; cs[u] * i <= target; i ++ ){ // i表示枚举当前数选几个, 但不能超过target
            dfs(cs, u + 1, target - cs[u] * i);
            path.push_back(cs[u]);
        }
        for (int i = 0; cs[u] * i <= target; i ++)
            path.pop_back();
    }
};

40. 组合总和 II

分析

可能包含重复元素, 但每个元素只能选1次.
举个例子: 可能包含3个3, 但是每个3只能选1次
上一题枚举的时候, 枚举到 > target为止, 这一题, 不仅要被总和限制, 还要被个数限制
在循环的时候 加个限制
需要将每个数的个数求出来, 排个序, 相同的数挨在一起, 方便求出相同数的个数

code

注意递归下一层, 从不相同的数开始

class Solution {
public:
    vector<vector<int>> res;
    vector<int> path;
    vector<vector<int>> combinationSum2(vector<int>& cs, int target) {
        sort(cs.begin(), cs.end());
        dfs(cs, 0, target);
        return res;
    }
    void dfs(vector<int>& cs, int u, int target){
        if (target == 0){
            res.push_back(path);
            return;
        }
        if (u == cs.size()) return;
        int k = u;
        while (k < cs.size() && cs[k] == cs[u]) k ++;
        int cnt = k - u;
        for (int i = 0; cs[u] * i <= target && i <= cnt; i ++ ){
            dfs(cs, k, target - cs[u] * i); // 注意这里下一层要从k开始了
            path.push_back(cs[u]);
        }
        for (int i = 0; cs[u] * i <= target && i <= cnt; i ++ )
            path.pop_back();
    }
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
LeetCode-Editor是一种在线编码工具,它提供了一个用户友好的界面编写和运行代码。在使用LeetCode-Editor时,有时候会出现乱码的问题。 乱码的原因可能是由于编码格式不兼容或者编码错误导致的。在这种情况下,我们可以尝试以下几种解决方法: 1. 检查文件编码格式:首先,我们可以检查所编辑的文件的编码格式。通常来说,常用的编码格式有UTF-8和ASCII等。我们可以将编码格式更改为正确的格式。在LeetCode-Editor中,可以通过界面设置或编辑器设置来更改编码格式。 2. 使用正确的字符集:如果乱码是由于使用了不同的字符集导致的,我们可以尝试更改使用正确的字符集。常见的字符集如Unicode或者UTF-8等。在LeetCode-Editor中,可以在编辑器中选择正确的字符集。 3. 使用合适的编辑器:有时候,乱码问题可能与LeetCode-Editor自身相关。我们可以尝试使用其他编码工具,如Text Editor、Sublime Text或者IDE,看是否能够解决乱码问题。 4. 查找特殊字符:如果乱码问题只出现在某些特殊字符上,我们可以尝试找到并替换这些字符。通过仔细检查代码,我们可以找到导致乱码的特定字符,并进行修正或替换。 总之,解决LeetCode-Editor乱码问题的方法有很多。根据具体情况,我们可以尝试更改文件编码格式、使用正确的字符集、更换编辑器或者查找并替换特殊字符等方法来解决这个问题。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值