LeetCode 31 ~ 40

本文精选了LeetCode上的多个经典算法题目,包括下一个排列、最长有效括号等,并提供了详细的解决方案。通过这些题目,读者可以深入理解算法设计的思路与技巧。
31. 下一个排列
  1. 先从后往前找到 k 使得 nums[k - 1] < nums[k]
  2. 如果不存在这样的 k,说明这个数的排列已经是最大值,直接将数组逆转即可
  3. 如果存在这样的 k,则接着从后往前找到一个 t,使得nums[t] <= nums[k - 1]
  4. 交换nuts[t]nuts[k - 1],然后逆转该数组 k位之后的部分
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[t - 1], nums[k - 1]);
            reverse(nums.begin() + k, nums.end());
        }
    }
};
32. 最长有效括号
class Solution {
public:
    int longestValidParentheses(string s) {
        stack<int> stk;
        int res = 0;
        for(int i = 0, st = -1; i < s.size(); i ++ ) {
            if(s[i] == '(') stk.push(i);
            else {
                if(stk.size()) {
                    stk.pop();
                    if(stk.size()) {
                        res = max(res, i - stk.top());
                    } else {
                        res = max(res, i - st);
                    }
                } else {
                    st = i;
                }
            }
        }
        return res;
    }
};
33. 搜索旋转排序数组

使用二分搜索查找出数组中的最小值在哪里,再确定target值在数组的前一半还是后一半,在这一半区间继续使用二分搜索

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

        if(target >= nums[0]) l = 0;
        else l = r + 1, r = nums.size() - 1;

        while(l < r) {
            int mid = l + r >> 1;
            if(nums[mid] >= target) r = mid;
            else l = mid + 1;
        }

        if(nums[r] == target) return r;
        return -1;
    }
};
34. 在排序数组中查找元素的第一个和最后一个位置
  1. 二分查找第一个值大于等于target的数字位置,若该位置的值不等于 target,返回{-1, -1}
  2. 二分查找第一个target大于其他值的数字位置
class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        if(nums.empty()) return {-1, -1};

        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[r] != target) return {-1, -1};

        int L = 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;
        }

        return {L, r};
    }
};
35. 搜索插入位置
  1. l = 0, r = n,这样可以将查找到的位置直接返回
  2. 二分查找值大于等于target的第一个位置
  3. 返回l
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. 有效的数独
  1. 使用三个数组记录行列和九宫格上中的数字存在情况
  2. 利用九位二进制数记录某一个位对应的数字是否出现过,如 010,000,000表示8
  3. 九宫格内的位置则从二维展开成一维
class Solution {
public:
    bool isValidSudoku(vector<vector<char>>& board) {
        vector<int> row(9), col(9), squ(9); // 使用三个整型数组判重。
        for (int i = 0; i < 9; i++)
            for (int j = 0; j < 9; j++) {
                if (board[i][j] == '.')
                    continue;
                if (board[i][j] < '1' || board[i][j] > '9') return false;
                int num = board[i][j] - '0';

                // 以row[i] & (1 << num)为例,这是判断第i行中,num数字是否出现过。
                // 即row[i]值的二进制表示中,第num位是否是1。
                // 以下col和squ同理。

                if ((row[i] & (1 << num)) ||
                    (col[j] & (1 << num)) ||
                    (squ[(i / 3) * 3 + (j / 3)] & (1 << num)))
                    return false;

                row[i] |= (1 << num);
                col[j] |= (1 << num);
                squ[(i / 3) * 3 + (j / 3)] |= (1 << num);
            }
        return true;
    }
};
37. 解数独
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] = true;
                }

        dfs(board, 0, 0);
    }

    bool dfs(vector<vector<char>>& board, int x, int y) {
        if(y == 9) x ++ , y = 0;
        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]) {
                board[x][y] = '1' + i;
                row[x][i] = col[y][i] = cell[x / 3][y / 3][i] = true;
                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. 外观数列
  1. 1
  2. 11
  3. 21
  4. 1211
  5. 111221
    第一项是数字 1
    描述前一项,这个数是 1 即 “ 一 个 1 ”,记作 “11”
    描述前一项,这个数是 11 即 “ 二 个 1 ” ,记作 “21”
    描述前一项,这个数是 21 即 “ 一 个 2 + 一 个 1 ” ,记作 “1211”
    描述前一项,这个数是 1211 即 “ 一 个 1 + 一 个 2 + 二 个 1 ” ,记作 “111221”

模拟从 2n的顺序生成字符串,重复将连续的相同值进行基数,迭代下去即可

class Solution {
public:
    string countAndSay(int n) {
        string s = "1";
        for(int i = 0; i < n - 1; i ++ ) {
            string 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. 组合总和

暴力搜索即可

  1. 在每一层搜索中,枚举当前数字可以添加几次
  2. 边界条件是数组中的和为target,或者当前层数比数组长度长
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) {								//目前数组中的值已经等于target
            res.push_back(path);
            return;
        }

        if(u == cs.size()) return;			//枚举完当前数组所有数字,返回

        for(int i = 0; cs[u] * i <= target; i ++ ) {
          	//压入一个当前值,dfs搜索一次
            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. 与39相似,不同点在于每个数字只能使用一次,计算每个数字总共有几个,每次压入数组中不超过个数即可
  2. 使用sort从小到大排序,可以实现剪枝的效果
class Solution {
public:

    vector<vector<int>> ans;
    vector<int> path;

    vector<vector<int>> combinationSum2(vector<int>& c, int target) {
        sort(c.begin(), c.end());	//优化搜索顺序
        dfs(c, 0, target);

        return ans;
    }

    void dfs(vector<int>& c, int u, int target) {
        if(target == 0) {
            ans.push_back(path);
            return;
        }
        if(u == c.size()) return;

        int k = u + 1;
        while(k < c.size() && c[k] == c[u]) k ++ ;
        int cnt =  k - u; //计算总共有几个当前值

        for(int i = 0; c[u] * i <= target && i <= cnt; i ++ ) {
            dfs(c, k, target - c[u] * i);
            path.push_back(c[u]);
        }

        for(int i = 0; c[u] * i <= target && i <= cnt; i ++ ) {
            path.pop_back();
        }
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值